/** * JWT utilities — configurable issuer and expiry. * Uses jose library for standards-compliant JWT handling. */ import { SignJWT, jwtVerify } from 'jose'; import type { JwtUtils, JwtUtilsOptions, TokenPayload } 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); } /** * Create a JWT utility set with the given issuer and expiry configuration. * * @example * ```ts * const jwt = createJwtUtils({ issuer: "lysnrai", accessTokenExpiry: "1h" }); * const token = await jwt.createAccessToken({ sub: "u1", email: "a@b.com", role: "admin" }); * const payload = await jwt.verifyToken(token); * ``` */ export function createJwtUtils(options: JwtUtilsOptions): JwtUtils { const { issuer, accessTokenExpiry = '1h', refreshTokenExpiry = '30d' } = options; return { async createAccessToken(payload) { return new SignJWT({ ...payload, productId: payload.productId || issuer, type: 'access', }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime(accessTokenExpiry) .setIssuer(issuer) .sign(getSecret()); }, async createRefreshToken(payload) { return new SignJWT({ sub: payload.sub, productId: payload.productId || issuer, type: 'refresh', }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime(refreshTokenExpiry) .setIssuer(issuer) .sign(getSecret()); }, async verifyToken(token: string) { try { const { payload } = await jwtVerify(token, getSecret(), { issuer, }); return payload as unknown as TokenPayload; } catch { return null; } }, }; }