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

404 lines (403 loc) 12.7 kB
/** * Authentication Middleware * Provides flexible authentication support for server adapters */ import { AuthenticationError, AuthorizationError, InvalidAuthenticationError, } from "../errors.js"; /** * Check if request is from development playground. * Detects playground requests via special headers for development mode. * * @param headers - Request headers to check * @returns True if request is from dev playground * * @example * ```typescript * if (isDevPlayground(ctx.headers)) { * // Skip authentication for playground * } * ``` */ export function isDevPlayground(headers) { const getHeader = (name) => { const value = headers[name.toLowerCase()]; return Array.isArray(value) ? value[0] : value; }; return (getHeader("x-neurolink-dev-playground") === "true" || getHeader("x-neurolink-playground") === "true"); } /** * Default dev user for playground requests */ export const DEV_PLAYGROUND_USER = { id: "playground", roles: ["developer"], }; /** * Authentication result */ /** * Create authentication middleware * * @example * ```typescript * const authMiddleware = createAuthMiddleware({ * type: "bearer", * validate: async (token) => { * const user = await verifyJWT(token); * return user ? { id: user.id, email: user.email } : null; * }, * skipPaths: ["/api/health", "/api/ready"], * }); * * server.registerMiddleware(authMiddleware); * ``` */ export function createAuthMiddleware(config) { const { type, validate, skipPaths = [], errorMessage = "Authentication required", skipDevPlayground = true, } = config; // Determine header name based on auth type const headerName = config.headerName ?? getDefaultHeaderName(type); return { name: "authentication", order: 10, // Run early but after request ID excludePaths: skipPaths, handler: async (ctx, next) => { // Skip auth for dev playground in non-production if (skipDevPlayground && process.env.NODE_ENV !== "production" && isDevPlayground(ctx.headers)) { ctx.user = { id: DEV_PLAYGROUND_USER.id, email: DEV_PLAYGROUND_USER.email, roles: DEV_PLAYGROUND_USER.roles, }; return next(); } try { // Extract token based on auth type const token = extractToken(ctx, type, headerName, config.extractToken); if (!token) { throw new AuthenticationError(errorMessage); } // Validate token const result = await validate(token, ctx); if (!result) { throw new InvalidAuthenticationError("Invalid authentication token"); } // Set user information in context ctx.user = { id: result.id, email: result.email, roles: result.roles, }; // Store additional metadata if (result.metadata) { ctx.metadata.auth = result.metadata; } return next(); } catch (error) { if (error instanceof AuthenticationError || error instanceof InvalidAuthenticationError) { throw error; } throw new AuthenticationError(error instanceof Error ? error.message : "Authentication failed"); } }, }; } /** * Get default header name for auth type */ function getDefaultHeaderName(type) { switch (type) { case "bearer": case "basic": return "authorization"; case "api-key": return "x-api-key"; case "custom": return "authorization"; default: return "authorization"; } } /** * Extract token from request based on auth type */ function extractToken(ctx, type, headerName, customExtractor) { // Use custom extractor if provided if (type === "custom" && customExtractor) { return customExtractor(ctx); } const headerValue = ctx.headers[headerName.toLowerCase()]; if (!headerValue) { return null; } switch (type) { case "bearer": { // Extract token from "Bearer <token>" const match = headerValue.match(/^Bearer\s+(.+)$/i); return match ? match[1] : null; } case "basic": { // Extract credentials from "Basic <base64>" const match = headerValue.match(/^Basic\s+(.+)$/i); return match ? match[1] : null; } case "api-key": // API key is the raw header value return headerValue; case "custom": // For custom type without extractor, return raw header return headerValue; default: return null; } } /** * Role-based access control middleware * Use after authentication middleware * * @example * ```typescript * const adminOnly = createRoleMiddleware({ * requiredRoles: ["admin"], * errorMessage: "Admin access required", * }); * ``` */ export function createRoleMiddleware(config) { const { requiredRoles, requireAll = false, errorMessage = "Insufficient permissions", } = config; return { name: "role-check", order: 11, // Run after authentication handler: async (ctx, next) => { if (!ctx.user) { throw new AuthenticationError("Authentication required"); } const userRoles = ctx.user.roles || []; const hasAccess = requireAll ? requiredRoles.every((role) => userRoles.includes(role)) : requiredRoles.some((role) => userRoles.includes(role)); if (!hasAccess) { throw new AuthorizationError(errorMessage, ctx.requestId, requiredRoles); } return next(); }, }; } // ============================================ // API Key Store // ============================================ /** * In-memory API key store for managing API keys * * @example * ```typescript * const store = new ApiKeyStore(); * * // Add an API key * store.addKey("my-api-key", { id: "user_1", email: "user@example.com" }); * * // Validate a key * const user = store.validate("my-api-key"); * if (user) { * console.log("Valid key for user:", user.id); * } * * // Remove a key * store.removeKey("my-api-key"); * * // Clear all keys * store.clear(); * ``` */ export class ApiKeyStore { keys = new Map(); /** * Add an API key with associated user */ addKey(apiKey, user) { this.keys.set(apiKey, user); } /** * Validate an API key * @returns The user associated with the key, or null if invalid */ validate(apiKey) { return this.keys.get(apiKey) ?? null; } /** * Remove an API key */ removeKey(apiKey) { return this.keys.delete(apiKey); } /** * Clear all API keys */ clear() { this.keys.clear(); } /** * Get the number of stored keys */ get size() { return this.keys.size; } } // ============================================ // Bearer Token Authentication // ============================================ /** * Options for bearer auth middleware */ /** * Create bearer token authentication middleware * * @example * ```typescript * const authMiddleware = createBearerAuthMiddleware( * async (token) => { * const user = await verifyJWT(token); * return user ? { id: user.id, roles: user.roles } : null; * }, * { required: true } * ); * ``` */ export function createBearerAuthMiddleware(validate, options = {}) { const { required = true, headerName = "authorization", skipPaths = [], } = options; return { name: "bearer-auth", order: 10, excludePaths: skipPaths, handler: async (ctx, next) => { const authHeader = ctx.headers[headerName.toLowerCase()]; // Extract bearer token let token = null; if (authHeader) { const match = authHeader.match(/^Bearer\s+(.+)$/i); token = match ? match[1] : null; } // Handle missing token if (!token) { if (required) { throw new AuthenticationError("Authentication required"); } return next(); } // Validate token const user = await validate(token); if (!user) { throw new AuthenticationError("Invalid authentication token"); } // Set user in context ctx.user = user; return next(); }, }; } // ============================================ // API Key Authentication // ============================================ /** * Options for API key auth middleware */ /** * Create API key authentication middleware * * @example * ```typescript * const store = new ApiKeyStore(); * store.addKey("my-key", { id: "user_1" }); * * const authMiddleware = createApiKeyAuthMiddleware(store); * ``` */ export function createApiKeyAuthMiddleware(store, options = {}) { const { headerName = "x-api-key", skipPaths = [] } = options; return { name: "api-key-auth", order: 10, excludePaths: skipPaths, handler: async (ctx, next) => { const apiKey = ctx.headers[headerName.toLowerCase()]; if (!apiKey) { throw new AuthenticationError("API key required"); } const user = store.validate(apiKey); if (!user) { throw new AuthenticationError("Invalid API key"); } ctx.user = user; return next(); }, }; } // ============================================ // Role-based Authorization (Simple) // ============================================ /** * Create role-based authorization middleware (simple version) * * @example * ```typescript * const adminMiddleware = createRoleAuthMiddleware(["admin"]); * const editorMiddleware = createRoleAuthMiddleware(["admin", "editor"]); * ``` */ export function createRoleAuthMiddleware(requiredRoles, options = {}) { const { requireAll = false } = options; return { name: "role-auth", order: 11, handler: async (ctx, next) => { if (!ctx.user) { throw new AuthenticationError("Authentication required"); } const userRoles = ctx.user.roles || []; const hasAccess = requireAll ? requiredRoles.every((role) => userRoles.includes(role)) : requiredRoles.some((role) => userRoles.includes(role)); if (!hasAccess) { throw new AuthorizationError(`Required roles: ${requiredRoles.join(", ")}. User has: ${userRoles.join(", ") || "none"}`, ctx.requestId, requiredRoles); } return next(); }, }; } // ============================================ // Permission-based Authorization // ============================================ /** * Create permission-based authorization middleware * * @example * ```typescript * const canEditMiddleware = createPermissionAuthMiddleware(["posts:edit"]); * const canManageMiddleware = createPermissionAuthMiddleware(["users:read", "users:write"]); * ``` */ export function createPermissionAuthMiddleware(requiredPermissions, options = {}) { const { requireAll = false } = options; return { name: "permission-auth", order: 11, handler: async (ctx, next) => { if (!ctx.user) { throw new AuthenticationError("Authentication required"); } // Get permissions from user metadata or roles const userPermissions = ctx.user .permissions || []; const hasAccess = requireAll ? requiredPermissions.every((perm) => userPermissions.includes(perm)) : requiredPermissions.some((perm) => userPermissions.includes(perm)); if (!hasAccess) { throw new AuthorizationError(`Required permissions: ${requiredPermissions.join(", ")}. User has: ${userPermissions.join(", ") || "none"}`, ctx.requestId, requiredPermissions); } return next(); }, }; }