{"version":3,"file":"fastify.mjs","names":[],"sources":["../src/fastify/plugin.ts"],"sourcesContent":["import { FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify';\n\nimport { AuthenticationError, AuthorizationError, BadRequestError, NotFoundError } from '../errors';\nimport { GrantClient } from '../grant-client';\nimport type { AuthorizationResult, GrantServerConfig, ResourceResolver } from '../types';\nimport { debugGrant } from '../utils/debug';\nimport { extractTokenFromRequest } from '../utils/token-extractor';\n\n/**\n * Extended Fastify Request with authorization result\n */\nexport interface AuthorizedFastifyRequest extends FastifyRequest {\n  authorization?: AuthorizationResult;\n}\n\n/**\n * Options for Fastify preHandler hook\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 a Fastify preHandler hook to check if user is granted permission\n *\n * @example\n * ```ts\n * import { grant } from '@grantjs/server/fastify';\n * import { GrantClient } from '@grantjs/server';\n * import Fastify from 'fastify';\n *\n * const fastify = Fastify();\n * const grantClient = new GrantClient({ apiUrl: 'https://api.grant.com' });\n *\n * fastify.get('/organizations', {\n *   preHandler: grant(grantClient, {\n *     resource: 'Organization',\n *     action: 'Query',\n *   }),\n * }, async (request, reply) => {\n *   return { organizations: [] };\n * });\n * ```\n */\nexport function grant(\n  client: GrantClient,\n  options: GrantOptions\n): (request: AuthorizedFastifyRequest, reply: FastifyReply) => Promise<void> {\n  return async (request: AuthorizedFastifyRequest, reply: FastifyReply): Promise<void> => {\n    try {\n      debugGrant('Fastify', { resource: options.resource, action: options.action });\n\n      // 1. Check authentication (token must be present)\n      const token = await extractTokenFromRequest(request, client.config);\n      if (!token) {\n        reply.status(401).send({\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: request,\n        });\n\n        if (!resolvedResource) {\n          reply.status(404).send({\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        request\n      );\n\n      debugGrant('Fastify', {\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        reply.status(403).send({\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      request.authorization = result;\n    } catch (error) {\n      // Handle known errors\n      if (error instanceof AuthenticationError) {\n        reply.status(401).send({\n          error: error.message,\n          code: error.code,\n        });\n        return;\n      }\n\n      if (error instanceof AuthorizationError) {\n        reply.status(403).send({\n          error: error.message,\n          code: error.code,\n          reason: error.reason,\n        });\n        return;\n      }\n\n      if (error instanceof BadRequestError) {\n        reply.status(400).send({\n          error: error.message,\n          code: error.code,\n        });\n        return;\n      }\n\n      if (error instanceof NotFoundError) {\n        reply.status(404).send({\n          error: error.message,\n          code: error.code,\n        });\n        return;\n      }\n\n      // Unknown error - rethrow to let Fastify handle it\n      throw error;\n    }\n  };\n}\n\n/**\n * Fastify plugin options\n * Extends GrantServerConfig - plugin-specific options can be added here in the future\n */\nexport type GrantPluginOptions = GrantServerConfig;\n\n/**\n * Fastify plugin that decorates the instance with GrantClient\n *\n * @example\n * ```ts\n * import Fastify from 'fastify';\n * import { grantPlugin } from '@grantjs/server/fastify';\n *\n * const fastify = Fastify();\n *\n * await fastify.register(grantPlugin, {\n *   apiUrl: 'https://api.grant.com',\n *   cookieName: 'grant-access-token',\n * });\n *\n * // GrantClient is now available on fastify.grant\n * const canEdit = await fastify.grant.isGranted('Document', 'Update', { scope });\n * ```\n */\nexport const grantPlugin: FastifyPluginAsync<GrantPluginOptions> = async (fastify, options) => {\n  const grant = new GrantClient(options);\n\n  // Decorate Fastify instance with GrantClient\n  fastify.decorate('grant', grant);\n};\n\n// Type declaration for TypeScript\n// This will work once Fastify is installed (it's a peer dependency)\ndeclare module 'fastify' {\n  interface FastifyInstance {\n    grant: GrantClient;\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,MACd,QACA,SAC2E;CAC3E,OAAO,OAAO,SAAmC,UAAuC;EACtF,IAAI;GACF,WAAW,WAAW;IAAE,UAAU,QAAQ;IAAU,QAAQ,QAAQ;GAAO,CAAC;GAI5E,IAAI,CAAC,MADe,wBAAwB,SAAS,OAAO,MAAM,GACtD;IACV,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;KACrB,OAAO;KACP,MAAM;IACR,CAAC;IACD;GACF;GAGA,IAAI,mBAAmD;GAEvD,IAAI,QAAQ,kBAAkB;IAC5B,mBAAmB,MAAM,QAAQ,iBAAiB;KAChD,cAAc,QAAQ;KACb;IACX,CAAC;IAED,IAAI,CAAC,kBAAkB;KACrB,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;MACrB,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,OACF;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,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;KACrB,OAAO;KACP,MAAM;KACN,QAAQ,OAAO;IACjB,CAAC;IACD;GACF;GAGA,QAAQ,gBAAgB;EAC1B,SAAS,OAAO;GAEd,IAAI,iBAAiB,qBAAqB;IACxC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;IACd,CAAC;IACD;GACF;GAEA,IAAI,iBAAiB,oBAAoB;IACvC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;KACZ,QAAQ,MAAM;IAChB,CAAC;IACD;GACF;GAEA,IAAI,iBAAiB,iBAAiB;IACpC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;IACd,CAAC;IACD;GACF;GAEA,IAAI,iBAAiB,eAAe;IAClC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;IACd,CAAC;IACD;GACF;GAGA,MAAM;EACR;CACF;AACF;;;;;;;;;;;;;;;;;;;;AA2BA,IAAa,cAAsD,OAAO,SAAS,YAAY;CAC7F,MAAM,QAAQ,IAAI,YAAY,OAAO;CAGrC,QAAQ,SAAS,SAAS,KAAK;AACjC"}