@serve.zone/dcrouter 13.43.5 → 13.44.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 (70) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +491 -436
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.dcrouter.d.ts +16 -13
  5. package/dist_ts/classes.dcrouter.js +320 -82
  6. package/dist_ts/config/classes.route-config-manager.d.ts +1 -0
  7. package/dist_ts/config/classes.route-config-manager.js +4 -1
  8. package/dist_ts/db/documents/classes.email-server-settings.doc.d.ts +14 -0
  9. package/dist_ts/db/documents/classes.email-server-settings.doc.js +103 -0
  10. package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.d.ts +3 -0
  11. package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.js +20 -2
  12. package/dist_ts/db/documents/index.d.ts +1 -0
  13. package/dist_ts/db/documents/index.js +2 -1
  14. package/dist_ts/email/classes.email-domain.manager.d.ts +3 -1
  15. package/dist_ts/email/classes.email-domain.manager.js +6 -3
  16. package/dist_ts/email/classes.email-settings.manager.d.ts +25 -0
  17. package/dist_ts/email/classes.email-settings.manager.js +184 -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/config.handler.js +17 -14
  23. package/dist_ts/opsserver/handlers/email-settings.handler.d.ts +10 -0
  24. package/dist_ts/opsserver/handlers/email-settings.handler.js +57 -0
  25. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  26. package/dist_ts/opsserver/handlers/index.js +2 -1
  27. package/dist_ts/opsserver/handlers/remoteingress.handler.js +24 -6
  28. package/dist_ts/opsserver/handlers/workhoster.handler.js +2 -2
  29. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +4 -6
  30. package/dist_ts/remoteingress/classes.remoteingress-manager.js +58 -19
  31. package/dist_ts_interfaces/data/email-settings.d.ts +39 -0
  32. package/dist_ts_interfaces/data/email-settings.js +2 -0
  33. package/dist_ts_interfaces/data/index.d.ts +1 -0
  34. package/dist_ts_interfaces/data/index.js +2 -1
  35. package/dist_ts_interfaces/data/remoteingress.d.ts +7 -0
  36. package/dist_ts_interfaces/requests/email-settings.d.ts +26 -0
  37. package/dist_ts_interfaces/requests/email-settings.js +2 -0
  38. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  39. package/dist_ts_interfaces/requests/index.js +2 -1
  40. package/dist_ts_interfaces/requests/remoteingress.d.ts +4 -1
  41. package/dist_ts_migrations/index.d.ts +14 -1
  42. package/dist_ts_migrations/index.js +107 -2
  43. package/dist_ts_web/00_commitinfo_data.js +1 -1
  44. package/dist_ts_web/appstate.d.ts +6 -1
  45. package/dist_ts_web/appstate.js +23 -1
  46. package/dist_ts_web/elements/email/ops-view-email-domains.d.ts +4 -0
  47. package/dist_ts_web/elements/email/ops-view-email-domains.js +122 -1
  48. package/dist_ts_web/elements/network/ops-view-remoteingress.d.ts +2 -1
  49. package/dist_ts_web/elements/network/ops-view-remoteingress.js +57 -17
  50. package/package.json +1 -1
  51. package/ts/00_commitinfo_data.ts +1 -1
  52. package/ts/classes.dcrouter.ts +361 -100
  53. package/ts/config/classes.route-config-manager.ts +4 -0
  54. package/ts/db/documents/classes.email-server-settings.doc.ts +40 -0
  55. package/ts/db/documents/classes.remote-ingress-hub-settings.doc.ts +9 -0
  56. package/ts/db/documents/index.ts +1 -0
  57. package/ts/email/classes.email-domain.manager.ts +6 -2
  58. package/ts/email/classes.email-settings.manager.ts +221 -0
  59. package/ts/email/index.ts +1 -0
  60. package/ts/opsserver/classes.opsserver.ts +2 -0
  61. package/ts/opsserver/handlers/config.handler.ts +16 -13
  62. package/ts/opsserver/handlers/email-settings.handler.ts +72 -0
  63. package/ts/opsserver/handlers/index.ts +1 -0
  64. package/ts/opsserver/handlers/remoteingress.handler.ts +25 -5
  65. package/ts/opsserver/handlers/workhoster.handler.ts +1 -1
  66. package/ts/remoteingress/classes.remoteingress-manager.ts +65 -18
  67. package/ts_web/00_commitinfo_data.ts +1 -1
  68. package/ts_web/appstate.ts +33 -1
  69. package/ts_web/elements/email/ops-view-email-domains.ts +126 -0
  70. package/ts_web/elements/network/ops-view-remoteingress.ts +59 -17
