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

318 lines 11.4 kB
// src/lib/auth/providers/clerk.ts import { BaseAuthProvider } from "./BaseAuthProvider.js"; import { AuthError } from "../errors.js"; import { logger } from "../../utils/logger.js"; import { createProxyFetch } from "../../proxy/proxyFetch.js"; import * as jose from "jose"; /** * Clerk Authentication Provider * * Supports Clerk's session-based and JWT authentication. * Can validate both JWT tokens and session tokens via Clerk API. * * Features: * - JWT validation using Clerk's JWKS * - Session token validation via Clerk API * - User profile fetching * - Organization support for multi-tenant apps * * @example * ```typescript * const clerk = new ClerkProvider({ * type: "clerk", * publishableKey: "pk_test_...", * secretKey: "sk_test_..." * }); * * const result = await clerk.authenticateToken(sessionToken); * if (result.valid) { * console.log("Authenticated user:", result.user); * } * ``` */ export class ClerkProvider extends BaseAuthProvider { type = "clerk"; secretKey; jwtKey; publishableKey; jwks = null; localKey = null; constructor(config) { super(config); if (!config.secretKey) { throw AuthError.create("CONFIGURATION_ERROR", "Clerk secretKey is required", { details: { missingFields: ["secretKey"] } }); } this.secretKey = config.secretKey; this.jwtKey = config.jwtKey; this.publishableKey = config.publishableKey; } /** * Initialize Clerk JWKS */ async initialize() { // Clerk JWKS endpoint (v1 API) const jwksUrl = new URL("https://api.clerk.com/v1/jwks"); this.jwks = jose.createRemoteJWKSet(jwksUrl); logger.debug("Clerk provider initialized"); } /** * Validate Clerk session token or JWT */ async authenticateToken(token, _context) { // First try JWT validation (tokens with dots) if (token.includes(".") && token.split(".").length === 3) { return this.validateJWT(token); } // Otherwise treat as session token return this.validateSessionToken(token); } /** * Validate JWT using local jwtKey (if configured) or JWKS */ async validateJWT(token) { try { let payload; if (this.jwtKey) { // Use locally provided JWT key for verification if (!this.localKey) { this.localKey = new TextEncoder().encode(this.jwtKey); } ({ payload } = await jose.jwtVerify(token, this.localKey)); } else { // Fall back to Clerk JWKS endpoint if (!this.jwks) { await this.initialize(); } if (!this.jwks) { return { valid: false, error: "Clerk JWKS not initialized", }; } ({ payload } = await jose.jwtVerify(token, this.jwks)); } // Validate azp (authorized party) claim if publishableKey is configured if (this.publishableKey && payload.azp) { if (payload.azp !== this.publishableKey) { return { valid: false, error: `Invalid authorized party: ${payload.azp}. Expected: ${this.publishableKey}`, }; } } const user = { id: payload.sub, email: payload.email, name: payload.name, picture: payload.picture, emailVerified: payload.email_verified, roles: payload["https://clerk.dev/roles"] || [], permissions: payload["https://clerk.dev/permissions"] || [], organizationId: payload.org_id, metadata: { azp: payload.azp, sid: payload.sid, }, }; return { valid: true, payload: payload, user, expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined, tokenType: "jwt", }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : String(error), }; } } /** * Validate session token via Clerk API */ async validateSessionToken(token) { try { const proxyFetch = createProxyFetch(); const response = await proxyFetch("https://api.clerk.com/v1/sessions/verify", { method: "POST", headers: { Authorization: `Bearer ${this.secretKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ token }), }); if (!response.ok) { const error = (await response.json()); return { valid: false, error: error.errors?.[0]?.message || "Session validation failed", }; } const session = (await response.json()); const userData = session.user; const emailAddresses = userData?.email_addresses; const user = { id: session.user_id, email: emailAddresses?.[0]?.email_address, name: userData?.first_name ? `${userData.first_name} ${userData.last_name || ""}`.trim() : undefined, picture: userData?.image_url, roles: userData?.public_metadata ?.roles || [], permissions: userData?.public_metadata ?.permissions || [], organizationId: session.active_organization_id, }; return { valid: true, payload: session, user, expiresAt: session.expire_at ? new Date(session.expire_at) : undefined, tokenType: "session", }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : String(error), }; } } /** * Get user by ID from Clerk API */ async getUser(userId) { try { const proxyFetch = createProxyFetch(); const response = await proxyFetch(`https://api.clerk.com/v1/users/${userId}`, { headers: { Authorization: `Bearer ${this.secretKey}`, }, }); if (!response.ok) { if (response.status === 404) { return null; } throw AuthError.create("PROVIDER_ERROR", `Clerk API returned ${response.status}`, { details: { statusCode: response.status } }); } const data = (await response.json()); const emailAddresses = data.email_addresses; return { id: data.id, email: emailAddresses?.[0]?.email_address, name: data.first_name ? `${data.first_name} ${data.last_name || ""}`.trim() : undefined, picture: data.image_url, emailVerified: emailAddresses?.[0]?.verification?.status === "verified", roles: data.public_metadata ?.roles || [], permissions: data.public_metadata ?.permissions || [], createdAt: data.created_at ? new Date(data.created_at) : undefined, lastLoginAt: data.last_sign_in_at ? new Date(data.last_sign_in_at) : undefined, metadata: data.private_metadata, }; } catch (error) { logger.error("Failed to fetch Clerk user:", error); if (error && typeof error === "object" && "code" in error && typeof error.code === "string") { throw error; } return null; } } /** * Get user by email from Clerk API */ async getUserByEmail(email) { try { const proxyFetch = createProxyFetch(); const response = await proxyFetch(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}`, { headers: { Authorization: `Bearer ${this.secretKey}`, }, }); if (!response.ok) { throw AuthError.create("PROVIDER_ERROR", `Clerk API returned ${response.status}`, { details: { statusCode: response.status } }); } const users = (await response.json()); if (users.length === 0) { return null; } const data = users[0]; const emailAddresses = data.email_addresses; return { id: data.id, email: emailAddresses?.[0]?.email_address, name: data.first_name ? `${data.first_name} ${data.last_name || ""}`.trim() : undefined, picture: data.image_url, emailVerified: emailAddresses?.[0]?.verification?.status === "verified", roles: data.public_metadata ?.roles || [], permissions: data.public_metadata ?.permissions || [], createdAt: data.created_at ? new Date(data.created_at) : undefined, lastLoginAt: data.last_sign_in_at ? new Date(data.last_sign_in_at) : undefined, metadata: data.private_metadata, }; } catch (error) { logger.error("Failed to fetch Clerk user by email:", error); if (error && typeof error === "object" && "code" in error && typeof error.code === "string") { throw error; } return null; } } /** * Health check */ async healthCheck() { try { const proxyFetch = createProxyFetch(); // Use a lightweight endpoint to check connectivity const response = await proxyFetch("https://api.clerk.com/v1/organizations?limit=1", { headers: { Authorization: `Bearer ${this.secretKey}`, }, }); return { healthy: response.ok, providerConnected: response.ok, sessionStorageHealthy: true, }; } catch (error) { return { healthy: false, providerConnected: false, sessionStorageHealthy: true, error: error instanceof Error ? error.message : String(error), }; } } } //# sourceMappingURL=clerk.js.map