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'; function hasOwn(objectArg: object, keyArg: string): boolean { return Object.prototype.hasOwnProperty.call(objectArg, keyArg); } export class RemoteIngressHandler { constructor(private opsServerRef: OpsServer) { this.registerHandlers(); } private registerHandlers(): void { const viewRouter = this.opsServerRef.viewRouter; const adminRouter = this.opsServerRef.adminRouter; // ---- Read endpoints (viewRouter — valid identity required via middleware) ---- // Get all remote ingress edges viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRemoteIngresses', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:read' }); const manager = this.opsServerRef.dcRouterRef.remoteIngressManager; if (!manager) { return { edges: [] }; } // Return edges without secrets, enriched with effective listen ports and breakdown const edges = manager.getAllEdges().map((e) => { const breakdown = manager.getPortBreakdown(e); return { ...e, secret: '********', // Never expose secrets via API effectiveListenPorts: manager.getEffectiveListenPorts(e), effectiveListenPortsUdp: manager.getEffectiveListenPortsUdp(e), manualPorts: breakdown.manual, derivedPorts: breakdown.derived, }; }); return { edges }; }, ), ); // ---- Write endpoints (adminRouter) ---- // Create a new remote ingress edge adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createRemoteIngress', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:write', requireAdminIdentity: true, }); try { const edge = await this.opsServerRef.dcRouterRef.mutateRemoteIngressEdges((manager) => manager.createEdge( dataArg.name, dataArg.listenPorts || [], dataArg.tags, dataArg.autoDerivePorts ?? true, dataArg.performance, dataArg.egress, { publicIp: dataArg.publicIp, publicIpV6: dataArg.publicIpV6, mailHostname: dataArg.mailHostname, }, )); this.opsServerRef.invalidateRealtime?.(['edges', 'emailDomains', 'dns', 'routes'], { changedIds: [edge.id], reason: 'remote-ingress-created', }); return { success: true, edge }; } catch (err: unknown) { return { success: false, edge: null as any, message: (err as Error).message, }; } }, ), ); // Delete a remote ingress edge adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteRemoteIngress', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:write', requireAdminIdentity: true, }); const deleted = await this.opsServerRef.dcRouterRef.mutateRemoteIngressEdges( (manager) => manager.deleteEdge(dataArg.id), ).catch((err: unknown) => { if ((err as Error).message.includes('RemoteIngress')) { return false; } throw err; }); if (deleted) { this.opsServerRef.invalidateRealtime?.(['edges', 'emailDomains', 'dns', 'routes'], { changedIds: [dataArg.id], reason: 'remote-ingress-deleted', }); } return { success: deleted, message: deleted ? undefined : 'Edge not found or RemoteIngress not configured', }; }, ), ); // Update a remote ingress edge adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateRemoteIngress', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:write', requireAdminIdentity: true, }); let failureMessage: string | undefined; const result = await this.opsServerRef.dcRouterRef.mutateRemoteIngressEdges(async (manager) => { const edge = await manager.updateEdge(dataArg.id, { name: dataArg.name, listenPorts: dataArg.listenPorts, autoDerivePorts: dataArg.autoDerivePorts, enabled: dataArg.enabled, performance: dataArg.performance, egress: dataArg.egress, tags: dataArg.tags, publicIp: dataArg.publicIp, publicIpV6: dataArg.publicIpV6, mailHostname: dataArg.mailHostname, }); if (!edge) { return null; } const breakdown = manager.getPortBreakdown(edge); return { ...edge, secret: '********', effectiveListenPorts: manager.getEffectiveListenPorts(edge), effectiveListenPortsUdp: manager.getEffectiveListenPortsUdp(edge), manualPorts: breakdown.manual, derivedPorts: breakdown.derived, }; }).catch((err: unknown) => { // Validation rejections (bad IP, hub-IP guard, bad FQDN) and a // missing manager both surface as success:false with the reason. failureMessage = (err as Error).message; return null; }); if (!result) { return { success: false, edge: null as any, message: failureMessage }; } this.opsServerRef.invalidateRealtime?.(['edges', 'emailDomains', 'dns', 'routes'], { changedIds: [dataArg.id], reason: 'remote-ingress-updated', }); return { success: true, edge: result, }; }, ), ); // Regenerate secret for an edge adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'regenerateRemoteIngressSecret', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:write', requireAdminIdentity: true, }); const secret = await this.opsServerRef.dcRouterRef.mutateRemoteIngressEdges( (manager) => manager.regenerateSecret(dataArg.id), ).catch((err: unknown) => { if ((err as Error).message.includes('RemoteIngress')) { return null; } throw err; }); if (!secret) { return { success: false, secret: '' }; } this.opsServerRef.invalidateRealtime?.('edges', { changedIds: [dataArg.id], reason: 'remote-ingress-secret-regenerated', }); return { success: true, secret }; }, ), ); // Get runtime status of all edges (read) viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRemoteIngressStatus', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:read' }); const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager; if (!tunnelManager) { return { statuses: [] }; } return { statuses: tunnelManager.getEdgeStatuses() }; }, ), ); // Get hub-level settings (read) viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRemoteIngressHubSettings', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:read' }); const manager = this.opsServerRef.dcRouterRef.remoteIngressManager; return { settings: manager?.getHubSettings() || { enabled: false, tunnelPort: 8443, updatedAt: 0, updatedBy: 'default', }, }; }, ), ); // Update hub-level settings (write) adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateRemoteIngressHubSettings', async (dataArg, toolsArg) => { const auth = await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:write', requireAdminIdentity: true, }); try { const updates: interfaces.data.TRemoteIngressHubSettingsUpdate = {}; if (hasOwn(dataArg, 'enabled') && dataArg.enabled !== undefined) { updates.enabled = dataArg.enabled; } if (hasOwn(dataArg, 'tunnelPort') && dataArg.tunnelPort !== undefined) { updates.tunnelPort = dataArg.tunnelPort; } if (hasOwn(dataArg, 'hubDomain')) { updates.hubDomain = dataArg.hubDomain ?? null; } if (hasOwn(dataArg, 'performance')) { updates.performance = dataArg.performance ?? null; } const settings = await this.opsServerRef.dcRouterRef.updateRemoteIngressHubSettings( updates, auth.userId, ); this.opsServerRef.invalidateRealtime?.(['edges', 'emailDomains', 'dns', 'routes'], { reason: 'remote-ingress-hub-settings-updated', }); return { success: true, settings }; } catch (err: unknown) { return { success: false, message: (err as Error).message }; } }, ), ); // Get a connection token for an edge (write — exposes secret) adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRemoteIngressConnectionToken', async (dataArg, toolsArg) => { await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'remote-ingress:write', requireAdminIdentity: true, }); const manager = this.opsServerRef.dcRouterRef.remoteIngressManager; if (!manager) { return { success: false, message: 'RemoteIngress not configured' }; } const edge = manager.getEdge(dataArg.edgeId); if (!edge) { return { success: false, message: 'Edge not found' }; } if (!edge.enabled) { return { success: false, message: 'Edge is disabled' }; } const hubSettings = manager.getHubSettings(); const hubHost = dataArg.hubHost || hubSettings.hubDomain; if (!hubHost) { return { success: false, message: 'No hub hostname configured. Set the RemoteIngress hub domain or provide hubHost.', }; } const hubPort = hubSettings.tunnelPort; const token = plugins.remoteingress.encodeConnectionToken({ hubHost, hubPort, edgeId: edge.id, secret: edge.secret, }); return { success: true, token }; }, ), ); } }