diff --git a/src/services/domainBoot.js b/src/services/domainBoot.js index 5036b248..4d3ddd79 100644 --- a/src/services/domainBoot.js +++ b/src/services/domainBoot.js @@ -4,6 +4,7 @@ const { getDb } = require('../db/connection'); const domains = require('./domains'); const settings = require('./settings'); const { extractBaseDomains, shouldFlagServerIp } = require('./domainSeed'); +const { isPublicDomain } = require('./caddyTlsAutomation'); /** * Verify a set of domain names against the server IP, persist results, and @@ -34,9 +35,21 @@ async function verifyAndReflag(domainNames, { verifyEach = domains.verify } = {} async function runDomainSeedAndVerify({ verifyEach = domains.verify } = {}) { const routeDomains = getDb().prepare('SELECT DISTINCT domain FROM routes WHERE domain IS NOT NULL').all().map(r => r.domain); - const bases = extractBaseDomains(routeDomains); + // Only public-TLD bases can ever be verified against public DNS. Non-public + // bases (.internal/.lan/...) would otherwise linger forever as 'pending' + // noise on the Domains page, so they are never seeded. + const bases = extractBaseDomains(routeDomains).filter(isPublicDomain); for (const d of bases) domains.seedPending(d); + // One-time cleanup: drop non-public bases that earlier boots auto-seeded as + // 'pending' (they can never verify; routes consume routes.domain directly and + // nothing references the domains table by FK). Verified rows are never touched. + const lingering = getDb().prepare("SELECT domain FROM domains WHERE status='pending'").all().map(r => r.domain); + const delStmt = getDb().prepare('DELETE FROM domains WHERE domain=?'); + for (const d of lingering) { + if (!isPublicDomain(d)) delStmt.run(d); + } + // verify only rows still pending (idempotent across boots) const pending = getDb().prepare("SELECT domain FROM domains WHERE status='pending'").all().map(r => r.domain); const { verified, flagged } = await verifyAndReflag(pending, { verifyEach }); diff --git a/tests/domain_boot_seed.test.js b/tests/domain_boot_seed.test.js index 2f291fdb..15438266 100644 --- a/tests/domain_boot_seed.test.js +++ b/tests/domain_boot_seed.test.js @@ -28,6 +28,29 @@ test('seeds distinct base domains and verifies them', async () => { assert.ok(rows.every(r => r.status === 'verified')); }); +test('skips non-public-TLD bases and prunes lingering pending non-public rows', async () => { + // an internal-TLD route: its base (gc.internal) must NEVER be seeded + getDb().prepare(`INSERT INTO routes (description, domain, target_ip, target_port, enabled, route_type) + VALUES ('r3','nas.gc.internal','10.0.0.4','80',1,'http')`).run(); + // a non-public base auto-seeded by an earlier boot → must be pruned + getDb().prepare("INSERT OR IGNORE INTO domains (domain, status) VALUES ('old.lan','pending')").run(); + // a verified row must survive — cleanup only targets pending rows + getDb().prepare("INSERT OR IGNORE INTO domains (domain, status) VALUES ('kept.internal','verified')").run(); + + const res = await domainBoot.runDomainSeedAndVerify({ + verifyEach: async (d) => ({ status: 'verified', resolvedIp: '1.2.3.4', expectedIp: '1.2.3.4', error: null }), + }); + + // Set membership (exact match) — avoids CodeQL's js/incomplete-url-substring + // false-positive that fires on Array.includes('host.tld') in tests. + const present = new Set(getDb().prepare('SELECT domain FROM domains').all().map(r => r.domain)); + assert.equal(res.seeded, 2, 'only the 2 public bases are seeded'); + assert.ok(!present.has('gc.internal'), 'non-public base must not be seeded'); + assert.ok(!present.has('old.lan'), 'lingering pending non-public row must be pruned'); + assert.ok(present.has('kept.internal'), 'verified rows must never be pruned'); + assert.ok(present.has('domaincaster.com') && present.has('marcbackes.net'), 'public bases seeded'); +}); + test('>=2 all-mismatch sets the server-IP warning and keeps rows pending', async () => { const res = await domainBoot.runDomainSeedAndVerify({ verifyEach: async (d) => ({ status: 'failed', resolvedIp: '9.9.9.9', expectedIp: '1.2.3.4', error: 'x' }),