@re-auth/http-adapters
Version:
HTTP adapters for ReAuth authentication framework
433 lines • 18.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FastifyFrameworkAdapter = exports.OAuth2ContextRules = exports.createContextRule = exports.introspectReAuthEngine = exports.createAutoIntrospectionConfig = exports.createCustomRoute = exports.createRouteOverride = exports.FastifyAdapterV2 = void 0;
exports.createFastifyAdapterV2 = createFastifyAdapterV2;
exports.createFastifyAdapter = createFastifyAdapter;
const http_adapter_factory_1 = require("../../utils/http-adapter-factory");
Object.defineProperty(exports, "createRouteOverride", { enumerable: true, get: function () { return http_adapter_factory_1.createRouteOverride; } });
Object.defineProperty(exports, "createCustomRoute", { enumerable: true, get: function () { return http_adapter_factory_1.createCustomRoute; } });
Object.defineProperty(exports, "createAutoIntrospectionConfig", { enumerable: true, get: function () { return http_adapter_factory_1.createAutoIntrospectionConfig; } });
Object.defineProperty(exports, "introspectReAuthEngine", { enumerable: true, get: function () { return http_adapter_factory_1.introspectReAuthEngine; } });
Object.defineProperty(exports, "createContextRule", { enumerable: true, get: function () { return http_adapter_factory_1.createContextRule; } });
Object.defineProperty(exports, "OAuth2ContextRules", { enumerable: true, get: function () { return http_adapter_factory_1.OAuth2ContextRules; } });
/**
* Fastify framework adapter implementation
*/
class FastifyFrameworkAdapter {
constructor(fastify, engine) {
this.contextRules = [];
this.adapterConfig = {};
this.routes = [];
this.fastify = fastify; // Will be injected later if not provided
this.engine = engine;
}
/**
* Set the ReAuth engine instance (for shared adapter pattern)
*/
setEngine(engine) {
this.engine = engine;
}
/**
* Set context rules (for shared adapter pattern)
*/
setContextRules(rules) {
this.contextRules = rules;
}
/**
* Set adapter config (for shared adapter pattern)
*/
setAdapterConfig(config) {
this.adapterConfig = config;
}
/**
* Get expected inputs for a plugin step
*/
getExpectedInputs(pluginName, stepName) {
return this.engine?.getStepInputs?.(pluginName, stepName) || [];
}
setFastifyInstance(fastify) {
this.fastify = fastify;
// Register accumulated routes
this.routes.forEach(({ method, path, handler, middleware }) => {
this.createRoute(method, path, handler, middleware);
});
this.routes = []; // Clear accumulated routes
}
setupMiddleware(context) {
// Store context rules and adapter config at instance level
this.setContextRules(context.config.contextRules);
this.setAdapterConfig({
cookieName: context.config.cookieName || 'auth_token',
cookieOptions: context.config.cookieOptions || {},
});
if (!this.fastify) {
console.warn('Fastify instance not available during setupMiddleware');
return;
}
// Global middleware
if (context.config.globalMiddleware) {
context.config.globalMiddleware.forEach((middleware) => {
this.fastify.addHook('onRequest', middleware);
});
}
// Auth middleware (much lighter without heavy context attachments)
this.fastify.addHook('onRequest', async (request, reply) => {
const token = this.extractToken(request);
if (token) {
try {
const session = await context.engine.checkSession(token);
if (session.valid && session.entity) {
request.user = session.entity;
request.token = session.token;
request.isAuthenticated = true;
}
}
catch (error) {
console.warn('Invalid token:', error);
}
}
if (!request.isAuthenticated) {
request.isAuthenticated = false;
}
});
}
createRoute(method, path, handler, middleware = []) {
if (!this.fastify) {
// Store routes to register later when Fastify instance is available
this.routes.push({ method, path, handler, middleware });
return;
}
const fastifyMethod = method.toLowerCase();
const routeOptions = {};
if (middleware && middleware.length > 0) {
routeOptions.preHandler = middleware;
}
// No need to wrap handler for context attachment - everything is at adapter level now
if (typeof this.fastify[fastifyMethod] === 'function') {
this.fastify[fastifyMethod](path, routeOptions, handler);
}
}
async extractInputs(request, pluginName, stepName) {
// Get expected inputs from the shared engine instance instead of request
const expectedInputs = this.getExpectedInputs(pluginName, stepName);
const inputs = {};
// Extract from body, query, and params
expectedInputs.forEach((inputName) => {
if (request.body && request.body[inputName] !== undefined) {
inputs[inputName] = request.body[inputName];
}
else if (request.query &&
request.query[inputName] !== undefined) {
inputs[inputName] = request.query[inputName];
}
else if (request.params &&
request.params[inputName] !== undefined) {
inputs[inputName] = request.params[inputName];
}
});
return inputs;
}
/**
* Add configurable context inputs from cookies and headers based on context rules
*/
addConfigurableContextInputs(request, inputs, pluginName, stepName, contextRules) {
// Use stored context rules instead of parameter (for compatibility)
const rulesToUse = contextRules.length > 0 ? contextRules : this.contextRules;
// Find applicable context extraction rules
const applicableRules = (0, http_adapter_factory_1.findContextRules)(pluginName, stepName, rulesToUse);
applicableRules.forEach((rule) => {
// Extract cookies
if (rule.extractCookies && request.cookies) {
rule.extractCookies.forEach((cookieName) => {
if (request.cookies[cookieName]) {
let value = request.cookies[cookieName];
// Apply transform if provided
if (rule.transformInput) {
value = rule.transformInput(cookieName, value, request);
}
inputs[cookieName] = value;
}
});
}
// Extract headers
if (rule.extractHeaders && request.headers) {
const headerConfig = rule.extractHeaders;
if (Array.isArray(headerConfig)) {
// Simple array format: ['header-name']
headerConfig.forEach((headerName) => {
const headerValue = request.headers[headerName.toLowerCase()];
if (headerValue) {
let value = headerValue;
// Apply transform if provided
if (rule.transformInput) {
value = rule.transformInput(headerName, value, request);
}
inputs[headerName.replace(/-/g, '_')] = value; // Convert header-name to header_name
}
});
}
else {
// Object format: { 'header-name': 'inputName' }
Object.entries(headerConfig).forEach(([headerName, inputName]) => {
const headerValue = request.headers[headerName.toLowerCase()];
if (headerValue) {
let value = headerValue;
// Apply transform if provided
if (rule.transformInput) {
value = rule.transformInput(inputName, value, request);
}
inputs[inputName] = value;
}
});
}
}
});
}
handleStepResponse(request, reply, result, httpConfig) {
const { token, redirect, success, status, cookies, ...data } = result;
// Handle token (set cookie) using stored adapter config
if (token) {
reply.cookie(this.adapterConfig.cookieName, token, this.adapterConfig.cookieOptions);
}
// Handle additional cookies from result.cookies
if (cookies) {
Object.entries(cookies).forEach(([name, value]) => {
reply.cookie(name, value);
});
}
// Handle redirect
if (redirect) {
reply.redirect(redirect);
}
// Determine status code
const statusCode = this.getStatusCode(result, httpConfig);
// Send response
reply.status(statusCode).send({
success,
...data,
});
}
/**
* Handle configurable context outputs (set cookies and headers) based on context rules
*/
handleConfigurableContextOutputs(request, reply, result, pluginName, stepName, contextRules) {
// Use stored context rules instead of parameter (for compatibility)
const rulesToUse = contextRules.length > 0 ? contextRules : this.contextRules;
// Find applicable context extraction rules
const applicableRules = (0, http_adapter_factory_1.findContextRules)(pluginName, stepName, rulesToUse);
applicableRules.forEach((rule) => {
// Set cookies from result
if (rule.setCookies && reply.cookie) {
rule.setCookies.forEach((cookieName) => {
if (result[cookieName] !== undefined) {
let value = result[cookieName];
// Apply transform if provided
if (rule.transformOutput) {
value = rule.transformOutput(cookieName, value, result, request);
}
// Handle complex cookie options
if (typeof value === 'object' &&
value !== null &&
'value' in value) {
// Value is a cookie options object
const { value: cookieValue, ...cookieOptions } = value;
reply.cookie(cookieName, cookieValue, cookieOptions);
}
else {
// Simple value
reply.cookie(cookieName, value);
}
}
});
}
// Set headers from result
if (rule.setHeaders && reply.header) {
const headerConfig = rule.setHeaders;
if (Array.isArray(headerConfig)) {
// Simple array format: ['header-name']
headerConfig.forEach((headerName) => {
const inputName = headerName.replace(/-/g, '_'); // Convert header-name to header_name
if (result[inputName] !== undefined) {
let value = result[inputName];
// Apply transform if provided
if (rule.transformOutput) {
value = rule.transformOutput(headerName, value, result, request);
}
reply.header(headerName, value);
}
});
}
else {
// Object format: { 'header-name': 'resultKey' }
Object.entries(headerConfig).forEach(([headerName, resultKey]) => {
if (result[resultKey] !== undefined) {
let value = result[resultKey];
// Apply transform if provided
if (rule.transformOutput) {
value = rule.transformOutput(headerName, value, result, request);
}
reply.header(headerName, value);
}
});
}
}
});
}
getStatusCode(result, httpConfig) {
// First, check if the plugin step defines a specific status code for the result status
if (result.status && httpConfig[result.status]) {
return httpConfig[result.status];
}
// Fallback to generic success/error codes from httpConfig
if (result.success && httpConfig.success) {
return httpConfig.success;
}
if (!result.success && httpConfig.error) {
return httpConfig.error;
}
// Fallback to standard HTTP status codes based on result.status
if (result.status === 'redirect')
return 302;
if (result.status === 'unauthorized')
return 401;
if (result.status === 'forbidden')
return 403;
if (result.status === 'not_found')
return 404;
if (result.status === 'conflict')
return 409;
if (result.status === 'error')
return 400;
// Default fallback
return result.success ? 200 : 400;
}
extractToken(request) {
// From cookie
if (request.cookies && request.cookies.auth_token) {
return request.cookies.auth_token;
}
// From Authorization header
const authHeader = request.headers.authorization;
if (authHeader?.startsWith('Bearer ')) {
return authHeader.substring(7);
}
return null;
}
requireAuth() {
return async (request, reply) => {
if (!request.isAuthenticated) {
return reply.status(401).send({ error: 'Authentication required' });
}
};
}
errorResponse(reply, error) {
console.error('HTTP Adapter Error:', error);
reply.status(500).send({
success: false,
message: error.message || 'Internal server error',
error: process.env.NODE_ENV === 'development' ? error.stack : undefined,
});
}
getAdapter() {
return this.fastify;
}
}
exports.FastifyFrameworkAdapter = FastifyFrameworkAdapter;
/**
* Create Fastify adapter using the factory pattern with shared instance
* Note: Fastify instance must be provided when calling this function
*/
function createFastifyAdapterV2(fastify, frameworkAdapter) {
return (0, http_adapter_factory_1.createHttpAdapter)(frameworkAdapter);
}
/**
* Fastify adapter class with additional features
*/
class FastifyAdapterV2 {
constructor(fastify, engine, config = {}, frameworkAdapter) {
this.fastify = fastify;
this.engine = engine;
this.config = config;
this.frameworkAdapter =
frameworkAdapter || new FastifyFrameworkAdapter(fastify, engine);
// Set the engine on the shared adapter before creating the HTTP adapter
this.frameworkAdapter.setEngine(engine);
// Create the adapter function using the factory
this.adapterFunction = createFastifyAdapterV2(fastify, this.frameworkAdapter);
// Apply the adapter to the fastify instance
this.adapterFunction(engine, config);
// No need for hooks to attach config - it's stored at adapter level now
}
/**
* Get the Fastify instance
*/
getFastify() {
return this.fastify;
}
/**
* Add a custom route
*/
addRoute(method, path, handler, options = {}) {
const middleware = options.middleware || [];
if (options.requireAuth) {
middleware.push(this.frameworkAdapter.requireAuth());
}
const fastifyMethod = method.toLowerCase();
const routeOptions = {};
if (middleware.length > 0) {
routeOptions.preHandler = middleware;
}
if (typeof this.fastify[fastifyMethod] === 'function') {
this.fastify[fastifyMethod](path, routeOptions, handler);
}
}
/**
* Protection middleware for routes
*/
protect(options = {}) {
return async (request, reply) => {
// Check authentication
if (!request.isAuthenticated) {
return reply.status(401).send({ error: 'Authentication required' });
}
// Check roles
if (options.roles && options.roles.length > 0) {
const userRole = request.user?.role;
if (!userRole || !options.roles.includes(userRole)) {
return reply.status(403).send({ error: 'Insufficient permissions' });
}
}
// Custom authorization
if (options.authorize) {
try {
const isAuthorized = await options.authorize(request.user, request, reply);
if (!isAuthorized) {
return reply.status(403).send({ error: 'Access denied' });
}
}
catch (error) {
console.error('Authorization error:', error);
return reply
.status(500)
.send({ error: 'Authorization check failed' });
}
}
};
}
/**
* Register this adapter as a Fastify plugin
*/
static async createPlugin(engine, config = {}) {
return async function fastifyReAuthPlugin(fastify, options) {
new FastifyAdapterV2(fastify, engine, { ...config, ...options });
};
}
}
exports.FastifyAdapterV2 = FastifyAdapterV2;
/**
* Convenience function to create Fastify adapter with options
*/
function createFastifyAdapter(fastify, engine, config = {}) {
return new FastifyAdapterV2(fastify, engine, config);
}
//# sourceMappingURL=fastify-adapter-v2.js.map