Fix wildcard-parent edit greying and deprecated nginx http2 directive
- The edit form's "Parent Wildcard" option stayed greyed out even when a valid wildcard existed, since hostEditOpen() never ran the eligibility check (only the host field's keyup handler did, which setting .val() programmatically doesn't fire) -- and the check itself, GET /host/lookup/:item, had the same self-match bug as the recently-fixed Host.prototype.update() case: it resolves an already-existing host to its own record instead of a sibling wildcard. Added a dedicated /host/wildcard-parent/:item route combining lookUp() (handles a brand-new subdomain) with lookUpWildcardParent() (handles an already-existing host), and hostEditOpen() now actually runs it. - Migrated ops/nginx_conf/autossl.conf's deprecated "listen ... http2" directive to the standalone "http2 on;" directive (nginx 1.25.1+). Bumps to v1.1.12. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
This commit is contained in:
+9
-2
@@ -6,7 +6,13 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.1.11] - 2026-07-17
|
||||
## [1.1.12] - 2026-07-17
|
||||
|
||||
### Fixed
|
||||
- The host edit form's "Parent Wildcard" option stayed greyed out even when a valid wildcard actually existed for that host, so an already-created host could never be switched onto one from the edit modal (only brand-new hosts, via the field's `keyup` handler, ever saw it become available). The underlying `/host/lookup/:item` check also had the same self-match issue as the recently-fixed backend bug: it resolved an already-existing host to its own record instead of a sibling wildcard. Added a dedicated `/host/wildcard-parent/:item` endpoint that checks both directions, and the edit form now actually runs the check when it opens.
|
||||
- Fixed an nginx startup warning: `the "listen ... http2" directive is deprecated, use the "http2" directive instead`. Migrated to the standalone `http2 on;` directive (nginx 1.25.1+).
|
||||
|
||||
Bumps to v1.1.12.
|
||||
|
||||
### Changed
|
||||
- Moved the help (❓) link out of the global header and onto each relevant card individually (Proxy List, Add/Edit host, Add DNS Provider, Dynamic A Records, Add New User, User List, Add Permission, Permissions, Add Group) — each now deep-links straight to the doc that actually covers it, instead of one generic header icon.
|
||||
@@ -82,7 +88,8 @@ First tagged release. Establishes the `vX.Y.Z` tag convention that the in-app up
|
||||
- Standalone backup script (`ops/backup.sh`) for deployments not using theta-env's orchestrator — snapshots Redis and `./config`, with retention.
|
||||
- Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates.
|
||||
|
||||
[Unreleased]: https://github.com/theta42/proxy/compare/v1.1.11...HEAD
|
||||
[Unreleased]: https://github.com/theta42/proxy/compare/v1.1.12...HEAD
|
||||
[1.1.12]: https://github.com/theta42/proxy/compare/v1.1.11...v1.1.12
|
||||
[1.1.11]: https://github.com/theta42/proxy/compare/v1.1.10...v1.1.11
|
||||
[1.1.10]: https://github.com/theta42/proxy/compare/v1.1.9...v1.1.10
|
||||
[1.1.9]: https://github.com/theta42/proxy/compare/v1.1.8...v1.1.9
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "proxy-api",
|
||||
"version": "1.1.11",
|
||||
"version": "1.1.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "proxy-api",
|
||||
"version": "1.1.11",
|
||||
"version": "1.1.12",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "proxy-api",
|
||||
"version": "1.1.11",
|
||||
"version": "1.1.12",
|
||||
"private": true,
|
||||
"author": [
|
||||
{
|
||||
|
||||
@@ -128,6 +128,29 @@ router.get('/lookup/:item', authz.requireDomainRole('viewer', authz.resolve.host
|
||||
}
|
||||
});
|
||||
|
||||
// Is there a wildcard host that could serve as :item's parent (i.e. an
|
||||
// already-issued cert :item could reuse instead of getting its own)? Two
|
||||
// cases, covered by two different lookups: a brand-new subdomain that has
|
||||
// never been created (lookUp()'s normal wildcard fallback finds it, since
|
||||
// the name has no leaf of its own yet), and an ALREADY-EXISTING host or the
|
||||
// wildcard's own base domain (lookUp() would just resolve to that host's
|
||||
// own leaf -- lookUpWildcardParent() checks the sibling "*" slot instead;
|
||||
// see its comment in models/host.js). Used by the host create/edit form to
|
||||
// decide whether to offer "Parent Wildcard" as a challenge type.
|
||||
router.get('/wildcard-parent/:item', authz.requireDomainRole('viewer', authz.resolve.hostParam), async function(req, res, next){
|
||||
try{
|
||||
let match = Model.lookUp(req.params.item);
|
||||
if(!match || !match.is_wildcard){
|
||||
match = Model.lookUpWildcardParent(req.params.item);
|
||||
}
|
||||
return res.json({
|
||||
results: (match && match.is_wildcard) ? match : null,
|
||||
});
|
||||
}catch(error){
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// The full lookup tree exposes every host, so restrict it to admins.
|
||||
router.get('/lookupobj', authz.requireAdmin, async function(req, res, next){
|
||||
try{
|
||||
|
||||
@@ -187,6 +187,52 @@ describe('Host wildcard base-domain lookup', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests for the exact fallback combination used by
|
||||
* routes/host.js's GET /wildcard-parent/:item (and, via hostMatchWildcard(),
|
||||
* the host create/edit form's "Parent Wildcard" option) -- lookUp() first
|
||||
* (handles a brand-new subdomain that has no leaf of its own yet), falling
|
||||
* back to lookUpWildcardParent() only when lookUp() didn't resolve to a
|
||||
* wildcard (handles an ALREADY-EXISTING host, which lookUp() would resolve
|
||||
* to its own record). Regression coverage for the edit-form bug where the
|
||||
* "Parent Wildcard" option stayed permanently greyed out for an existing
|
||||
* host, because the route only ever tried lookUp().
|
||||
*/
|
||||
describe('Host wildcard-parent route fallback (lookUp then lookUpWildcardParent)', () => {
|
||||
|
||||
let Host;
|
||||
|
||||
before(async () => {
|
||||
Host = createMockHostClassWithWildcardParentFix();
|
||||
});
|
||||
|
||||
function findWildcardParent(host){
|
||||
let match = Host.lookUp(host);
|
||||
if(!match || !match.is_wildcard) match = Host.lookUpWildcardParent(host);
|
||||
return (match && match.is_wildcard) ? match : null;
|
||||
}
|
||||
|
||||
test('finds the wildcard for a brand-new subdomain that was never created', async () => {
|
||||
await populateTree(Host, ['*.cool.mysite.com']);
|
||||
const result = findWildcardParent('newthing.cool.mysite.com');
|
||||
assert.ok(result);
|
||||
assert.strictEqual(result.host, '*.cool.mysite.com');
|
||||
});
|
||||
|
||||
test('finds the wildcard for the wildcard\'s own base domain, whether or not it is already a plain host', async () => {
|
||||
await populateTree(Host, ['*.cool.mysite.com']);
|
||||
assert.strictEqual(findWildcardParent('cool.mysite.com').host, '*.cool.mysite.com');
|
||||
|
||||
await populateTree(Host, ['*.cool.mysite.com', 'cool.mysite.com']);
|
||||
assert.strictEqual(findWildcardParent('cool.mysite.com').host, '*.cool.mysite.com');
|
||||
});
|
||||
|
||||
test('returns null when the host has no wildcard sibling at all', async () => {
|
||||
await populateTree(Host, ['cool.mysite.com']);
|
||||
assert.strictEqual(findWildcardParent('cool.mysite.com'), null);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Same mock shape as createMockHostClass() above, plus the parent-record
|
||||
* stamp in the tree-population loop and the lookUpWildcardParent() method --
|
||||
@@ -243,7 +289,11 @@ async function populateTree(Host, hosts) {
|
||||
}
|
||||
|
||||
if(fragments.length === 0){
|
||||
pointer[fragment]['#record'] = {host};
|
||||
// is_wildcard mirrors the real Host model's own field (set
|
||||
// whenever a host is DNS-01 wildcard-issued, i.e. starts with
|
||||
// "*."), needed by tests that check it the same way the real
|
||||
// /wildcard-parent/:item route does.
|
||||
pointer[fragment]['#record'] = {host, is_wildcard: host.startsWith('*.')};
|
||||
|
||||
if(fragment === '*' && !pointer['#record']){
|
||||
pointer['#record'] = pointer[fragment]['#record'];
|
||||
|
||||
+30
-5
@@ -212,7 +212,7 @@
|
||||
hostModal().show();
|
||||
}
|
||||
|
||||
function hostEditOpen(host){
|
||||
async function hostEditOpen(host){
|
||||
hostFormReset();
|
||||
let h = $.scope.hosts.getByKey(host);
|
||||
let $f = $('#hostForm');
|
||||
@@ -258,8 +258,31 @@
|
||||
let hostRenameable = !h.is_wildcard && !h.wildcard_parent && !h.is_cache;
|
||||
$f.find('[name=host]').prop('disabled', !hostRenameable);
|
||||
$('#host-rename-help').toggle(!hostRenameable);
|
||||
|
||||
// Reflect + enable the challenge-type options actually available for
|
||||
// this host. Setting the host field's .val() above does not fire a
|
||||
// 'keyup' event, so without this the "Parent Wildcard" option stayed
|
||||
// permanently greyed out on edit even when a valid parent wildcard
|
||||
// existed -- it only ever got un-greyed by the user re-typing the
|
||||
// hostname (the keyup handler further down).
|
||||
$('#challengeType-child-container, #challengeType-DNS-01-wildcard-container, #wildcard_matchAny-container')
|
||||
.addClass('challengeType-container');
|
||||
|
||||
if(h.is_wildcard){
|
||||
$('#challengeType-DNS-01-wildcard-container').removeClass('challengeType-container');
|
||||
$('#challengeType-DNS-01-wildcard').prop('checked', true);
|
||||
$('#wildcard_matchAny-container').removeClass('challengeType-container');
|
||||
}else{
|
||||
let wildcardParent = await hostMatchWildcard(h.host);
|
||||
if(wildcardParent){
|
||||
$('#challengeType-child-container').removeClass('challengeType-container');
|
||||
$('#challengeType-child-relatedHost').text(wildcardParent.host);
|
||||
}
|
||||
if(h.wildcard_parent){
|
||||
$('#challengeType-wildcardChild').prop('checked', true);
|
||||
}else{
|
||||
$('#challengeType-HTTP-01').prop('checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
hostModal().show();
|
||||
@@ -306,10 +329,12 @@
|
||||
|
||||
async function hostMatchWildcard(host){
|
||||
try{
|
||||
let res = await app.api.get(`host/lookup/${host}`);
|
||||
if(res.results && res.results.is_wildcard){
|
||||
return res.results;
|
||||
}
|
||||
// Not /host/lookup/ -- that resolves an ALREADY-EXISTING host to its
|
||||
// own record, not a sibling wildcard (see the route's comment). This
|
||||
// dedicated endpoint correctly finds a usable wildcard parent whether
|
||||
// @host is brand new or already exists as its own host.
|
||||
let res = await app.api.get(`host/wildcard-parent/${host}`);
|
||||
return res.results || false;
|
||||
}catch(error){
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
listen 443 ssl http2;
|
||||
listen 443 ssl;
|
||||
listen 4443 ssl;
|
||||
# The "http2" listen parameter is deprecated since nginx 1.25.1 in favor of
|
||||
# this standalone directive, which applies to every "listen ... ssl" in the
|
||||
# server block (both 443 and 4443 here).
|
||||
http2 on;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
Reference in New Issue
Block a user