{"version":3,"file":"express.mjs","names":[],"sources":["../src/express/middleware.ts"],"sourcesContent":["import type { NextFunction, Request, Response } from 'express';\n\nimport { AuthenticationError, AuthorizationError, BadRequestError, NotFoundError } from '../errors';\nimport { GrantClient } from '../grant-client';\nimport type { AuthorizationResult, ResourceResolver } from '../types';\nimport { debugGrant } from '../utils/debug';\nimport { extractTokenFromRequest } from '../utils/token-extractor';\n\n/**\n * Extended Express Request with authorization result\n */\nexport interface AuthorizedRequest extends Request {\n  authorization?: AuthorizationResult;\n}\n\n/**\n * Options for Express middleware\n */\nexport interface GrantOptions {\n  /** The resource slug to check (e.g., \"Organization\", \"Project\", \"Document\") */\n  resource: string;\n  /** The action to check (e.g., \"Query\", \"Create\", \"Update\", \"Delete\") */\n  action: string;\n  /** Custom resource resolver for condition evaluation */\n  resourceResolver?: ResourceResolver;\n}\n\n/**\n * Create Express middleware to check if user is granted permission\n *\n * @example\n * ```ts\n * import { grant } from '@grantjs/server/express';\n * import { GrantClient } from '@grantjs/server';\n *\n * const grantClient = new GrantClient({ apiUrl: 'https://api.grant.com' });\n *\n * router.get('/organizations', grant(grantClient, {\n *   resource: 'Organization',\n *   action: 'Query',\n * }), handler);\n * ```\n */\nexport function grant(\n  client: GrantClient,\n  options: GrantOptions\n): (req: AuthorizedRequest, res: Response, next: NextFunction) => Promise<void> {\n  return async (req: AuthorizedRequest, res: Response, next: NextFunction): Promise<void> => {\n    try {\n      debugGrant('Express', { resource: options.resource, action: options.action });\n\n      // 1. Check authentication (token must be present)\n      const token = await extractTokenFromRequest(req, client.config);\n      if (!token) {\n        res.status(401).json({\n          error: 'Unauthorized',\n          code: 'UNAUTHENTICATED',\n        });\n        return;\n      }\n\n      // 2. Optionally resolve resource for condition evaluation\n      let resolvedResource: Record<string, unknown> | null = null;\n\n      if (options.resourceResolver) {\n        resolvedResource = await options.resourceResolver({\n          resourceSlug: options.resource,\n          request: req,\n        });\n\n        if (!resolvedResource) {\n          res.status(404).json({\n            error: 'Resource not found',\n            code: 'NOT_FOUND',\n          });\n          return;\n        }\n      }\n\n      // 3. Check authorization (pass request for token extraction; scope comes from JWT claims)\n      const result = await client.isAuthorized(\n        options.resource,\n        options.action,\n        {\n          context: {\n            resource: resolvedResource || undefined,\n          },\n        },\n        req\n      );\n\n      debugGrant('Express', {\n        resource: options.resource,\n        action: options.action,\n        authorized: result.authorized,\n        ...(result.authorized ? {} : { reason: result.reason }),\n      });\n\n      if (!result.authorized) {\n        res.status(403).json({\n          error: 'Forbidden',\n          code: 'FORBIDDEN',\n          reason: result.reason,\n        });\n        return;\n      }\n\n      // 4. Attach authorization result to request for downstream use\n      (req as AuthorizedRequest).authorization = result;\n\n      // 5. Proceed to next middleware/handler\n      next();\n    } catch (error) {\n      // Handle known errors\n      if (error instanceof AuthenticationError) {\n        res.status(401).json({\n          error: error.message,\n          code: error.code,\n        });\n        return;\n      }\n\n      if (error instanceof AuthorizationError) {\n        res.status(403).json({\n          error: error.message,\n          code: error.code,\n          reason: error.reason,\n        });\n        return;\n      }\n\n      if (error instanceof BadRequestError) {\n        res.status(400).json({\n          error: error.message,\n          code: error.code,\n        });\n        return;\n      }\n\n      if (error instanceof NotFoundError) {\n        res.status(404).json({\n          error: error.message,\n          code: error.code,\n        });\n        return;\n      }\n\n      // Unknown error - pass to Express error handler\n      next(error);\n    }\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,MACd,QACA,SAC8E;CAC9E,OAAO,OAAO,KAAwB,KAAe,SAAsC;EACzF,IAAI;GACF,WAAW,WAAW;IAAE,UAAU,QAAQ;IAAU,QAAQ,QAAQ;GAAO,CAAC;GAI5E,IAAI,CAAC,MADe,wBAAwB,KAAK,OAAO,MAAM,GAClD;IACV,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;KACnB,OAAO;KACP,MAAM;IACR,CAAC;IACD;GACF;GAGA,IAAI,mBAAmD;GAEvD,IAAI,QAAQ,kBAAkB;IAC5B,mBAAmB,MAAM,QAAQ,iBAAiB;KAChD,cAAc,QAAQ;KACtB,SAAS;IACX,CAAC;IAED,IAAI,CAAC,kBAAkB;KACrB,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;MACnB,OAAO;MACP,MAAM;KACR,CAAC;KACD;IACF;GACF;GAGA,MAAM,SAAS,MAAM,OAAO,aAC1B,QAAQ,UACR,QAAQ,QACR,EACE,SAAS,EACP,UAAU,oBAAoB,KAAA,EAChC,EACF,GACA,GACF;GAEA,WAAW,WAAW;IACpB,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IAChB,YAAY,OAAO;IACnB,GAAI,OAAO,aAAa,CAAC,IAAI,EAAE,QAAQ,OAAO,OAAO;GACvD,CAAC;GAED,IAAI,CAAC,OAAO,YAAY;IACtB,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;KACnB,OAAO;KACP,MAAM;KACN,QAAQ,OAAO;IACjB,CAAC;IACD;GACF;GAGA,IAA2B,gBAAgB;GAG3C,KAAK;EACP,SAAS,OAAO;GAEd,IAAI,iBAAiB,qBAAqB;IACxC,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;KACnB,OAAO,MAAM;KACb,MAAM,MAAM;IACd,CAAC;IACD;GACF;GAEA,IAAI,iBAAiB,oBAAoB;IACvC,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;KACnB,OAAO,MAAM;KACb,MAAM,MAAM;KACZ,QAAQ,MAAM;IAChB,CAAC;IACD;GACF;GAEA,IAAI,iBAAiB,iBAAiB;IACpC,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;KACnB,OAAO,MAAM;KACb,MAAM,MAAM;IACd,CAAC;IACD;GACF;GAEA,IAAI,iBAAiB,eAAe;IAClC,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK;KACnB,OAAO,MAAM;KACb,MAAM,MAAM;IACd,CAAC;IACD;GACF;GAGA,KAAK,KAAK;EACZ;CACF;AACF"}