@serve.zone/dcrouter 13.30.0 → 13.32.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 (88) hide show
  1. package/dist_serve/bundle.js +1042 -1014
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +1 -1
  4. package/dist_ts/opsserver/classes.opsserver.d.ts +4 -2
  5. package/dist_ts/opsserver/classes.opsserver.js +2 -11
  6. package/dist_ts/opsserver/handlers/acme-config.handler.js +7 -24
  7. package/dist_ts/opsserver/handlers/admin.handler.d.ts +12 -0
  8. package/dist_ts/opsserver/handlers/admin.handler.js +129 -95
  9. package/dist_ts/opsserver/handlers/api-token.handler.js +28 -2
  10. package/dist_ts/opsserver/handlers/certificate.handler.js +7 -24
  11. package/dist_ts/opsserver/handlers/config.handler.js +3 -1
  12. package/dist_ts/opsserver/handlers/dns-provider.handler.js +7 -24
  13. package/dist_ts/opsserver/handlers/dns-record.handler.js +7 -24
  14. package/dist_ts/opsserver/handlers/domain.handler.js +7 -24
  15. package/dist_ts/opsserver/handlers/email-domain.handler.js +7 -24
  16. package/dist_ts/opsserver/handlers/email-ops.handler.js +8 -1
  17. package/dist_ts/opsserver/handlers/logs.handler.js +4 -1
  18. package/dist_ts/opsserver/handlers/network-target.handler.js +7 -24
  19. package/dist_ts/opsserver/handlers/radius.handler.js +32 -1
  20. package/dist_ts/opsserver/handlers/remoteingress.handler.js +24 -1
  21. package/dist_ts/opsserver/handlers/route-management.handler.js +7 -26
  22. package/dist_ts/opsserver/handlers/security.handler.js +32 -7
  23. package/dist_ts/opsserver/handlers/source-profile.handler.js +7 -24
  24. package/dist_ts/opsserver/handlers/stats.handler.js +8 -1
  25. package/dist_ts/opsserver/handlers/target-profile.handler.js +7 -24
  26. package/dist_ts/opsserver/handlers/users.handler.d.ts +1 -1
  27. package/dist_ts/opsserver/handlers/users.handler.js +35 -4
  28. package/dist_ts/opsserver/handlers/vpn.handler.js +34 -1
  29. package/dist_ts/opsserver/handlers/workhoster.handler.js +16 -35
  30. package/dist_ts/opsserver/helpers/auth.d.ts +21 -0
  31. package/dist_ts/opsserver/helpers/auth.js +63 -0
  32. package/dist_ts_interfaces/data/route-management.d.ts +2 -1
  33. package/dist_ts_interfaces/data/route-management.js +48 -2
  34. package/dist_ts_interfaces/requests/api-tokens.d.ts +10 -5
  35. package/dist_ts_interfaces/requests/combined.stats.d.ts +2 -1
  36. package/dist_ts_interfaces/requests/config.d.ts +2 -1
  37. package/dist_ts_interfaces/requests/email-ops.d.ts +6 -3
  38. package/dist_ts_interfaces/requests/logs.d.ts +4 -2
  39. package/dist_ts_interfaces/requests/radius.d.ts +24 -12
  40. package/dist_ts_interfaces/requests/remoteingress.d.ts +14 -7
  41. package/dist_ts_interfaces/requests/security-policy.d.ts +16 -8
  42. package/dist_ts_interfaces/requests/stats.d.ts +18 -9
  43. package/dist_ts_interfaces/requests/users.d.ts +39 -2
  44. package/dist_ts_interfaces/requests/vpn.d.ts +22 -11
  45. package/dist_ts_interfaces/requests/workhoster.d.ts +10 -5
  46. package/dist_ts_migrations/index.js +3 -1
  47. package/dist_ts_web/00_commitinfo_data.js +1 -1
  48. package/dist_ts_web/appstate.d.ts +8 -0
  49. package/dist_ts_web/appstate.js +52 -2
  50. package/dist_ts_web/elements/access/ops-view-apitokens.js +2 -21
  51. package/dist_ts_web/elements/access/ops-view-users.d.ts +2 -0
  52. package/dist_ts_web/elements/access/ops-view-users.js +133 -2
  53. package/dist_ts_web/elements/network/ops-view-routes.js +11 -1
  54. package/dist_ts_web/elements/ops-dashboard.js +2 -4
  55. package/package.json +3 -3
  56. package/readme.md +1 -1
  57. package/ts/00_commitinfo_data.ts +1 -1
  58. package/ts/classes.dcrouter.ts +1 -1
  59. package/ts/opsserver/classes.opsserver.ts +3 -14
  60. package/ts/opsserver/handlers/acme-config.handler.ts +6 -23
  61. package/ts/opsserver/handlers/admin.handler.ts +155 -111
  62. package/ts/opsserver/handlers/api-token.handler.ts +27 -1
  63. package/ts/opsserver/handlers/certificate.handler.ts +6 -23
  64. package/ts/opsserver/handlers/config.handler.ts +2 -0
  65. package/ts/opsserver/handlers/dns-provider.handler.ts +6 -23
  66. package/ts/opsserver/handlers/dns-record.handler.ts +6 -23
  67. package/ts/opsserver/handlers/domain.handler.ts +6 -23
  68. package/ts/opsserver/handlers/email-domain.handler.ts +6 -23
  69. package/ts/opsserver/handlers/email-ops.handler.ts +7 -0
  70. package/ts/opsserver/handlers/logs.handler.ts +3 -0
  71. package/ts/opsserver/handlers/network-target.handler.ts +6 -23
  72. package/ts/opsserver/handlers/radius.handler.ts +31 -0
  73. package/ts/opsserver/handlers/remoteingress.handler.ts +23 -0
  74. package/ts/opsserver/handlers/route-management.handler.ts +6 -25
  75. package/ts/opsserver/handlers/security.handler.ts +31 -6
  76. package/ts/opsserver/handlers/source-profile.handler.ts +6 -23
  77. package/ts/opsserver/handlers/stats.handler.ts +7 -0
  78. package/ts/opsserver/handlers/target-profile.handler.ts +6 -23
  79. package/ts/opsserver/handlers/users.handler.ts +46 -3
  80. package/ts/opsserver/handlers/vpn.handler.ts +33 -0
  81. package/ts/opsserver/handlers/workhoster.handler.ts +18 -33
  82. package/ts/opsserver/helpers/auth.ts +91 -0
  83. package/ts_web/00_commitinfo_data.ts +1 -1
  84. package/ts_web/appstate.ts +69 -1
  85. package/ts_web/elements/access/ops-view-apitokens.ts +1 -20
  86. package/ts_web/elements/access/ops-view-users.ts +139 -1
  87. package/ts_web/elements/network/ops-view-routes.ts +9 -0
  88. package/ts_web/elements/ops-dashboard.ts +1 -3
