@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.
- package/deno.json +1 -1
- package/dist_serve/bundle.js +491 -436
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +16 -13
- package/dist_ts/classes.dcrouter.js +320 -82
- package/dist_ts/config/classes.route-config-manager.d.ts +1 -0
- package/dist_ts/config/classes.route-config-manager.js +4 -1
- package/dist_ts/db/documents/classes.email-server-settings.doc.d.ts +14 -0
- package/dist_ts/db/documents/classes.email-server-settings.doc.js +103 -0
- package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.d.ts +3 -0
- package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.js +20 -2
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +2 -1
- package/dist_ts/email/classes.email-domain.manager.d.ts +3 -1
- package/dist_ts/email/classes.email-domain.manager.js +6 -3
- package/dist_ts/email/classes.email-settings.manager.d.ts +25 -0
- package/dist_ts/email/classes.email-settings.manager.js +184 -0
- package/dist_ts/email/index.d.ts +1 -0
- package/dist_ts/email/index.js +2 -1
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/config.handler.js +17 -14
- package/dist_ts/opsserver/handlers/email-settings.handler.d.ts +10 -0
- package/dist_ts/opsserver/handlers/email-settings.handler.js +57 -0
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +24 -6
- package/dist_ts/opsserver/handlers/workhoster.handler.js +2 -2
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +4 -6
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +58 -19
- package/dist_ts_interfaces/data/email-settings.d.ts +39 -0
- package/dist_ts_interfaces/data/email-settings.js +2 -0
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/remoteingress.d.ts +7 -0
- package/dist_ts_interfaces/requests/email-settings.d.ts +26 -0
- package/dist_ts_interfaces/requests/email-settings.js +2 -0
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/remoteingress.d.ts +4 -1
- package/dist_ts_migrations/index.d.ts +14 -1
- package/dist_ts_migrations/index.js +107 -2
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +6 -1
- package/dist_ts_web/appstate.js +23 -1
- package/dist_ts_web/elements/email/ops-view-email-domains.d.ts +4 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +122 -1
- package/dist_ts_web/elements/network/ops-view-remoteingress.d.ts +2 -1
- package/dist_ts_web/elements/network/ops-view-remoteingress.js +57 -17
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +361 -100
- package/ts/config/classes.route-config-manager.ts +4 -0
- package/ts/db/documents/classes.email-server-settings.doc.ts +40 -0
- package/ts/db/documents/classes.remote-ingress-hub-settings.doc.ts +9 -0
- package/ts/db/documents/index.ts +1 -0
- package/ts/email/classes.email-domain.manager.ts +6 -2
- package/ts/email/classes.email-settings.manager.ts +221 -0
- package/ts/email/index.ts +1 -0
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/config.handler.ts +16 -13
- package/ts/opsserver/handlers/email-settings.handler.ts +72 -0
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/handlers/remoteingress.handler.ts +25 -5
- package/ts/opsserver/handlers/workhoster.handler.ts +1 -1
- package/ts/remoteingress/classes.remoteingress-manager.ts +65 -18
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +33 -1
- package/ts_web/elements/email/ops-view-email-domains.ts +126 -0
- 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
|
|
package/ts/db/documents/index.ts
CHANGED
|
@@ -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
|
|
20
|
+
private baseEmailDomains: IEmailDomainConfig[] = [];
|
|
21
21
|
|
|
22
22
|
constructor(dcRouterRef: any) {
|
|
23
23
|
this.dcRouter = dcRouterRef;
|
|
24
|
-
this.
|
|
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
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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 (
|
|
198
|
+
} else if (riSettings?.hubDomain) {
|
|
196
199
|
try {
|
|
197
200
|
const { ProxyCertDoc } = await import('../../db/index.js');
|
|
198
|
-
const stored = await ProxyCertDoc.findByDomain(
|
|
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: !!
|
|
207
|
-
tunnelPort:
|
|
208
|
-
hubDomain:
|
|
209
|
+
enabled: !!riSettings?.enabled,
|
|
210
|
+
tunnelPort: riSettings?.tunnelPort || null,
|
|
211
|
+
hubDomain: riSettings?.hubDomain || null,
|
|
209
212
|
tlsMode,
|
|
210
213
|
connectedEdgeIps,
|
|
211
|
-
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
|
-
|
|
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
|
|
254
|
-
|
|
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
|
|
278
|
+
message: 'No hub hostname configured. Set the RemoteIngress hub domain or provide hubHost.',
|
|
259
279
|
};
|
|
260
280
|
}
|
|
261
281
|
|
|
262
|
-
const hubPort =
|
|
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.
|
|
285
|
+
enabled: Boolean(dcRouter.remoteIngressManager?.getHubSettings().enabled),
|
|
286
286
|
},
|
|
287
287
|
dns: {
|
|
288
288
|
authoritative: Boolean(dcRouter.options.dnsScopes?.length),
|