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