UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

217 lines 7.31 kB
// src/lib/auth/providers/jwt.ts import * as jose from "jose"; import { logger } from "../../utils/logger.js"; import { AuthError } from "../errors.js"; import { BaseAuthProvider } from "./BaseAuthProvider.js"; /** * Generic JWT Provider * * Supports validation of JWT tokens using either symmetric secrets (HS256/384/512) * or asymmetric keys (RS256/384/512, ES256/384/512). * * Features: * - Symmetric secret validation (HMAC) * - Asymmetric key validation (RSA, ECDSA) * - Configurable algorithms * - Issuer and audience validation * - Token signing (symmetric keys only) * - Session management (inherited from BaseAuthProvider) * * @example * ```typescript * // Symmetric key (HMAC) * const jwtProvider = new JWTProvider({ * type: "jwt", * secret: "your-256-bit-secret", * algorithms: ["HS256"], * issuer: "your-app", * audience: "your-api", * }); * * // Asymmetric key (RSA/ECDSA) * const jwtProvider = new JWTProvider({ * type: "jwt", * publicKey: "-----BEGIN PUBLIC KEY-----...", * algorithms: ["RS256"], * issuer: "your-app", * }); * * const result = await jwtProvider.authenticateToken(token); * ``` */ export class JWTProvider extends BaseAuthProvider { type = "jwt"; secret; publicKey; algorithms; issuer; audience; keyObject = null; constructor(config) { super(config); if (!config.secret && !config.publicKey) { throw AuthError.create("CONFIGURATION_ERROR", "JWT requires either secret (for HMAC) or publicKey (for RSA/ECDSA)", { details: { provider: "jwt", missingFields: ["secret", "publicKey"] }, }); } this.secret = config.secret; this.publicKey = config.publicKey; this.algorithms = config.algorithms ?? (config.secret ? ["HS256"] : ["RS256"]); this.issuer = config.issuer; this.audience = config.audience; } /** * Initialize the key for verification */ async initialize() { try { if (this.secret) { // Symmetric key (HMAC) this.keyObject = new TextEncoder().encode(this.secret); logger.debug("JWT provider initialized with symmetric secret"); } else if (this.publicKey) { // Asymmetric key (RSA/ECDSA) this.keyObject = await jose.importSPKI(this.publicKey, this.algorithms[0]); logger.debug("JWT provider initialized with asymmetric public key"); } } catch (error) { throw AuthError.create("PROVIDER_INIT_FAILED", `Failed to initialize JWT key: ${error instanceof Error ? error.message : String(error)}`, { details: { provider: "jwt" }, cause: error instanceof Error ? error : undefined, }); } } /** * Validate JWT token */ async authenticateToken(token, _context) { if (!this.keyObject) { await this.initialize(); } try { const keyObject = this.keyObject; if (!keyObject) { throw AuthError.create("PROVIDER_INIT_FAILED", "JWT verification key was not initialized", { details: { provider: "jwt" } }); } const verifyOptions = {}; if (this.algorithms.length > 0) { verifyOptions.algorithms = this .algorithms; } if (this.issuer) { verifyOptions.issuer = this.issuer; } if (this.audience) { verifyOptions.audience = this.audience; } const { payload } = await jose.jwtVerify(token, keyObject, verifyOptions); // Reject tokens without a non-empty sub claim if (!payload.sub) { return { valid: false, error: "JWT is missing required 'sub' claim: cannot identify user", }; } // Extract user from standard JWT claims const user = { id: payload.sub, email: payload.email, name: payload.name, picture: payload.picture, emailVerified: payload.email_verified, roles: payload.roles ?? [], permissions: payload.permissions ?? payload.scope?.split(" ") ?? [], metadata: { iss: payload.iss, aud: payload.aud, jti: payload.jti, }, }; return { valid: true, payload: payload, user, expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined, tokenType: "jwt", }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.warn("JWT validation failed:", message); // Provide specific error messages let errorDetail = message; if (message.includes("JWTExpired")) { errorDetail = "Token has expired"; } else if (message.includes("signature")) { errorDetail = "Invalid token signature"; } else if (message.includes("audience")) { errorDetail = "Invalid token audience"; } else if (message.includes("issuer")) { errorDetail = "Invalid token issuer"; } return { valid: false, error: errorDetail, }; } } /** * Create a signed JWT token * * Useful for issuing tokens from this provider. */ async signToken(payload, options) { if (!this.secret) { throw AuthError.create("CONFIGURATION_ERROR", "Token signing requires a secret (symmetric key)", { details: { provider: "jwt" } }); } if (!this.keyObject) { await this.initialize(); } const jwt = new jose.SignJWT(payload) .setProtectedHeader({ alg: this.algorithms[0] }) .setIssuedAt(); if (this.issuer) { jwt.setIssuer(this.issuer); } if (this.audience) { jwt.setAudience(this.audience); } if (options?.expiresIn) { jwt.setExpirationTime(options.expiresIn); } return jwt.sign(this.keyObject); } /** * Health check */ async healthCheck() { try { // Verify the key is properly initialized if (!this.keyObject) { await this.initialize(); } return { healthy: this.keyObject !== null, providerConnected: true, sessionStorageHealthy: true, }; } catch (error) { return { healthy: false, providerConnected: false, sessionStorageHealthy: true, error: error instanceof Error ? error.message : String(error), }; } } } //# sourceMappingURL=jwt.js.map