import express from 'express'; import { AppError } from '../lib/errors.js'; import { getSessionForActor, parseClientSignIn, parseClientSignUp, parseStaffPhoneStart, parseStaffPhoneVerify, signInClient, signOutActor, signUpClient, startStaffPhoneAuth, verifyStaffPhoneAuth, } from '../services/auth-service.js'; import { verifyFirebaseToken } from '../services/firebase-auth.js'; const defaultAuthService = { parseClientSignIn, parseClientSignUp, parseStaffPhoneStart, parseStaffPhoneVerify, signInClient, signOutActor, signUpClient, startStaffPhoneAuth, verifyStaffPhoneAuth, getSessionForActor, }; function getBearerToken(header) { if (!header) return null; const [scheme, token] = header.split(' '); if (!scheme || scheme.toLowerCase() !== 'bearer' || !token) return null; return token; } async function requireAuth(req, _res, next) { try { const token = getBearerToken(req.get('Authorization')); if (!token) { throw new AppError('UNAUTHENTICATED', 'Missing bearer token', 401); } if (process.env.AUTH_BYPASS === 'true') { req.actor = { uid: 'test-user', email: 'test@krow.local', role: 'TEST' }; return next(); } const decoded = await verifyFirebaseToken(token); req.actor = { uid: decoded.uid, email: decoded.email || null, role: decoded.role || null, }; return next(); } catch (error) { if (error instanceof AppError) return next(error); return next(new AppError('UNAUTHENTICATED', 'Token verification failed', 401)); } } export function createAuthRouter(options = {}) { const router = express.Router(); const fetchImpl = options.fetchImpl || fetch; const authService = options.authService || defaultAuthService; router.use(express.json({ limit: '1mb' })); router.post('/client/sign-in', async (req, res, next) => { try { const payload = authService.parseClientSignIn(req.body); const session = await authService.signInClient(payload, { fetchImpl }); return res.status(200).json({ ...session, requestId: req.requestId, }); } catch (error) { return next(error); } }); router.post('/client/sign-up', async (req, res, next) => { try { const payload = authService.parseClientSignUp(req.body); const session = await authService.signUpClient(payload, { fetchImpl }); return res.status(201).json({ ...session, requestId: req.requestId, }); } catch (error) { return next(error); } }); router.post('/staff/phone/start', async (req, res, next) => { try { const payload = authService.parseStaffPhoneStart(req.body); const result = await authService.startStaffPhoneAuth(payload, { fetchImpl }); return res.status(200).json({ ...result, requestId: req.requestId, }); } catch (error) { return next(error); } }); router.post('/staff/phone/verify', async (req, res, next) => { try { const payload = authService.parseStaffPhoneVerify(req.body); const session = await authService.verifyStaffPhoneAuth(payload, { fetchImpl }); return res.status(200).json({ ...session, requestId: req.requestId, }); } catch (error) { return next(error); } }); router.get('/session', requireAuth, async (req, res, next) => { try { const session = await authService.getSessionForActor(req.actor); return res.status(200).json({ ...session, requestId: req.requestId, }); } catch (error) { return next(error); } }); router.post('/sign-out', requireAuth, async (req, res, next) => { try { const result = await authService.signOutActor(req.actor); return res.status(200).json({ ...result, requestId: req.requestId, }); } catch (error) { return next(error); } }); router.post('/client/sign-out', requireAuth, async (req, res, next) => { try { const result = await authService.signOutActor(req.actor); return res.status(200).json({ ...result, requestId: req.requestId, }); } catch (error) { return next(error); } }); router.post('/staff/sign-out', requireAuth, async (req, res, next) => { try { const result = await authService.signOutActor(req.actor); return res.status(200).json({ ...result, requestId: req.requestId, }); } catch (error) { return next(error); } }); return router; }