118 lines
9.5 KiB
JavaScript
118 lines
9.5 KiB
JavaScript
import { securityLogger as logger } from '../commons/logger.js';
|
||
import { PublicEndpointRegistry, AdminEndpointRegistry } from '../commons/registry.js';
|
||
import { getUserCached, supabase } from '../commons/supabase.js';
|
||
/**
|
||
* Strict authentication middleware – requires a valid Bearer token.
|
||
*/
|
||
export async function authMiddleware(c, next) {
|
||
const authHeader = c.req.header('authorization');
|
||
if (!authHeader?.startsWith('Bearer ')) {
|
||
return c.json({ error: 'Unauthorized - Missing or invalid authorization header' }, 401);
|
||
}
|
||
const token = authHeader.substring(7);
|
||
try {
|
||
const user = await getUserCached(token);
|
||
if (!user) {
|
||
return c.json({ error: 'Invalid or expired token' }, 401);
|
||
}
|
||
c.set('userId', user.id);
|
||
c.set('user', user);
|
||
c.set('userEmail', user.email);
|
||
await next();
|
||
}
|
||
catch (err) {
|
||
logger.error({ err }, 'Auth middleware error');
|
||
return c.json({ error: 'Authentication failed' }, 401);
|
||
}
|
||
}
|
||
/**
|
||
* Optional authentication middleware.
|
||
* - Public endpoint: GET /api/products (no auth required).
|
||
* - Otherwise respects REQUIRE_AUTH flag, but skips auth in test/dev environments.
|
||
*/
|
||
export async function optionalAuthMiddleware(c, next) {
|
||
const path = c.req.path;
|
||
const method = c.req.method;
|
||
// Public endpoint – allow unauthenticated access
|
||
const isPublicEndpoint = PublicEndpointRegistry.isPublic(path, method);
|
||
const isProductsEndpoint = method === 'GET' && path === '/api/products';
|
||
if (isProductsEndpoint || isPublicEndpoint) {
|
||
return await next();
|
||
}
|
||
const requireAuth = process.env.REQUIRE_AUTH === 'true';
|
||
const isTestEnv = false; // process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development';
|
||
const authHeader = c.req.header('authorization');
|
||
// If no auth header, or it's not a Bearer token...
|
||
let token;
|
||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||
token = authHeader.substring(7);
|
||
}
|
||
else {
|
||
// Check for token in query param (for SSE)
|
||
const queryToken = c.req.query('token');
|
||
if (queryToken) {
|
||
token = queryToken;
|
||
}
|
||
}
|
||
if (!token) {
|
||
// ...and we are in test env or auth not required, just continue.
|
||
if (!requireAuth) {
|
||
return await next();
|
||
}
|
||
// ...otherwise reject
|
||
return c.json({ error: 'Unauthorized' }, 401);
|
||
}
|
||
try {
|
||
const user = await getUserCached(token);
|
||
if (!user) {
|
||
logger.warn('[Auth] Token verification failed');
|
||
if (isTestEnv) {
|
||
return await next();
|
||
}
|
||
return c.json({ error: 'Unauthorized' }, 401);
|
||
}
|
||
c.set('userId', user.id);
|
||
c.set('user', user);
|
||
c.set('userEmail', user.email);
|
||
await next();
|
||
}
|
||
catch (err) {
|
||
logger.error({ err }, '[Auth] Optional auth middleware error - REJECTING');
|
||
return c.json({ error: 'Authentication failed' }, 401);
|
||
}
|
||
}
|
||
/**
|
||
* Admin‑only middleware – requires authentication and admin role.
|
||
* Checks AdminEndpointRegistry to see if the route requires admin access.
|
||
*/
|
||
export async function adminMiddleware(c, next) {
|
||
const path = c.req.path;
|
||
const method = c.req.method;
|
||
// Check if this is an admin endpoint
|
||
if (!AdminEndpointRegistry.isAdmin(path, method)) {
|
||
return await next();
|
||
}
|
||
// If it is an admin endpoint, enforce auth and role
|
||
const userId = c.get('userId');
|
||
if (!userId) {
|
||
return c.json({ error: 'Unauthorized - Authentication required' }, 401);
|
||
}
|
||
try {
|
||
const { data: profile, error } = await supabase
|
||
.from('user_roles')
|
||
.select('role')
|
||
.eq('user_id', userId)
|
||
.single();
|
||
// @todo : fix db - type | multiple - currently single string
|
||
if (error || !profile || profile.role !== 'admin') {
|
||
return c.json({ error: 'Forbidden - Admin access required' }, 403);
|
||
}
|
||
c.set('isAdmin', true);
|
||
await next();
|
||
}
|
||
catch (err) {
|
||
logger.error({ err }, 'Admin middleware error');
|
||
return c.json({ error: 'Authorization check failed' }, 500);
|
||
}
|
||
}
|
||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWRkbGV3YXJlL2F1dGgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLGNBQWMsSUFBSSxNQUFNLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNoRSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN2RixPQUFPLEVBQUUsYUFBYSxFQUFFLFFBQVEsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBR2pFOztHQUVHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxjQUFjLENBQUMsQ0FBVSxFQUFFLElBQVU7SUFDdkQsTUFBTSxVQUFVLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUM7SUFDakQsSUFBSSxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztRQUNyQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUsd0RBQXdELEVBQUUsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUM1RixDQUFDO0lBQ0QsTUFBTSxLQUFLLEdBQUcsVUFBVSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN0QyxJQUFJLENBQUM7UUFDRCxNQUFNLElBQUksR0FBRyxNQUFNLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN4QyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDUixPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUsMEJBQTBCLEVBQUUsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUM5RCxDQUFDO1FBQ0QsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ3pCLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO1FB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
|