@serve.zone/dcrouter 13.24.0 → 13.26.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.
Files changed (81) hide show
  1. package/.smartconfig.json +3 -11
  2. package/dist_serve/bundle.js +4046 -3552
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.dcrouter.d.ts +2 -1
  5. package/dist_ts/classes.dcrouter.js +6 -4
  6. package/dist_ts/config/classes.api-token-manager.d.ts +2 -2
  7. package/dist_ts/config/classes.api-token-manager.js +31 -3
  8. package/dist_ts/config/classes.route-config-manager.d.ts +1 -0
  9. package/dist_ts/config/classes.route-config-manager.js +38 -1
  10. package/dist_ts/db/documents/classes.api-token.doc.d.ts +2 -1
  11. package/dist_ts/db/documents/classes.api-token.doc.js +8 -2
  12. package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +1 -0
  13. package/dist_ts/db/documents/classes.ip-intelligence.doc.js +8 -2
  14. package/dist_ts/email/classes.email-domain.manager.d.ts +4 -1
  15. package/dist_ts/email/classes.email-domain.manager.js +30 -1
  16. package/dist_ts/email/classes.workapp-mail-manager.d.ts +35 -0
  17. package/dist_ts/email/classes.workapp-mail-manager.js +273 -0
  18. package/dist_ts/email/index.d.ts +1 -0
  19. package/dist_ts/email/index.js +2 -1
  20. package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
  21. package/dist_ts/opsserver/classes.opsserver.js +3 -1
  22. package/dist_ts/opsserver/handlers/admin.handler.js +9 -4
  23. package/dist_ts/opsserver/handlers/api-token.handler.js +2 -2
  24. package/dist_ts/opsserver/handlers/certificate.handler.d.ts +4 -0
  25. package/dist_ts/opsserver/handlers/certificate.handler.js +41 -11
  26. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  27. package/dist_ts/opsserver/handlers/index.js +2 -1
  28. package/dist_ts/opsserver/handlers/workhoster.handler.d.ts +26 -0
  29. package/dist_ts/opsserver/handlers/workhoster.handler.js +402 -0
  30. package/dist_ts/security/classes.security-policy-manager.d.ts +8 -2
  31. package/dist_ts/security/classes.security-policy-manager.js +83 -7
  32. package/dist_ts_apiclient/classes.dcrouterapiclient.d.ts +2 -0
  33. package/dist_ts_apiclient/classes.dcrouterapiclient.js +4 -1
  34. package/dist_ts_apiclient/classes.workhoster.d.ts +14 -0
  35. package/dist_ts_apiclient/classes.workhoster.js +29 -0
  36. package/dist_ts_apiclient/index.d.ts +1 -0
  37. package/dist_ts_apiclient/index.js +2 -1
  38. package/dist_ts_interfaces/data/index.d.ts +1 -0
  39. package/dist_ts_interfaces/data/index.js +2 -1
  40. package/dist_ts_interfaces/data/route-management.d.ts +38 -1
  41. package/dist_ts_interfaces/data/workhoster.d.ts +131 -0
  42. package/dist_ts_interfaces/data/workhoster.js +2 -0
  43. package/dist_ts_interfaces/requests/api-tokens.d.ts +2 -1
  44. package/dist_ts_interfaces/requests/certificate.d.ts +12 -6
  45. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  46. package/dist_ts_interfaces/requests/index.js +2 -1
  47. package/dist_ts_interfaces/requests/workhoster.d.ts +98 -0
  48. package/dist_ts_interfaces/requests/workhoster.js +2 -0
  49. package/dist_ts_migrations/index.js +8 -2
  50. package/dist_ts_web/00_commitinfo_data.js +1 -1
  51. package/dist_ts_web/appstate.d.ts +1 -1
  52. package/dist_ts_web/appstate.js +3 -2
  53. package/dist_ts_web/elements/access/ops-view-apitokens.d.ts +1 -0
  54. package/dist_ts_web/elements/access/ops-view-apitokens.js +58 -3
  55. package/package.json +24 -23
  56. package/readme.md +108 -128
  57. package/ts/00_commitinfo_data.ts +1 -1
  58. package/ts/classes.dcrouter.ts +5 -3
  59. package/ts/config/classes.api-token-manager.ts +32 -1
  60. package/ts/config/classes.route-config-manager.ts +37 -0
  61. package/ts/db/documents/classes.api-token.doc.ts +4 -1
  62. package/ts/db/documents/classes.ip-intelligence.doc.ts +3 -0
  63. package/ts/email/classes.email-domain.manager.ts +33 -1
  64. package/ts/email/classes.workapp-mail-manager.ts +343 -0
  65. package/ts/email/index.ts +1 -0
  66. package/ts/opsserver/classes.opsserver.ts +3 -1
  67. package/ts/opsserver/handlers/admin.handler.ts +11 -4
  68. package/ts/opsserver/handlers/api-token.handler.ts +1 -0
  69. package/ts/opsserver/handlers/certificate.handler.ts +45 -12
  70. package/ts/opsserver/handlers/index.ts +2 -1
  71. package/ts/opsserver/handlers/workhoster.handler.ts +490 -0
  72. package/ts/readme.md +2 -2
  73. package/ts/security/classes.security-policy-manager.ts +90 -8
  74. package/ts_apiclient/classes.dcrouterapiclient.ts +3 -0
  75. package/ts_apiclient/classes.workhoster.ts +49 -0
  76. package/ts_apiclient/index.ts +1 -0
  77. package/ts_apiclient/readme.md +54 -44
  78. package/ts_web/00_commitinfo_data.ts +1 -1
  79. package/ts_web/appstate.ts +7 -1
  80. package/ts_web/elements/access/ops-view-apitokens.ts +58 -3
  81. package/ts_web/readme.md +36 -19
