// --------------------------------------------------------------------------- // App-only auth shim (NEW; runs first — the `00.` prefix orders it before auth.ts / fetch.ts). // // The bundled Capacitor app is token-based and cross-origin: it sends `Authorization: Bearer` + // `X-Logship-*` context headers (and the marker `X-Logship-App: 1`) instead of cookies. This // middleware: // 1. emits CORS for the app's WebView origin (https://localhost / capacitor://localhost) and // answers the OPTIONS preflight, so normal WebView fetch (incl. multipart uploads) works; // 2. translates the Bearer token + context headers into the EXACT cookies the existing routes, // getTokenHelper, auth.ts and fetch.ts already read — so every existing /api route serves the // app with ZERO changes. // // It is a strict no-op for normal web traffic: web requests carry no app Origin and no // `X-Logship-App` header, so neither branch runs and existing behavior is byte-identical. // --------------------------------------------------------------------------- // The Capacitor Android WebView origin (androidScheme 'https' → https://localhost). The others // cover older schemes / iOS so the same build keeps working if the scheme changes. const APP_ORIGINS = new Set([ 'https://localhost', 'http://localhost', 'capacitor://localhost', 'ionic://localhost', ]) // X-Logship-* header → the cookie name the existing server code reads via getCookie(). const CONTEXT_HEADER_TO_COOKIE: Record = { 'x-logship-organization-id': 'logship_organization_id', 'x-logship-warehouse-id': 'logship_warehouse_id', 'x-logship-role-id': 'logship_role_id', 'x-logship-client-id': 'logship_client_id', 'x-logship-user-id': 'logship_user_id', 'x-logship-language': 'logship_language', } const ALLOW_HEADERS = [ 'Authorization', 'Content-Type', 'X-Logship-App', 'X-Logship-Organization-Id', 'X-Logship-Warehouse-Id', 'X-Logship-Role-Id', 'X-Logship-Client-Id', 'X-Logship-User-Id', 'X-Logship-Language', 'X-Logship-User', ].join(',') export default defineEventHandler((event) => { const origin = getHeader(event, 'origin') || '' const isAppOrigin = APP_ORIGINS.has(origin) // --- 1) CORS for the bundled app (token auth → no credentials/cookies needed cross-origin) --- if (isAppOrigin) { setHeader(event, 'Access-Control-Allow-Origin', origin) setHeader(event, 'Vary', 'Origin') setHeader(event, 'Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS') setHeader(event, 'Access-Control-Allow-Headers', ALLOW_HEADERS) setHeader(event, 'Access-Control-Max-Age', '600') if (event.method === 'OPTIONS') { // Preflight must return 2xx with the CORS headers above, then stop. return sendNoContent(event, 204) } } // --- 2) Translate the app's Bearer token + context headers into request cookies --- if (getHeader(event, 'x-logship-app') === '1') { const cookies: Record = {} const auth = getHeader(event, 'authorization') || '' if (auth.startsWith('Bearer ')) cookies['logship_it'] = auth.slice(7) for (const [header, cookieName] of Object.entries(CONTEXT_HEADER_TO_COOKIE)) { const v = getHeader(event, header) if (v) cookies[cookieName] = v } // X-Logship-User carries the base64'd user JSON so authenticated.get can return the user. const userB64 = getHeader(event, 'x-logship-user') if (userB64) { try { cookies['logship_user'] = Buffer.from(userB64, 'base64').toString('utf-8') } catch { /* ignore */ } } const pairs = Object.entries(cookies).map(([k, v]) => `${k}=${encodeURIComponent(v)}`) if (pairs.length) { // getCookie() re-parses this header on every call (h3 doesn't cache), and this middleware // runs before anything reads cookies, so all downstream getCookie() calls see these values. const existing = event.node.req.headers.cookie event.node.req.headers.cookie = existing ? `${existing}; ${pairs.join('; ')}` : pairs.join('; ') } } })