Files
Krow-workspace/backend/unified-api/src/routes/auth.js
2026-03-13 17:02:24 +01:00

171 lines
4.5 KiB
JavaScript

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;
}