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

308 lines 11.7 kB
/** * CognitoProvider - AWS Cognito User Pools provider implementation * * Provides JWT validation, session management, and RBAC for AWS Cognito. */ import { importJWK, jwtVerify } from "jose"; import { logger } from "../../utils/logger.js"; import { AuthError } from "../errors.js"; import { BaseAuthProvider } from "./BaseAuthProvider.js"; // ============================================================================= // JWKS CACHE // ============================================================================= const jwksCache = new Map(); // ============================================================================= // COGNITO PROVIDER // ============================================================================= /** * CognitoProvider - AWS Cognito User Pools integration * * Features: * - Cognito ID token and access token validation * - JWKS-based signature verification * - Cognito groups for roles * - Custom attributes support * - Session management * * @example * ```typescript * const provider = new CognitoProvider({ * type: 'cognito', * userPoolId: 'us-east-1_xxxxx', * clientId: 'your-client-id', * region: 'us-east-1', * }); * * const result = await provider.authenticateToken(idToken); * if (result.valid) { * console.log('User:', result.user); * } * ``` */ export class CognitoProvider extends BaseAuthProvider { type = "cognito"; cognitoConfig; jwksUri; jwksCacheDuration; expectedIssuer; constructor(config) { super(config); if (config.type !== "cognito") { throw AuthError.create("CONFIGURATION_ERROR", `Invalid provider type: ${config.type}. Expected: cognito`); } this.cognitoConfig = config; if (!this.cognitoConfig.userPoolId) { throw AuthError.create("CONFIGURATION_ERROR", "Cognito userPoolId is required"); } if (!this.cognitoConfig.clientId) { throw AuthError.create("CONFIGURATION_ERROR", "Cognito clientId is required"); } if (!this.cognitoConfig.region) { throw AuthError.create("CONFIGURATION_ERROR", "Cognito region is required"); } // Set up JWKS URI and issuer this.expectedIssuer = `https://cognito-idp.${this.cognitoConfig.region}.amazonaws.com/${this.cognitoConfig.userPoolId}`; this.jwksUri = `${this.expectedIssuer}/.well-known/jwks.json`; this.jwksCacheDuration = config.tokenValidation?.jwksCacheDuration ?? 600000; // 10 minutes logger.debug(`[CognitoProvider] Initialized for user pool: ${this.cognitoConfig.userPoolId}`); } /** * Validate and authenticate a Cognito JWT token */ async authenticateToken(token) { try { // Parse token without verification first const claims = this.parseJWT(token); if (!claims) { return { valid: false, error: "Failed to decode token", errorCode: "AUTH-006", }; } // Validate issuer if (claims.iss !== this.expectedIssuer) { return { valid: false, error: `Invalid issuer: ${claims.iss}. Expected: ${this.expectedIssuer}`, errorCode: "AUTH-001", }; } // Validate token_use (id or access) const tokenUse = claims.token_use; if (tokenUse !== "id" && tokenUse !== "access") { return { valid: false, error: `Invalid token_use: ${tokenUse}. Expected: id or access`, errorCode: "AUTH-001", }; } // Validate client_id for ID tokens, or client_id in aud for access tokens if (tokenUse === "id") { if (claims.aud !== this.cognitoConfig.clientId) { return { valid: false, error: `Invalid audience: ${claims.aud}. Expected: ${this.cognitoConfig.clientId}`, errorCode: "AUTH-001", }; } } else { // Access tokens have client_id claim if (claims.client_id !== this.cognitoConfig.clientId) { return { valid: false, error: `Invalid client_id: ${claims.client_id}. Expected: ${this.cognitoConfig.clientId}`, errorCode: "AUTH-001", }; } } // Check expiration const clockTolerance = this.config.tokenValidation?.clockTolerance ?? 30; if (this.isTokenExpired(claims, clockTolerance)) { return { valid: false, error: "Token has expired", errorCode: "AUTH-002", expiresAt: claims.exp ? new Date(claims.exp * 1000) : undefined, }; } // Verify signature if enabled if (this.config.tokenValidation?.validateSignature !== false) { const signatureValid = await this.verifySignature(token); if (!signatureValid) { return { valid: false, error: "Invalid token signature", errorCode: "AUTH-004", }; } } // Extract user from claims const user = this.extractCognitoUser(claims, tokenUse); // Convert claims to Record<string, JsonValue> by filtering out undefined const validClaims = {}; for (const [key, value] of Object.entries(claims)) { if (value !== undefined) { validClaims[key] = value; } } return { valid: true, user, claims: validClaims, expiresAt: claims.exp ? new Date(claims.exp * 1000) : undefined, issuer: claims.iss, audience: claims.aud, }; } catch (error) { logger.error(`[CognitoProvider] Token validation error:`, error); return { valid: false, error: error instanceof Error ? error.message : "Token validation failed", errorCode: "AUTH-014", }; } } /** * Verify token signature using JWKS */ async verifySignature(token) { try { const parts = token.split("."); if (parts.length !== 3) { return false; } // Decode header to get kid const header = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf-8")); const kid = header.kid; if (!kid) { logger.warn("[CognitoProvider] Token missing kid in header"); return false; } // Get JWKS const jwks = await this.getJWKS(); const key = jwks.keys.find((k) => k.kid === kid); if (!key) { logger.warn(`[CognitoProvider] Key not found for kid: ${kid}`); return false; } // Verify the JWT signature against the public key const publicKey = await importJWK(key, header.alg); const clockTolerance = this.config.tokenValidation?.clockTolerance ?? 30; await jwtVerify(token, publicKey, { clockTolerance }); return true; } catch (error) { logger.error(`[CognitoProvider] Signature verification error:`, error); return false; } } /** * Fetch JWKS with caching */ async getJWKS() { const cached = jwksCache.get(this.jwksUri); if (cached && cached.expiresAt > Date.now()) { return cached.jwks; } try { const response = await fetch(this.jwksUri, { signal: AbortSignal.timeout(5000), }); if (!response.ok) { throw new Error(`JWKS fetch failed: ${response.status}`); } const jwks = (await response.json()); // Cache the JWKS jwksCache.set(this.jwksUri, { jwks, expiresAt: Date.now() + this.jwksCacheDuration, }); return jwks; } catch (error) { throw AuthError.create("JWKS_FETCH_FAILED", `Failed to fetch JWKS from ${this.jwksUri}: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined }); } } /** * Extract Cognito-specific user data from claims */ extractCognitoUser(claims, tokenUse) { // User ID (sub claim) const userId = claims.sub ?? ""; // Email (from ID token or custom attributes) const email = claims.email ?? claims["custom:email"]; // Name (various possible claims) const name = claims.name ?? claims["cognito:username"] ?? claims.preferred_username; // Picture (custom attribute) const picture = claims.picture ?? claims["custom:picture"]; // Get roles from Cognito groups let roles = []; const cognitoGroups = claims["cognito:groups"]; if (cognitoGroups && Array.isArray(cognitoGroups)) { roles = cognitoGroups; } // Apply default roles if (roles.length === 0 && this.rbacConfig.defaultRoles) { roles = this.rbacConfig.defaultRoles; } // Extract custom attributes as permissions if configured const permissions = []; if (this.cognitoConfig.customAttributes) { for (const attr of this.cognitoConfig.customAttributes) { const value = claims[`custom:${attr}`]; if (value) { // If it looks like a comma-separated list, split it if (value.includes(",")) { permissions.push(...value.split(",").map((p) => p.trim())); } else { permissions.push(value); } } } } // Build provider data, filtering out undefined values const providerData = { provider: "cognito", }; if (claims["cognito:username"] !== undefined) { providerData.username = claims["cognito:username"]; } providerData.token_use = tokenUse; if (claims.auth_time !== undefined) { providerData.auth_time = claims.auth_time; } const clientId = claims.client_id ?? claims.aud; if (clientId !== undefined) { providerData.client_id = clientId; } if (cognitoGroups !== undefined) { providerData.cognito_groups = cognitoGroups; } return { id: userId, email, name, picture, roles, permissions, emailVerified: claims.email_verified, providerData, }; } /** * Get user from Cognito * Note: Requires AWS SDK for full implementation */ async getUser(_userId) { logger.debug("[CognitoProvider] getUser() is not implemented. Requires AWS SDK (@aws-sdk/client-cognito-identity-provider)."); return null; } } //# sourceMappingURL=CognitoProvider.js.map