79 lines
4.0 KiB
JavaScript
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 })));
|