UNPKG

@taukala/xs-ctrl

Version:

A flexible and powerful access control library for JavaScript applications with dynamic validation support

135 lines (127 loc) 4.34 kB
import { validateClaim } from './validateClaim'; /** * Creates a permission validator that combines authentication and authorization checks. * This factory function generates a validator that can be used to protect routes or resources. * Supports both static claim-based and dynamic resource-based authorization rules. * * @param {Object} options - Configuration options * @param {Function} options.getSession - Async function to retrieve the current session * Should return null/undefined if no session exists * * @param {Function} options.getClaims - Async function to retrieve user claims * Receives the session object and should return claims object * Structure: async (session) => ({ * role: ['business'], * businessOwner: ['business-1', 'business-2'] * }) * * @param {Function} options.onUnauthenticated - Callback for handling unauthenticated users * Usually redirects to login page or returns 401 response * * @param {Function} options.onUnauthorized - Callback for handling unauthorized access * Usually redirects to home page or returns 403 response * * @returns {Function} An async validator function that accepts access rules and context * * @example * // Basic usage with Next.js and static rules * const validatePermission = createPermissionValidator({ * getSession: auth, * getClaims: async (session) => ({ * role: ['admin'], * department: ['IT'] * }), * onUnauthenticated: () => redirect('/auth/signin'), * onUnauthorized: () => redirect('/') * }); * * @example * // Usage with dynamic business rules * const validatePermission = createPermissionValidator({ * getSession: auth, * getClaims: async (session) => ({ * role: ['business'], * businessOwner: ['business-1', 'business-2'], * businessOperator: ['business-3'] * }), * onUnauthenticated: () => redirect('/auth/signin'), * onUnauthorized: () => redirect('/') * }); * * // Using with dynamic validation * const businessRule = createAccessRule() * .addCondition('role', 'business') * .addDynamicCondition(({ claims, resources }) => { * const businessIds = claims.businessOwner || []; * return businessIds.includes(resources.business?.id); * }) * .build(); * * const { session, claims } = await validatePermission( * [businessRule], * { resources: { business: { id: 'business-1' } } } * ); */ export function createPermissionValidator({ getSession, getClaims, onUnauthenticated, onUnauthorized }) { if (!getSession || !getClaims || !onUnauthenticated || !onUnauthorized) { throw new Error('All parameters are required'); } if (typeof getSession !== 'function' || typeof getClaims !== 'function' || typeof onUnauthenticated !== 'function' || typeof onUnauthorized !== 'function') { throw new Error('All parameters must be functions'); } /** * Validates user permission against given access rules * * @param {Array<Object>} criteria - Array of access rules * @param {Object} [context] - Optional context for dynamic validation * @param {Object} [context.resources] - Resources for dynamic validation * @returns {Promise<Object>} Object containing session, claims, and status * * @example * // Static validation * const { session, claims } = await validatePermission([adminRule]); * * @example * // Dynamic validation with resources * const { session, claims } = await validatePermission( * [businessRule], * { * resources: { * business: await getBusiness(businessId) * } * } * ); */ return async function validatePermission(criteria, context = {}) { // Check authentication const session = await getSession(); if (!session) { onUnauthenticated(); return { session, claims: {}, status: 401 }; } // Get claims const claims = await getClaims(session); // Prepare validation context const validationContext = { ...context, session, claims }; // Validate claims try { await validateClaim(criteria, claims, validationContext); } catch (error) { onUnauthorized(); return { session, claims, status: 403 }; } return { session, claims, status: 200 }; }; }