MotorSignal-App/website/test-support/qa-auth-backend.mjs

79 lines
4.0 KiB
JavaScript

import { createServer } from 'node:http';
import { randomBytes } from 'node:crypto';
import { initializeApp, getApps } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
const projectId = process.env.FIREBASE_PROJECT_ID;
const authEmulatorHost = process.env.FIREBASE_AUTH_EMULATOR_HOST;
const port = Number(process.env.QA_AUTH_BACKEND_PORT ?? 8189);
if (!projectId?.startsWith('demo-') || !authEmulatorHost?.startsWith('127.0.0.1:')) {
throw new Error('QA auth backend requires a demo project and loopback Auth emulator.');
}
const app = getApps()[0] ?? initializeApp({ projectId });
const auth = getAuth(app);
const sessions = new Set();
function json(response, status, body) {
response.writeHead(status, { 'content-type': 'application/json' });
response.end(JSON.stringify(body));
}
const server = createServer(async (request, response) => {
const url = new URL(request.url ?? '/', 'http://127.0.0.1');
if (url.pathname === '/api/v1/auth/firebase/login' && request.method === 'POST') {
let payload;
try { payload = JSON.parse(await new Promise((resolve, reject) => {
let body = '';
request.on('data', (chunk) => { body += chunk; if (body.length > 100_000) reject(new Error('too large')); });
request.on('end', () => resolve(body));
request.on('error', reject);
})); } catch {
console.log(JSON.stringify({ endpoint: url.pathname, status: 400, reason: 'invalid-json' }));
return json(response, 400, { detail: 'Invalid request body.' });
}
try {
const decoded = await auth.verifyIdToken(payload?.id_token);
if (decoded.aud !== projectId || decoded.iss !== `https://securetoken.google.com/${projectId}`) {
throw new Error('Project claims mismatch.');
}
const accessToken = `qa-session-${randomBytes(24).toString('hex')}`;
const refreshToken = `qa-refresh-${randomBytes(24).toString('hex')}`;
sessions.add(accessToken);
sessions.add(refreshToken);
console.log(JSON.stringify({ endpoint: url.pathname, status: 200, verified: true, projectId, uidPrefix: decoded.uid.slice(0, 8) }));
return json(response, 200, { access_token: accessToken, refresh_token: refreshToken });
} catch {
console.log(JSON.stringify({ endpoint: url.pathname, status: 401, verified: false, projectId }));
return json(response, 401, { detail: 'Firebase token verification failed.' });
}
}
if (url.pathname === '/api/v1/auth/login' && request.method === 'POST') {
console.log(JSON.stringify({ endpoint: url.pathname, status: 401, reason: 'email-auth-not-configured-in-local-fixture' }));
return json(response, 401, { detail: 'Email authentication is not configured in this local QA fixture.' });
}
if (url.pathname === '/api/v1/auth/sessions' && request.method === 'GET') {
const token = request.headers.authorization?.replace(/^Bearer\s+/i, '');
const ok = Boolean(token && sessions.has(token));
console.log(JSON.stringify({ endpoint: url.pathname, status: ok ? 200 : 401, cookieBearerPresent: Boolean(token), sessionMatched: ok }));
return ok ? json(response, 200, [{ id: 'qa-current-session', device_name: 'Isolated local browser', current: true, created_at: new Date().toISOString() }])
: json(response, 401, { detail: 'Authentication required.' });
}
if (url.pathname.startsWith('/api/v1/auth/') && ['POST', 'DELETE'].includes(request.method ?? '')) {
const token = request.headers.authorization?.replace(/^Bearer\s+/i, '');
const ok = Boolean(token && sessions.has(token));
console.log(JSON.stringify({ endpoint: url.pathname, status: ok ? 204 : 401, cookieBearerPresent: Boolean(token), sessionMatched: ok }));
response.writeHead(ok ? 204 : 401);
return response.end();
}
console.log(JSON.stringify({ endpoint: url.pathname, status: 404 }));
return json(response, 404, { detail: 'Local QA endpoint not found.' });
});
server.listen(port, '127.0.0.1', () => console.log(JSON.stringify({ listening: `127.0.0.1:${port}`, projectId, authEmulatorHost })));