@serve.zone/dcrouter 13.7.1 → 13.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_serve/bundle.js +1763 -1519
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/acme/index.d.ts +1 -0
- package/dist_ts/acme/index.js +2 -0
- package/dist_ts/acme/manager.acme-config.d.ts +48 -0
- package/dist_ts/acme/manager.acme-config.js +156 -0
- package/dist_ts/classes.dcrouter.d.ts +2 -0
- package/dist_ts/classes.dcrouter.js +60 -21
- package/dist_ts/db/documents/classes.acme-config.doc.d.ts +22 -0
- package/dist_ts/db/documents/classes.acme-config.doc.js +121 -0
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +3 -1
- package/dist_ts/dns/manager.dns.d.ts +17 -15
- package/dist_ts/dns/manager.dns.js +33 -27
- package/dist_ts/dns/providers/factory.js +10 -1
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/acme-config.handler.d.ts +16 -0
- package/dist_ts/opsserver/handlers/acme-config.handler.js +77 -0
- package/dist_ts/opsserver/handlers/dns-provider.handler.js +42 -5
- package/dist_ts/opsserver/handlers/domain.handler.js +3 -3
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts_interfaces/data/acme-config.d.ts +25 -0
- package/dist_ts_interfaces/data/acme-config.js +2 -0
- package/dist_ts_interfaces/data/dns-provider.d.ts +28 -4
- package/dist_ts_interfaces/data/dns-provider.js +15 -1
- package/dist_ts_interfaces/data/dns-record.d.ts +9 -7
- package/dist_ts_interfaces/data/domain.d.ts +8 -7
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/route-management.d.ts +1 -1
- package/dist_ts_interfaces/requests/acme-config.d.ts +42 -0
- package/dist_ts_interfaces/requests/acme-config.js +2 -0
- package/dist_ts_interfaces/requests/dns-records.d.ts +1 -1
- package/dist_ts_interfaces/requests/domains.d.ts +3 -3
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_migrations/index.js +17 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +16 -1
- package/dist_ts_web/appstate.js +61 -2
- package/dist_ts_web/elements/domains/dns-provider-form.d.ts +4 -2
- package/dist_ts_web/elements/domains/dns-provider-form.js +11 -5
- package/dist_ts_web/elements/domains/ops-view-certificates.d.ts +3 -0
- package/dist_ts_web/elements/domains/ops-view-certificates.js +208 -4
- package/dist_ts_web/elements/domains/ops-view-dns.js +3 -3
- package/dist_ts_web/elements/domains/ops-view-domains.d.ts +1 -1
- package/dist_ts_web/elements/domains/ops-view-domains.js +10 -10
- package/dist_ts_web/elements/domains/ops-view-providers.js +19 -5
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/acme/index.ts +1 -0
- package/ts/acme/manager.acme-config.ts +182 -0
- package/ts/classes.dcrouter.ts +74 -26
- package/ts/db/documents/classes.acme-config.doc.ts +49 -0
- package/ts/db/documents/index.ts +3 -0
- package/ts/dns/manager.dns.ts +38 -27
- package/ts/dns/providers/factory.ts +11 -0
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/acme-config.handler.ts +94 -0
- package/ts/opsserver/handlers/dns-provider.handler.ts +41 -3
- package/ts/opsserver/handlers/domain.handler.ts +2 -2
- package/ts/opsserver/handlers/index.ts +2 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +89 -1
- package/ts_web/elements/domains/dns-provider-form.ts +12 -4
- package/ts_web/elements/domains/ops-view-certificates.ts +205 -2
- package/ts_web/elements/domains/ops-view-dns.ts +2 -2
- package/ts_web/elements/domains/ops-view-domains.ts +9 -9
- package/ts_web/elements/domains/ops-view-providers.ts +18 -4
package/ts/dns/manager.dns.ts
CHANGED
|
@@ -25,9 +25,9 @@ import type {
|
|
|
25
25
|
* Responsibilities:
|
|
26
26
|
* - Load Domain/DnsRecord docs from the DB on start
|
|
27
27
|
* - First-boot seeding from legacy constructor config (dnsScopes/dnsRecords/dnsNsDomains)
|
|
28
|
-
* - Register
|
|
29
|
-
* - Provide CRUD methods used by OpsServer handlers (
|
|
30
|
-
* provider domains hit the provider API)
|
|
28
|
+
* - Register dcrouter-hosted domain records with smartdns.DnsServer at startup
|
|
29
|
+
* - Provide CRUD methods used by OpsServer handlers (dcrouter-hosted domains hit
|
|
30
|
+
* smartdns, provider domains hit the provider API)
|
|
31
31
|
* - Expose a provider lookup used by the ACME DNS-01 wiring in setupSmartProxy()
|
|
32
32
|
*
|
|
33
33
|
* Provider-managed domains are NEVER served from the embedded DnsServer — the
|
|
@@ -69,12 +69,12 @@ export class DnsManager {
|
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Wire the embedded DnsServer instance after it has been created by
|
|
72
|
-
* DcRouter.setupDnsWithSocketHandler(). After this,
|
|
73
|
-
* from the DB are registered with the server.
|
|
72
|
+
* DcRouter.setupDnsWithSocketHandler(). After this, local records on
|
|
73
|
+
* dcrouter-hosted domains loaded from the DB are registered with the server.
|
|
74
74
|
*/
|
|
75
75
|
public async attachDnsServer(dnsServer: plugins.smartdns.dnsServerMod.DnsServer): Promise<void> {
|
|
76
76
|
this.dnsServer = dnsServer;
|
|
77
|
-
await this.
|
|
77
|
+
await this.applyDcrouterDomainsToDnsServer();
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// ==========================================================================
|
|
@@ -83,7 +83,8 @@ export class DnsManager {
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* If no DomainDocs exist yet but the constructor has legacy DNS fields,
|
|
86
|
-
* seed them as `source: '
|
|
86
|
+
* seed them as dcrouter-hosted (`domain.source: 'dcrouter'`) zones with
|
|
87
|
+
* local (`record.source: 'local'`) records. On subsequent boots (DB has
|
|
87
88
|
* entries), constructor config is ignored with a warning.
|
|
88
89
|
*/
|
|
89
90
|
private async seedFromConstructorConfigIfEmpty(): Promise<void> {
|
|
@@ -117,7 +118,7 @@ export class DnsManager {
|
|
|
117
118
|
const domain = new DomainDoc();
|
|
118
119
|
domain.id = plugins.uuid.v4();
|
|
119
120
|
domain.name = scope.toLowerCase();
|
|
120
|
-
domain.source = '
|
|
121
|
+
domain.source = 'dcrouter';
|
|
121
122
|
domain.authoritative = true;
|
|
122
123
|
domain.createdAt = now;
|
|
123
124
|
domain.updatedAt = now;
|
|
@@ -144,7 +145,7 @@ export class DnsManager {
|
|
|
144
145
|
record.type = rec.type as TDnsRecordType;
|
|
145
146
|
record.value = rec.value;
|
|
146
147
|
record.ttl = rec.ttl ?? 300;
|
|
147
|
-
record.source = '
|
|
148
|
+
record.source = 'local';
|
|
148
149
|
record.createdAt = now;
|
|
149
150
|
record.updatedAt = now;
|
|
150
151
|
record.createdBy = 'seed';
|
|
@@ -174,28 +175,31 @@ export class DnsManager {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
// ==========================================================================
|
|
177
|
-
//
|
|
178
|
+
// DcRouter-hosted domain DnsServer wiring
|
|
178
179
|
// ==========================================================================
|
|
179
180
|
|
|
180
181
|
/**
|
|
181
|
-
* Register all
|
|
182
|
-
* Called once after attachDnsServer().
|
|
182
|
+
* Register all records from dcrouter-hosted domains in the DB with the
|
|
183
|
+
* embedded DnsServer. Called once after attachDnsServer().
|
|
183
184
|
*/
|
|
184
|
-
private async
|
|
185
|
+
private async applyDcrouterDomainsToDnsServer(): Promise<void> {
|
|
185
186
|
if (!this.dnsServer) {
|
|
186
187
|
return;
|
|
187
188
|
}
|
|
188
189
|
const allDomains = await DomainDoc.findAll();
|
|
189
|
-
const
|
|
190
|
+
const dcrouterDomains = allDomains.filter((d) => d.source === 'dcrouter');
|
|
190
191
|
let registered = 0;
|
|
191
|
-
for (const domain of
|
|
192
|
+
for (const domain of dcrouterDomains) {
|
|
192
193
|
const records = await DnsRecordDoc.findByDomainId(domain.id);
|
|
193
194
|
for (const rec of records) {
|
|
194
195
|
this.registerRecordWithDnsServer(rec);
|
|
195
196
|
registered++;
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
|
-
logger.log(
|
|
199
|
+
logger.log(
|
|
200
|
+
'info',
|
|
201
|
+
`DnsManager: registered ${registered} dcrouter-hosted DNS record(s) from DB`,
|
|
202
|
+
);
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
/**
|
|
@@ -381,6 +385,12 @@ export class DnsManager {
|
|
|
381
385
|
credentials: TDnsProviderCredentials;
|
|
382
386
|
createdBy: string;
|
|
383
387
|
}): Promise<string> {
|
|
388
|
+
if (args.type === 'dcrouter') {
|
|
389
|
+
throw new Error(
|
|
390
|
+
'createProvider: cannot create a DnsProviderDoc with type "dcrouter" — ' +
|
|
391
|
+
'that type is reserved for the built-in pseudo-provider surfaced at read time.',
|
|
392
|
+
);
|
|
393
|
+
}
|
|
384
394
|
const now = Date.now();
|
|
385
395
|
const doc = new DnsProviderDoc();
|
|
386
396
|
doc.id = plugins.uuid.v4();
|
|
@@ -473,10 +483,10 @@ export class DnsManager {
|
|
|
473
483
|
}
|
|
474
484
|
|
|
475
485
|
/**
|
|
476
|
-
* Create a
|
|
477
|
-
* for this domain via the embedded smartdns.DnsServer.
|
|
486
|
+
* Create a dcrouter-hosted (authoritative) domain. dcrouter will serve
|
|
487
|
+
* DNS records for this domain via the embedded smartdns.DnsServer.
|
|
478
488
|
*/
|
|
479
|
-
public async
|
|
489
|
+
public async createDcrouterDomain(args: {
|
|
480
490
|
name: string;
|
|
481
491
|
description?: string;
|
|
482
492
|
createdBy: string;
|
|
@@ -485,7 +495,7 @@ export class DnsManager {
|
|
|
485
495
|
const doc = new DomainDoc();
|
|
486
496
|
doc.id = plugins.uuid.v4();
|
|
487
497
|
doc.name = args.name.toLowerCase();
|
|
488
|
-
doc.source = '
|
|
498
|
+
doc.source = 'dcrouter';
|
|
489
499
|
doc.authoritative = true;
|
|
490
500
|
doc.description = args.description;
|
|
491
501
|
doc.createdAt = now;
|
|
@@ -571,10 +581,11 @@ export class DnsManager {
|
|
|
571
581
|
/**
|
|
572
582
|
* Delete a domain and all of its DNS records. For provider domains, only
|
|
573
583
|
* removes the local mirror — does NOT touch the provider.
|
|
574
|
-
* For
|
|
584
|
+
* For dcrouter-hosted domains, also unregisters records from the embedded
|
|
585
|
+
* DnsServer.
|
|
575
586
|
*
|
|
576
587
|
* Note: smartdns has no public unregister-by-name API in the version pinned
|
|
577
|
-
* here, so
|
|
588
|
+
* here, so local record deletes only take effect after a restart. The DB
|
|
578
589
|
* is the source of truth and the next start will not register the deleted
|
|
579
590
|
* record.
|
|
580
591
|
*/
|
|
@@ -652,7 +663,7 @@ export class DnsManager {
|
|
|
652
663
|
doc.value = args.value;
|
|
653
664
|
doc.ttl = args.ttl ?? 300;
|
|
654
665
|
if (args.proxied !== undefined) doc.proxied = args.proxied;
|
|
655
|
-
doc.source = '
|
|
666
|
+
doc.source = 'local';
|
|
656
667
|
doc.createdAt = now;
|
|
657
668
|
doc.updatedAt = now;
|
|
658
669
|
doc.createdBy = args.createdBy;
|
|
@@ -678,7 +689,7 @@ export class DnsManager {
|
|
|
678
689
|
return { success: false, message: `Provider rejected record: ${(err as Error).message}` };
|
|
679
690
|
}
|
|
680
691
|
} else {
|
|
681
|
-
//
|
|
692
|
+
// dcrouter-hosted / authoritative — register with embedded DnsServer immediately
|
|
682
693
|
this.registerRecordWithDnsServer(doc);
|
|
683
694
|
}
|
|
684
695
|
|
|
@@ -722,7 +733,7 @@ export class DnsManager {
|
|
|
722
733
|
return { success: false, message: `Provider rejected update: ${(err as Error).message}` };
|
|
723
734
|
}
|
|
724
735
|
} else {
|
|
725
|
-
// Re-register the
|
|
736
|
+
// Re-register the local record so the new closure picks up the updated fields
|
|
726
737
|
this.registerRecordWithDnsServer(doc);
|
|
727
738
|
}
|
|
728
739
|
|
|
@@ -748,7 +759,7 @@ export class DnsManager {
|
|
|
748
759
|
}
|
|
749
760
|
}
|
|
750
761
|
}
|
|
751
|
-
// For
|
|
762
|
+
// For local records: smartdns has no unregister API in the pinned version,
|
|
752
763
|
// so the record stays served until the next restart. The DB delete still
|
|
753
764
|
// takes effect — on restart, the record will not be re-registered.
|
|
754
765
|
|
|
@@ -807,7 +818,7 @@ export class DnsManager {
|
|
|
807
818
|
public toPublicDomain(doc: DomainDoc): {
|
|
808
819
|
id: string;
|
|
809
820
|
name: string;
|
|
810
|
-
source: '
|
|
821
|
+
source: 'dcrouter' | 'provider';
|
|
811
822
|
providerId?: string;
|
|
812
823
|
authoritative: boolean;
|
|
813
824
|
nameservers?: string[];
|
|
@@ -38,6 +38,17 @@ export function createDnsProvider(
|
|
|
38
38
|
}
|
|
39
39
|
return new CloudflareDnsProvider(credentials.apiToken);
|
|
40
40
|
}
|
|
41
|
+
case 'dcrouter': {
|
|
42
|
+
// The built-in DcRouter pseudo-provider has no runtime client — dcrouter
|
|
43
|
+
// itself serves the records via the embedded smartdns.DnsServer. This
|
|
44
|
+
// case exists only to satisfy the exhaustive switch; it should never
|
|
45
|
+
// actually run because the handler layer rejects any CRUD that would
|
|
46
|
+
// result in a DnsProviderDoc with type: 'dcrouter'.
|
|
47
|
+
throw new Error(
|
|
48
|
+
`createDnsProvider: 'dcrouter' is a built-in pseudo-provider — no runtime client exists. ` +
|
|
49
|
+
`This call indicates a DnsProviderDoc with type: 'dcrouter' was persisted, which should never happen.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
41
52
|
default: {
|
|
42
53
|
// If you see a TypeScript error here after extending TDnsProviderType,
|
|
43
54
|
// add a `case` for the new type above. The `never` enforces exhaustiveness.
|
|
@@ -36,6 +36,7 @@ export class OpsServer {
|
|
|
36
36
|
private dnsProviderHandler!: handlers.DnsProviderHandler;
|
|
37
37
|
private domainHandler!: handlers.DomainHandler;
|
|
38
38
|
private dnsRecordHandler!: handlers.DnsRecordHandler;
|
|
39
|
+
private acmeConfigHandler!: handlers.AcmeConfigHandler;
|
|
39
40
|
|
|
40
41
|
constructor(dcRouterRefArg: DcRouter) {
|
|
41
42
|
this.dcRouterRef = dcRouterRefArg;
|
|
@@ -102,6 +103,7 @@ export class OpsServer {
|
|
|
102
103
|
this.dnsProviderHandler = new handlers.DnsProviderHandler(this);
|
|
103
104
|
this.domainHandler = new handlers.DomainHandler(this);
|
|
104
105
|
this.dnsRecordHandler = new handlers.DnsRecordHandler(this);
|
|
106
|
+
this.acmeConfigHandler = new handlers.AcmeConfigHandler(this);
|
|
105
107
|
|
|
106
108
|
console.log('✅ OpsServer TypedRequest handlers initialized');
|
|
107
109
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import type { OpsServer } from '../classes.opsserver.js';
|
|
3
|
+
import * as interfaces from '../../../ts_interfaces/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CRUD handler for the singleton `AcmeConfigDoc`.
|
|
7
|
+
*
|
|
8
|
+
* Auth: same dual-mode pattern as other handlers — admin JWT or API token
|
|
9
|
+
* with `acme-config:read` / `acme-config:write` scope.
|
|
10
|
+
*/
|
|
11
|
+
export class AcmeConfigHandler {
|
|
12
|
+
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
13
|
+
|
|
14
|
+
constructor(private opsServerRef: OpsServer) {
|
|
15
|
+
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
16
|
+
this.registerHandlers();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private async requireAuth(
|
|
20
|
+
request: { identity?: interfaces.data.IIdentity; apiToken?: string },
|
|
21
|
+
requiredScope?: interfaces.data.TApiTokenScope,
|
|
22
|
+
): Promise<string> {
|
|
23
|
+
if (request.identity?.jwt) {
|
|
24
|
+
try {
|
|
25
|
+
const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
|
|
26
|
+
identity: request.identity,
|
|
27
|
+
});
|
|
28
|
+
if (isAdmin) return request.identity.userId;
|
|
29
|
+
} catch { /* fall through */ }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (request.apiToken) {
|
|
33
|
+
const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
|
34
|
+
if (tokenManager) {
|
|
35
|
+
const token = await tokenManager.validateToken(request.apiToken);
|
|
36
|
+
if (token) {
|
|
37
|
+
if (!requiredScope || tokenManager.hasScope(token, requiredScope)) {
|
|
38
|
+
return token.createdBy;
|
|
39
|
+
}
|
|
40
|
+
throw new plugins.typedrequest.TypedResponseError('insufficient scope');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new plugins.typedrequest.TypedResponseError('unauthorized');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private registerHandlers(): void {
|
|
49
|
+
// Get current ACME config
|
|
50
|
+
this.typedrouter.addTypedHandler(
|
|
51
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAcmeConfig>(
|
|
52
|
+
'getAcmeConfig',
|
|
53
|
+
async (dataArg) => {
|
|
54
|
+
await this.requireAuth(dataArg, 'acme-config:read');
|
|
55
|
+
const mgr = this.opsServerRef.dcRouterRef.acmeConfigManager;
|
|
56
|
+
if (!mgr) return { config: null };
|
|
57
|
+
return { config: mgr.getConfig() };
|
|
58
|
+
},
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Update (upsert) the ACME config
|
|
63
|
+
this.typedrouter.addTypedHandler(
|
|
64
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateAcmeConfig>(
|
|
65
|
+
'updateAcmeConfig',
|
|
66
|
+
async (dataArg) => {
|
|
67
|
+
const userId = await this.requireAuth(dataArg, 'acme-config:write');
|
|
68
|
+
const mgr = this.opsServerRef.dcRouterRef.acmeConfigManager;
|
|
69
|
+
if (!mgr) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
message: 'AcmeConfigManager not initialized (DB disabled?)',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const updated = await mgr.updateConfig(
|
|
77
|
+
{
|
|
78
|
+
accountEmail: dataArg.accountEmail,
|
|
79
|
+
enabled: dataArg.enabled,
|
|
80
|
+
useProduction: dataArg.useProduction,
|
|
81
|
+
autoRenew: dataArg.autoRenew,
|
|
82
|
+
renewThresholdDays: dataArg.renewThresholdDays,
|
|
83
|
+
},
|
|
84
|
+
userId,
|
|
85
|
+
);
|
|
86
|
+
return { success: true, config: updated };
|
|
87
|
+
} catch (err: unknown) {
|
|
88
|
+
return { success: false, message: (err as Error).message };
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -46,15 +46,28 @@ export class DnsProviderHandler {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
private registerHandlers(): void {
|
|
49
|
-
// Get all providers
|
|
49
|
+
// Get all providers — prepends the built-in DcRouter pseudo-provider
|
|
50
|
+
// so operators see a uniform "who serves this?" list that includes the
|
|
51
|
+
// authoritative dcrouter alongside external accounts.
|
|
50
52
|
this.typedrouter.addTypedHandler(
|
|
51
53
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsProviders>(
|
|
52
54
|
'getDnsProviders',
|
|
53
55
|
async (dataArg) => {
|
|
54
56
|
await this.requireAuth(dataArg, 'dns-providers:read');
|
|
55
57
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
const synthetic: interfaces.data.IDnsProviderPublic = {
|
|
59
|
+
id: interfaces.data.DCROUTER_BUILTIN_PROVIDER_ID,
|
|
60
|
+
name: 'DcRouter',
|
|
61
|
+
type: 'dcrouter',
|
|
62
|
+
status: 'ok',
|
|
63
|
+
createdAt: 0,
|
|
64
|
+
updatedAt: 0,
|
|
65
|
+
createdBy: 'system',
|
|
66
|
+
hasCredentials: false,
|
|
67
|
+
builtIn: true,
|
|
68
|
+
};
|
|
69
|
+
const real = dnsManager ? await dnsManager.listProviders() : [];
|
|
70
|
+
return { providers: [synthetic, ...real] };
|
|
58
71
|
},
|
|
59
72
|
),
|
|
60
73
|
);
|
|
@@ -78,6 +91,12 @@ export class DnsProviderHandler {
|
|
|
78
91
|
'createDnsProvider',
|
|
79
92
|
async (dataArg) => {
|
|
80
93
|
const userId = await this.requireAuth(dataArg, 'dns-providers:write');
|
|
94
|
+
if (dataArg.type === 'dcrouter') {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
message: 'cannot create built-in provider',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
81
100
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
82
101
|
if (!dnsManager) {
|
|
83
102
|
return { success: false, message: 'DnsManager not initialized (DB disabled?)' };
|
|
@@ -99,6 +118,9 @@ export class DnsProviderHandler {
|
|
|
99
118
|
'updateDnsProvider',
|
|
100
119
|
async (dataArg) => {
|
|
101
120
|
await this.requireAuth(dataArg, 'dns-providers:write');
|
|
121
|
+
if (dataArg.id === interfaces.data.DCROUTER_BUILTIN_PROVIDER_ID) {
|
|
122
|
+
return { success: false, message: 'cannot edit built-in provider' };
|
|
123
|
+
}
|
|
102
124
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
103
125
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
104
126
|
const ok = await dnsManager.updateProvider(dataArg.id, {
|
|
@@ -116,6 +138,9 @@ export class DnsProviderHandler {
|
|
|
116
138
|
'deleteDnsProvider',
|
|
117
139
|
async (dataArg) => {
|
|
118
140
|
await this.requireAuth(dataArg, 'dns-providers:write');
|
|
141
|
+
if (dataArg.id === interfaces.data.DCROUTER_BUILTIN_PROVIDER_ID) {
|
|
142
|
+
return { success: false, message: 'cannot delete built-in provider' };
|
|
143
|
+
}
|
|
119
144
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
120
145
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
121
146
|
return await dnsManager.deleteProvider(dataArg.id, dataArg.force ?? false);
|
|
@@ -129,6 +154,13 @@ export class DnsProviderHandler {
|
|
|
129
154
|
'testDnsProvider',
|
|
130
155
|
async (dataArg) => {
|
|
131
156
|
await this.requireAuth(dataArg, 'dns-providers:read');
|
|
157
|
+
if (dataArg.id === interfaces.data.DCROUTER_BUILTIN_PROVIDER_ID) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
error: 'built-in provider has no external connection to test',
|
|
161
|
+
testedAt: Date.now(),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
132
164
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
133
165
|
if (!dnsManager) {
|
|
134
166
|
return { ok: false, error: 'DnsManager not initialized', testedAt: Date.now() };
|
|
@@ -144,6 +176,12 @@ export class DnsProviderHandler {
|
|
|
144
176
|
'listProviderDomains',
|
|
145
177
|
async (dataArg) => {
|
|
146
178
|
await this.requireAuth(dataArg, 'dns-providers:read');
|
|
179
|
+
if (dataArg.providerId === interfaces.data.DCROUTER_BUILTIN_PROVIDER_ID) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
message: 'built-in provider has no external domain listing — use "Add DcRouter Domain" instead',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
147
185
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
148
186
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
149
187
|
try {
|
|
@@ -71,7 +71,7 @@ export class DomainHandler {
|
|
|
71
71
|
),
|
|
72
72
|
);
|
|
73
73
|
|
|
74
|
-
// Create
|
|
74
|
+
// Create dcrouter-hosted domain
|
|
75
75
|
this.typedrouter.addTypedHandler(
|
|
76
76
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateDomain>(
|
|
77
77
|
'createDomain',
|
|
@@ -80,7 +80,7 @@ export class DomainHandler {
|
|
|
80
80
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
81
81
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
82
82
|
try {
|
|
83
|
-
const id = await dnsManager.
|
|
83
|
+
const id = await dnsManager.createDcrouterDomain({
|
|
84
84
|
name: dataArg.name,
|
|
85
85
|
description: dataArg.description,
|
|
86
86
|
createdBy: userId,
|
|
@@ -16,4 +16,5 @@ export * from './network-target.handler.js';
|
|
|
16
16
|
export * from './users.handler.js';
|
|
17
17
|
export * from './dns-provider.handler.js';
|
|
18
18
|
export * from './domain.handler.js';
|
|
19
|
-
export * from './dns-record.handler.js';
|
|
19
|
+
export * from './dns-record.handler.js';
|
|
20
|
+
export * from './acme-config.handler.js';
|
package/ts_web/appstate.ts
CHANGED
|
@@ -197,6 +197,28 @@ export const certificateStatePart = await appState.getStatePart<ICertificateStat
|
|
|
197
197
|
'soft'
|
|
198
198
|
);
|
|
199
199
|
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// ACME Config State (DB-backed singleton, managed via Domains > Certificates)
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
export interface IAcmeConfigState {
|
|
205
|
+
config: interfaces.data.IAcmeConfig | null;
|
|
206
|
+
isLoading: boolean;
|
|
207
|
+
error: string | null;
|
|
208
|
+
lastUpdated: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const acmeConfigStatePart = await appState.getStatePart<IAcmeConfigState>(
|
|
212
|
+
'acmeConfig',
|
|
213
|
+
{
|
|
214
|
+
config: null,
|
|
215
|
+
isLoading: false,
|
|
216
|
+
error: null,
|
|
217
|
+
lastUpdated: 0,
|
|
218
|
+
},
|
|
219
|
+
'soft',
|
|
220
|
+
);
|
|
221
|
+
|
|
200
222
|
// ============================================================================
|
|
201
223
|
// Remote Ingress State
|
|
202
224
|
// ============================================================================
|
|
@@ -1771,7 +1793,7 @@ export async function fetchProviderDomains(
|
|
|
1771
1793
|
return await request.fire({ identity: context.identity, providerId });
|
|
1772
1794
|
}
|
|
1773
1795
|
|
|
1774
|
-
export const
|
|
1796
|
+
export const createDcrouterDomainAction = domainsStatePart.createAction<{
|
|
1775
1797
|
name: string;
|
|
1776
1798
|
description?: string;
|
|
1777
1799
|
}>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
|
|
@@ -1953,6 +1975,72 @@ export const deleteDnsRecordAction = domainsStatePart.createAction<{ id: string;
|
|
|
1953
1975
|
},
|
|
1954
1976
|
);
|
|
1955
1977
|
|
|
1978
|
+
// ============================================================================
|
|
1979
|
+
// ACME Config Actions
|
|
1980
|
+
// ============================================================================
|
|
1981
|
+
|
|
1982
|
+
export const fetchAcmeConfigAction = acmeConfigStatePart.createAction(
|
|
1983
|
+
async (statePartArg): Promise<IAcmeConfigState> => {
|
|
1984
|
+
const context = getActionContext();
|
|
1985
|
+
const currentState = statePartArg.getState()!;
|
|
1986
|
+
if (!context.identity) return currentState;
|
|
1987
|
+
|
|
1988
|
+
try {
|
|
1989
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1990
|
+
interfaces.requests.IReq_GetAcmeConfig
|
|
1991
|
+
>('/typedrequest', 'getAcmeConfig');
|
|
1992
|
+
const response = await request.fire({ identity: context.identity });
|
|
1993
|
+
return {
|
|
1994
|
+
config: response.config,
|
|
1995
|
+
isLoading: false,
|
|
1996
|
+
error: null,
|
|
1997
|
+
lastUpdated: Date.now(),
|
|
1998
|
+
};
|
|
1999
|
+
} catch (error: unknown) {
|
|
2000
|
+
return {
|
|
2001
|
+
...currentState,
|
|
2002
|
+
isLoading: false,
|
|
2003
|
+
error: error instanceof Error ? error.message : 'Failed to fetch ACME config',
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
},
|
|
2007
|
+
);
|
|
2008
|
+
|
|
2009
|
+
export const updateAcmeConfigAction = acmeConfigStatePart.createAction<{
|
|
2010
|
+
accountEmail?: string;
|
|
2011
|
+
enabled?: boolean;
|
|
2012
|
+
useProduction?: boolean;
|
|
2013
|
+
autoRenew?: boolean;
|
|
2014
|
+
renewThresholdDays?: number;
|
|
2015
|
+
}>(async (statePartArg, dataArg, actionContext): Promise<IAcmeConfigState> => {
|
|
2016
|
+
const context = getActionContext();
|
|
2017
|
+
try {
|
|
2018
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2019
|
+
interfaces.requests.IReq_UpdateAcmeConfig
|
|
2020
|
+
>('/typedrequest', 'updateAcmeConfig');
|
|
2021
|
+
const response = await request.fire({
|
|
2022
|
+
identity: context.identity!,
|
|
2023
|
+
accountEmail: dataArg.accountEmail,
|
|
2024
|
+
enabled: dataArg.enabled,
|
|
2025
|
+
useProduction: dataArg.useProduction,
|
|
2026
|
+
autoRenew: dataArg.autoRenew,
|
|
2027
|
+
renewThresholdDays: dataArg.renewThresholdDays,
|
|
2028
|
+
});
|
|
2029
|
+
if (!response.success) {
|
|
2030
|
+
return {
|
|
2031
|
+
...statePartArg.getState()!,
|
|
2032
|
+
error: response.message || 'Failed to update ACME config',
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
return await actionContext!.dispatch(fetchAcmeConfigAction, null);
|
|
2036
|
+
} catch (error: unknown) {
|
|
2037
|
+
return {
|
|
2038
|
+
...statePartArg.getState()!,
|
|
2039
|
+
error: error instanceof Error ? error.message : 'Failed to update ACME config',
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
});
|
|
2043
|
+
|
|
1956
2044
|
// ============================================================================
|
|
1957
2045
|
// Route Management Actions
|
|
1958
2046
|
// ============================================================================
|
|
@@ -44,12 +44,15 @@ export class DnsProviderForm extends DeesElement {
|
|
|
44
44
|
accessor providerName: string = '';
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Currently selected provider type. Initialized to the first
|
|
48
|
-
* caller can override before mounting (e.g. for edit dialogs).
|
|
47
|
+
* Currently selected provider type. Initialized to the first user-creatable
|
|
48
|
+
* descriptor; caller can override before mounting (e.g. for edit dialogs).
|
|
49
|
+
* The built-in 'dcrouter' pseudo-provider is excluded from the picker —
|
|
50
|
+
* operators cannot create another one.
|
|
49
51
|
*/
|
|
50
52
|
@state()
|
|
51
53
|
accessor selectedType: interfaces.data.TDnsProviderType =
|
|
52
|
-
interfaces.data.dnsProviderTypeDescriptors
|
|
54
|
+
interfaces.data.dnsProviderTypeDescriptors.find((d) => d.type !== 'dcrouter')?.type ??
|
|
55
|
+
'cloudflare';
|
|
53
56
|
|
|
54
57
|
/** When true, hide the type picker — used in edit dialogs. */
|
|
55
58
|
@property({ type: Boolean })
|
|
@@ -102,7 +105,12 @@ export class DnsProviderForm extends DeesElement {
|
|
|
102
105
|
];
|
|
103
106
|
|
|
104
107
|
public render(): TemplateResult {
|
|
105
|
-
|
|
108
|
+
// Exclude the built-in 'dcrouter' pseudo-provider from the type picker —
|
|
109
|
+
// operators cannot create another one, it's surfaced at read time by the
|
|
110
|
+
// backend handler instead.
|
|
111
|
+
const descriptors = interfaces.data.dnsProviderTypeDescriptors.filter(
|
|
112
|
+
(d) => d.type !== 'dcrouter',
|
|
113
|
+
);
|
|
106
114
|
const descriptor = interfaces.data.getDnsProviderTypeDescriptor(this.selectedType);
|
|
107
115
|
|
|
108
116
|
return html`
|