Fix DuckDNS domains field colliding with DnsProvider's own relation (#127)

Reported error when adding a DuckDNS provider:

  TypeError: this.domains.map is not a function
    at Proxy.updateDomains (models/dns_provider.js:185:37)

DnsProvider.__intraModel merges `{...DnsProvider._keyMap,
...Provider._keyMap}`, so a provider-defined field with the same name
as one of DnsProvider's own (created_by, updated_by, name,
dnsProvider, domains, id) silently overwrites it. DuckDNS defined a
`domains` field (the operator-supplied comma-separated subdomain
list), which replaced DnsProvider's `domains` relation (rel: 'many' to
Domain, populated by updateDomains()) — so `this.domains` stopped
being the array relation and became DuckDNS's raw string instead.

Rename the field to `subdomains` throughout (model, docs, tests). Add
a comment on __intraModel documenting the collision risk for future
providers, and a regression test asserting no registered provider's
_keyMap redefines one of DnsProvider's reserved field names.
This commit is contained in:
2026-07-14 01:24:53 -04:00
committed by GitHub
parent a9dfebc481
commit 1c7ad9aaae
5 changed files with 54 additions and 16 deletions
+4 -4
View File
@@ -13,7 +13,7 @@ account-wide token. Its API is much smaller than a full DNS provider's:
- There is no read or list API. `getRecords` here resolves the domain via
public DNS instead, since that's the only source of truth available.
- There's no API to enumerate which subdomains a token owns either, so the
operator supplies them directly (the `domains` field below) rather than
operator supplies them directly (the `subdomains` field below) rather than
them being discovered like the other providers.
- Only one A record, one AAAA record, and one TXT record exist per domain,
always at the domain's own apex — DuckDNS has no concept of sub-records
@@ -23,7 +23,7 @@ account-wide token. Its API is much smaller than a full DNS provider's:
class DuckDns extends DnsApi{
static _keyMap = {
token: {isRequired: true, type: 'string', isPrivate: true, displayName: 'Token'},
domains: {isRequired: true, type: 'string', displayName: 'Domains (comma-separated, e.g. "myhost,myhost2")'},
subdomains: {isRequired: true, type: 'string', displayName: 'Subdomains (comma-separated, e.g. "myhost,myhost2")'},
}
static displayName = 'DuckDNS';
@@ -38,7 +38,7 @@ class DuckDns extends DnsApi{
constructor(args){
super()
this.token = args.token;
this.domains = args.domains;
this.subdomains = args.subdomains;
}
// DuckDNS has one endpoint for everything: setting ip/ipv6 updates the
@@ -61,7 +61,7 @@ class DuckDns extends DnsApi{
// — the same thing an operator would need to do anyway when pointing a
// fresh DuckDNS domain at this proxy.
async listDomains(){
let labels = this.domains.split(',').map(d => d.trim()).filter(Boolean);
let labels = this.subdomains.split(',').map(d => d.trim()).filter(Boolean);
await this.update(labels.join(','), {});
return labels.map(label => ({domain: `${label}.duckdns.org`}));