UNPKG

permix

Version:

Permix is a lightweight, framework-agnostic, type-safe permissions management library for JavaScript applications on the client and server sides.

114 lines (113 loc) 3.43 kB
import { PermixNotFoundError, createCheckContext, createHooks, createPermix as createPermix$1, createTemplate } from "../core/index.mjs"; import { ForbiddenException, SetMetadata, UseGuards, applyDecorators } from "@nestjs/common"; //#region src/nest/permix.ts function getRequest(context) { return context.switchToHttp().getRequest(); } function readCheckArgs(metadataKey, context) { const handler = context.getHandler(); const classRef = context.getClass(); const fromHandler = Reflect.getMetadata(metadataKey, handler); if (fromHandler) return fromHandler; return Reflect.getMetadata(metadataKey, classRef); } function buildPermix(resolveKey, options = {}) { const checkMetadataKey = Symbol("permix:check"); const onForbidden = options.onForbidden ?? (() => { throw new ForbiddenException({ error: "Forbidden" }); }); const hooks = createHooks(); function get(req) { return req[resolveKey()] ?? null; } function getOrThrow(req) { const instance = get(req); if (!instance) throw new PermixNotFoundError(resolveKey()); return instance; } function attach(req, rules) { const instance = createPermix$1(rules); instance.hook("check", (context) => { hooks.callHook("check", context); }); req[resolveKey()] = instance; return instance; } /** * Nest guard that sets up a per-request Permix instance. Register globally * with `APP_GUARD`, or per-controller / per-route with `@UseGuards`. * * Non-HTTP contexts (RPC, WebSockets, GraphQL) are left untouched. */ function guard(callbackOrRules) { return { async canActivate(context) { if (context.getType() !== "http") return true; const req = getRequest(context); attach(req, typeof callbackOrRules === "function" ? await callbackOrRules({ req, context }) : callbackOrRules); return true; } }; } const enforce = { async canActivate(context) { const args = readCheckArgs(checkMetadataKey, context); if (!args) return true; if (context.getType() !== "http") throw new PermixNotFoundError(resolveKey()); const req = getRequest(context); if (getOrThrow(req).check(...args)) return true; await onForbidden({ req, context, ...createCheckContext(...args) }); return false; } }; /** * Method or class decorator that enforces a permission. A handler-level * `@Check` overrides a controller-level one. * * Throws `PermixNotFoundError` when `guard()` has not run for the request, * so a forgotten setup guard is a loud failure, not an unprotected route. */ const Check = (...args) => applyDecorators(SetMetadata(checkMetadataKey, args), UseGuards(enforce)); function getRules(req) { return get(req)?.getRules() ?? null; } function template(rules) { return createTemplate(rules); } return { guard, Check, template, get, getOrThrow, getRules, hook: hooks.hook, hookOnce: hooks.hookOnce, get key() { return resolveKey(); }, $inferDefinition: void 0, $inferPath: void 0 }; } /** * Create a guard factory that wires Permix into NestJS routes. * * Use `.contextKey('name')` to set a custom request key (defaults to a unique * `Symbol('permix')`). * * @link https://permix.letstri.dev/docs/integrations/nest */ function createPermix(options = {}) { let key = Symbol("permix"); const permix = buildPermix(() => key, options); return Object.assign(permix, { contextKey(newKey) { key = newKey; return permix; } }); } //#endregion export { createPermix };