import { Context, Next } from "@oak/oak"; import { verify, create, getNumericDate } from "djwt"; import { config } from "../config/env.ts"; import type { JWTPayload, UserRole } from "../types/index.ts"; // Create crypto key from secret const encoder = new TextEncoder(); const keyData = encoder.encode(config.JWT_SECRET); const cryptoKey = await crypto.subtle.importKey( "raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"] ); // Generate JWT token export async function generateToken(payload: Omit): Promise { const expiresIn = config.JWT_EXPIRES_IN; let expSeconds = 7 * 24 * 60 * 60; // Default 7 days if (expiresIn.endsWith("d")) { expSeconds = parseInt(expiresIn) * 24 * 60 * 60; } else if (expiresIn.endsWith("h")) { expSeconds = parseInt(expiresIn) * 60 * 60; } else if (expiresIn.endsWith("m")) { expSeconds = parseInt(expiresIn) * 60; } const token = await create( { alg: "HS256", typ: "JWT" }, { ...payload, exp: getNumericDate(expSeconds), iat: getNumericDate(0), }, cryptoKey ); return token; } // Verify JWT token export async function verifyToken(token: string): Promise { try { const payload = await verify(token, cryptoKey); return payload as unknown as JWTPayload; } catch { return null; } } // Authentication middleware export async function authenticateToken(ctx: Context, next: Next): Promise { const authHeader = ctx.request.headers.get("Authorization"); const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null; if (!token) { ctx.response.status = 401; ctx.response.body = { error: "Access token required" }; return; } const payload = await verifyToken(token); if (!payload) { ctx.response.status = 403; ctx.response.body = { error: "Invalid or expired token" }; return; } // Attach user to context state ctx.state.user = payload; await next(); } // Authorization middleware factory export function authorize(...roles: UserRole[]) { return async (ctx: Context, next: Next): Promise => { const user = ctx.state.user as JWTPayload | undefined; if (!user) { ctx.response.status = 401; ctx.response.body = { error: "Unauthorized" }; return; } if (!roles.includes(user.role)) { ctx.response.status = 403; ctx.response.body = { error: "Insufficient permissions" }; return; } await next(); }; } // Get current user from context export function getCurrentUser(ctx: Context): JWTPayload { return ctx.state.user as JWTPayload; }