// Helper: forward a request to platform-api using the signed-in operator's // access token. Every operator proxy route uses this — it's the only place // we touch the encrypted session. // // Also propagates the originating client IP via X-Forwarded-For so the // platform-api can record it in the audit log. Without this, the API would // only see the operator container's IP. import type { H3Event } from 'h3' import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js' const BASE = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001' function originatingIp(event: H3Event): string | undefined { // Traefik already injects X-Forwarded-For on the way in. Take the leftmost // entry (the original client), trimming any whitespace. const fwd = getHeader(event, 'x-forwarded-for') if (fwd) { const first = fwd.split(',')[0]?.trim() if (first) return first } // Direct request (no proxy header) — fall back to the socket address. return event.node.req.socket?.remoteAddress } export async function platformApi( event: H3Event, path: string, init: { method?: string body?: BodyInit | Record | null query?: Record } = {}, ): Promise { const session = await getUserSession(event).catch(() => null) const accessToken = (session as { accessToken?: string } | null)?.accessToken if (!accessToken) { throw createError({ statusCode: 401, statusMessage: 'Not signed in' }) } const clientIp = originatingIp(event) const headers: Record = { Authorization: `Bearer ${accessToken}` } if (clientIp) headers['x-forwarded-for'] = clientIp try { return (await $fetch(`${BASE}${path}`, { method: (init.method as 'GET' | 'POST' | 'PATCH' | 'DELETE') ?? 'GET', headers, body: init.body, query: init.query, })) as T } catch (err: unknown) { const e = err as { statusCode?: number; data?: unknown } throw createError({ statusCode: e.statusCode ?? 500, data: e.data }) } }