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