@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
JavaScript
// 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