import type { UpstreamRequest, UpstreamResponse, } from '../../application/connections/upstream-session-client.js'; import type { TransportResponse } from './safe-instance-transport.js'; import { UpstreamError } from './upstream-error.js'; import { OperationNotDispatchedError } from '../../application/operations/secure-operation-execution.js'; export interface SafeUpstreamTransport { get(url: string, headers?: Readonly>): Promise; post( url: string, headers: Readonly>, body: string, ): Promise; } export class SafeUpstreamGateway { constructor(private readonly options: { readonly transport: SafeUpstreamTransport }) {} async postNetworkRegisterAuto(origin: string): Promise { let parsed: URL; try { parsed = new URL(origin); } catch { throw this.notDispatched(); } if ( (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') || parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash ) throw this.notDispatched(); const url = `${parsed.origin}/api/network/register-auto`; try { return await this.options.transport.post(url, {}, ''); } catch (error) { if ( error instanceof UpstreamError && (error.code === 'UNSAFE_ORIGIN' || error.code === 'UNSAFE_RESOLUTION') ) throw new OperationNotDispatchedError(error.code); throw error; } } async request(request: UpstreamRequest): Promise { const url = new URL(request.url); if (request.method === 'GET') { if ( request.secret !== undefined || request.body !== undefined || (url.pathname !== '/api/stats' && url.pathname !== '/api/sim') || url.search || url.hash || url.username || url.password || (request.headers.cookie !== undefined && (typeof request.headers.cookie !== 'string' || !/^simadmin_session=[^;\s,]+$/.test(request.headers.cookie))) ) throw new UpstreamError('UPSTREAM_REQUEST_INVALID'); return this.options.transport.get(request.url, request.headers); } if (url.protocol !== 'https:') throw new UpstreamError('UPSTREAM_INSECURE_AUTH'); if (request.method !== 'POST') throw new UpstreamError('UPSTREAM_REQUEST_INVALID'); if (request.url.endsWith('/api/auth/login')) { if (typeof request.secret !== 'string' || request.body !== '[REDACTED]') throw new UpstreamError('UPSTREAM_REQUEST_INVALID'); return this.options.transport.post( request.url, request.headers, JSON.stringify({ password: request.secret }), ); } if (request.url.endsWith('/api/auth/logout')) { if (request.secret !== undefined || request.body !== undefined) throw new UpstreamError('UPSTREAM_REQUEST_INVALID'); return this.options.transport.post(request.url, request.headers, ''); } throw new UpstreamError('UPSTREAM_REQUEST_INVALID'); } private notDispatched(): OperationNotDispatchedError { return new OperationNotDispatchedError('UPSTREAM_REQUEST_INVALID'); } }