/** * JWT utilities for SentryAgent.ai AgentIdP. * Signs and verifies RS256 JWTs for agent access tokens. */ import jwt from 'jsonwebtoken'; import { ITokenPayload } from '../types/index.js'; const TOKEN_EXPIRES_IN = 3600; // 1 hour in seconds /** * Signs a JWT access token using RS256 (RSA private key). * * @param payload - The token payload containing sub, client_id, scope, jti. * @param privateKey - PEM-encoded RSA private key. * @returns The signed JWT string. * @throws Error if signing fails. */ export function signToken( payload: Omit, privateKey: string, ): string { const now = Math.floor(Date.now() / 1000); const fullPayload: ITokenPayload = { ...payload, iat: now, exp: now + TOKEN_EXPIRES_IN, }; return jwt.sign(fullPayload, privateKey, { algorithm: 'RS256' }); } /** * Verifies a JWT access token using RS256 (RSA public key). * Throws if the token is expired, has an invalid signature, or is malformed. * * @param token - The JWT string to verify. * @param publicKey - PEM-encoded RSA public key. * @returns The decoded, verified token payload. * @throws JsonWebTokenError | TokenExpiredError if verification fails. */ export function verifyToken(token: string, publicKey: string): ITokenPayload { const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] }); return decoded as ITokenPayload; } /** * Decodes a JWT without verifying the signature. * Used for extracting claims (e.g. jti, exp) from tokens that may be expired. * * @param token - The JWT string to decode. * @returns The decoded payload or null if the token is malformed. */ export function decodeToken(token: string): ITokenPayload | null { const decoded = jwt.decode(token); if (!decoded || typeof decoded === 'string') { return null; } return decoded as ITokenPayload; } /** * Returns the token lifetime in seconds. * * @returns Token lifetime (3600 seconds = 1 hour). */ export function getTokenExpiresIn(): number { return TOKEN_EXPIRES_IN; }