UNPKG

@re-auth/http-adapters

Version:

HTTP adapters for ReAuth authentication framework

336 lines 13.1 kB
/** * Introspect ReAuth engine and extract all available routes */ export function introspectReAuthEngine(engine, config = {}) { const routes = []; const plugins = engine.getAllPlugins(); const { includePlugins, excludePlugins = [], excludeSteps = [], pathGenerator = defaultPathGenerator, } = config; for (const plugin of plugins) { // Check if plugin should be included if (includePlugins && !includePlugins.includes(plugin.name)) { continue; } // Check if plugin should be excluded if (excludePlugins.includes(plugin.name)) { continue; } for (const step of plugin.steps) { // Only include steps with HTTP protocol if (!step.protocol?.http) continue; const stepKey = `${plugin.name}.${step.name}`; // Check if step should be excluded if (excludeSteps.includes(stepKey)) { continue; } const basePath = "/auth"; // This will be updated in the factory const autoRoute = { pluginName: plugin.name, stepName: step.name, method: step.protocol.http.method, path: pathGenerator(plugin.name, step.name, basePath), requiresAuth: step.protocol.http.auth || false, description: step.description, inputs: step.inputs, }; routes.push(autoRoute); } } return routes; } /** * Default path generator for auto-generated routes */ function defaultPathGenerator(pluginName, stepName, basePath) { return `${basePath}/${pluginName}/${stepName}`; } /** * Creates a generic HTTP adapter for any framework */ export function createHttpAdapter(frameworkAdapter) { return (engine, config = {}) => { // Merge with defaults const mergedConfig = { basePath: "/auth", cookieName: "auth_token", cookieOptions: {}, autoIntrospection: { enabled: true }, contextRules: [], routeOverrides: [], customRoutes: [], globalMiddleware: [], ...config, }; // Validate required environment variables for introspection const requiredAuthKey = process.env.REAUTH_INTROSPECTION_KEY; if (!requiredAuthKey) { console.warn("REAUTH_INTROSPECTION_KEY environment variable not set. Introspection endpoint will be disabled."); } // Auto-introspect the engine const autoGeneratedRoutes = mergedConfig.autoIntrospection.enabled ? introspectReAuthEngine(engine, { ...mergedConfig.autoIntrospection, pathGenerator: mergedConfig.autoIntrospection.pathGenerator || defaultPathGenerator, }) : []; // Create adapter context const context = { engine, config: mergedConfig, extractToken: frameworkAdapter.extractToken.bind(frameworkAdapter), requireAuth: frameworkAdapter.requireAuth.bind(frameworkAdapter), handleStepResponse: frameworkAdapter.handleStepResponse.bind(frameworkAdapter), errorResponse: frameworkAdapter.errorResponse.bind(frameworkAdapter), autoGeneratedRoutes, }; // Setup middleware frameworkAdapter.setupMiddleware(context); // Setup introspection endpoint first (before other routes) if (requiredAuthKey) { setupIntrospectionEndpoint(engine, frameworkAdapter, context, requiredAuthKey); } // Setup auto-generated routes if (mergedConfig.autoIntrospection.enabled) { setupAutoGeneratedRoutes(engine, frameworkAdapter, context); } // Setup custom routes setupCustomRoutes(frameworkAdapter, context); return frameworkAdapter.getAdapter(); }; } /** * Setup the introspection endpoint for SDK generation */ function setupIntrospectionEndpoint(engine, frameworkAdapter, context, requiredAuthKey) { const introspectionHandler = async (request, response) => { try { // Validate auth key const authKey = request.headers["x-auth-key"] || request.headers["x-reauth-key"]; if (!authKey || authKey !== requiredAuthKey) { console.warn("Introspection attempt with invalid auth key:", authKey ? "provided" : "missing"); return frameworkAdapter.errorResponse(response, new Error("Invalid or missing auth key")); } // Get introspection data const introspectionData = engine.getIntrospectionData(); // Send successful response if (response.json) { // Express-like response response.status(200).json(introspectionData); } else if (response.send) { // Fastify-like response response.status(200).send(introspectionData); } else { // Hono/Web framework-like response - return Response object return new Response(JSON.stringify(introspectionData), { status: 200, headers: { "Content-Type": "application/json" }, }); } } catch (error) { console.error("Introspection endpoint error:", error); return frameworkAdapter.errorResponse(response, error); } }; // Create the introspection route const introspectionPath = `${context.config.basePath}/introspect`; frameworkAdapter.createRoute("GET", introspectionPath, introspectionHandler, []); console.log(`✅ Introspection endpoint available at: GET ${introspectionPath}`); console.log("🔑 Auth key required in header: x-auth-key or x-reauth-key"); } /** * Find applicable context extraction rules for a plugin/step combination */ export function findContextRules(pluginName, stepName, contextRules) { return contextRules.filter((rule) => { if (rule.pluginName !== pluginName) return false; if (rule.stepName && rule.stepName !== stepName) return false; return true; }); } /** * Setup auto-generated routes from ReAuth engine introspection */ function setupAutoGeneratedRoutes(engine, frameworkAdapter, context) { for (const autoRoute of context.autoGeneratedRoutes) { // Check for route overrides const override = context.config.routeOverrides.find((override) => override.pluginName === autoRoute.pluginName && override.stepName === autoRoute.stepName); // Determine method, path, and handler const method = override?.method || autoRoute.method; const path = override?.path || autoRoute.path; let handler; if (override?.handler) { // Use custom handler handler = override.handler; } else { // Create default handler with optional override hooks handler = createAutoGeneratedStepHandler(autoRoute, frameworkAdapter, context, override); } // Determine middleware const middleware = [ ...(context.config.globalMiddleware || []), ...(override?.middleware || []), ]; // Add auth middleware if required if (autoRoute.requiresAuth && !override?.handler) { middleware.push(context.requireAuth()); } // Create the route frameworkAdapter.createRoute(method.toUpperCase(), path, handler, middleware); } } /** * Setup custom routes */ function setupCustomRoutes(frameworkAdapter, context) { for (const route of context.config.customRoutes) { const middleware = [ ...(context.config.globalMiddleware || []), ...(route.middleware || []), ]; // Add auth middleware if required if (route.requireAuth) { middleware.push(context.requireAuth()); } frameworkAdapter.createRoute(route.method, route.path, route.handler, middleware); } } /** * Create a step handler for auto-generated routes with override support</edit> */ function createAutoGeneratedStepHandler(autoRoute, frameworkAdapter, context, override) { return async (request, response, next) => { try { // Get the actual step definition to access its HTTP protocol configuration const plugin = context.engine.getPlugin(autoRoute.pluginName); const step = plugin?.steps.find((s) => s.name === autoRoute.stepName); const httpConfig = step?.protocol?.http || {}; // Extract inputs (with override support) let inputs; if (override?.extractInputs) { inputs = await override.extractInputs(request, autoRoute.pluginName, autoRoute.stepName); } else { inputs = await frameworkAdapter.extractInputs(request, autoRoute.pluginName, autoRoute.stepName); } // Add auth context if authenticated if (request.user) { inputs.entity = request.user; inputs.token = request.token; } // Add configurable context inputs from cookies and headers frameworkAdapter.addConfigurableContextInputs(request, inputs, autoRoute.pluginName, autoRoute.stepName, context.config.contextRules); // Execute the step const result = await context.engine.executeStep(autoRoute.pluginName, autoRoute.stepName, inputs); // Handle the response (with override support) if (override?.handleResponse) { return override.handleResponse(request, response, result, httpConfig); // biome-ignore lint/style/noUselessElse: <explanation> } else { // Handle configurable context outputs (cookies and headers) frameworkAdapter.handleConfigurableContextOutputs(request, response, result, autoRoute.pluginName, autoRoute.stepName, context.config.contextRules); return context.handleStepResponse(request, response, result, httpConfig); } } catch (error) { console.error(`Error in ${autoRoute.pluginName}.${autoRoute.stepName}:`, error); // Use custom error handler if provided if (context.config.errorHandler) { return context.config.errorHandler(error, { request, response, pluginName: autoRoute.pluginName, stepName: autoRoute.stepName, }); } return context.errorResponse({ request, response, pluginName: autoRoute.pluginName, stepName: autoRoute.stepName, }, error); } }; } /** * Utility function to create route override */ export function createRouteOverride(pluginName, stepName, override) { return { pluginName, stepName, ...override, }; } /** * Utility function to create custom route */ export function createCustomRoute(method, path, handler, options = {}) { return { method, path, handler, middleware: options.middleware || [], requireAuth: options.requireAuth || false, }; } /** * Utility function to create auto-introspection config */ export function createAutoIntrospectionConfig(options = {}) { return { enabled: true, ...options, }; } /** * Utility function to create context extraction rule */ export function createContextRule(pluginName, options = {}) { return { pluginName, ...options, }; } /** * Predefined context rules for common OAuth scenarios */ export const OAuth2ContextRules = { /** * OAuth 2.0 with PKCE context rule for extracting state and code verifier */ pkce: (pluginName) => createContextRule(pluginName, { extractCookies: ["oauth_state", "oauth_code_verifier"], setCookies: ["oauth_state", "oauth_code_verifier"], }), /** * OAuth 2.0 regular flow context rule for extracting state */ regular: (pluginName) => createContextRule(pluginName, { extractCookies: ["oauth_state"], setCookies: ["oauth_state"], }), /** * OAuth callback context rule for all OAuth steps */ callback: (pluginName) => createContextRule(pluginName, { stepName: "callback", extractCookies: ["oauth_state", "oauth_code_verifier"], }), /** * OAuth start context rule for setting state and code verifier */ start: (pluginName) => createContextRule(pluginName, { stepName: "start", setCookies: ["oauth_state", "oauth_code_verifier"], }), }; //# sourceMappingURL=http-adapter-factory.js.map