import * as plugins from '../../plugins.js'; import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; import { requireOpsAuth } from '../helpers/auth.js'; const isRecord = (valueArg: unknown): valueArg is Record => { return typeof valueArg === 'object' && valueArg !== null; }; const normalizeActionErrors = ( valueArg: unknown, ): interfaces.requests.IEmailDomainActionError[] | undefined => { if (!Array.isArray(valueArg)) return undefined; const errors: interfaces.requests.IEmailDomainActionError[] = []; for (const entry of valueArg) { if (!isRecord(entry) || typeof entry.message !== 'string') continue; errors.push({ code: typeof entry.code === 'string' ? entry.code : 'EMAIL_DOMAIN_ACTION_FAILED', message: entry.message, recordKey: typeof entry.recordKey === 'string' ? entry.recordKey : undefined, retryable: typeof entry.retryable === 'boolean' ? entry.retryable : undefined, }); } return errors.length > 0 ? errors : undefined; }; const normalizeEmailDomainActionResult = ( valueArg: unknown, defaultSuccess = true, ): interfaces.requests.IEmailDomainActionResult => { const value = isRecord(valueArg) ? valueArg : {}; const directDomain = isRecord(value.domain) ? value.domain : typeof value.id === 'string' && typeof value.domain === 'string' ? value : undefined; const reconciliation = isRecord(directDomain?.reconciliation) ? directDomain.reconciliation : undefined; const rawLifecycle = value.lifecycleStatus ?? reconciliation?.lifecycleStatus; const lifecycleStatus = rawLifecycle === 'active' || rawLifecycle === 'pending' || rawLifecycle === 'failed' || rawLifecycle === 'deleting' ? rawLifecycle : undefined; const reconciliationCapability = isRecord(reconciliation?.capability) ? reconciliation.capability.egressIdentityProof : undefined; const rawCapability = value.capability ?? reconciliationCapability; const capability = rawCapability === 'supported' || rawCapability === 'unsupported' ? rawCapability : undefined; const errors = normalizeActionErrors(value.errors ?? reconciliation?.errors); return { success: typeof value.success === 'boolean' ? value.success : lifecycleStatus === 'failed' ? false : defaultSuccess, message: typeof value.message === 'string' ? value.message : errors?.[0]?.message, lifecycleStatus, code: typeof value.code === 'string' ? value.code : errors?.[0]?.code, errors, retryAt: typeof value.retryAt === 'string' ? value.retryAt : typeof reconciliation?.retryAt === 'string' ? reconciliation.retryAt : undefined, capability, }; }; const normalizeEmailDomainActionFailure = ( errorArg: unknown, fallbackMessageArg: string, ): interfaces.requests.IEmailDomainActionResult => { const result = normalizeEmailDomainActionResult(errorArg, false); const errorMessage = errorArg instanceof Error ? errorArg.message : undefined; return { ...result, success: false, message: result.message || errorMessage || result.errors?.[0]?.message || fallbackMessageArg, }; }; /** * CRUD + DNS provisioning handler for email domains. * * Auth: admin JWT or API token with `email-domains:read` / `email-domains:write` scope. */ export class EmailDomainHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private async requireAuth( request: { identity?: interfaces.data.IIdentity; apiToken?: string }, requiredScope?: interfaces.data.TApiTokenScope, ): Promise { const auth = await requireOpsAuth(this.opsServerRef, request, { scope: requiredScope, requireAdminIdentity: requiredScope?.endsWith(':write'), }); return auth.userId; } private get manager() { return this.opsServerRef.dcRouterRef.emailDomainManager; } private managerUnavailable(): interfaces.requests.IEmailDomainActionResult { const message = 'EmailDomainManager not initialized'; return { success: false, message, code: 'EMAIL_DOMAIN_MANAGER_UNAVAILABLE', capability: 'unsupported', errors: [ { code: 'EMAIL_DOMAIN_MANAGER_UNAVAILABLE', message, retryable: true, }, ], }; } private registerHandlers(): void { // List all email domains this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getEmailDomains', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:read' as any); if (!this.manager) return { domains: [] }; return { domains: await this.manager.getAll() }; }, ), ); // Get single email domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getEmailDomain', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:read' as any); if (!this.manager) return { domain: null }; return { domain: await this.manager.getById(dataArg.id) }; }, ), ); // Create email domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createEmailDomain', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:write' as any); if (!this.manager) { return this.managerUnavailable(); } try { const rawResult: unknown = await this.manager.createEmailDomain({ linkedDomainId: dataArg.linkedDomainId, subdomain: dataArg.subdomain, dkimSelector: dataArg.dkimSelector, dkimKeySize: dataArg.dkimKeySize, rotateKeys: dataArg.rotateKeys, rotationIntervalDays: dataArg.rotationIntervalDays, remoteIngress: dataArg.remoteIngress, }); const result = normalizeEmailDomainActionResult(rawResult); const resultRecord = isRecord(rawResult) ? rawResult : undefined; const domain = isRecord(resultRecord?.domain) ? resultRecord.domain as unknown as interfaces.data.IEmailDomain : typeof resultRecord?.id === 'string' ? rawResult as interfaces.data.IEmailDomain : undefined; return { ...result, domain }; } catch (err: unknown) { return normalizeEmailDomainActionFailure(err, 'Failed to create email domain'); } }, ), ); // Update email domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateEmailDomain', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:write' as any); if (!this.manager) { return this.managerUnavailable(); } try { const rawResult: unknown = await this.manager.updateEmailDomain(dataArg.id, { rotateKeys: dataArg.rotateKeys, rotationIntervalDays: dataArg.rotationIntervalDays, rateLimits: dataArg.rateLimits, remoteIngress: dataArg.remoteIngress, }); const result = normalizeEmailDomainActionResult(rawResult); return result; } catch (err: unknown) { return normalizeEmailDomainActionFailure(err, 'Failed to update email domain'); } }, ), ); // Delete email domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteEmailDomain', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:write' as any); if (!this.manager) { return this.managerUnavailable(); } try { const rawResult: unknown = await this.manager.deleteEmailDomain(dataArg.id); const result = normalizeEmailDomainActionResult(rawResult); return result; } catch (err: unknown) { return normalizeEmailDomainActionFailure(err, 'Failed to delete email domain'); } }, ), ); // Validate DNS records this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'validateEmailDomain', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:read' as any); if (!this.manager) { return this.managerUnavailable(); } try { const rawResult: unknown = await this.manager.validateDns(dataArg.id); const result = normalizeEmailDomainActionResult(rawResult); const resultRecord = isRecord(rawResult) ? rawResult : undefined; const records = ( Array.isArray(rawResult) ? rawResult : Array.isArray(resultRecord?.records) ? resultRecord.records : undefined ) as interfaces.data.IEmailDnsRecord[] | undefined; const domain = isRecord(resultRecord?.domain) ? resultRecord.domain as unknown as interfaces.data.IEmailDomain : await this.manager.getById(dataArg.id) ?? undefined; return { ...result, domain, records }; } catch (err: unknown) { return normalizeEmailDomainActionFailure(err, 'Failed to validate email domain DNS'); } }, ), ); // Get required DNS records this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getEmailDomainDnsRecords', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:read' as any); if (!this.manager) return { records: [] }; return { records: await this.manager.getRequiredDnsRecords(dataArg.id) }; }, ), ); // Auto-provision DNS records this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'provisionEmailDomainDns', async (dataArg) => { await this.requireAuth(dataArg, 'email-domains:write' as any); if (!this.manager) { return this.managerUnavailable(); } try { const rawResult: unknown = await this.manager.provisionDnsRecords(dataArg.id); const result = normalizeEmailDomainActionResult(rawResult); if (!result.success) return result; const resultRecord = isRecord(rawResult) ? rawResult : undefined; const provisioned = typeof rawResult === 'number' ? rawResult : typeof resultRecord?.provisioned === 'number' ? resultRecord.provisioned : undefined; return { ...result, provisioned }; } catch (err: unknown) { return normalizeEmailDomainActionFailure(err, 'Failed to provision email domain DNS'); } }, ), ); } }