@@ -85,6 +85,10 @@ export class RouteConfigManager {
85
85
  this.getVpnClientAccessForRoute = resolver;
86
86
  }
87
87
 
88
+ public async runExclusiveRouteUpdate<T>(fn: () => Promise<T>): Promise<T> {
89
+ return await this.routeUpdateMutex.runExclusive(fn);
90
+ }
91
+
88
92
  /**
89
93
  * Load persisted routes, seed serializable config/email/dns routes,
90
94
  * compute warnings, and apply the combined DB-backed + runtime route set to SmartProxy.
@@ -0,0 +1,40 @@
1
+ import type { IUnifiedEmailServerOptions } from '@push.rocks/smartmta';
2
+ import * as plugins from '../../plugins.js';
3
+ import { DcRouterDb } from '../classes.dcrouter-db.js';
4
+ import type { IEmailPortConfig } from '../../../ts_interfaces/data/email-settings.js';
5
+
6
+ const getDb = () => DcRouterDb.getInstance().getDb();
7
+
8
+ @plugins.smartdata.Collection(() => getDb())
9
+ export class EmailServerSettingsDoc extends plugins.smartdata.SmartDataDbDoc<EmailServerSettingsDoc, EmailServerSettingsDoc> {
10
+ @plugins.smartdata.unI()
11
+ @plugins.smartdata.svDb()
12
+ public settingsId: string = 'email-server-settings';
13
+
14
+ @plugins.smartdata.svDb()
15
+ public enabled: boolean = false;
16
+
17
+ @plugins.smartdata.svDb()
18
+ public emailConfig?: IUnifiedEmailServerOptions;
19
+
20
+ @plugins.smartdata.svDb()
21
+ public emailPortConfig?: IEmailPortConfig;
22
+
23
+ @plugins.smartdata.svDb()
24
+ public updatedAt: number = 0;
25
+
26
+ @plugins.smartdata.svDb()
27
+ public updatedBy: string = '';
28
+
29
+ constructor() {
30
+ super();
31
+ }
32
+
33
+ public static async load(): Promise<EmailServerSettingsDoc | null> {
34
+ return await EmailServerSettingsDoc.getInstance({ settingsId: 'email-server-settings' });
35
+ }
36
+
37
+ public static async findAll(): Promise<EmailServerSettingsDoc[]> {
38
+ return await EmailServerSettingsDoc.getInstances({});
39
+ }
40
+ }
@@ -10,6 +10,15 @@ export class RemoteIngressHubSettingsDoc extends plugins.smartdata.SmartDataDbDo
10
10
  @plugins.smartdata.svDb()
11
11
  public settingsId: string = 'remote-ingress-hub-settings';
12
12
 
13
+ @plugins.smartdata.svDb()
14
+ public enabled?: boolean;
15
+
16
+ @plugins.smartdata.svDb()
17
+ public tunnelPort?: number;
18
+
19
+ @plugins.smartdata.svDb()
20
+ public hubDomain?: string;
21
+
13
22
  @plugins.smartdata.svDb()
14
23
  public performance?: IRemoteIngressPerformanceConfig;
15
24
 
@@ -40,3 +40,4 @@ export * from './classes.acme-config.doc.js';
40
40
 
41
41
  // Email domain management
42
42
  export * from './classes.email-domain.doc.js';
43
+ export * from './classes.email-server-settings.doc.js';
@@ -17,11 +17,15 @@ import { buildEmailDnsRecords } from './email-dns-records.js';
17
17
  */
