learning_ai_common_plat/packages/auth/src/middleware.ts
saravanakumardb1 99cbdf582c feat(auth): add middleware tests + E2E flow + migrate tracker-service to @bytelyst/auth
- Upgraded @bytelyst/auth middleware to throw ServiceError types (UnauthorizedError, ForbiddenError)
- Added @bytelyst/errors as dependency to auth package
- 11 new middleware tests (extractAuth + requireRole)
- 4 new E2E tests (full login → JWT → auth → role check flow)
- Refactored tracker-service lib/auth.ts from 48-line local impl to 1-line re-export
- Added @bytelyst/auth as tracker-service dependency
- All 277 tests pass, 0 regressions
2026-02-12 23:41:46 -08:00

55 lines
1.7 KiB
TypeScript

/**
* Fastify auth middleware — validates JWT tokens from Authorization headers.
*/
import { jwtVerify } from 'jose';
import { UnauthorizedError, ForbiddenError } from '@bytelyst/errors';
import type { AuthPayload } from './types.js';
function getSecret(): Uint8Array {
const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('JWT_SECRET must be set');
return new TextEncoder().encode(secret);
}
/**
* Extract and verify auth payload from an Authorization header.
* Works with any request-like object that has headers.authorization.
*
* @throws Error with message "Unauthorized" if no valid Bearer token
* @throws Error with message "Invalid or expired token" if verification fails
*/
export async function extractAuth(req: {
headers: { authorization?: string };
}): Promise<AuthPayload> {
const auth = req.headers.authorization;
if (!auth?.startsWith('Bearer ')) {
throw new UnauthorizedError();
}
const token = auth.slice(7);
try {
const { payload } = await jwtVerify(token, getSecret());
const p = payload as unknown as AuthPayload;
if (p.type !== 'access') throw new Error('Not an access token');
return p;
} catch {
throw new UnauthorizedError('Invalid or expired token');
}
}
/**
* Require specific roles. Extracts auth first, then checks role.
*
* @throws Error with statusCode 403 if role doesn't match
*/
export async function requireRole(
req: { headers: { authorization?: string } },
...roles: string[]
): Promise<AuthPayload> {
const payload = await extractAuth(req);
if (roles.length > 0 && (!payload.role || !roles.includes(payload.role))) {
throw new ForbiddenError('Insufficient permissions');
}
return payload;
}