Files
sentryagent-idp/src/utils/jwt.ts
SentryAgent.ai Developer d3530285b9 feat: Phase 1 MVP — complete AgentIdP implementation
Implements all P0 features per OpenSpec change phase-1-mvp-implementation:
- Agent Registry Service (CRUD) — full lifecycle management
- OAuth 2.0 Token Service (Client Credentials flow)
- Credential Management (generate, rotate, revoke)
- Immutable Audit Log Service

Tech: Node.js 18+, TypeScript 5.3+ strict, Express 4.18+, PostgreSQL 14+, Redis 7+
Standards: OpenAPI 3.0 specs, DRY/SOLID, zero `any` types
Quality: 18 unit test suites, 244 tests passing, 97%+ coverage
OpenAPI: 4 complete specs (14 endpoints total)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:14:41 +00:00

70 lines
2.0 KiB
TypeScript

/**
* 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<ITokenPayload, 'iat' | 'exp'>,
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;
}