18
18
  export class EmailDomainManager {
19
19
  private dcRouter: any; // DcRouter — avoids circular import
20
- private readonly baseEmailDomains: IEmailDomainConfig[];
20
+ private baseEmailDomains: IEmailDomainConfig[] = [];
21
21
 
22
22
  constructor(dcRouterRef: any) {
23
23
  this.dcRouter = dcRouterRef;
24
- this.baseEmailDomains = ((this.dcRouter.options?.emailConfig?.domains || []) as IEmailDomainConfig[])
24
+ this.setBaseEmailDomains(this.dcRouter.options?.emailConfig?.domains as IEmailDomainConfig[] | undefined);
25
+ }
26
+
27
+ public setBaseEmailDomains(domains: IEmailDomainConfig[] | undefined): void {
28
+ this.baseEmailDomains = (domains || [])
25
29
  .map((domainConfig) => JSON.parse(JSON.stringify(domainConfig)) as IEmailDomainConfig);
26
30
  }
27
31
 
@@ -0,0 +1,221 @@
1
+ import type { IUnifiedEmailServerOptions } from '@push.rocks/smartmta';
2
+ import { EmailServerSettingsDoc } from '../db/index.js';
3
+ import type { IDcRouterOptions } from '../classes.dcrouter.js';
4
+ import type {
5
+ IEmailPortConfig,
6
+ IEmailServerSettings,
7
+ TEmailServerSettingsUpdate,
8
+ } from '../../ts_interfaces/data/email-settings.js';
9
+
10
+ const defaultEmailPorts = [25, 587, 465];
11
+
12
+ function clonePlain<T>(value: T | undefined): T | undefined {
13
+ if (value === undefined) return undefined;
14
+ return JSON.parse(JSON.stringify(value)) as T;
15
+ }
16
+
17
+ function hasOwn(objectArg: object, keyArg: string): boolean {
18
+ return Object.prototype.hasOwnProperty.call(objectArg, keyArg);
19
+ }
20
+
21
+ export class EmailSettingsManager {
22
+ private cachedEmailConfig?: IUnifiedEmailServerOptions;
23
+ private cachedEmailPortConfig?: IEmailPortConfig;
24
+ private enabled = false;
25
+ private updatedAt = 0;
26
+ private updatedBy = 'default';
27
+
28
+ constructor(private options: IDcRouterOptions) {}
29
+
30
+ public async start(): Promise<void> {
31
+ let doc = await EmailServerSettingsDoc.load();
32
+
33
+ if (!doc) {
34
+ doc = new EmailServerSettingsDoc();
35
+ doc.settingsId = 'email-server-settings';
36
+ doc.enabled = false;
37
+ doc.updatedAt = Date.now();
38
+ doc.updatedBy = 'default';
39
+ await doc.save();
40
+ }
41
+
42
+ this.loadFromDoc(doc);
43
+ this.applyToRuntimeOptions();
44
+ }
45
+
46
+ public async stop(): Promise<void> {
47
+ this.cachedEmailConfig = undefined;
48
+ this.cachedEmailPortConfig = undefined;
49
+ this.enabled = false;
50
+ }
51
+
52
+ public isEnabled(): boolean {
53
+ return this.enabled && Boolean(this.cachedEmailConfig);
54
+ }
55
+
56
+ public getEmailConfig(): IUnifiedEmailServerOptions | undefined {
57
+ return this.isEnabled() ? clonePlain(this.cachedEmailConfig) : undefined;
58
+ }
59
+
60
+ public getEmailPortConfig(): IEmailPortConfig | undefined {
61
+ return this.isEnabled() ? clonePlain(this.cachedEmailPortConfig) : undefined;
62
+ }
63
+
64
+ public getPublicSettings(): IEmailServerSettings {
65
+ const emailConfig = this.cachedEmailConfig;
66
+ const emailPortConfig = this.cachedEmailPortConfig;
67
+ return {
68
+ enabled: this.isEnabled(),
69
+ hostname: emailConfig?.hostname || null,
70
+ ports: [...(emailConfig?.ports || [])],
71
+ portMapping: emailPortConfig?.portMapping ? { ...emailPortConfig.portMapping } : null,
72
+ receivedEmailsPath: emailPortConfig?.receivedEmailsPath || null,
73
+ maxMessageSize: emailConfig?.maxMessageSize ?? null,
74
+ domainCount: emailConfig?.domains?.length || 0,
75
+ routeCount: emailConfig?.routes?.length || 0,
76
+ authUserCount: emailConfig?.auth?.users?.length || 0,
77
+ updatedAt: this.updatedAt,
78
+ updatedBy: this.updatedBy,
79
+ };
80
+ }
81
+
82
+ public async updateSettings(
83
+ updates: TEmailServerSettingsUpdate,
84
+ updatedBy: string,
85
+ ): Promise<IEmailServerSettings> {
86
+ let doc = await EmailServerSettingsDoc.load();
87
+ if (!doc) {
88
+ doc = new EmailServerSettingsDoc();
89
+ doc.settingsId = 'email-server-settings';
90
+ }
91
+
92
+ const nextEnabled = hasOwn(updates, 'enabled') ? Boolean(updates.enabled) : doc.enabled;
93
+ const nextEmailConfig = this.patchEmailConfig(doc.emailConfig, updates, nextEnabled);
94
+ const nextEmailPortConfig = this.patchEmailPortConfig(doc.emailPortConfig, updates);
95
+
96
+ doc.enabled = nextEnabled;
97
+ doc.emailConfig = nextEmailConfig;
98
+ doc.emailPortConfig = nextEmailPortConfig;
99
+ doc.updatedAt = Date.now();
100
+ doc.updatedBy = updatedBy;
101
+ await doc.save();
102
+
103
+ this.loadFromDoc(doc);
104
+ this.applyToRuntimeOptions();
105
+ return this.getPublicSettings();
106
+ }
107
+
108
+ private loadFromDoc(doc: EmailServerSettingsDoc): void {
109
+ this.enabled = doc.enabled;
110
+ this.cachedEmailConfig = clonePlain(doc.emailConfig);
111
+ this.cachedEmailPortConfig = clonePlain(doc.emailPortConfig);
112
+ this.updatedAt = doc.updatedAt;
113
+ this.updatedBy = doc.updatedBy;
114
+ }
115
+
116
+ private applyToRuntimeOptions(): void {
117
+ this.options.emailConfig = this.getEmailConfig();
118
+ this.options.emailPortConfig = this.getEmailPortConfig();
119
+ }
120
+
121
+ private patchEmailConfig(
122
+ existingConfig: IUnifiedEmailServerOptions | undefined,
123
+ updates: TEmailServerSettingsUpdate,
124
+ nextEnabled: boolean,
125
+ ): IUnifiedEmailServerOptions | undefined {
126
+ const nextConfig: IUnifiedEmailServerOptions | undefined = clonePlain(existingConfig) || (nextEnabled ? {
127
+ hostname: 'localhost',
128
+ ports: [...defaultEmailPorts],
129
+ domains: [],
130
+ routes: [],
131
+ } : undefined);
132
+
133
+ if (!nextConfig) return undefined;
134
+
135
+ if (hasOwn(updates, 'hostname')) {
136
+ const hostname = updates.hostname?.trim() || '';
137
+ if (nextEnabled && !hostname) {
138
+ throw new Error('Email hostname is required when email is enabled');
139
+ }
140
+ nextConfig.hostname = hostname || nextConfig.hostname;
141
+ }
142
+
143
+ if (hasOwn(updates, 'ports')) {
144
+ nextConfig.ports = this.normalizePorts(updates.ports || []);
145
+ }
146
+
147
+ if (hasOwn(updates, 'maxMessageSize')) {
148
+ if (updates.maxMessageSize === null || updates.maxMessageSize === undefined) {
149
+ delete nextConfig.maxMessageSize;
150
+ } else {
151
+ const maxMessageSize = Number(updates.maxMessageSize);
152
+ if (!Number.isInteger(maxMessageSize) || maxMessageSize <= 0) {
153
+ throw new Error('maxMessageSize must be a positive integer');
154
+ }
155
+ nextConfig.maxMessageSize = maxMessageSize;
156
+ }
157
+ }
158
+
159
+ if (nextEnabled) {
160
+ if (!nextConfig.hostname?.trim()) {
161
+ throw new Error('Email hostname is required when email is enabled');
162
+ }
163
+ nextConfig.ports = this.normalizePorts(nextConfig.ports || []);
164
+ }
165
+
166
+ nextConfig.domains = nextConfig.domains || [];
167
+ nextConfig.routes = nextConfig.routes || [];
168
+ return nextConfig;
169
+ }
170
+
171
+ private patchEmailPortConfig(
172
+ existingPortConfig: IEmailPortConfig | undefined,
173
+ updates: TEmailServerSettingsUpdate,
174
+ ): IEmailPortConfig | undefined {
175
+ const nextPortConfig: IEmailPortConfig = clonePlain(existingPortConfig) || {};
176
+ if (hasOwn(updates, 'portMapping')) {
177
+ if (updates.portMapping === null) {
178
+ delete nextPortConfig.portMapping;
179
+ } else {
180
+ nextPortConfig.portMapping = this.normalizePortMapping(updates.portMapping || {});
181
+ }
182
+ }
183
+ if (hasOwn(updates, 'receivedEmailsPath')) {
184
+ const receivedEmailsPath = updates.receivedEmailsPath?.trim() || '';
185
+ if (receivedEmailsPath) {
186
+ nextPortConfig.receivedEmailsPath = receivedEmailsPath;
187
+ } else {
188
+ delete nextPortConfig.receivedEmailsPath;
189
+ }
190
+ }
191
+ return Object.keys(nextPortConfig).length > 0 ? nextPortConfig : undefined;
192
+ }
193
+
194
+ private normalizePorts(ports: number[]): number[] {
195
+ const normalized = [...new Set(ports.map((port) => Number(port)))];
196
+ if (normalized.length === 0) {
197
+ throw new Error('At least one email port is required when email is enabled');
198
+ }
199
+ for (const port of normalized) {
200
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
201
+ throw new Error(`Invalid email port: ${port}`);
202
+ }
203
+ }
204
+ return normalized.sort((a, b) => a - b);
205
+ }
206
+
207
+ private normalizePortMapping(portMapping: Record<number, number>): Record<number, number> {
208
+ const normalized: Record<number, number> = {};
209
+ for (const [externalPortString, internalPortValue] of Object.entries(portMapping)) {
210
+ const externalPort = Number(externalPortString);
211
+ const internalPort = Number(internalPortValue);
212
+ for (const port of [externalPort, internalPort]) {
213
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
214
+ throw new Error(`Invalid email port mapping value: ${port}`);
215
+ }
216
+ }
217
+ normalized[externalPort] = internalPort;
218
+ }
219
+ return normalized;
220
+ }
221
+ }
package/ts/email/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './classes.email-domain.manager.js';
2
+ export * from './classes.email-settings.manager.js';
2
3
  export * from './classes.smartmta-storage-manager.js';
