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>
70 lines
2.0 KiB
TypeScript
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;
|
|
}
|