@@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
4
  import { MetricsManager } from '../../monitoring/index.js';
5
+ import { requireOpsAuth } from '../helpers/auth.js';
5
6
 
6
7
  export class SecurityHandler {
7
8
  constructor(private opsServerRef: OpsServer) {
@@ -17,6 +18,7 @@ export class SecurityHandler {
17
18
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityMetrics>(
18
19
  'getSecurityMetrics',
19
20
  async (dataArg, toolsArg) => {
21
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'security:read' });
20
22
  const metrics = await this.collectSecurityMetrics();
21
23
  return {
22
24
  metrics: {
@@ -43,6 +45,7 @@ export class SecurityHandler {
43
45
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetActiveConnections>(
44
46
  'getActiveConnections',
45
47
  async (dataArg, toolsArg) => {
48
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
46
49
  const connections = await this.getActiveConnections(dataArg.protocol, dataArg.state);
47
50
  const connectionInfos: interfaces.data.IConnectionInfo[] = connections.map(conn => ({
48
51
  id: conn.id,
@@ -82,6 +85,7 @@ export class SecurityHandler {
82
85
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkStats>(
83
86
  'getNetworkStats',
84
87
  async (dataArg, toolsArg) => {
88
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
85
89
  // Get network stats from MetricsManager if available
86
90
  if (this.opsServerRef.dcRouterRef.metricsManager) {
87
91
  const networkStats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
@@ -136,6 +140,7 @@ export class SecurityHandler {
136
140
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRateLimitStatus>(
137
141
  'getRateLimitStatus',
138
142
  async (dataArg, toolsArg) => {
143
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'security:read' });
139
144
  const status = await this.getRateLimitStatus(dataArg.domain, dataArg.ip);
140
145
  const limits: interfaces.data.IRateLimitInfo[] = status.limits.map(limit => ({
141
146
  domain: limit.identifier,
@@ -161,7 +166,8 @@ export class SecurityHandler {
161
166
  router.addTypedHandler(
162
167
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListSecurityBlockRules>(
163
168
  'listSecurityBlockRules',
164
- async () => {
169
+ async (dataArg) => {
170
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'security:read' });
165
171
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
166
172
  return { rules: manager ? await manager.listBlockRules() : [] };
167
173
  },
@@ -171,7 +177,8 @@ export class SecurityHandler {
171
177
  router.addTypedHandler(
172
178
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListIpIntelligence>(
173
179
  'listIpIntelligence',
174
- async () => {
180
+ async (dataArg) => {
181
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'security:read' });
175
182
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
176
183
  return { records: manager ? await manager.listIpIntelligence() : [] };
177
184
  },
@@ -181,7 +188,8 @@ export class SecurityHandler {
181
188
  router.addTypedHandler(
182
189
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCompiledSecurityPolicy>(
183
190
  'getCompiledSecurityPolicy',
184
- async () => {
191
+ async (dataArg) => {
192
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'security:read' });
185
193
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
186
194
  return {
187
195
  policy: manager
@@ -196,6 +204,7 @@ export class SecurityHandler {
196
204
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListSecurityPolicyAudit>(
197
205
  'listSecurityPolicyAudit',
198
206
  async (dataArg) => {
207
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'security:read' });
199
208
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
200
209
  return { events: manager ? await manager.listAuditEvents(dataArg.limit || 100) : [] };
201
210
  },
@@ -208,6 +217,10 @@ export class SecurityHandler {
208
217
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateSecurityBlockRule>(
209
218
  'createSecurityBlockRule',
210
219
  async (dataArg) => {
220
+ const auth = await requireOpsAuth(this.opsServerRef, dataArg, {
221
+ scope: 'security:write',
222
+ requireAdminIdentity: true,
223
+ });
211
224
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
212
225
  if (!manager) return { success: false, message: 'Security policy manager not initialized' };
213
226
  const rule = await manager.createBlockRule({
@@ -216,7 +229,7 @@ export class SecurityHandler {
216
229
  matchMode: dataArg.matchMode,
217
230
  reason: dataArg.reason,
218
231
  enabled: dataArg.enabled,
219
- }, dataArg.identity.userId);
232
+ }, auth.userId);
220
233
  return { success: true, rule };
221
234
  },
222
235
  ),
@@ -226,6 +239,10 @@ export class SecurityHandler {
226
239
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSecurityBlockRule>(
227
240
  'updateSecurityBlockRule',
228
241
  async (dataArg) => {
242
+ const auth = await requireOpsAuth(this.opsServerRef, dataArg, {
243
+ scope: 'security:write',
244
+ requireAdminIdentity: true,
245
+ });
229
246
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
230
247
  if (!manager) return { success: false, message: 'Security policy manager not initialized' };
231
248
  const rule = await manager.updateBlockRule(dataArg.id, {
@@ -233,7 +250,7 @@ export class SecurityHandler {
233
250
  matchMode: dataArg.matchMode,
234
251
  reason: dataArg.reason,
235
252
  enabled: dataArg.enabled,
236
- }, dataArg.identity.userId);
253
+ }, auth.userId);
237
254
  return rule ? { success: true, rule } : { success: false, message: 'Rule not found' };
238
255
  },
239
256
  ),
@@ -243,9 +260,13 @@ export class SecurityHandler {
243
260
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteSecurityBlockRule>(
244
261
  'deleteSecurityBlockRule',
245
262
  async (dataArg) => {
263
+ const auth = await requireOpsAuth(this.opsServerRef, dataArg, {
264
+ scope: 'security:write',
265
+ requireAdminIdentity: true,
266
+ });
246
267
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
247
268
  if (!manager) return { success: false, message: 'Security policy manager not initialized' };
248
- const success = await manager.deleteBlockRule(dataArg.id, dataArg.identity.userId);
269
+ const success = await manager.deleteBlockRule(dataArg.id, auth.userId);
249
270
  return { success, message: success ? undefined : 'Rule not found' };
250
271
  },
251
272
  ),
@@ -255,6 +276,10 @@ export class SecurityHandler {
255
276
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RefreshIpIntelligence>(
256
277
  'refreshIpIntelligence',
257
278
  async (dataArg) => {
279
+ await requireOpsAuth(this.opsServerRef, dataArg, {
280
+ scope: 'security:write',
281
+ requireAdminIdentity: true,
282
+ });
258
283
  const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
259
284
  if (!manager) return { success: false, message: 'Security policy manager not initialized' };
260
285
  const record = await manager.refreshIpIntelligence(dataArg.ipAddress);
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
+ import { requireOpsAuth } from '../helpers/auth.js';
4
5
 
5
6
  export class SourceProfileHandler {
6
7
  public typedrouter = new plugins.typedrequest.TypedRouter();
@@ -14,29 +15,11 @@ export class SourceProfileHandler {
14
15
  request: { identity?: interfaces.data.IIdentity; apiToken?: string },
15
16
  requiredScope?: interfaces.data.TApiTokenScope,
16
17
  ): Promise<string> {
17
- if (request.identity?.jwt) {
18
- try {
19
- const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
20
- identity: request.identity,
21
- });
22
- if (isAdmin) return request.identity.userId;
23
- } catch { /* fall through */ }
24
- }
25
-
26
- if (request.apiToken) {
27
- const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
28
- if (tokenManager) {
29
- const token = await tokenManager.validateToken(request.apiToken);
30
- if (token) {
31
- if (!requiredScope || tokenManager.hasScope(token, requiredScope)) {
32
- return token.createdBy;
33
- }
34
- throw new plugins.typedrequest.TypedResponseError('insufficient scope');
35
- }
36
- }
37
- }
38
-
39
- throw new plugins.typedrequest.TypedResponseError('unauthorized');
18
+ const auth = await requireOpsAuth(this.opsServerRef, request, {
19
+ scope: requiredScope,
20
+ requireAdminIdentity: requiredScope?.endsWith(':write'),
21
+ });
22
+ return auth.userId;
40
23
  }
41
24
 
42
25
  private registerHandlers(): void {
@@ -4,6 +4,7 @@ import * as interfaces from '../../../ts_interfaces/index.js';
4
4
  import { MetricsManager } from '../../monitoring/index.js';
5
5
  import { SecurityLogger } from '../../security/classes.securitylogger.js';
6
6
  import { commitinfo } from '../../00_commitinfo_data.js';
7
+ import { requireOpsAuth } from '../helpers/auth.js';
7
8
 
8
9
  export class StatsHandler {
9
10
  constructor(private opsServerRef: OpsServer) {
@@ -19,6 +20,7 @@ export class StatsHandler {
19
20
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServerStatistics>(
20
21
  'getServerStatistics',
21
22
  async (dataArg, toolsArg) => {
23
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
22
24
  const stats = await this.collectServerStats();
23
25
  return {
24
26
  stats: {
@@ -42,6 +44,7 @@ export class StatsHandler {
42
44
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailStatistics>(
43
45
  'getEmailStatistics',
44
46
  async (dataArg, toolsArg) => {
47
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
45
48
  const emailServer = this.opsServerRef.dcRouterRef.emailServer;
46
49
  if (!emailServer) {
47
50
  return {
@@ -81,6 +84,7 @@ export class StatsHandler {
81
84
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsStatistics>(
82
85
  'getDnsStatistics',
83
86
  async (dataArg, toolsArg) => {
87
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
84
88
  const dnsServer = this.opsServerRef.dcRouterRef.dnsServer;
85
89
  if (!dnsServer) {
86
90
  return {
@@ -118,6 +122,7 @@ export class StatsHandler {
118
122
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueueStatus>(
119
123
  'getQueueStatus',
120
124
  async (dataArg, toolsArg) => {
125
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
121
126
  const emailServer = this.opsServerRef.dcRouterRef.emailServer;
122
127
  const queues: interfaces.data.IQueueStatus[] = [];
123
128
 
@@ -146,6 +151,7 @@ export class StatsHandler {
146
151
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHealthStatus>(
147
152
  'getHealthStatus',
148
153
  async (dataArg, toolsArg) => {
154
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
149
155
  const health = await this.checkHealthStatus();
150
156
  return {
151
157
  health: {
@@ -171,6 +177,7 @@ export class StatsHandler {
171
177
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCombinedMetrics>(
172
178
  'getCombinedMetrics',
173
179
  async (dataArg, toolsArg) => {
180
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
174
181
  const sections = dataArg.sections || {
175
182
  server: true,
176
183
  email: true,
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
+ import { requireOpsAuth } from '../helpers/auth.js';
4
5
 
5
6
  export class TargetProfileHandler {
6
7
  public typedrouter = new plugins.typedrequest.TypedRouter();
@@ -14,29 +15,11 @@ export class TargetProfileHandler {
14
15
  request: { identity?: interfaces.data.IIdentity; apiToken?: string },
15
16
  requiredScope?: interfaces.data.TApiTokenScope,
16
17
  ): Promise<string> {
17
- if (request.identity?.jwt) {
18
- try {
19
- const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
20
- identity: request.identity,
21
- });
22
- if (isAdmin) return request.identity.userId;
23
- } catch { /* fall through */ }
24
- }
25
-
26
- if (request.apiToken) {
27
- const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
28
- if (tokenManager) {
29
- const token = await tokenManager.validateToken(request.apiToken);
30
- if (token) {
31
- if (!requiredScope || tokenManager.hasScope(token, requiredScope)) {
32
- return token.createdBy;
33
- }
34
- throw new plugins.typedrequest.TypedResponseError('insufficient scope');
35
- }
36
- }
37
- }
38
-
39
- throw new plugins.typedrequest.TypedResponseError('unauthorized');
18
+ const auth = await requireOpsAuth(this.opsServerRef, request, {
19
+ scope: requiredScope,
20
+ requireAdminIdentity: requiredScope?.endsWith(':write'),
21
+ });
22
+ return auth.userId;
40
23
  }
41
24
 
42
25
  private registerHandlers(): void {
@@ -1,9 +1,10 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
+ import { requireOpsAuth } from '../helpers/auth.js';
4
5
 
5
6
  /**
6
- * Read-only handler for OpsServer user accounts. Registers on adminRouter,
7
+ * Handler for OpsServer user accounts. Registers on adminRouter,
7
8
  * so admin middleware enforces auth + role check before the handler runs.
8
9
  * User data is owned by AdminHandler; this handler just exposes a safe
9
10
  * projection of it via TypedRequest.
@@ -16,15 +17,57 @@ export class UsersHandler {
16
17
  private registerHandlers(): void {
17
18
  const router = this.opsServerRef.adminRouter;
18
19
 
19
- // List users (admin-only, read-only)
20
+ // List users (admin-only)
20
21
  router.addTypedHandler(
21
22
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListUsers>(
22
23
  'listUsers',
23
- async (_dataArg) => {
24
+ async (dataArg) => {
25
+ await requireOpsAuth(this.opsServerRef, dataArg, {
26
+ scope: 'users:read',
27
+ requireAdminIdentity: true,
28
+ requireAdminToken: true,
29
+ });
24
30
  const users = await this.opsServerRef.adminHandler.listUsers();
25
31
  return { users };
26
32
  },
27
33
  ),
28
34
  );
35
+
36
+ router.addTypedHandler(
37
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateUser>(
38
+ 'createUser',
39
+ async (dataArg) => {
40
+ await requireOpsAuth(this.opsServerRef, dataArg, {
41
+ scope: 'users:manage',
42
+ requireAdminIdentity: true,
43
+ requireAdminToken: true,
44
+ });
45
+ return this.opsServerRef.adminHandler.createUser({
46
+ email: dataArg.email,
47
+ name: dataArg.name,
48
+ role: dataArg.role,
49
+ password: dataArg.password,
50
+ enableIdpGlobalAuth: dataArg.enableIdpGlobalAuth,
51
+ });
52
+ },
53
+ ),
54
+ );
55
+
56
+ router.addTypedHandler(
57
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteUser>(
58
+ 'deleteUser',
59
+ async (dataArg) => {
60
+ const auth = await requireOpsAuth(this.opsServerRef, dataArg, {
61
+ scope: 'users:manage',
62
+ requireAdminIdentity: true,
63
+ requireAdminToken: true,
64
+ });
65
+ return this.opsServerRef.adminHandler.deleteUser({
66
+ id: dataArg.id,
67
+ requestingUserId: auth.userId,
68
+ });
69
+ },
70
+ ),
71
+ );
29
72
  }
30
73
  }
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
+ import { requireOpsAuth } from '../helpers/auth.js';
4
5
 
5
6
  export class VpnHandler {
6
7
  constructor(private opsServerRef: OpsServer) {
@@ -18,6 +19,7 @@ export class VpnHandler {
18
19
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnClients>(
19
20
  'getVpnClients',
20
21
  async (dataArg, toolsArg) => {
22
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
21
23
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
22
24
  if (!manager) {
23
25
  return { clients: [] };
@@ -49,6 +51,7 @@ export class VpnHandler {
49
51
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnStatus>(
50
52
  'getVpnStatus',
51
53
  async (dataArg, toolsArg) => {
54
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
52
55
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
53
56
  const vpnConfig = this.opsServerRef.dcRouterRef.options.vpnConfig;
54
57
  if (!manager) {
@@ -84,6 +87,7 @@ export class VpnHandler {
84
87
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnConnectedClients>(
85
88
  'getVpnConnectedClients',
86
89
  async (dataArg, toolsArg) => {
90
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
87
91
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
88
92
  if (!manager) {
89
93
  return { connectedClients: [] };
@@ -111,6 +115,10 @@ export class VpnHandler {
111
115
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateVpnClient>(
112
116
  'createVpnClient',
113
117
  async (dataArg, toolsArg) => {
118
+ await requireOpsAuth(this.opsServerRef, dataArg, {
119
+ scope: 'vpn:write',
120
+ requireAdminIdentity: true,
121
+ });
114
122
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
115
123
  if (!manager) {
116
124
  return { success: false, message: 'VPN not configured' };
@@ -168,6 +176,10 @@ export class VpnHandler {
168
176
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVpnClient>(
169
177
  'updateVpnClient',
170
178
  async (dataArg, toolsArg) => {
179
+ await requireOpsAuth(this.opsServerRef, dataArg, {
180
+ scope: 'vpn:write',
181
+ requireAdminIdentity: true,
182
+ });
171
183
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
172
184
  if (!manager) {
173
185
  return { success: false, message: 'VPN not configured' };
@@ -198,6 +210,10 @@ export class VpnHandler {
198
210
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteVpnClient>(
199
211
  'deleteVpnClient',
200
212
  async (dataArg, toolsArg) => {
213
+ await requireOpsAuth(this.opsServerRef, dataArg, {
214
+ scope: 'vpn:write',
215
+ requireAdminIdentity: true,
216
+ });
201
217
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
202
218
  if (!manager) {
203
219
  return { success: false, message: 'VPN not configured' };
@@ -218,6 +234,10 @@ export class VpnHandler {
218
234
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_EnableVpnClient>(
219
235
  'enableVpnClient',
220
236
  async (dataArg, toolsArg) => {
237
+ await requireOpsAuth(this.opsServerRef, dataArg, {
238
+ scope: 'vpn:write',
239
+ requireAdminIdentity: true,
240
+ });
221
241
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
222
242
  if (!manager) {
223
243
  return { success: false, message: 'VPN not configured' };
@@ -238,6 +258,10 @@ export class VpnHandler {
238
258
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisableVpnClient>(
239
259
  'disableVpnClient',
240
260
  async (dataArg, toolsArg) => {
261
+ await requireOpsAuth(this.opsServerRef, dataArg, {
262
+ scope: 'vpn:write',
263
+ requireAdminIdentity: true,
264
+ });
241
265
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
242
266
  if (!manager) {
243
267
  return { success: false, message: 'VPN not configured' };
@@ -258,6 +282,10 @@ export class VpnHandler {
258
282
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RotateVpnClientKey>(
259
283
  'rotateVpnClientKey',
260
284
  async (dataArg, toolsArg) => {
285
+ await requireOpsAuth(this.opsServerRef, dataArg, {
286
+ scope: 'vpn:write',
287
+ requireAdminIdentity: true,
288
+ });
261
289
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
262
290
  if (!manager) {
263
291
  return { success: false, message: 'VPN not configured' };
@@ -281,6 +309,10 @@ export class VpnHandler {
281
309
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportVpnClientConfig>(
282
310
  'exportVpnClientConfig',
283
311
  async (dataArg, toolsArg) => {
312
+ await requireOpsAuth(this.opsServerRef, dataArg, {
313
+ scope: 'vpn:write',
314
+ requireAdminIdentity: true,
315
+ });
284
316
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
285
317
  if (!manager) {
286
318
  return { success: false, message: 'VPN not configured' };
@@ -301,6 +333,7 @@ export class VpnHandler {
301
333
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnClientTelemetry>(
302
334
  'getVpnClientTelemetry',
303
335
  async (dataArg, toolsArg) => {
336
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
304
337
  const manager = this.opsServerRef.dcRouterRef.vpnManager;
305
338
  if (!manager) {
306
339
  return { success: false, message: 'VPN not configured' };
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
+ import { requireOpsAuth } from '../helpers/auth.js';
4
5
 
5
6
  type TAuthContext = {
6
7
  userId: string;
@@ -20,39 +21,23 @@ export class WorkHosterHandler {
20
21
  request: { identity?: interfaces.data.IIdentity; apiToken?: string },
21
22
  requiredScope?: interfaces.data.TApiTokenScope,
22
23
  ): Promise<TAuthContext> {
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 { userId: request.identity.userId, isAdmin: true };
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 { userId: token.createdBy, isAdmin: token.policy?.role === 'admin', token };
39
- }
40
- throw new plugins.typedrequest.TypedResponseError('insufficient scope');
41
- }
42
- }
43
- }
44
-
45
- throw new plugins.typedrequest.TypedResponseError('unauthorized');
24
+ const auth = await requireOpsAuth(this.opsServerRef, request, {
25
+ scope: requiredScope,
26
+ requireAdminIdentity: requiredScope?.endsWith(':write'),
27
+ });
28
+ return { userId: auth.userId, isAdmin: auth.isAdmin, token: auth.token };
46
29
  }
47
30
 
48
- private async requireAdmin(request: { identity?: interfaces.data.IIdentity }): Promise<string> {
49
- if (request.identity?.jwt) {
50
- const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
51
- identity: request.identity,
52
- });
53
- if (isAdmin) return request.identity.userId;
54
- }
55
- throw new plugins.typedrequest.TypedResponseError('admin identity required');
31
+ private async requireAdmin(
32
+ request: { identity?: interfaces.data.IIdentity; apiToken?: string },
33
+ scope: interfaces.data.TApiTokenScope = 'gateway-clients:write',
34
+ ): Promise<string> {
35
+ const auth = await requireOpsAuth(this.opsServerRef, request, {
36
+ scope,
37
+ requireAdminIdentity: true,
38
+ requireAdminToken: true,
39
+ });
40
+ return auth.userId;
56
41
  }
57
42
 
58
43
  private registerHandlers(): void {
@@ -83,7 +68,7 @@ export class WorkHosterHandler {
83
68
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListGatewayClients>(
84
69
  'listGatewayClients',
85
70
  async (dataArg) => {
86
- await this.requireAdmin(dataArg);
71
+ await this.requireAdmin(dataArg, 'gateway-clients:read');
87
72
  return { gatewayClients: await this.listManagedGatewayClients() };
88
73
  },
89
74
  ),
@@ -154,7 +139,7 @@ export class WorkHosterHandler {
154
139
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateGatewayClientToken>(
155
140
  'createGatewayClientToken',
156
141
  async (dataArg) => {
157
- const userId = await this.requireAdmin(dataArg);
142
+ const userId = await this.requireAdmin(dataArg, 'tokens:manage');
158
143
  const gatewayClient = await this.opsServerRef.dcRouterRef.gatewayClientManager?.getClient(dataArg.gatewayClientId);
159
144
  const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
160
145
  if (!gatewayClient || !gatewayClient.enabled) {
@@ -0,0 +1,91 @@
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
+ export interface IAuthRequest {
6
+ identity?: interfaces.data.IIdentity;
7
+ apiToken?: string;
8
+ }
9
+
10
+ export interface IAuthRequirement {
11
+ scope?: interfaces.data.TApiTokenScope;
12
+ requireAdminIdentity?: boolean;
13
+ requireAdminToken?: boolean;
14
+ }
15
+
16
+ export interface IAuthContext {
17
+ type: 'identity' | 'apiToken';
18
+ userId: string;
19
+ role?: string;
20
+ isAdmin: boolean;
21
+ scopes: interfaces.data.TApiTokenScope[];
22
+ identity?: interfaces.data.IIdentity;
23
+ token?: interfaces.data.IStoredApiToken;
24
+ }
25
+
26
+ const typedAuthError = (messageArg: string) => {
27
+ return new plugins.typedrequest.TypedResponseError(messageArg);
28
+ };
29
+
30
+ export async function requireOpsAuth(
31
+ opsServerRefArg: OpsServer,
32
+ requestArg: IAuthRequest,
33
+ requirementArg: IAuthRequirement = {},
34
+ ): Promise<IAuthContext> {
35
+ let identityNeedsAdmin = false;
36
+ let tokenNeedsAdmin = false;
37
+ let tokenNeedsScope = false;
38
+
39
+ if (requestArg.identity?.jwt) {
40
+ const identity = await opsServerRefArg.adminHandler.validateIdentity(requestArg.identity);
41
+ if (identity) {
42
+ const isAdmin = identity.role === 'admin';
43
+ if (!requirementArg.requireAdminIdentity || isAdmin) {
44
+ return {
45
+ type: 'identity',
46
+ userId: identity.userId,
47
+ role: identity.role,
48
+ isAdmin,
49
+ scopes: [],
50
+ identity,
51
+ };
52
+ }
53
+ identityNeedsAdmin = true;
54
+ }
55
+ }
56
+
57
+ if (requestArg.apiToken) {
58
+ const tokenManager = opsServerRefArg.dcRouterRef.apiTokenManager;
59
+ const token = tokenManager ? await tokenManager.validateToken(requestArg.apiToken) : null;
60
+ if (token) {
61
+ if (requirementArg.requireAdminToken && token.policy?.role !== 'admin') {
62
+ tokenNeedsAdmin = true;
63
+ } else if (requirementArg.scope && !tokenManager!.hasScope(token, requirementArg.scope)) {
64
+ tokenNeedsScope = true;
65
+ } else {
66
+ const scopes = token.policy?.role === 'admin'
67
+ ? ['*' as interfaces.data.TApiTokenScope]
68
+ : Array.from(new Set([...(token.scopes || []), ...(token.policy?.scopes || [])]));
69
+ return {
70
+ type: 'apiToken',
71
+ userId: token.createdBy,
72
+ role: token.policy?.role || 'operator',
73
+ isAdmin: token.policy?.role === 'admin',
74
+ scopes,
75
+ token,
76
+ };
77
+ }
78
+ }
79
+ }
80
+
81
+ if (tokenNeedsScope) {
82
+ throw typedAuthError('insufficient scope');
83
+ }
84
+ if (tokenNeedsAdmin) {
85
+ throw typedAuthError('admin API token required');
86
+ }
87
+ if (identityNeedsAdmin) {
88
+ throw typedAuthError('admin identity required');
89
+ }
90
+ throw typedAuthError('unauthorized');
91
+ }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.30.0',
6
+ version: '13.32.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }