@serve.zone/dcrouter 14.0.1 → 14.2.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.
@@ -479,6 +479,7 @@ export class ReferenceResolver {
479
479
  if (override.basicAuth !== undefined) merged.basicAuth = override.basicAuth;
480
480
  if (override.jwtAuth !== undefined) merged.jwtAuth = override.jwtAuth;
481
481
  if (override.vpn !== undefined) merged.vpn = override.vpn;
482
+ if (override.challenge !== undefined) merged.challenge = override.challenge;
482
483
 
483
484
  return merged;
484
485
  }
@@ -6,7 +6,7 @@ const TTL = plugins.smartdata.smartdataTtlValues;
6
6
  /**
7
7
  * Email status in the cache
8
8
  */
9
- export type TCachedEmailStatus = 'pending' | 'processing' | 'delivered' | 'failed' | 'deferred';
9
+ export type TCachedEmailStatus = 'pending' | 'processing' | 'queued' | 'delivered' | 'failed' | 'deferred';
10
10
 
11
11
  /**
12
12
  * Helper to get the smartdata database instance
@@ -169,12 +169,38 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
169
169
  /**
170
170
  * Find all emails pending delivery (status = pending and nextAttempt <= now)
171
171
  */
172
- public static async findPendingForDelivery(): Promise<CachedEmail[]> {
172
+ public static async findPendingForDelivery(limit = 25): Promise<CachedEmail[]> {
173
173
  const now = new Date();
174
- return await CachedEmail.getInstances({
175
- status: 'pending',
174
+ return await CachedEmail.findLimited({
175
+ status: { $in: ['pending', 'deferred', 'processing'] },
176
176
  nextAttempt: { $lte: now },
177
- });
177
+ }, limit);
178
+ }
179
+
180
+ public static async findQueuedForRecovery(limit = 25): Promise<CachedEmail[]> {
181
+ return await CachedEmail.findLimited({
182
+ status: 'queued',
183
+ }, limit);
184
+ }
185
+
186
+ private static async findLimited(filter: Record<string, any>, limit: number): Promise<CachedEmail[]> {
187
+ const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 25;
188
+ if (safeLimit === 0) {
189
+ return [];
190
+ }
191
+ const collection = (CachedEmail as typeof CachedEmail & {
192
+ collection: plugins.smartdata.SmartdataCollection<CachedEmail>;
193
+ }).collection;
194
+ const cursor = await collection.getCursor(
195
+ filter,
196
+ CachedEmail as unknown as typeof plugins.smartdata.SmartDataDbDoc,
197
+ );
198
+ cursor.mongodbCursor.limit(safeLimit);
199
+ try {
200
+ return await cursor.toArray();
201
+ } finally {
202
+ await cursor.close();
203
+ }
178
204
  }
179
205
 
180
206
  /**
@@ -6,6 +6,12 @@ import * as plugins from '../plugins.js';
6
6
  import type * as interfaces from '../../ts_interfaces/index.js';
7
7
 
8
8
  type TSyncRequest = interfaces.requests.IReq_SyncWorkAppMailIdentity['request'];
9
+ type TMailResourceOwner = plugins.servezoneInterfaces.data.IMailResourceOwner;
10
+ type TMailAddressBinding = plugins.servezoneInterfaces.data.IMailAddressBinding;
11
+ type TMailAddressBindingSync = plugins.servezoneInterfaces.requests.mail.TMailAddressBindingSync;
12
+ type TMailAddressBindingSyncResponse = plugins.servezoneInterfaces.requests.mail.IReq_SyncMailAddressBinding['response'];
13
+ type TMailAddressBindingDeleteResponse = plugins.servezoneInterfaces.requests.mail.IReq_DeleteMailAddressBinding['response'];
14
+ type TWorkAppMailBinding = plugins.servezoneInterfaces.data.IWorkAppMailBinding;
9
15
 
10
16
  interface IStoredWorkAppMailIdentity extends interfaces.data.IWorkAppMailIdentity {
11
17
  smtpPassword: string;
@@ -109,6 +115,89 @@ export class WorkAppMailManager {
109
115
  return response;
110
116
  }
111
117
 
118
+ public async listMailAddressBindings(options: {
119
+ owner?: Partial<TMailResourceOwner>;
120
+ domain?: string;
121
+ address?: string;
122
+ } = {}): Promise<TMailAddressBinding[]> {
123
+ const domain = options.domain ? this.normalizeDomain(options.domain) : undefined;
124
+ const address = options.address ? this.normalizeAddress(options.address) : undefined;
125
+ const identities = await this.readStoredIdentities();
126
+
127
+ return identities
128
+ .filter((identity) => this.matchesMailOwner(this.toMailOwner(identity.ownership), options.owner))
129
+ .filter((identity) => domain ? identity.domain === domain : true)
130
+ .filter((identity) => address ? identity.address === address : true)
131
+ .map((identity) => this.toMailAddressBinding(identity));
132
+ }
133
+
134
+ public async listWorkAppMailBindings(
135
+ owner?: Partial<TMailResourceOwner>,
136
+ ): Promise<TWorkAppMailBinding[]> {
137
+ const identities = (await this.readStoredIdentities())
138
+ .filter((identity) => this.matchesMailOwner(this.toMailOwner(identity.ownership), owner));
139
+ const groups = new Map<string, IStoredWorkAppMailIdentity[]>();
140
+
141
+ for (const identity of identities) {
142
+ const ownerKey = this.buildMailOwnerKey(this.toMailOwner(identity.ownership));
143
+ const group = groups.get(ownerKey) || [];
144
+ group.push(identity);
145
+ groups.set(ownerKey, group);
146
+ }
147
+
148
+ return Array.from(groups.values()).map((group) => this.toWorkAppMailBinding(group));
149
+ }
150
+
151
+ public async syncMailAddressBinding(
152
+ binding: TMailAddressBindingSync,
153
+ createdBy: string,
154
+ ): Promise<TMailAddressBindingSyncResponse> {
155
+ const ownership = this.normalizeMailResourceOwner(binding.owner);
156
+ const { localPart, domain } = this.normalizeMailAddressParts(binding);
157
+ const syncRequest: TSyncRequest = {
158
+ ownership,
159
+ localPart,
160
+ domain,
161
+ inbound: this.toLegacyInboundRoute(binding.inboundTarget),
162
+ enabled: binding.enabled,
163
+ };
164
+
165
+ if (binding.outboundIdentityId !== undefined) {
166
+ syncRequest.smtpEnabled = Boolean(binding.outboundIdentityId);
167
+ }
168
+
169
+ const result = await this.syncMailIdentity(syncRequest, createdBy);
170
+
171
+ return {
172
+ success: result.success,
173
+ binding: result.identity ? this.toMailAddressBinding(result.identity) : undefined,
174
+ message: result.message,
175
+ };
176
+ }
177
+
178
+ public async deleteMailAddressBinding(
179
+ id: string,
180
+ createdBy: string,
181
+ ): Promise<TMailAddressBindingDeleteResponse> {
182
+ const identities = await this.readStoredIdentities();
183
+ const identity = identities.find((storedIdentity) => storedIdentity.id === id || storedIdentity.externalKey === id);
184
+ if (!identity) {
185
+ return { success: true };
186
+ }
187
+
188
+ const result = await this.syncMailIdentity({
189
+ ownership: identity.ownership,
190
+ localPart: identity.localPart,
191
+ domain: identity.domain,
192
+ delete: true,
193
+ }, createdBy);
194
+
195
+ return {
196
+ success: result.success,
197
+ message: result.message,
198
+ };
199
+ }
200
+
112
201
  public async applyStoredIdentitiesToEmailConfig<TConfig extends IUnifiedEmailServerOptions>(
113
202
  emailConfig: TConfig,
114
203
  ): Promise<TConfig> {
@@ -251,6 +340,63 @@ export class WorkAppMailManager {
251
340
  return normalized;
252
341
  }
253
342
 
343
+ private normalizeAddress(address: string): string {
344
+ const normalized = address?.trim().toLowerCase();
345
+ const [localPart, domain, extra] = normalized?.split('@') || [];
346
+ if (!localPart || !domain || extra) {
347
+ throw new Error(`Invalid email address: ${address}`);
348
+ }
349
+ return `${this.normalizeLocalPart(localPart)}@${this.normalizeDomain(domain)}`;
350
+ }
351
+
352
+ private normalizeMailResourceOwner(owner: TMailResourceOwner): interfaces.data.IWorkAppMailOwnership {
353
+ const gatewayClientType = owner.gatewayClientType;
354
+ const gatewayClientId = owner.gatewayClientId?.trim();
355
+ const appInstanceId = owner.appInstanceId?.trim();
356
+
357
+ if (gatewayClientType !== 'onebox' && gatewayClientType !== 'cloudly' && gatewayClientType !== 'custom') {
358
+ throw new Error(`Invalid gateway client type: ${gatewayClientType}`);
359
+ }
360
+ if (!gatewayClientId) throw new Error('gatewayClientId is required');
361
+ if (!appInstanceId) throw new Error('appInstanceId is required');
362
+
363
+ return {
364
+ workHosterType: gatewayClientType as interfaces.data.TGatewayClientType,
365
+ workHosterId: gatewayClientId,
366
+ workAppId: appInstanceId,
367
+ };
368
+ }
369
+
370
+ private normalizeMailAddressParts(binding: TMailAddressBindingSync): {
371
+ localPart: string;
372
+ domain: string;
373
+ } {
374
+ const localPart = this.normalizeLocalPart(binding.localPart);
375
+ const domain = this.normalizeDomain(binding.domain);
376
+ const address = this.normalizeAddress(binding.address);
377
+ if (address !== `${localPart}@${domain}`) {
378
+ throw new Error('mail address, localPart, and domain do not match');
379
+ }
380
+ return { localPart, domain };
381
+ }
382
+
383
+ private toLegacyInboundRoute(
384
+ inboundTarget?: TMailAddressBinding['inboundTarget'],
385
+ ): interfaces.data.IWorkAppMailInboundRoute | undefined {
386
+ if (!inboundTarget) return undefined;
387
+ if (inboundTarget.type !== 'smtpForward' || !inboundTarget.smtpForward) {
388
+ throw new Error(`Unsupported WorkApp mail inbound target: ${inboundTarget.type}`);
389
+ }
390
+
391
+ return this.normalizeInboundRoute({
392
+ enabled: true,
393
+ targetHost: inboundTarget.smtpForward.host,
394
+ targetPort: inboundTarget.smtpForward.port,
395
+ preserveHeaders: inboundTarget.smtpForward.preserveHeaders,
396
+ addHeaders: inboundTarget.smtpForward.addHeaders,
397
+ });
398
+ }
399
+
254
400
  private normalizeInboundRoute(
255
401
  inbound?: interfaces.data.IWorkAppMailInboundRoute,
256
402
  ): interfaces.data.IWorkAppMailInboundRoute | undefined {
@@ -282,6 +428,17 @@ export class WorkAppMailManager {
282
428
  return true;
283
429
  }
284
430
 
431
+ private matchesMailOwner(
432
+ owner: TMailResourceOwner,
433
+ filter?: Partial<TMailResourceOwner>,
434
+ ): boolean {
435
+ if (!filter) return true;
436
+ if (filter.gatewayClientType && filter.gatewayClientType !== owner.gatewayClientType) return false;
437
+ if (filter.gatewayClientId && filter.gatewayClientId !== owner.gatewayClientId) return false;
438
+ if (filter.appInstanceId && filter.appInstanceId !== owner.appInstanceId) return false;
439
+ return true;
440
+ }
441
+
285
442
  private buildExternalKey(
286
443
  ownership: interfaces.data.IWorkAppMailOwnership,
287
444
  address: string,
@@ -298,6 +455,14 @@ export class WorkAppMailManager {
298
455
  return `workapp-${this.hashExternalKey(externalKey).slice(0, 24)}`;
299
456
  }
300
457
 
458
+ private buildMailOwnerKey(owner: TMailResourceOwner): string {
459
+ return [
460
+ owner.gatewayClientType,
461
+ owner.gatewayClientId,
462
+ owner.appInstanceId,
463
+ ].join(':');
464
+ }
465
+
301
466
  private buildRouteName(externalKey: string): string {
302
467
  return `workapp-mail-${this.hashExternalKey(externalKey).slice(0, 32)}`;
303
468
  }
@@ -334,6 +499,75 @@ export class WorkAppMailManager {
334
499
  };
335
500
  }
336
501
 
502
+ private toMailOwner(ownership: interfaces.data.IWorkAppMailOwnership): TMailResourceOwner & { appInstanceId: string } {
503
+ return {
504
+ gatewayClientType: ownership.workHosterType,
505
+ gatewayClientId: ownership.workHosterId,
506
+ appInstanceId: ownership.workAppId,
507
+ };
508
+ }
509
+
510
+ private toMailInboundTarget(
511
+ inbound?: interfaces.data.IWorkAppMailInboundRoute,
512
+ ): TMailAddressBinding['inboundTarget'] {
513
+ if (!inbound?.enabled) return undefined;
514
+ return {
515
+ type: 'smtpForward',
516
+ smtpForward: {
517
+ host: inbound.targetHost,
518
+ port: inbound.targetPort,
519
+ preserveHeaders: inbound.preserveHeaders,
520
+ addHeaders: inbound.addHeaders,
521
+ },
522
+ };
523
+ }
524
+
525
+ private toMailAddressBinding(
526
+ identity: interfaces.data.IWorkAppMailIdentity,
527
+ ): TMailAddressBinding {
528
+ return {
529
+ id: identity.id,
530
+ owner: this.toMailOwner(identity.ownership),
531
+ address: identity.address,
532
+ localPart: identity.localPart,
533
+ domain: identity.domain,
534
+ enabled: identity.enabled,
535
+ status: identity.enabled ? 'active' : 'disabled',
536
+ inboundTarget: this.toMailInboundTarget(identity.inbound),
537
+ outboundIdentityId: identity.smtp.enabled ? identity.smtp.username : undefined,
538
+ recipientPolicy: {
539
+ mode: 'staticList',
540
+ staticRecipients: [identity.address],
541
+ },
542
+ createdAt: identity.createdAt,
543
+ updatedAt: identity.updatedAt,
544
+ createdBy: identity.createdBy,
545
+ };
546
+ }
547
+
548
+ private toWorkAppMailBinding(
549
+ identities: IStoredWorkAppMailIdentity[],
550
+ ): TWorkAppMailBinding {
551
+ const [firstIdentity] = identities;
552
+ const owner = this.toMailOwner(firstIdentity.ownership);
553
+ const enabledIdentities = identities.filter((identity) => identity.enabled);
554
+ const smtpIdentities = identities.filter((identity) => identity.smtp.enabled);
555
+
556
+ return {
557
+ id: `workapp-mail-${this.hashExternalKey(this.buildMailOwnerKey(owner)).slice(0, 32)}`,
558
+ owner,
559
+ enabled: enabledIdentities.length > 0,
560
+ status: enabledIdentities.length > 0 ? 'active' : 'disabled',
561
+ addressBindingIds: identities.map((identity) => identity.id),
562
+ outboundIdentityIds: smtpIdentities.map((identity) => identity.smtp.username),
563
+ defaultFrom: enabledIdentities[0]?.address || firstIdentity.address,
564
+ inboundTarget: identities.length === 1 ? this.toMailInboundTarget(firstIdentity.inbound) : undefined,
565
+ createdAt: Math.min(...identities.map((identity) => identity.createdAt)),
566
+ updatedAt: Math.max(...identities.map((identity) => identity.updatedAt)),
567
+ createdBy: firstIdentity.createdBy,
568
+ };
569
+ }
570
+
337
571
  private toPublicIdentity(
338
572
  identity: IStoredWorkAppMailIdentity,
339
573
  ): interfaces.data.IWorkAppMailIdentity {
@@ -103,6 +103,7 @@ export function augmentRouteWithHttp3(
103
103
  match: {
104
104
  ...route.match,
105
105
  transport: 'all' as const,
106
+ ...(route.security?.challenge ? { protocol: 'http' as const } : {}),
106
107
  },
107
108
  action: {
108
109
  ...route.action,
@@ -257,6 +257,83 @@ export class WorkHosterHandler {
257
257
  },
258
258
  ),
259
259
  );
260
+
261
+ this.typedrouter.addTypedHandler(
262
+ new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_ListMailAddressBindings>(
263
+ 'listMailAddressBindings',
264
+ async (dataArg) => {
265
+ const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:read');
266
+ const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
267
+ if (!manager) return { bindings: [] };
268
+ return {
269
+ bindings: await manager.listMailAddressBindings({
270
+ owner: this.resolveMailOwnerFilter(auth, dataArg.owner),
271
+ domain: dataArg.domain,
272
+ address: dataArg.address,
273
+ }),
274
+ };
275
+ },
276
+ ),
277
+ );
278
+
279
+ this.typedrouter.addTypedHandler(
280
+ new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_SyncMailAddressBinding>(
281
+ 'syncMailAddressBinding',
282
+ async (dataArg) => {
283
+ const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:write');
284
+ this.assertCapability(auth, 'syncRoutes');
285
+ const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
286
+ if (!manager) {
287
+ return { success: false, message: 'WorkApp mail manager not initialized' };
288
+ }
289
+ try {
290
+ const binding = {
291
+ ...dataArg.binding,
292
+ owner: this.resolveMailOwner(auth, dataArg.binding.owner),
293
+ };
294
+ this.assertMailForwardTargetAllowed(auth, binding.inboundTarget);
295
+ return await manager.syncMailAddressBinding(binding, auth.userId);
296
+ } catch (error) {
297
+ return { success: false, message: (error as Error).message };
298
+ }
299
+ },
300
+ ),
301
+ );
302
+
303
+ this.typedrouter.addTypedHandler(
304
+ new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_DeleteMailAddressBinding>(
305
+ 'deleteMailAddressBinding',
306
+ async (dataArg) => {
307
+ const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:write');
308
+ this.assertCapability(auth, 'syncRoutes');
309
+ const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
310
+ if (!manager) {
311
+ return { success: false, message: 'WorkApp mail manager not initialized' };
312
+ }
313
+ if (auth.token?.policy?.role === 'gatewayClient') {
314
+ const bindings = await manager.listMailAddressBindings({
315
+ owner: this.resolveMailOwnerFilter(auth),
316
+ });
317
+ const binding = bindings.find((candidate) => candidate.id === dataArg.id);
318
+ if (!binding) return { success: true };
319
+ return await manager.deleteMailAddressBinding(binding.id, auth.userId);
320
+ }
321
+ return await manager.deleteMailAddressBinding(dataArg.id, auth.userId);
322
+ },
323
+ ),
324
+ );
325
+
326
+ this.typedrouter.addTypedHandler(
327
+ new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_ListWorkAppMailBindings>(
328
+ 'listWorkAppMailBindings',
329
+ async (dataArg) => {
330
+ const auth = await this.requireAuth(dataArg.auth || {}, 'gateway-clients:read');
331
+ const manager = this.opsServerRef.dcRouterRef.workAppMailManager;
332
+ if (!manager) return { bindings: [] };
333
+ return { bindings: await manager.listWorkAppMailBindings(this.resolveMailOwnerFilter(auth, dataArg.owner)) };
334
+ },
335
+ ),
336
+ );
260
337
  }
261
338
 
262
339
  private getGatewayCapabilities(): interfaces.data.IGatewayCapabilities {
@@ -335,7 +412,7 @@ export class WorkHosterHandler {
335
412
  const policy = auth.token?.policy;
336
413
  if (!policy || policy.role !== 'gatewayClient') return;
337
414
  if (policy.capabilities?.[capability] === true) return;
338
- throw new plugins.typedrequest.TypedResponseError(`token capability missing: ${capability}`);
415
+ throw new plugins.typedrequest.TypedResponseError(`token capability missing: ${String(capability)}`);
339
416
  }
340
417
 
341
418
  private resolveGatewayClientId(auth: TAuthContext, requestedId?: string): string | undefined {
@@ -376,6 +453,39 @@ export class WorkHosterHandler {
376
453
  return ownership as Required<interfaces.data.IGatewayClientOwnership>;
377
454
  }
378
455
 
456
+ private resolveMailOwnerFilter(
457
+ auth: TAuthContext,
458
+ owner?: Partial<plugins.servezoneInterfaces.data.IMailResourceOwner>,
459
+ ): Partial<plugins.servezoneInterfaces.data.IMailResourceOwner> | undefined {
460
+ const policy = auth.token?.policy;
461
+ if (policy?.role !== 'gatewayClient') return owner;
462
+ if (!policy.gatewayClient) {
463
+ throw new plugins.typedrequest.TypedResponseError('gateway client token is missing gatewayClient binding');
464
+ }
465
+ if (owner?.gatewayClientType && owner.gatewayClientType !== policy.gatewayClient.type) {
466
+ throw new plugins.typedrequest.TypedResponseError('gateway client token cannot act for this ownership');
467
+ }
468
+ if (owner?.gatewayClientId && owner.gatewayClientId !== policy.gatewayClient.id) {
469
+ throw new plugins.typedrequest.TypedResponseError('gateway client token cannot act for this ownership');
470
+ }
471
+ return {
472
+ ...owner,
473
+ gatewayClientType: policy.gatewayClient.type,
474
+ gatewayClientId: policy.gatewayClient.id,
475
+ };
476
+ }
477
+
478
+ private resolveMailOwner(
479
+ auth: TAuthContext,
480
+ owner: plugins.servezoneInterfaces.data.IMailResourceOwner,
481
+ ): plugins.servezoneInterfaces.data.IMailResourceOwner {
482
+ const resolvedOwner = this.resolveMailOwnerFilter(auth, owner);
483
+ if (!resolvedOwner?.gatewayClientType || !resolvedOwner.gatewayClientId) {
484
+ throw new plugins.typedrequest.TypedResponseError('mail owner is missing gateway client type or id');
485
+ }
486
+ return resolvedOwner as plugins.servezoneInterfaces.data.IMailResourceOwner;
487
+ }
488
+
379
489
  private assertGatewayClientOwnership(auth: TAuthContext, ownership: Required<interfaces.data.IGatewayClientOwnership>): void {
380
490
  const policy = auth.token?.policy;
381
491
  if (!policy || policy.role !== 'gatewayClient') return;
@@ -404,6 +514,26 @@ export class WorkHosterHandler {
404
514
  }
405
515
  }
406
516
 
517
+ private assertMailForwardTargetAllowed(
518
+ auth: TAuthContext,
519
+ target?: plugins.servezoneInterfaces.data.IMailInboundTarget,
520
+ ): void {
521
+ const policy = auth.token?.policy;
522
+ if (!policy || policy.role !== 'gatewayClient' || !target?.smtpForward) return;
523
+ const allowedTargets = policy.allowedRouteTargets || [];
524
+ if (allowedTargets.length === 0) {
525
+ throw new plugins.typedrequest.TypedResponseError('gateway client token has no allowed route targets');
526
+ }
527
+ const host = target.smtpForward.host.trim().toLowerCase();
528
+ const port = Number(target.smtpForward.port);
529
+ const allowed = allowedTargets.some((allowedTarget) => {
530
+ return allowedTarget.host.trim().toLowerCase() === host && allowedTarget.ports.includes(port);
531
+ });
532
+ if (!allowed) {
533
+ throw new plugins.typedrequest.TypedResponseError(`mail target is outside token policy: ${host}:${port}`);
534
+ }
535
+ }
536
+
407
537
  private matchesHostnamePatterns(hostname: string, patterns: string[]): boolean {
408
538
  const normalizedHostname = hostname.trim().toLowerCase();
409
539
  if (!normalizedHostname) return false;
package/ts/plugins.ts CHANGED
@@ -4,6 +4,7 @@ import * as fs from 'node:fs';
4
4
  import * as crypto from 'node:crypto';
5
5
  import * as http from 'node:http';
6
6
  import * as net from 'node:net';
7
+ import * as buffer from 'node:buffer';
7
8
  import * as os from 'node:os';
8
9
  import * as path from 'node:path';
9
10
  import * as tls from 'node:tls';
@@ -15,6 +16,7 @@ export {
15
16
  crypto,
16
17
  http,
17
18
  net,
19
+ buffer,
18
20
  os,
19
21
  path,
20
22
  tls,
@@ -52,6 +54,7 @@ export {
52
54
  import * as projectinfo from '@push.rocks/projectinfo';
53
55
  import * as qenv from '@push.rocks/qenv';
54
56
  import * as smartacme from '@push.rocks/smartacme';
57
+ import * as smartchallenge from '@push.rocks/smartchallenge';
55
58
  import * as smartdata from '@push.rocks/smartdata';
56
59
  import * as smartdns from '@push.rocks/smartdns';
57
60
  import * as smartfs from '@push.rocks/smartfs';
@@ -72,7 +75,7 @@ import * as smartrx from '@push.rocks/smartrx';
72
75
  import * as smartunique from '@push.rocks/smartunique';
73
76
  import * as taskbuffer from '@push.rocks/taskbuffer';
74
77
 
75
- export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfs, smartguard, smartjwt, smartlog, smartmetrics, smartdb, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique, smartvpn, taskbuffer };
78
+ export { projectinfo, qenv, smartacme, smartchallenge, smartdata, smartdns, smartfs, smartguard, smartjwt, smartlog, smartmetrics, smartdb, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique, smartvpn, taskbuffer };
76
79
 
77
80
  // Define SmartLog types for use in error handling
78
81
  export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '14.0.1',
6
+ version: '14.2.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -240,6 +240,7 @@ export class OpsViewSourceProfiles extends DeesElement {
240
240
  ipBlockList,
241
241
  ...(maxConnections ? { maxConnections } : {}),
242
242
  ...(rateLimit ? { rateLimit } : {}),
243
+ ...(profile.security?.challenge ? { challenge: profile.security.challenge } : {}),
243
244
  },
244
245
  });
245
246
  modalArg.destroy();