{"version":3,"file":"index.cjs","names":["iamExtractEnvironment","IAM_METHOD_ACTION_MAP","iamDefaultCsrfCheck","iamWithAdminAudit"],"sources":["../../../src/server/nest/index.ts"],"sourcesContent":["import type { IamEngine } from '../../core'\nimport type { AccessControl, IamRequest } from '../../core/types'\nimport {\n  IAM_METHOD_ACTION_MAP,\n  type IamAdminAudit,\n  iamDefaultCsrfCheck,\n  iamExtractEnvironment,\n  iamNoticeCsrfDefaultIfNeeded,\n  iamWithAdminAudit,\n} from '../generic'\n\n// Reflect.defineMetadata/getMetadata come from reflect-metadata (used by NestJS)\ndeclare namespace Reflect {\n  function defineMetadata(key: string, value: unknown, target: object): void\n  function getMetadata(key: string, target: object): unknown\n}\n\n// NestJS is a peer dep; these are the minimum shapes the guard touches.\n\n/** Minimal NestJS request shape. */\ninterface NestRequest {\n  user?: { id?: string; sub?: string; [key: string]: unknown }\n  params?: Record<string, string>\n  method: string\n  path?: string\n  route?: { path?: string }\n  headers?: Record<string, string | string[] | undefined>\n  ip?: string\n  [key: string]: unknown\n}\n\n/** Minimal NestJS execution context. */\ninterface NestExecutionContext {\n  switchToHttp(): { getRequest(): NestRequest }\n  // NestJS returns Function; we use `object` as the compatible supertype.\n  getHandler(): object\n}\n\n/** Metadata key for the @IamAuthorize decorator. */\nexport const IAM_ACCESS_METADATA_KEY = 'duck-iam:authorize'\n\n/** NestJS server integration types. Type-only namespace - zero bundle cost. */\nexport namespace IamNest {\n  /**\n   * Describes the metadata payload attached by the {@link IamAuthorize} decorator.\n   *\n   * @template TAction - Constrains valid action strings.\n   * @template TResource - Constrains valid resource strings.\n   * @template TScope - Constrains valid scope strings.\n   */\n  export interface IAuthorizeMeta<\n    TAction extends string = string,\n    TResource extends string = string,\n    TScope extends string = string,\n  > {\n    /** Specifies the required action (e.g. `'delete'`, `'manage'`). */\n    action?: TAction\n    /** Specifies the target resource type (e.g. `'post'`, `'user'`). */\n    resource?: TResource\n    /** Optional scope constraint applied to the check. */\n    scope?: TScope\n    /** When `true`, infers action from HTTP method and resource from route path. */\n    infer?: boolean\n  }\n\n  /**\n   * Describes options for {@link iamNestAccessGuard}.\n   *\n   * Each extractor has a sensible default.\n   *\n   * @template TScope - Constrains valid scope strings.\n   */\n  export interface IGuardOptions<TScope extends string = string> {\n    /** Extracts the current user ID from the request. */\n    getUserId?: (request: NestRequest) => string | null\n    /** Extracts environment context (IP, user-agent, etc.) from the request. */\n    getEnvironment?: (request: NestRequest) => IamRequest.IEnvironment\n    /** Extracts the resource ID from the request. */\n    getResourceId?: (request: NestRequest) => string | undefined\n    /** Determines the scope used for the access check. */\n    getScope?: (request: NestRequest) => TScope | undefined\n    /** Handles thrown errors during evaluation; return `true` to allow, `false` to deny. */\n    onError?: (err: Error, request: NestRequest) => boolean\n  }\n\n  /** Required guard callback for the admin controller methods. */\n  export type IAdminAuthorize = (request: NestRequest) => boolean | Promise<boolean>\n\n  /** Describes options for {@link createIamAdminOperations}. `authorize` is required. */\n  export interface IAdminOptions extends IamAdminAudit.IOptions {\n    /** Required. Runs before every admin operation. */\n    authorize: IAdminAuthorize\n    /**\n     * Optional audit hook fired AFTER every mutation handler (savePolicy/\n     * saveRole/assignRole/revokeRole) completes - success or failure. The\n     * hook is fire-and-forget: a slow or throwing implementation never\n     * blocks the request and can never alter the response. `listPolicies` /\n     * `listRoles` (reads) do not fire it.\n     *\n     * See {@link IamAdminAudit.IOptions} for additional hardening knobs:\n     * `redactPath`, `onAuditHookError`, and `includeErrorMessage`.\n     */\n    onAdminMutation?: IamAdminAudit.Hook\n  }\n}\n\n/** Handler function with attached authorize metadata. */\ninterface HandlerWithMeta {\n  __accessMeta?: IamNest.IAuthorizeMeta\n}\n\n/**\n * Marks a controller method with access requirements.\n *\n * Stores metadata via `reflect-metadata` when available and also attaches\n * `__accessMeta` so the guard works without that package.\n *\n * @template TAction - Constrains valid action strings.\n * @template TResource - Constrains valid resource strings.\n * @template TScope - Constrains valid scope strings.\n * @param meta - Configures the access metadata; defaults to `{ infer: true }`.\n * @returns A NestJS `MethodDecorator`.\n */\nexport function IamAuthorize<\n  TAction extends string = string,\n  TResource extends string = string,\n  TScope extends string = string,\n>(\n  meta: IamNest.IAuthorizeMeta<TAction, TResource, TScope> = { infer: true } as IamNest.IAuthorizeMeta<\n    TAction,\n    TResource,\n    TScope\n  >,\n): MethodDecorator {\n  return (_target, _propertyKey, descriptor) => {\n    if (Reflect?.defineMetadata) {\n      Reflect.defineMetadata(IAM_ACCESS_METADATA_KEY, meta, descriptor.value as object)\n    }\n    if (descriptor.value != null) {\n      Object.defineProperty(descriptor.value, '__accessMeta', { value: meta, configurable: true, writable: true })\n    }\n    return descriptor\n  }\n}\n\n/** Extract authorize metadata from a handler. */\nfunction getHandlerMeta(handler: object): IamNest.IAuthorizeMeta | undefined {\n  if ('__accessMeta' in handler) {\n    return (handler as HandlerWithMeta).__accessMeta\n  }\n  if (Reflect?.getMetadata) {\n    return Reflect.getMetadata(IAM_ACCESS_METADATA_KEY, handler) as IamNest.IAuthorizeMeta | undefined\n  }\n  return undefined\n}\n\n/**\n * Builds a NestJS `canActivate` function that reads {@link IamAuthorize} metadata\n * off the handler and runs `engine.can(...)`.\n *\n * Handlers without metadata pass through (allow).\n *\n * @template TAction - Constrains valid action strings.\n * @template TResource - Constrains valid resource strings.\n * @template TRole - Constrains valid role strings.\n * @template TScope - Constrains valid scope strings.\n * @param engine - Provides the access engine to consult.\n * @param opts - Configures optional extractors and error handler.\n * @returns A function suitable as a NestJS guard's `canActivate` body.\n * @example\n * ```ts\n * @Injectable()\n * class AccessGuard implements CanActivate {\n *   canActivate = iamNestAccessGuard(engine)\n * }\n * ```\n */\nexport function iamNestAccessGuard<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n>(engine: IamEngine<TAction, TResource, TRole, TScope>, opts: IamNest.IGuardOptions<TScope> = {}) {\n  const {\n    getUserId = (req: NestRequest) => (req.user?.id as string) ?? (req.user?.sub as string) ?? null,\n    getEnvironment = (req: NestRequest) => iamExtractEnvironment(req),\n    getResourceId = (req: NestRequest) => req.params?.id,\n    getScope,\n    onError = () => false,\n  } = opts\n\n  return async (context: NestExecutionContext): Promise<boolean> => {\n    const request = context.switchToHttp().getRequest()\n    const handler = context.getHandler()\n\n    const meta = getHandlerMeta(handler)\n\n    if (!meta) return true // No @IamAuthorize decorator: allow.\n\n    const userId = getUserId(request)\n    if (!userId) return false\n\n    const action = meta.infer ? (IAM_METHOD_ACTION_MAP[request.method] ?? 'read') : (meta.action ?? 'read')\n\n    const resource = meta.infer ? inferResource(request) : (meta.resource ?? 'unknown')\n\n    const scope = (meta.scope as TScope | undefined) ?? getScope?.(request)\n\n    try {\n      return await engine.can(\n        userId,\n        action as TAction,\n        { type: resource as TResource, id: getResourceId(request), attributes: {} },\n        getEnvironment(request),\n        scope,\n      )\n    } catch (err) {\n      return onError(err instanceof Error ? err : new Error(String(err)), request)\n    }\n  }\n}\n\n/** Infer resource type from request route path. */\nfunction inferResource(request: NestRequest): string {\n  const path: string = request.route?.path ?? request.path ?? '/'\n  const segments = path.split('/').filter((s: string) => s && !s.startsWith(':'))\n  return segments[segments.length - 1] ?? 'root'\n}\n\n/**\n * Builds a pre-typed `IamAuthorize` decorator constrained to your app's\n * action/resource/scope unions.\n *\n * Typos like `@IamAuthorize({ action: 'craete' })` become compile errors.\n *\n * @template TAction - Constrains valid action strings.\n * @template TResource - Constrains valid resource strings.\n * @template TScope - Constrains valid scope strings.\n * @returns A typed wrapper around {@link IamAuthorize}.\n */\nexport function createIamTypedAuthorize<\n  TAction extends string,\n  TResource extends string,\n  TScope extends string = string,\n>() {\n  return IamAuthorize as (meta?: IamNest.IAuthorizeMeta<TAction, TResource, TScope>) => MethodDecorator\n}\n\n/** DI token for the access Engine in NestJS. */\nexport const IAM_ACCESS_ENGINE_TOKEN = 'ACCESS_ENGINE'\n\n/**\n * Builds a NestJS provider descriptor bound to {@link IAM_ACCESS_ENGINE_TOKEN}.\n *\n * @template TAction - Constrains valid action strings.\n * @template TResource - Constrains valid resource strings.\n * @template TRole - Constrains valid role strings.\n * @template TScope - Constrains valid scope strings.\n * @param factory - Provides the sync or async engine factory.\n * @returns A `{ provide, useFactory }` descriptor for NestJS DI.\n */\nexport function createIamEngineProvider<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n>(factory: () => IamEngine<TAction, TResource, TRole, TScope> | Promise<IamEngine<TAction, TResource, TRole, TScope>>) {\n  return {\n    provide: IAM_ACCESS_ENGINE_TOKEN,\n    useFactory: factory,\n  }\n}\n\n/**\n * Builds framework-agnostic admin operations for use inside a NestJS controller.\n *\n * IamNest's decorator-driven routing means we do not ship a router factory;\n * instead this returns a record of admin handlers the user wires into their\n * `@Controller` methods. Enforces `authorize` at construction time so the\n * controller cannot be instantiated unguarded.\n *\n * @template TAction - Constrains valid action strings.\n * @template TResource - Constrains valid resource strings.\n * @template TRole - Constrains valid role strings.\n * @template TScope - Constrains valid scope strings.\n * @param engine - Provides the access engine whose `admin` operations are exposed.\n * @param opts - Must include `authorize`.\n * @returns A record of `(req, ...args) => Promise` admin handlers.\n * @throws Error when `opts.authorize` is not a function.\n * @example\n * ```ts\n * @Controller('admin')\n * class IamAdminController {\n *   private h = createIamAdminOperations(engine, {\n *     authorize: (req) => isAdmin(req.user),\n *     onAdminMutation: (e) => auditLog.write(e),\n *   })\n *   @Get('policies') listPolicies(@Req() req) { return this.h.listPolicies(req) }\n * }\n * ```\n * @example\n * Rate limiting is out of scope; compose with IamNest's `@nestjs/throttler` or a\n * global guard. Pseudocode:\n * ```ts\n * @UseGuards(ThrottlerGuard)\n * @Throttle({ default: { limit: 30, ttl: 60_000 } })\n * @Controller('admin') class IamAdminController { ... }\n * ```\n */\nexport function createIamAdminOperations<\n  TAction extends string = string,\n  TResource extends string = string,\n  TRole extends string = string,\n  TScope extends string = string,\n>(engine: IamEngine<TAction, TResource, TRole, TScope>, opts: IamNest.IAdminOptions) {\n  if (!opts || typeof opts.authorize !== 'function') {\n    throw new Error('[@gentleduck/iam] createIamAdminOperations requires an `authorize` callback.')\n  }\n  const { authorize, onAdminMutation, redactPath, onAuditHookError, includeErrorMessage, csrfCheck } = opts\n  // Default to the built-in Sec-Fetch-Site check; pass `false` to disable.\n  const effectiveCsrfCheck = csrfCheck === false ? null : (csrfCheck ?? iamDefaultCsrfCheck)\n  iamNoticeCsrfDefaultIfNeeded(csrfCheck !== undefined)\n\n  /**\n   * Gate that returns whatever {@link IamNest.IAdminAuthorize} returned so the value\n   * can be forwarded into the audit event as `actor`. Throws a 401-flavoured\n   * Error on denial so the calling controller surfaces a NestJS exception.\n   */\n  const gateWithActor = async (req: NestRequest): Promise<unknown> => {\n    // CSRF guard runs before authorize so a cookie-based authorize cannot\n    // be tricked by a cross-origin POST. No-op when csrfCheck is omitted.\n    if (effectiveCsrfCheck && !effectiveCsrfCheck(req)) {\n      const err = new Error('Forbidden (CSRF check failed)') as Error & { status?: number }\n      err.status = 403\n      throw err\n    }\n    const result = await authorize(req)\n    if (!result) {\n      const err = new Error('Unauthorized') as Error & { status?: number }\n      err.status = 401\n      throw err\n    }\n    return result\n  }\n\n  /**\n   * Run a mutation, firing `onAdminMutation` in a finally block so the\n   * audit event lands even when the handler throws.\n   */\n  const runMutation = async <T>(\n    req: NestRequest,\n    action: IamAdminAudit.Action,\n    target: IamAdminAudit.Target,\n    targetId: string | undefined,\n    handler: () => Promise<T>,\n  ): Promise<T> => {\n    // IamAuthorize denial or throw - do NOT emit audit (mutation never started).\n    const actor = await gateWithActor(req)\n    // Shared audit wrapper.\n    return iamWithAdminAudit(\n      {\n        actor,\n        action,\n        target,\n        targetId,\n        method: req.method,\n        path: req.route?.path ?? req.path ?? '',\n        onAdminMutation,\n        redactPath,\n        onAuditHookError,\n        includeErrorMessage,\n      },\n      handler,\n    )\n  }\n\n  const gate = async (req: NestRequest): Promise<void> => {\n    await gateWithActor(req)\n  }\n\n  return {\n    async listPolicies(req: NestRequest) {\n      await gate(req)\n      return engine.admin.listPolicies()\n    },\n    async listRoles(req: NestRequest) {\n      await gate(req)\n      return engine.admin.listRoles()\n    },\n    async savePolicy(req: NestRequest, body: AccessControl.IPolicy<TAction, TResource, TRole>) {\n      return runMutation(req, 'replace', 'policy', (body as { id?: string } | undefined)?.id, async () => {\n        await engine.admin.savePolicy(body)\n        return { ok: true as const }\n      })\n    },\n    async saveRole(req: NestRequest, body: AccessControl.IRole<TAction, TResource, TRole, TScope>) {\n      return runMutation(req, 'replace', 'role', (body as { id?: string } | undefined)?.id, async () => {\n        await engine.admin.saveRole(body)\n        return { ok: true as const }\n      })\n    },\n    async assignRole(req: NestRequest, subjectId: string, body: { roleId: TRole; scope?: TScope }) {\n      return runMutation(req, 'create', 'role-assignment', subjectId, async () => {\n        await engine.admin.assignRole(subjectId, body.roleId, body.scope)\n        return { ok: true as const }\n      })\n    },\n    async revokeRole(req: NestRequest, subjectId: string, roleId: TRole) {\n      return runMutation(req, 'delete', 'role-assignment', subjectId, async () => {\n        await engine.admin.revokeRole(subjectId, roleId)\n        return { ok: true as const }\n      })\n    },\n  }\n}\n"],"mappings":"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"}