@@ -16,7 +16,7 @@ export interface ISecurityPolicyManagerOptions {
16
16
  }
17
17
 
18
18
  export interface IRemoteIngressFirewallSnapshot {
19
- blockedIps?: string[];
19
+ blockedIps: string[];
20
20
  }
21
21
 
22
22
  export class SecurityPolicyManager {
@@ -122,6 +122,7 @@ export class SecurityPolicyManager {
122
122
  registrantOrg: doc.registrantOrg,
123
123
  registrantCountry: doc.registrantCountry,
124
124
  networkRange: doc.networkRange,
125
+ networkCidrs: doc.networkCidrs,
125
126
  abuseContact: doc.abuseContact,
126
127
  country: doc.country,
127
128
  countryCode: doc.countryCode,
@@ -205,16 +206,22 @@ export class SecurityPolicyManager {
205
206
  }
206
207
 
207
208
  if (rule.type === 'cidr') {
208
- const cidr = this.normalizeCidr(normalizedValue);
209
- if (cidr) blockedCidrs.add(cidr);
209
+ for (const cidr of this.normalizeNetworkEntries(normalizedValue)) {
210
+ blockedCidrs.add(cidr);
211
+ }
210
212
  continue;
211
213
  }
212
214
 
213
215
  for (const doc of intelligenceDocs) {
214
216
  if (!this.ruleMatchesIntelligence(rule, doc)) continue;
215
- const cidr = this.normalizeCidr(doc.networkRange || '');
216
- if (cidr) {
217
- blockedCidrs.add(cidr);
217
+ const networkEntries = this.normalizeNetworkEntryList([
218
+ ...(doc.networkCidrs || []),
219
+ doc.networkRange,
220
+ ]);
221
+ if (networkEntries.length > 0) {
222
+ for (const cidr of networkEntries) {
223
+ blockedCidrs.add(cidr);
224
+ }
218
225
  } else if (this.normalizeIp(doc.ipAddress)) {
219
226
  blockedIps.add(this.normalizeIp(doc.ipAddress)!);
220
227
  }
@@ -231,13 +238,13 @@ export class SecurityPolicyManager {
231
238
  return await this.compilePolicy();
232
239
  }
233
240
 
234
- public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot | undefined> {
241
+ public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot> {
235
242
  const policy = await this.compilePolicy();
236
243
  const blockedIps = [
237
244
  ...policy.blockedIps.filter((ip) => plugins.net.isIP(ip) === 4),
238
245
  ...policy.blockedCidrs.filter((cidr) => plugins.net.isIP(cidr.split('/')[0]) === 4),
239
246
  ];
240
- return blockedIps.length > 0 ? { blockedIps } : undefined;
247
+ return { blockedIps };
241
248
  }
242
249
 
243
250
  private async matchesAnyReactiveRule(doc: IpIntelligenceDoc): Promise<boolean> {
@@ -287,6 +294,81 @@ export class SecurityPolicyManager {
287
294
  return `${ip}/${prefix}`;
288
295
  }
289
296
 
297
+ private normalizeNetworkEntries(value: string): string[] {
298
+ const trimmed = value.trim();
299
+ if (!trimmed) return [];
300
+
301
+ const cidr = this.normalizeCidr(trimmed);
302
+ if (cidr) return [cidr];
303
+
304
+ const rangeParts = trimmed.split(/\s+-\s+/);
305
+ if (rangeParts.length === 2) {
306
+ return this.ipv4RangeToCidrs(rangeParts[0], rangeParts[1]);
307
+ }
308
+
309
+ return [];
310
+ }
311
+
312
+ private normalizeNetworkEntryList(values: Array<string | null | undefined>): string[] {
313
+ const cidrs = new Set<string>();
314
+ for (const value of values) {
315
+ if (!value) continue;
316
+ for (const entry of value.split(',').map((part) => part.trim()).filter(Boolean)) {
317
+ for (const cidr of this.normalizeNetworkEntries(entry)) {
318
+ cidrs.add(cidr);
319
+ }
320
+ }
321
+ }
322
+ return [...cidrs];
323
+ }
324
+
325
+ private ipv4RangeToCidrs(startIp: string, endIp: string): string[] {
326
+ const start = this.ipv4ToBigInt(startIp);
327
+ const end = this.ipv4ToBigInt(endIp);
328
+ if (start === undefined || end === undefined || start > end) return [];
329
+
330
+ const cidrs: string[] = [];
331
+ let current = start;
332
+ while (current <= end) {
333
+ let maxBlockSize = current === 0n ? 1n << 32n : current & -current;
334
+ const remaining = end - current + 1n;
335
+ while (maxBlockSize > remaining) {
336
+ maxBlockSize = maxBlockSize / 2n;
337
+ }
338
+ const prefixLength = 32 - this.powerOfTwoExponent(maxBlockSize);
339
+ cidrs.push(`${this.numberToIpv4(current)}/${prefixLength}`);
340
+ current += maxBlockSize;
341
+ }
342
+ return cidrs;
343
+ }
344
+
345
+ private ipv4ToBigInt(ip: string): bigint | undefined {
346
+ const normalized = this.normalizeIp(ip);
347
+ if (!normalized || plugins.net.isIP(normalized) !== 4) return undefined;
348
+ return normalized
349
+ .split('.')
350
+ .reduce((sum, part) => (sum * 256n) + BigInt(Number(part)), 0n);
351
+ }
352
+
353
+ private numberToIpv4(value: bigint): string {
354
+ return [
355
+ Number((value >> 24n) & 255n),
356
+ Number((value >> 16n) & 255n),
357
+ Number((value >> 8n) & 255n),
358
+ Number(value & 255n),
359
+ ].join('.');
360
+ }
361
+
362
+ private powerOfTwoExponent(value: bigint): number {
363
+ let exponent = 0;
364
+ let remaining = value;
365
+ while (remaining > 1n) {
366
+ remaining >>= 1n;
367
+ exponent++;
368
+ }
369
+ return exponent;
370
+ }
371
+
290
372
  private isPublicIp(ip: string): boolean {
291
373
  const family = plugins.net.isIP(ip);
292
374
  if (family === 4) {
@@ -10,6 +10,7 @@ import { ConfigManager } from './classes.config.js';
10
10
  import { LogManager } from './classes.logs.js';
11
11
  import { EmailManager } from './classes.email.js';
12
12
  import { RadiusManager } from './classes.radius.js';
13
+ import { WorkHosterManager } from './classes.workhoster.js';
13
14
 
14
15
  export interface IDcRouterApiClientOptions {
15
16
  baseUrl: string;
@@ -31,6 +32,7 @@ export class DcRouterApiClient {
31
32
  public logs: LogManager;
32
33
  public emails: EmailManager;
33
34
  public radius: RadiusManager;
35
+ public workHosters: WorkHosterManager;
34
36
 
35
37
  constructor(options: IDcRouterApiClientOptions) {
36
38
  this.baseUrl = options.baseUrl.replace(/\/+$/, '');
@@ -45,6 +47,7 @@ export class DcRouterApiClient {
45
47
  this.logs = new LogManager(this);
46
48
  this.emails = new EmailManager(this);
47
49
  this.radius = new RadiusManager(this);
50
+ this.workHosters = new WorkHosterManager(this);
48
51
  }
49
52
 
50
53
  // =====================
@@ -0,0 +1,49 @@
1
+ import * as interfaces from '../ts_interfaces/index.js';
2
+ import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
3
+
4
+ export class WorkHosterManager {
5
+ constructor(private clientRef: DcRouterApiClient) {}
6
+
7
+ public async getCapabilities(): Promise<interfaces.data.IGatewayCapabilities> {
8
+ const response = await this.clientRef.request<interfaces.requests.IReq_GetGatewayCapabilities>(
9
+ 'getGatewayCapabilities',
10
+ this.clientRef.buildRequestPayload() as any,
11
+ );
12
+ return response.capabilities;
13
+ }
14
+
15
+ public async getDomains(): Promise<interfaces.data.IWorkHosterDomain[]> {
16
+ const response = await this.clientRef.request<interfaces.requests.IReq_GetWorkHosterDomains>(
17
+ 'getWorkHosterDomains',
18
+ this.clientRef.buildRequestPayload() as any,
19
+ );
20
+ return response.domains;
21
+ }
22
+
23
+ public async syncRoute(options: {
24
+ ownership: interfaces.data.IWorkAppRouteOwnership;
25
+ route: interfaces.data.IDcRouterRouteConfig;
26
+ enabled?: boolean;
27
+ }): Promise<interfaces.data.IWorkAppRouteSyncResult> {
28
+ return this.clientRef.request<interfaces.requests.IReq_SyncWorkAppRoute>(
29
+ 'syncWorkAppRoute',
30
+ this.clientRef.buildRequestPayload({
31
+ ownership: options.ownership,
32
+ route: options.route,
33
+ enabled: options.enabled,
34
+ }) as any,
35
+ );
36
+ }
37
+
38
+ public async deleteRoute(
39
+ ownership: interfaces.data.IWorkAppRouteOwnership,
40
+ ): Promise<interfaces.data.IWorkAppRouteSyncResult> {
41
+ return this.clientRef.request<interfaces.requests.IReq_SyncWorkAppRoute>(
42
+ 'syncWorkAppRoute',
43
+ this.clientRef.buildRequestPayload({
44
+ ownership,
45
+ delete: true,
46
+ }) as any,
47
+ );
48
+ }
49
+ }
@@ -7,6 +7,7 @@ export { Certificate, CertificateManager, type ICertificateSummary } from './cla
7
7
  export { ApiToken, ApiTokenBuilder, ApiTokenManager } from './classes.apitoken.js';
8
8
  export { RemoteIngress, RemoteIngressBuilder, RemoteIngressManager } from './classes.remoteingress.js';
9
9
  export { Email, EmailManager } from './classes.email.js';
10
+ export { WorkHosterManager } from './classes.workhoster.js';
10
11
 
11
12
  // Read-only managers
12
13
  export { StatsManager } from './classes.stats.js';
@@ -1,18 +1,18 @@
1
1
  # @serve.zone/dcrouter-apiclient
2
2
 
3
- Typed, object-oriented client for operating a running dcrouter instance. It wraps the OpsServer `/typedrequest` API in managers and resource classes so your scripts can work with routes, certificates, tokens, remote ingress edges, emails, stats, config, logs, and RADIUS without hand-rolling requests.
3
+ `@serve.zone/dcrouter-apiclient` is the object-oriented TypeScript client for the dcrouter OpsServer API. It wraps `/typedrequest` calls in managers, builders, and resource classes for routes, certificates, API tokens, remote ingress, email, stats, config, logs, RADIUS, and WorkHoster integrations.
4
4
 
5
5
  ## Issue Reporting and Security
6
6
 
7
7
  For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
8
8
 
9
- ## Installation
9
+ ## Install
10
10
 
11
11
  ```bash
12
12
  pnpm add @serve.zone/dcrouter-apiclient
13
13
  ```
14
14
 
15
- You can also import the same client through the main package subpath:
15
+ The same client is also exposed as a subpath of the main package:
16
16
 
17
17
  ```typescript
18
18
  import { DcRouterApiClient } from '@serve.zone/dcrouter/apiclient';
@@ -30,7 +30,7 @@ const client = new DcRouterApiClient({
30
30
  await client.login('admin', 'admin');
31
31
 
32
32
  const { routes, warnings } = await client.routes.list();
33
- console.log('route count', routes.length, 'warnings', warnings.length);
33
+ console.log(routes.length, warnings.length);
34
34
 
35
35
  const route = await client.routes.build()
36
36
  .setName('api-gateway')
@@ -38,47 +38,46 @@ const route = await client.routes.build()
38
38
  .setAction({ type: 'forward', targets: [{ host: '127.0.0.1', port: 8080 }] })
39
39
  .save();
40
40
 
41
- await route.toggle(false);
41
+ await route.toggle(true);
42
42
  ```
43
43
 
44
- ## What the Client Gives You
44
+ ## Authentication
45
45
 
46
- | Manager | Purpose |
47
- | --- | --- |
48
- | `client.routes` | List merged routes, create API routes, toggle routes |
49
- | `client.certificates` | Inspect certificates and run certificate operations |
50
- | `client.apiTokens` | Create, list, toggle, roll, and revoke API tokens |
51
- | `client.remoteIngress` | Manage edge registrations, statuses, and connection tokens |
52
- | `client.emails` | Inspect email items and trigger resend flows |
53
- | `client.stats` | Health, statistics, and operational summaries |
54
- | `client.config` | Read the current configuration view |
55
- | `client.logs` | Read recent logs and log-related data |
56
- | `client.radius` | Manage RADIUS clients, VLANs, and sessions |
57
-
58
- ## Authentication Modes
59
-
60
- | Mode | How it works |
61
- | --- | --- |
62
- | Admin login | Call `login(username, password)` and the returned identity is stored on the client |
63
- | API token | Pass `apiToken` in the constructor and it is injected into requests automatically |
46
+ The client supports session login and API-token authentication.
64
47
 
65
48
  ```typescript
66
- const client = new DcRouterApiClient({
49
+ const sessionClient = new DcRouterApiClient({
67
50
  baseUrl: 'https://dcrouter.example.com',
68
- apiToken: 'dcr_your_token_here',
51
+ });
52
+ await sessionClient.login('admin', 'admin');
53
+
54
+ const tokenClient = new DcRouterApiClient({
55
+ baseUrl: 'https://dcrouter.example.com',
56
+ apiToken: 'dcr_token_value',
69
57
  });
70
58
  ```
71
59
 
72
- Important behavior:
60
+ `baseUrl` is normalized by removing trailing slashes. Requests are sent to `${baseUrl}/typedrequest`. `buildRequestPayload()` injects the current identity and optional API token for manager methods.
73
61
 
74
- - `baseUrl` is normalized, and the client automatically calls `${baseUrl}/typedrequest`
75
- - `buildRequestPayload()` injects the current identity and optional API token for you
76
- - system routes can be toggled, but only API routes are meant for edit and delete flows
62
+ ## Manager Map
77
63
 
78
- ## Route Builder Example
64
+ | Manager | Purpose |
65
+ | --- | --- |
66
+ | `client.routes` | List merged routes, build API routes, update/delete API routes, and toggle routes. |
67
+ | `client.certificates` | Inspect certificate summaries and trigger certificate operations. |
68
+ | `client.apiTokens` | Create, list, toggle, roll, and revoke API tokens. |
69
+ | `client.remoteIngress` | Manage edge registrations, statuses, ports, tags, and connection tokens. |
70
+ | `client.emails` | Inspect received/cached email items and trigger resend flows. |
71
+ | `client.workHosters` | Manage WorkHoster-facing route/application integration calls. |
72
+ | `client.stats` | Read health, counters, summaries, and runtime status. |
73
+ | `client.config` | Read the current configuration view. |
74
+ | `client.logs` | Read recent log information. |
75
+ | `client.radius` | Manage RADIUS clients, VLAN mappings, and accounting sessions. |
76
+
77
+ ## Route Builder
79
78
 
80
79
  ```typescript
81
- const newRoute = await client.routes.build()
80
+ const route = await client.routes.build()
82
81
  .setName('internal-app')
83
82
  .setMatch({
84
83
  ports: 443,
@@ -91,7 +90,7 @@ const newRoute = await client.routes.build()
91
90
  .setEnabled(true)
92
91
  .save();
93
92
 
94
- await newRoute.update({
93
+ await route.update({
95
94
  action: {
96
95
  type: 'forward',
97
96
  targets: [{ host: '127.0.0.1', port: 3001 }],
@@ -99,17 +98,17 @@ await newRoute.update({
99
98
  });
100
99
  ```
101
100
 
102
- ## Token and Remote Ingress Example
101
+ System routes from `config`, `email`, and `dns` origins are designed to be toggled, not edited. Full create/update/delete behavior is for routes with origin `api`.
102
+
103
+ ## API Tokens and Remote Ingress
103
104
 
104
105
  ```typescript
105
106
  const token = await client.apiTokens.build()
106
- .setName('ci-token')
107
+ .setName('automation')
107
108
  .setScopes(['routes:read', 'routes:write'])
108
109
  .setExpiresInDays(30)
109
110
  .save();
110
111
 
111
- console.log('copy this once:', token.tokenValue);
112
-
113
112
  const edge = await client.remoteIngress.build()
114
113
  .setName('edge-eu-1')
115
114
  .setListenPorts([80, 443])
@@ -118,20 +117,31 @@ const edge = await client.remoteIngress.build()
118
117
  .save();
119
118
 
120
119
  const connectionToken = await edge.getConnectionToken();
121
- console.log(connectionToken);
120
+ console.log(token.tokenValue, connectionToken);
122
121
  ```
123
122
 
124
- ## What This Package Does Not Do
123
+ ## What This Package Is Not
125
124
 
126
125
  - It does not start dcrouter.
127
- - It does not bundle the dashboard.
128
- - It does not replace the raw interfaces package when you want low-level TypedRequest contracts.
126
+ - It does not serve or bundle the Ops dashboard.
127
+ - It does not replace `@serve.zone/dcrouter-interfaces` when you want raw TypedRequest contracts.
128
+
129
+ Use `@serve.zone/dcrouter` for the server runtime and `@serve.zone/dcrouter-interfaces` for shared request/data types.
130
+
131
+ ## Development
132
+
133
+ This folder is published from the dcrouter monorepo via `tspublish.json` with order `5`.
134
+
135
+ Useful source entry points:
129
136
 
130
- Use `@serve.zone/dcrouter` to run the server and `@serve.zone/dcrouter-interfaces` for the shared request/data types.
137
+ - `index.ts` exports the public client surface.
138
+ - `classes.dcrouterapiclient.ts` owns authentication and request dispatch.
139
+ - `classes.route.ts` owns route resources and builders.
140
+ - `classes.remoteingress.ts`, `classes.apitoken.ts`, `classes.radius.ts`, and the other manager files wrap focused API domains.
131
141
 
132
142
  ## License and Legal Information
133
143
 
134
- This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../license) file.
144
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](../license) file.
135
145
 
136
146
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
137
147
 
@@ -143,7 +153,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
143
153
 
144
154
  ### Company Information
145
155
 
146
- Task Venture Capital GmbH
156
+ Task Venture Capital GmbH
147
157
  Registered at District Court Bremen HRB 35230 HB, Germany
148
158
 
149
159
  For any legal inquiries or further information, please contact us via email at hello@task.vc.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.24.0',
6
+ version: '13.26.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -2506,7 +2506,12 @@ export const fetchUsersAction = usersStatePart.createAction(async (statePartArg)
2506
2506
  }
2507
2507
  });
2508
2508
 
2509
- export async function createApiToken(name: string, scopes: interfaces.data.TApiTokenScope[], expiresInDays?: number | null) {
2509
+ export async function createApiToken(
2510
+ name: string,
2511
+ scopes: interfaces.data.TApiTokenScope[],
2512
+ expiresInDays?: number | null,
2513
+ policy?: any,
2514
+ ) {
2510
2515
  const context = getActionContext();
2511
2516
  const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2512
2517
  interfaces.requests.IReq_CreateApiToken
@@ -2516,6 +2521,7 @@ export async function createApiToken(name: string, scopes: interfaces.data.TApiT
2516
2521
  identity: context.identity!,
2517
2522
  name,
2518
2523
  scopes,
2524
+ policy,
2519
2525
  expiresInDays,
2520
2526
  });
2521
2527
  }
@@ -199,12 +199,25 @@ export class OpsViewApiTokens extends DeesElement {
199
199
  private async showCreateTokenDialog() {
200
200
  const { DeesModal } = await import('@design.estate/dees-catalog');
201
201
 
202
- const allScopes: TApiTokenScope[] = [
202
+ const allScopes = [
203
+ '*',
203
204
  'routes:read',
204
205
  'routes:write',
205
206
  'config:read',
207
+ 'certificates:read',
208
+ 'certificates:write',
206
209
  'tokens:read',
207
210
  'tokens:manage',
211
+ 'domains:read',
212
+ 'domains:write',
213
+ 'dns-records:read',
214
+ 'dns-records:write',
215
+ 'email-domains:read',
216
+ 'email-domains:write',
217
+ 'gateway-clients:read',
218
+ 'gateway-clients:write',
219
+ 'workhosters:read',
220
+ 'workhosters:write',
208
221
  ];
209
222
 
210
223
  await DeesModal.createAndShow({
@@ -218,10 +231,15 @@ export class OpsViewApiTokens extends DeesElement {
218
231
  <dees-input-tags
219
232
  .key=${'scopes'}
220
233
  .label=${'Token Scopes'}
221
- .value=${['routes:read', 'routes:write']}
234
+ .value=${['gateway-clients:read', 'gateway-clients:write']}
222
235
  .suggestions=${allScopes}
223
236
  .required=${true}
224
237
  ></dees-input-tags>
238
+ <dees-input-text .key=${'policyRole'} .label=${'Policy Role'} .description=${'admin, gatewayClient, or operator'}></dees-input-text>
239
+ <dees-input-text .key=${'gatewayClientType'} .label=${'Gateway Client Type'} .description=${'For gatewayClient tokens: onebox, cloudly, or custom'}></dees-input-text>
240
+ <dees-input-text .key=${'gatewayClientId'} .label=${'Gateway Client ID'} .description=${'Required for gatewayClient tokens'}></dees-input-text>
241
+ <dees-input-text .key=${'hostnamePatterns'} .label=${'Hostname Patterns'} .description=${'Comma separated, e.g. *.apps.example.com'}></dees-input-text>
242
+ <dees-input-text .key=${'allowedRouteTarget'} .label=${'Allowed Route Target'} .description=${'Optional host:ports, e.g. 203.0.113.10:80,443'}></dees-input-text>
225
243
  <dees-input-text .key=${'expiresInDays'} .label=${'Expires in'} .description=${'Number of days; leave blank for no expiration'}></dees-input-text>
226
244
  </dees-form>
227
245
  `,
@@ -247,6 +265,7 @@ export class OpsViewApiTokens extends DeesElement {
247
265
  const rawScopes: string[] = tagsInput?.getValue?.() || tagsInput?.value || formData.scopes || [];
248
266
  const scopes = rawScopes
249
267
  .filter((s: string) => allScopes.includes(s as any)) as TApiTokenScope[];
268
+ const policy = this.buildPolicy(formData, scopes);
250
269
 
251
270
  const expiresInDays = formData.expiresInDays
252
271
  ? parseInt(formData.expiresInDays, 10)
@@ -255,7 +274,7 @@ export class OpsViewApiTokens extends DeesElement {
255
274
  await modalArg.destroy();
256
275
 
257
276
  try {
258
- const response = await appstate.createApiToken(formData.name, scopes, expiresInDays);
277
+ const response = await appstate.createApiToken(formData.name, scopes, expiresInDays, policy);
259
278
  if (response.success && response.tokenValue) {
260
279
  // Refresh the list first so it's ready when user dismisses the modal
261
280
  await appstate.routeManagementStatePart.dispatchAction(appstate.fetchApiTokensAction, null);
@@ -289,6 +308,42 @@ export class OpsViewApiTokens extends DeesElement {
289
308
  });
290
309
  }
291
310
 
311
+ private buildPolicy(formData: any, scopes: TApiTokenScope[]): any | undefined {
312
+ const role = String(formData.policyRole || '').trim();
313
+ if (!role) return undefined;
314
+ const policy: any = {
315
+ role,
316
+ scopes,
317
+ };
318
+ if (role === 'gatewayClient') {
319
+ const type = String(formData.gatewayClientType || 'onebox').trim() as 'onebox' | 'cloudly' | 'custom';
320
+ const id = String(formData.gatewayClientId || '').trim();
321
+ if (id) {
322
+ policy.gatewayClient = { type, id };
323
+ }
324
+ policy.hostnamePatterns = String(formData.hostnamePatterns || '')
325
+ .split(',')
326
+ .map((pattern) => pattern.trim())
327
+ .filter(Boolean);
328
+ const target = String(formData.allowedRouteTarget || '').trim();
329
+ if (target.includes(':')) {
330
+ const [host, portsValue] = target.split(':');
331
+ policy.allowedRouteTargets = [{
332
+ host: host.trim(),
333
+ ports: portsValue.split(',').map((port) => Number(port.trim())).filter((port) => Number.isInteger(port)),
334
+ }];
335
+ }
336
+ policy.capabilities = {
337
+ readDomains: true,
338
+ readDnsRecords: true,
339
+ syncRoutes: true,
340
+ syncDnsRecords: false,
341
+ requestCertificates: false,
342
+ };
343
+ }
344
+ return policy;
345
+ }
346
+
292
347
  private async showRollTokenDialog(token: interfaces.data.IApiTokenInfo) {
293
348
  const { DeesModal } = await import('@design.estate/dees-catalog');
294
349
 
package/ts_web/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @serve.zone/dcrouter-web
2
2
 
3
- Browser-side frontend for the dcrouter Ops dashboard. This folder is the SPA entrypoint, router, app state, and web-component UI rendered by OpsServer.
3
+ `@serve.zone/dcrouter-web` is the browser-side Ops dashboard module for dcrouter. It provides the SPA entry point, route synchronization, app state, and web-component views that OpsServer serves from the main dcrouter runtime.
4
4
 
5
5
  ## Issue Reporting and Security
6
6
 
@@ -8,10 +8,12 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
8
8
 
9
9
  ## What It Boots
10
10
 
11
- - `index.ts` initializes the app router and renders `<ops-dashboard>` into `document.body`
12
- - `router.ts` defines top-level dashboard routes and subviews
13
- - `appstate.ts` holds reactive state, TypedRequest actions, and TypedSocket log streaming
14
- - `elements/` contains the dashboard shell and feature views
11
+ | File | Purpose |
12
+ | --- | --- |
13
+ | `index.ts` | Initializes the app router and renders `<ops-dashboard>` into `document.body`. |
14
+ | `router.ts` | Defines top-level dashboard routes, subviews, redirects, and URL/state synchronization. |
15
+ | `appstate.ts` | Holds reactive login, UI, config, stats, route, DNS, email, remote ingress, VPN, and log state. |
16
+ | `elements/` | Contains the dashboard shell and feature-specific Dees web components. |
15
17
 
16
18
  ## View Map
17
19
 
@@ -20,37 +22,52 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
20
22
  | `overview` | `stats`, `configuration` |
21
23
  | `network` | `activity`, `routes`, `sourceprofiles`, `networktargets`, `targetprofiles`, `remoteingress`, `vpn` |
22
24
  | `email` | `log`, `security`, `domains` |
25
+ | `logs` | flat view |
23
26
  | `access` | `apitokens`, `users` |
24
27
  | `security` | `overview`, `blocked`, `authentication` |
25
28
  | `domains` | `providers`, `domains`, `dns`, `certificates` |
26
- | `logs` | flat view |
27
29
 
28
- ## How It Talks To dcrouter
30
+ ## Runtime Communication
31
+
32
+ The dashboard talks to the dcrouter OpsServer through:
33
+
34
+ - TypedRequest calls for normal API actions
35
+ - shared contracts from `@serve.zone/dcrouter-interfaces`
36
+ - TypedSocket log streaming for live operational output
37
+ - Dees web components and app-state subscriptions for UI updates
38
+ - QR code rendering for VPN client UX
39
+
40
+ ## Usage
41
+
42
+ This package is primarily consumed by the main dcrouter build and served by OpsServer. Install it directly only when you intentionally need the dashboard module boundary.
43
+
44
+ ```bash
45
+ pnpm add @serve.zone/dcrouter-web
46
+ ```
29
47
 
30
- - TypedRequest for the main API surface
31
- - shared request and data contracts from `@serve.zone/dcrouter-interfaces`
32
- - TypedSocket for real-time log streaming
33
- - QR code generation for VPN client UX
48
+ For the full server and hosted dashboard, use `@serve.zone/dcrouter`.
34
49
 
35
- ## Development Notes
50
+ ## Development
36
51
 
37
- This package is the frontend module boundary, but it is built and served as part of the main workspace.
52
+ This folder is published from the dcrouter monorepo via `tspublish.json` with order `4`.
38
53
 
39
54
  ```bash
40
55
  pnpm run build
41
56
  pnpm run watch
42
57
  ```
43
58
 
44
- The built dashboard assets are emitted into `dist_serve/` by the workspace build pipeline.
59
+ The dcrouter build emits served dashboard assets into `dist_serve/`.
45
60
 
46
- ## What This Package Is For
61
+ Useful source entry points:
47
62
 
48
- - Use it when you want the dashboard frontend as its own published module boundary.
49
- - Use `@serve.zone/dcrouter` when you want the server that actually hosts this UI and the backend API.
63
+ - `index.ts` boots the frontend.
64
+ - `router.ts` owns URL/view state synchronization.
65
+ - `elements/ops-dashboard.ts` defines the app shell and tab map.
66
+ - `elements/network/`, `elements/domains/`, `elements/email/`, `elements/security/`, `elements/access/`, and `elements/overview/` hold feature views.
50
67
 
51
68
  ## License and Legal Information
52
69
 
53
- This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../license) file.
70
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](../license) file.
54
71
 
55
72
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
56
73
 
@@ -62,7 +79,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
62
79
 
63
80
  ### Company Information
64
81
 
65
- Task Venture Capital GmbH
82
+ Task Venture Capital GmbH
66
83
  Registered at District Court Bremen HRB 35230 HB, Germany
67
84
 
68
85
  For any legal inquiries or further information, please contact us via email at hello@task.vc.