3
4
  export * from './classes.workapp-mail-manager.js';
4
5
  export * from './email-dns-records.js';
@@ -23,6 +23,7 @@ export class OpsServer {
23
23
  private statsHandler!: handlers.StatsHandler;
24
24
  private radiusHandler!: handlers.RadiusHandler;
25
25
  private emailOpsHandler!: handlers.EmailOpsHandler;
26
+ private emailSettingsHandler!: handlers.EmailSettingsHandler;
26
27
  private certificateHandler!: handlers.CertificateHandler;
27
28
  private remoteIngressHandler!: handlers.RemoteIngressHandler;
28
29
  private routeManagementHandler!: handlers.RouteManagementHandler;
@@ -82,6 +83,7 @@ export class OpsServer {
82
83
  this.statsHandler = new handlers.StatsHandler(this);
83
84
  this.radiusHandler = new handlers.RadiusHandler(this);
84
85
  this.emailOpsHandler = new handlers.EmailOpsHandler(this);
86
+ this.emailSettingsHandler = new handlers.EmailSettingsHandler(this);
85
87
  this.certificateHandler = new handlers.CertificateHandler(this);
86
88
  this.remoteIngressHandler = new handlers.RemoteIngressHandler(this);
87
89
  this.routeManagementHandler = new handlers.RouteManagementHandler(this);
@@ -100,21 +100,23 @@ export class ConfigHandler {
100
100
  }
101
101
 
102
102
  let portMapping: Record<string, number> | null = null;
103
- if (opts.emailPortConfig?.portMapping) {
103
+ const emailSettings = dcRouter.emailSettingsManager?.getPublicSettings();
104
+ const rawPortMapping = emailSettings?.portMapping || opts.emailPortConfig?.portMapping;
105
+ if (rawPortMapping) {
104
106
  portMapping = {};
105
- for (const [ext, int] of Object.entries(opts.emailPortConfig.portMapping)) {
107
+ for (const [ext, int] of Object.entries(rawPortMapping)) {
106
108
  portMapping[String(ext)] = int as number;
107
109
  }
108
110
  }
109
111
 
110
112
  const email: interfaces.requests.IConfigData['email'] = {
111
- enabled: !!dcRouter.emailServer,
112
- ports: opts.emailConfig?.ports || [],
113
+ enabled: emailSettings?.enabled ?? !!dcRouter.emailServer,
114
+ ports: emailSettings?.ports || opts.emailConfig?.ports || [],
113
115
  portMapping,
114
- hostname: opts.emailConfig?.hostname || null,
116
+ hostname: emailSettings?.hostname || opts.emailConfig?.hostname || null,
115
117
  domains: emailDomains,
116
- emailRouteCount: opts.emailConfig?.routes?.length || 0,
117
- receivedEmailsPath: opts.emailPortConfig?.receivedEmailsPath || null,
118
+ emailRouteCount: emailSettings?.routeCount ?? opts.emailConfig?.routes?.length ?? 0,
119
+ receivedEmailsPath: emailSettings?.receivedEmailsPath || opts.emailPortConfig?.receivedEmailsPath || null,
118
120
  };
119
121
 
120
122
  // --- DNS ---
@@ -186,16 +188,17 @@ export class ConfigHandler {
186
188
 
187
189
  // --- Remote Ingress ---
188
190
  const riCfg = opts.remoteIngressConfig;
191
+ const riSettings = dcRouter.remoteIngressManager?.getHubSettings();
189
192
  const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || [];
190
193
 
191
194
  // Determine TLS mode: custom certs > ACME from cert store > self-signed fallback
192
195
  let tlsMode: 'custom' | 'acme' | 'self-signed' = 'self-signed';
193
196
  if (riCfg?.tls?.certPath && riCfg?.tls?.keyPath) {
194
197
  tlsMode = 'custom';
195
- } else if (riCfg?.hubDomain) {
198
+ } else if (riSettings?.hubDomain) {
196
199
  try {
197
200
  const { ProxyCertDoc } = await import('../../db/index.js');
198
- const stored = await ProxyCertDoc.findByDomain(riCfg.hubDomain);
201
+ const stored = await ProxyCertDoc.findByDomain(riSettings.hubDomain);
199
202
  if (stored?.publicKey && stored?.privateKey) {
200
203
  tlsMode = 'acme';
201
204
  }
@@ -203,12 +206,12 @@ export class ConfigHandler {
203
206
  }
204
207
 
205
208
  const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
206
- enabled: !!dcRouter.remoteIngressManager,
207
- tunnelPort: riCfg?.tunnelPort || null,
208
- hubDomain: riCfg?.hubDomain || null,
209
+ enabled: !!riSettings?.enabled,
210
+ tunnelPort: riSettings?.tunnelPort || null,
211
+ hubDomain: riSettings?.hubDomain || null,
209
212
  tlsMode,
210
213
  connectedEdgeIps,
211
- performance: dcRouter.remoteIngressManager?.getHubPerformanceConfig() || riCfg?.performance,
214
+ performance: riSettings?.performance,
212
215
  };
213
216
 
214
217
  return {
@@ -0,0 +1,72 @@
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
+ import { requireOpsAuth } from '../helpers/auth.js';
5
+
6
+ export class EmailSettingsHandler {
7
+ public typedrouter = new plugins.typedrequest.TypedRouter();
8
+
9
+ constructor(private opsServerRef: OpsServer) {
10
+ this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
11
+ this.registerHandlers();
12
+ }
13
+
14
+ private registerHandlers(): void {
15
+ this.typedrouter.addTypedHandler(
16
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailServerSettings>(
17
+ 'getEmailServerSettings',
18
+ async (dataArg) => {
19
+ await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'email-domains:read' as any });
20
+ return { settings: this.getSettings() };
21
+ },
22
+ ),
23
+ );
24
+
25
+ this.typedrouter.addTypedHandler(
26
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateEmailServerSettings>(
27
+ 'updateEmailServerSettings',
28
+ async (dataArg) => {
29
+ const auth = await requireOpsAuth(this.opsServerRef, dataArg, {
30
+ scope: 'email-domains:write' as any,
31
+ requireAdminIdentity: true,
32
+ });
33
+ const manager = this.opsServerRef.dcRouterRef.emailSettingsManager;
34
+ if (!manager) {
35
+ return { success: false, message: 'EmailSettingsManager not initialized' };
36
+ }
37
+ try {
38
+ const settings = await this.opsServerRef.dcRouterRef.updateEmailServerSettings(
39
+ dataArg.settings,
40
+ auth.userId,
41
+ );
42
+ return { success: true, settings };
43
+ } catch (err: unknown) {
44
+ return { success: false, message: (err as Error).message };
45
+ }
46
+ },
47
+ ),
48
+ );
49
+ }
50
+
51
+ private getSettings(): interfaces.data.IEmailServerSettings {
52
+ const manager = this.opsServerRef.dcRouterRef.emailSettingsManager;
53
+ if (manager) {
54
+ return manager.getPublicSettings();
55
+ }
56
+ const emailConfig = this.opsServerRef.dcRouterRef.options.emailConfig;
57
+ const emailPortConfig = this.opsServerRef.dcRouterRef.options.emailPortConfig;
58
+ return {
59
+ enabled: Boolean(emailConfig),
60
+ hostname: emailConfig?.hostname || null,
61
+ ports: [...(emailConfig?.ports || [])],
62
+ portMapping: emailPortConfig?.portMapping ? { ...emailPortConfig.portMapping } : null,
63
+ receivedEmailsPath: emailPortConfig?.receivedEmailsPath || null,
64
+ maxMessageSize: emailConfig?.maxMessageSize ?? null,
65
+ domainCount: emailConfig?.domains?.length || 0,
66
+ routeCount: emailConfig?.routes?.length || 0,
67
+ authUserCount: emailConfig?.auth?.users?.length || 0,
68
+ updatedAt: 0,
69
+ updatedBy: 'legacy-options',
70
+ };
71
+ }
72
+ }
@@ -5,6 +5,7 @@ export * from './security.handler.js';
5
5
  export * from './stats.handler.js';
6
6
  export * from './radius.handler.js';
7
7
  export * from './email-ops.handler.js';
8
+ export * from './email-settings.handler.js';
8
9
  export * from './certificate.handler.js';
9
10
  export * from './remoteingress.handler.js';
10
11
  export * from './route-management.handler.js';
@@ -3,6 +3,10 @@ import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
4
  import { requireOpsAuth } from '../helpers/auth.js';
5
5
 
6
+ function hasOwn(objectArg: object, keyArg: string): boolean {
7
+ return Object.prototype.hasOwnProperty.call(objectArg, keyArg);
8
+ }
9
+
6
10
  export class RemoteIngressHandler {
7
11
  constructor(private opsServerRef: OpsServer) {
8
12
  this.registerHandlers();
@@ -197,6 +201,8 @@ export class RemoteIngressHandler {
197
201
  const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
198
202
  return {
199
203
  settings: manager?.getHubSettings() || {
204
+ enabled: false,
205
+ tunnelPort: 8443,
200
206
  updatedAt: 0,
201
207
  updatedBy: 'default',
202
208
  },
@@ -216,8 +222,22 @@ export class RemoteIngressHandler {
216
222
  });
217
223
 
218
224
  try {
225
+ const updates: interfaces.data.TRemoteIngressHubSettingsUpdate = {};
226
+ if (hasOwn(dataArg, 'enabled') && dataArg.enabled !== undefined) {
227
+ updates.enabled = dataArg.enabled;
228
+ }
229
+ if (hasOwn(dataArg, 'tunnelPort') && dataArg.tunnelPort !== undefined) {
230
+ updates.tunnelPort = dataArg.tunnelPort;
231
+ }
232
+ if (hasOwn(dataArg, 'hubDomain')) {
233
+ updates.hubDomain = dataArg.hubDomain ?? null;
234
+ }
235
+ if (hasOwn(dataArg, 'performance')) {
236
+ updates.performance = dataArg.performance ?? null;
237
+ }
238
+
219
239
  const settings = await this.opsServerRef.dcRouterRef.updateRemoteIngressHubSettings(
220
- { performance: dataArg.performance },
240
+ updates,
221
241
  auth.userId,
222
242
  );
223
243
  return { success: true, settings };
@@ -250,16 +270,16 @@ export class RemoteIngressHandler {
250
270
  return { success: false, message: 'Edge is disabled' };
251
271
  }
252
272
 
253
- const hubHost = dataArg.hubHost
254
- || this.opsServerRef.dcRouterRef.options.remoteIngressConfig?.hubDomain;
273
+ const hubSettings = manager.getHubSettings();
274
+ const hubHost = dataArg.hubHost || hubSettings.hubDomain;
255
275
  if (!hubHost) {
256
276
  return {
257
277
  success: false,
258
- message: 'No hub hostname configured. Set hubDomain in remoteIngressConfig or provide hubHost.',
278
+ message: 'No hub hostname configured. Set the RemoteIngress hub domain or provide hubHost.',
259
279
  };
260
280
  }
261
281
 
262
- const hubPort = this.opsServerRef.dcRouterRef.options.remoteIngressConfig?.tunnelPort ?? 8443;
282
+ const hubPort = hubSettings.tunnelPort;
263
283
 
264
284
  const token = plugins.remoteingress.encodeConnectionToken({
265
285
  hubHost,
@@ -282,7 +282,7 @@ export class WorkHosterHandler {
282
282
  outbound: Boolean(dcRouter.emailServer),
283
283
  },
284
284
  remoteIngress: {
285
- enabled: Boolean(dcRouter.options.remoteIngressConfig?.enabled),
285
+ enabled: Boolean(dcRouter.remoteIngressManager?.getHubSettings().enabled),
286
286
  },
287
287
  dns: {
288
288
  authoritative: Boolean(dcRouter.options.dnsScopes?.length),