@grantjs/server 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/dist/debug-DICR8lwQ.js +15 -0
  3. package/dist/debug-DICR8lwQ.js.map +1 -0
  4. package/dist/debug-DInHA4QB.cjs +20 -0
  5. package/dist/debug-DInHA4QB.cjs.map +1 -0
  6. package/dist/errors-BUeTn_3G.cjs +91 -0
  7. package/dist/errors-BUeTn_3G.cjs.map +1 -0
  8. package/dist/errors-CSC_Z_KP.js +62 -0
  9. package/dist/errors-CSC_Z_KP.js.map +1 -0
  10. package/dist/express/index.d.ts +3 -3
  11. package/dist/express/index.d.ts.map +1 -1
  12. package/dist/express/middleware.d.ts +2 -2
  13. package/dist/express/middleware.d.ts.map +1 -1
  14. package/dist/express.cjs +106 -95
  15. package/dist/express.cjs.map +1 -1
  16. package/dist/express.mjs +101 -99
  17. package/dist/express.mjs.map +1 -1
  18. package/dist/fastify/index.d.ts +4 -4
  19. package/dist/fastify/index.d.ts.map +1 -1
  20. package/dist/fastify/plugin.d.ts +2 -2
  21. package/dist/fastify/plugin.d.ts.map +1 -1
  22. package/dist/fastify.cjs +133 -97
  23. package/dist/fastify.cjs.map +1 -1
  24. package/dist/fastify.mjs +128 -101
  25. package/dist/fastify.mjs.map +1 -1
  26. package/dist/grant-client-f6IKMyk5.cjs +228 -0
  27. package/dist/grant-client-f6IKMyk5.cjs.map +1 -0
  28. package/dist/grant-client-t3KadxFo.js +205 -0
  29. package/dist/grant-client-t3KadxFo.js.map +1 -0
  30. package/dist/grant-client.d.ts +1 -1
  31. package/dist/grant-client.d.ts.map +1 -1
  32. package/dist/index.cjs +10 -49
  33. package/dist/index.d.ts +4 -4
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.mjs +3 -50
  36. package/dist/nest/grant.guard.d.ts +1 -1
  37. package/dist/nest/grant.guard.d.ts.map +1 -1
  38. package/dist/nest/grant.module.d.ts.map +1 -1
  39. package/dist/nest/index.d.ts +6 -6
  40. package/dist/nest/index.d.ts.map +1 -1
  41. package/dist/nest.cjs +163 -143
  42. package/dist/nest.cjs.map +1 -1
  43. package/dist/nest.mjs +150 -149
  44. package/dist/nest.mjs.map +1 -1
  45. package/dist/next/index.d.ts +4 -4
  46. package/dist/next/index.d.ts.map +1 -1
  47. package/dist/next/with-grant.d.ts +1 -1
  48. package/dist/next/with-grant.d.ts.map +1 -1
  49. package/dist/next.cjs +75 -58
  50. package/dist/next.cjs.map +1 -1
  51. package/dist/next.mjs +70 -60
  52. package/dist/next.mjs.map +1 -1
  53. package/package.json +17 -17
  54. package/dist/debug-C8ibbrE5.js +0 -9
  55. package/dist/debug-C8ibbrE5.js.map +0 -1
  56. package/dist/debug-Crb5gNDM.cjs +0 -8
  57. package/dist/debug-Crb5gNDM.cjs.map +0 -1
  58. package/dist/grant-client-D1LZI2f4.js +0 -180
  59. package/dist/grant-client-D1LZI2f4.js.map +0 -1
  60. package/dist/grant-client-DywfJN5P.cjs +0 -179
  61. package/dist/grant-client-DywfJN5P.cjs.map +0 -1
  62. package/dist/index.cjs.map +0 -1
  63. package/dist/index.mjs.map +0 -1
package/dist/fastify.mjs CHANGED
@@ -1,104 +1,131 @@
1
- import { AuthenticationError, AuthorizationError, BadRequestError, NotFoundError } from "./index.mjs";
2
- import { GrantServerError } from "./index.mjs";
3
- import { e as extractTokenFromRequest, G as GrantClient } from "./grant-client-D1LZI2f4.js";
4
- import { d as debugGrant } from "./debug-C8ibbrE5.js";
1
+ import { i as extractTokenFromRequest, t as GrantClient } from "./grant-client-t3KadxFo.js";
2
+ import { a as NotFoundError, i as GrantServerError, n as AuthorizationError, r as BadRequestError, t as AuthenticationError } from "./errors-CSC_Z_KP.js";
3
+ import { t as debugGrant } from "./debug-DICR8lwQ.js";
4
+ //#region src/fastify/plugin.ts
5
+ /**
6
+ * Create a Fastify preHandler hook to check if user is granted permission
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { grant } from '@grantjs/server/fastify';
11
+ * import { GrantClient } from '@grantjs/server';
12
+ * import Fastify from 'fastify';
13
+ *
14
+ * const fastify = Fastify();
15
+ * const grantClient = new GrantClient({ apiUrl: 'https://api.grant.com' });
16
+ *
17
+ * fastify.get('/organizations', {
18
+ * preHandler: grant(grantClient, {
19
+ * resource: 'Organization',
20
+ * action: 'Query',
21
+ * }),
22
+ * }, async (request, reply) => {
23
+ * return { organizations: [] };
24
+ * });
25
+ * ```
26
+ */
5
27
  function grant(client, options) {
6
- return async (request, reply) => {
7
- try {
8
- debugGrant("Fastify", { resource: options.resource, action: options.action });
9
- const token = await extractTokenFromRequest(request, client.config);
10
- if (!token) {
11
- reply.status(401).send({
12
- error: "Unauthorized",
13
- code: "UNAUTHENTICATED"
14
- });
15
- return;
16
- }
17
- let resolvedResource = null;
18
- if (options.resourceResolver) {
19
- resolvedResource = await options.resourceResolver({
20
- resourceSlug: options.resource,
21
- request
22
- });
23
- if (!resolvedResource) {
24
- reply.status(404).send({
25
- error: "Resource not found",
26
- code: "NOT_FOUND"
27
- });
28
- return;
29
- }
30
- }
31
- const result = await client.isAuthorized(
32
- options.resource,
33
- options.action,
34
- {
35
- context: {
36
- resource: resolvedResource || void 0
37
- }
38
- },
39
- request
40
- );
41
- debugGrant("Fastify", {
42
- resource: options.resource,
43
- action: options.action,
44
- authorized: result.authorized,
45
- ...result.authorized ? {} : { reason: result.reason }
46
- });
47
- if (!result.authorized) {
48
- reply.status(403).send({
49
- error: "Forbidden",
50
- code: "FORBIDDEN",
51
- reason: result.reason
52
- });
53
- return;
54
- }
55
- request.authorization = result;
56
- } catch (error) {
57
- if (error instanceof AuthenticationError) {
58
- reply.status(401).send({
59
- error: error.message,
60
- code: error.code
61
- });
62
- return;
63
- }
64
- if (error instanceof AuthorizationError) {
65
- reply.status(403).send({
66
- error: error.message,
67
- code: error.code,
68
- reason: error.reason
69
- });
70
- return;
71
- }
72
- if (error instanceof BadRequestError) {
73
- reply.status(400).send({
74
- error: error.message,
75
- code: error.code
76
- });
77
- return;
78
- }
79
- if (error instanceof NotFoundError) {
80
- reply.status(404).send({
81
- error: error.message,
82
- code: error.code
83
- });
84
- return;
85
- }
86
- throw error;
87
- }
88
- };
28
+ return async (request, reply) => {
29
+ try {
30
+ debugGrant("Fastify", {
31
+ resource: options.resource,
32
+ action: options.action
33
+ });
34
+ if (!await extractTokenFromRequest(request, client.config)) {
35
+ reply.status(401).send({
36
+ error: "Unauthorized",
37
+ code: "UNAUTHENTICATED"
38
+ });
39
+ return;
40
+ }
41
+ let resolvedResource = null;
42
+ if (options.resourceResolver) {
43
+ resolvedResource = await options.resourceResolver({
44
+ resourceSlug: options.resource,
45
+ request
46
+ });
47
+ if (!resolvedResource) {
48
+ reply.status(404).send({
49
+ error: "Resource not found",
50
+ code: "NOT_FOUND"
51
+ });
52
+ return;
53
+ }
54
+ }
55
+ const result = await client.isAuthorized(options.resource, options.action, { context: { resource: resolvedResource || void 0 } }, request);
56
+ debugGrant("Fastify", {
57
+ resource: options.resource,
58
+ action: options.action,
59
+ authorized: result.authorized,
60
+ ...result.authorized ? {} : { reason: result.reason }
61
+ });
62
+ if (!result.authorized) {
63
+ reply.status(403).send({
64
+ error: "Forbidden",
65
+ code: "FORBIDDEN",
66
+ reason: result.reason
67
+ });
68
+ return;
69
+ }
70
+ request.authorization = result;
71
+ } catch (error) {
72
+ if (error instanceof AuthenticationError) {
73
+ reply.status(401).send({
74
+ error: error.message,
75
+ code: error.code
76
+ });
77
+ return;
78
+ }
79
+ if (error instanceof AuthorizationError) {
80
+ reply.status(403).send({
81
+ error: error.message,
82
+ code: error.code,
83
+ reason: error.reason
84
+ });
85
+ return;
86
+ }
87
+ if (error instanceof BadRequestError) {
88
+ reply.status(400).send({
89
+ error: error.message,
90
+ code: error.code
91
+ });
92
+ return;
93
+ }
94
+ if (error instanceof NotFoundError) {
95
+ reply.status(404).send({
96
+ error: error.message,
97
+ code: error.code
98
+ });
99
+ return;
100
+ }
101
+ throw error;
102
+ }
103
+ };
89
104
  }
90
- const grantPlugin = async (fastify, options) => {
91
- const grant2 = new GrantClient(options);
92
- fastify.decorate("grant", grant2);
105
+ /**
106
+ * Fastify plugin that decorates the instance with GrantClient
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * import Fastify from 'fastify';
111
+ * import { grantPlugin } from '@grantjs/server/fastify';
112
+ *
113
+ * const fastify = Fastify();
114
+ *
115
+ * await fastify.register(grantPlugin, {
116
+ * apiUrl: 'https://api.grant.com',
117
+ * cookieName: 'grant-access-token',
118
+ * });
119
+ *
120
+ * // GrantClient is now available on fastify.grant
121
+ * const canEdit = await fastify.grant.isGranted('Document', 'Update', { scope });
122
+ * ```
123
+ */
124
+ var grantPlugin = async (fastify, options) => {
125
+ const grant = new GrantClient(options);
126
+ fastify.decorate("grant", grant);
93
127
  };
94
- export {
95
- AuthenticationError,
96
- AuthorizationError,
97
- BadRequestError,
98
- GrantClient,
99
- GrantServerError,
100
- NotFoundError,
101
- grant,
102
- grantPlugin
103
- };
104
- //# sourceMappingURL=fastify.mjs.map
128
+ //#endregion
129
+ export { AuthenticationError, AuthorizationError, BadRequestError, GrantClient, GrantServerError, NotFoundError, grant, grantPlugin };
130
+
131
+ //# sourceMappingURL=fastify.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"fastify.mjs","sources":["../src/fastify/plugin.ts"],"sourcesContent":["import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';\n\nimport { AuthenticationError, AuthorizationError, BadRequestError, NotFoundError } from '../errors';\nimport { GrantClient } from '../grant-client';\nimport { debugGrant } from '../utils/debug';\nimport { extractTokenFromRequest } from '../utils/token-extractor';\n\nimport type { ResourceResolver, AuthorizationResult, GrantServerConfig } from '../types';\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"],"names":["grant"],"mappings":";;;;AAkDO,SAAS,MACd,QACA,SAC2E;AAC3E,SAAO,OAAO,SAAmC,UAAuC;AACtF,QAAI;AACF,iBAAW,WAAW,EAAE,UAAU,QAAQ,UAAU,QAAQ,QAAQ,QAAQ;AAG5E,YAAM,QAAQ,MAAM,wBAAwB,SAAS,OAAO,MAAM;AAClE,UAAI,CAAC,OAAO;AACV,cAAM,OAAO,GAAG,EAAE,KAAK;AAAA,UACrB,OAAO;AAAA,UACP,MAAM;AAAA,QAAA,CACP;AACD;AAAA,MACF;AAGA,UAAI,mBAAmD;AAEvD,UAAI,QAAQ,kBAAkB;AAC5B,2BAAmB,MAAM,QAAQ,iBAAiB;AAAA,UAChD,cAAc,QAAQ;AAAA,UACtB;AAAA,QAAA,CACD;AAED,YAAI,CAAC,kBAAkB;AACrB,gBAAM,OAAO,GAAG,EAAE,KAAK;AAAA,YACrB,OAAO;AAAA,YACP,MAAM;AAAA,UAAA,CACP;AACD;AAAA,QACF;AAAA,MACF;AAGA,YAAM,SAAS,MAAM,OAAO;AAAA,QAC1B,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR;AAAA,UACE,SAAS;AAAA,YACP,UAAU,oBAAoB;AAAA,UAAA;AAAA,QAChC;AAAA,QAEF;AAAA,MAAA;AAGF,iBAAW,WAAW;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,YAAY,OAAO;AAAA,QACnB,GAAI,OAAO,aAAa,CAAA,IAAK,EAAE,QAAQ,OAAO,OAAA;AAAA,MAAO,CACtD;AAED,UAAI,CAAC,OAAO,YAAY;AACtB,cAAM,OAAO,GAAG,EAAE,KAAK;AAAA,UACrB,OAAO;AAAA,UACP,MAAM;AAAA,UACN,QAAQ,OAAO;AAAA,QAAA,CAChB;AACD;AAAA,MACF;AAGA,cAAQ,gBAAgB;AAAA,IAC1B,SAAS,OAAO;AAEd,UAAI,iBAAiB,qBAAqB;AACxC,cAAM,OAAO,GAAG,EAAE,KAAK;AAAA,UACrB,OAAO,MAAM;AAAA,UACb,MAAM,MAAM;AAAA,QAAA,CACb;AACD;AAAA,MACF;AAEA,UAAI,iBAAiB,oBAAoB;AACvC,cAAM,OAAO,GAAG,EAAE,KAAK;AAAA,UACrB,OAAO,MAAM;AAAA,UACb,MAAM,MAAM;AAAA,UACZ,QAAQ,MAAM;AAAA,QAAA,CACf;AACD;AAAA,MACF;AAEA,UAAI,iBAAiB,iBAAiB;AACpC,cAAM,OAAO,GAAG,EAAE,KAAK;AAAA,UACrB,OAAO,MAAM;AAAA,UACb,MAAM,MAAM;AAAA,QAAA,CACb;AACD;AAAA,MACF;AAEA,UAAI,iBAAiB,eAAe;AAClC,cAAM,OAAO,GAAG,EAAE,KAAK;AAAA,UACrB,OAAO,MAAM;AAAA,UACb,MAAM,MAAM;AAAA,QAAA,CACb;AACD;AAAA,MACF;AAGA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AA2BO,MAAM,cAAsD,OAAO,SAAS,YAAY;AAC7F,QAAMA,SAAQ,IAAI,YAAY,OAAO;AAGrC,UAAQ,SAAS,SAASA,MAAK;AACjC;"}
1
+ {"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;AAC3E,QAAO,OAAO,SAAmC,UAAuC;AACtF,MAAI;AACF,cAAW,WAAW;IAAE,UAAU,QAAQ;IAAU,QAAQ,QAAQ;IAAQ,CAAC;AAI7E,OAAI,CAAC,MADe,wBAAwB,SAAS,OAAO,OAAO,EACvD;AACV,UAAM,OAAO,IAAI,CAAC,KAAK;KACrB,OAAO;KACP,MAAM;KACP,CAAC;AACF;;GAIF,IAAI,mBAAmD;AAEvD,OAAI,QAAQ,kBAAkB;AAC5B,uBAAmB,MAAM,QAAQ,iBAAiB;KAChD,cAAc,QAAQ;KACb;KACV,CAAC;AAEF,QAAI,CAAC,kBAAkB;AACrB,WAAM,OAAO,IAAI,CAAC,KAAK;MACrB,OAAO;MACP,MAAM;MACP,CAAC;AACF;;;GAKJ,MAAM,SAAS,MAAM,OAAO,aAC1B,QAAQ,UACR,QAAQ,QACR,EACE,SAAS,EACP,UAAU,oBAAoB,KAAA,GAC/B,EACF,EACD,QACD;AAED,cAAW,WAAW;IACpB,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IAChB,YAAY,OAAO;IACnB,GAAI,OAAO,aAAa,EAAE,GAAG,EAAE,QAAQ,OAAO,QAAQ;IACvD,CAAC;AAEF,OAAI,CAAC,OAAO,YAAY;AACtB,UAAM,OAAO,IAAI,CAAC,KAAK;KACrB,OAAO;KACP,MAAM;KACN,QAAQ,OAAO;KAChB,CAAC;AACF;;AAIF,WAAQ,gBAAgB;WACjB,OAAO;AAEd,OAAI,iBAAiB,qBAAqB;AACxC,UAAM,OAAO,IAAI,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;KACb,CAAC;AACF;;AAGF,OAAI,iBAAiB,oBAAoB;AACvC,UAAM,OAAO,IAAI,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;KACZ,QAAQ,MAAM;KACf,CAAC;AACF;;AAGF,OAAI,iBAAiB,iBAAiB;AACpC,UAAM,OAAO,IAAI,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;KACb,CAAC;AACF;;AAGF,OAAI,iBAAiB,eAAe;AAClC,UAAM,OAAO,IAAI,CAAC,KAAK;KACrB,OAAO,MAAM;KACb,MAAM,MAAM;KACb,CAAC;AACF;;AAIF,SAAM;;;;;;;;;;;;;;;;;;;;;;;AA8BZ,IAAa,cAAsD,OAAO,SAAS,YAAY;CAC7F,MAAM,QAAQ,IAAI,YAAY,QAAQ;AAGtC,SAAQ,SAAS,SAAS,MAAM"}
@@ -0,0 +1,228 @@
1
+ //#region src/utils/token-extractor.ts
2
+ /**
3
+ * Extract Bearer token from Authorization header
4
+ */
5
+ function extractBearerToken(authHeader) {
6
+ if (!authHeader) return null;
7
+ const parts = authHeader.trim().split(/\s+/);
8
+ if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") return null;
9
+ return parts[1];
10
+ }
11
+ /**
12
+ * Parse cookie header string into key-value pairs
13
+ */
14
+ function parseCookieHeader(cookieHeader) {
15
+ const cookies = {};
16
+ cookieHeader.split(";").forEach((cookie) => {
17
+ const [name, ...rest] = cookie.trim().split("=");
18
+ if (name && rest.length > 0) cookies[name.trim()] = rest.join("=").trim();
19
+ });
20
+ return cookies;
21
+ }
22
+ /**
23
+ * Extract token from cookies
24
+ */
25
+ function extractTokenFromCookies(cookieHeader, cookieName) {
26
+ if (!cookieHeader) return null;
27
+ return parseCookieHeader(cookieHeader)[cookieName] || null;
28
+ }
29
+ /**
30
+ * Extract token from Express request
31
+ */
32
+ async function extractTokenFromRequest(request, config) {
33
+ if (config.getToken) {
34
+ const token = await config.getToken(request);
35
+ if (token) return token;
36
+ }
37
+ const req = request;
38
+ if (!req?.headers) return null;
39
+ const headersGet = req.headers.get;
40
+ if (typeof headersGet === "function") {
41
+ const bearerToken = extractBearerToken(headersGet.call(req.headers, "authorization") ?? void 0);
42
+ if (bearerToken) return bearerToken;
43
+ const cookieName = config.cookieName || "grant-access-token";
44
+ const cookieHeader = headersGet.call(req.headers, "cookie");
45
+ if (cookieHeader) {
46
+ const cookieToken = extractTokenFromCookies(cookieHeader, cookieName);
47
+ if (cookieToken) return cookieToken;
48
+ }
49
+ return null;
50
+ }
51
+ const plainHeaders = req.headers;
52
+ const authHeader = plainHeaders.authorization;
53
+ const bearerToken = extractBearerToken(authHeader);
54
+ if (bearerToken) return bearerToken;
55
+ const cookieName = config.cookieName || "grant-access-token";
56
+ if (req.cookies?.[cookieName]) return req.cookies[cookieName];
57
+ const cookieHeader = plainHeaders.cookie;
58
+ if (cookieHeader) {
59
+ const cookieToken = extractTokenFromCookies(cookieHeader, cookieName);
60
+ if (cookieToken) return cookieToken;
61
+ }
62
+ return null;
63
+ }
64
+ //#endregion
65
+ //#region \0@oxc-project+runtime@0.127.0/helpers/typeof.js
66
+ function _typeof(o) {
67
+ "@babel/helpers - typeof";
68
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
69
+ return typeof o;
70
+ } : function(o) {
71
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
72
+ }, _typeof(o);
73
+ }
74
+ //#endregion
75
+ //#region \0@oxc-project+runtime@0.127.0/helpers/toPrimitive.js
76
+ function toPrimitive(t, r) {
77
+ if ("object" != _typeof(t) || !t) return t;
78
+ var e = t[Symbol.toPrimitive];
79
+ if (void 0 !== e) {
80
+ var i = e.call(t, r || "default");
81
+ if ("object" != _typeof(i)) return i;
82
+ throw new TypeError("@@toPrimitive must return a primitive value.");
83
+ }
84
+ return ("string" === r ? String : Number)(t);
85
+ }
86
+ //#endregion
87
+ //#region \0@oxc-project+runtime@0.127.0/helpers/toPropertyKey.js
88
+ function toPropertyKey(t) {
89
+ var i = toPrimitive(t, "string");
90
+ return "symbol" == _typeof(i) ? i : i + "";
91
+ }
92
+ //#endregion
93
+ //#region \0@oxc-project+runtime@0.127.0/helpers/defineProperty.js
94
+ function _defineProperty(e, r, t) {
95
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
96
+ value: t,
97
+ enumerable: !0,
98
+ configurable: !0,
99
+ writable: !0
100
+ }) : e[r] = t, e;
101
+ }
102
+ //#endregion
103
+ //#region src/grant-client.ts
104
+ /**
105
+ * Grant Client for server-side applications
106
+ *
107
+ * Makes HTTP requests to the Grant API to check permissions.
108
+ * Uses API-key exchanged tokens (scope in JWT claims); token-based and cookie-based extraction supported.
109
+ * No caching - server-side caching is handled by grant-api.
110
+ */
111
+ var GrantClient = class {
112
+ constructor(config) {
113
+ _defineProperty(this, "config", void 0);
114
+ this.config = config;
115
+ }
116
+ /**
117
+ * Check if the current user has a specific permission
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const canEdit = await grant.isGranted('document', 'update', { scope });
122
+ * if (canEdit) {
123
+ * // Proceed with operation
124
+ * }
125
+ * ```
126
+ */
127
+ async isGranted(resource, action, options, request) {
128
+ return (await this.isAuthorized(resource, action, options, request)).authorized;
129
+ }
130
+ /**
131
+ * Check authorization with full result details
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const result = await grant.isAuthorized('document', 'update', { scope }, request);
136
+ * if (!result.authorized) {
137
+ * console.log('Denied:', result.reason);
138
+ * }
139
+ * ```
140
+ */
141
+ async isAuthorized(resource, action, options, request) {
142
+ try {
143
+ const response = await this.fetchWithAuth("/api/auth/is-authorized", {
144
+ method: "POST",
145
+ body: JSON.stringify({
146
+ permission: {
147
+ resource,
148
+ action
149
+ },
150
+ context: { resource: options?.context?.resource || null }
151
+ })
152
+ }, request);
153
+ if (!response.ok) return {
154
+ authorized: false,
155
+ reason: (await response.json().catch(() => ({}))).message || `API error: ${response.status}`
156
+ };
157
+ const json = await response.json();
158
+ return json.data ?? json;
159
+ } catch (error) {
160
+ return {
161
+ authorized: false,
162
+ reason: error instanceof Error ? error.message : "Unknown error"
163
+ };
164
+ }
165
+ }
166
+ /**
167
+ * Extract token from a request object
168
+ * Useful for middleware that needs to extract token before making authorization calls
169
+ */
170
+ async getTokenFromRequest(request) {
171
+ return extractTokenFromRequest(request, this.config);
172
+ }
173
+ /**
174
+ * Make an authenticated fetch request (token from request; no refresh).
175
+ */
176
+ async fetchWithAuth(url, init, request) {
177
+ return this.doFetch(url, init, request);
178
+ }
179
+ /**
180
+ * Perform the actual fetch request
181
+ */
182
+ async doFetch(url, init, request) {
183
+ const fetchFn = this.config.fetch ?? globalThis.fetch;
184
+ const fullUrl = url.startsWith("http") ? url : `${this.config.apiUrl}${url}`;
185
+ const headers = {
186
+ "Content-Type": "application/json",
187
+ ...init?.headers
188
+ };
189
+ if (request) {
190
+ const token = await extractTokenFromRequest(request, this.config);
191
+ if (token) headers["Authorization"] = `Bearer ${token}`;
192
+ const req = request;
193
+ if (req.headers?.cookie) headers["Cookie"] = req.headers.cookie;
194
+ }
195
+ return fetchFn(fullUrl, {
196
+ ...init,
197
+ headers,
198
+ credentials: this.config.credentials ?? "include"
199
+ });
200
+ }
201
+ };
202
+ //#endregion
203
+ Object.defineProperty(exports, "GrantClient", {
204
+ enumerable: true,
205
+ get: function() {
206
+ return GrantClient;
207
+ }
208
+ });
209
+ Object.defineProperty(exports, "_defineProperty", {
210
+ enumerable: true,
211
+ get: function() {
212
+ return _defineProperty;
213
+ }
214
+ });
215
+ Object.defineProperty(exports, "extractBearerToken", {
216
+ enumerable: true,
217
+ get: function() {
218
+ return extractBearerToken;
219
+ }
220
+ });
221
+ Object.defineProperty(exports, "extractTokenFromRequest", {
222
+ enumerable: true,
223
+ get: function() {
224
+ return extractTokenFromRequest;
225
+ }
226
+ });
227
+
228
+ //# sourceMappingURL=grant-client-f6IKMyk5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant-client-f6IKMyk5.cjs","names":[],"sources":["../src/utils/token-extractor.ts","../src/grant-client.ts"],"sourcesContent":["import type { GrantServerConfig } from '../types';\n\n/**\n * Extract Bearer token from Authorization header\n */\nexport function extractBearerToken(authHeader: string | undefined): string | null {\n if (!authHeader) return null;\n\n const parts = authHeader.trim().split(/\\s+/);\n if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {\n return null;\n }\n\n return parts[1];\n}\n\n/**\n * Parse cookie header string into key-value pairs\n */\nfunction parseCookieHeader(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {};\n\n cookieHeader.split(';').forEach((cookie) => {\n const [name, ...rest] = cookie.trim().split('=');\n if (name && rest.length > 0) {\n cookies[name.trim()] = rest.join('=').trim();\n }\n });\n\n return cookies;\n}\n\n/**\n * Extract token from cookies\n */\nexport function extractTokenFromCookies(\n cookieHeader: string | undefined,\n cookieName: string\n): string | null {\n if (!cookieHeader) return null;\n\n const cookies = parseCookieHeader(cookieHeader);\n return cookies[cookieName] || null;\n}\n\n/**\n * Extract token from Express request\n */\nexport async function extractTokenFromRequest(\n request: unknown,\n config: GrantServerConfig\n): Promise<string | null> {\n // Use custom token resolver if provided\n if (config.getToken) {\n const token = await config.getToken(request);\n if (token) return token;\n }\n\n // Try to extract from request (Express-style or Web API / NextRequest)\n const req = request as {\n headers?:\n | { authorization?: string; cookie?: string }\n | { get: (name: string) => string | null };\n cookies?: Record<string, string>;\n };\n\n if (!req?.headers) return null;\n\n // Web API Headers (NextRequest, Request): headers.get(name)\n const headersGet = (req.headers as { get?: (name: string) => string | null }).get;\n if (typeof headersGet === 'function') {\n const authHeader = headersGet.call(req.headers, 'authorization');\n const bearerToken = extractBearerToken(authHeader ?? undefined);\n if (bearerToken) return bearerToken;\n const cookieName = config.cookieName || 'grant-access-token';\n const cookieHeader = headersGet.call(req.headers, 'cookie');\n if (cookieHeader) {\n const cookieToken = extractTokenFromCookies(cookieHeader, cookieName);\n if (cookieToken) return cookieToken;\n }\n return null;\n }\n\n // Express-style: headers.authorization, headers.cookie\n const plainHeaders = req.headers as { authorization?: string; cookie?: string };\n const authHeader = plainHeaders.authorization;\n const bearerToken = extractBearerToken(authHeader);\n if (bearerToken) return bearerToken;\n\n const cookieName = config.cookieName || 'grant-access-token';\n\n if (req.cookies?.[cookieName]) {\n return req.cookies[cookieName];\n }\n\n const cookieHeader = plainHeaders.cookie;\n if (cookieHeader) {\n const cookieToken = extractTokenFromCookies(cookieHeader, cookieName);\n if (cookieToken) return cookieToken;\n }\n\n return null;\n}\n","import type { AuthorizationResult, GrantServerConfig, PermissionCheckOptions } from './types';\nimport { extractTokenFromRequest } from './utils/token-extractor';\n\n/**\n * Grant Client for server-side applications\n *\n * Makes HTTP requests to the Grant API to check permissions.\n * Uses API-key exchanged tokens (scope in JWT claims); token-based and cookie-based extraction supported.\n * No caching - server-side caching is handled by grant-api.\n */\nexport class GrantClient {\n public readonly config: Required<Pick<GrantServerConfig, 'apiUrl'>> & GrantServerConfig;\n\n constructor(config: GrantServerConfig) {\n this.config = config;\n }\n\n // ============================================================================\n // Public API - Permission Checks\n // ============================================================================\n\n /**\n * Check if the current user has a specific permission\n *\n * @example\n * ```ts\n * const canEdit = await grant.isGranted('document', 'update', { scope });\n * if (canEdit) {\n * // Proceed with operation\n * }\n * ```\n */\n async isGranted(\n resource: string,\n action: string,\n options?: PermissionCheckOptions,\n request?: unknown\n ): Promise<boolean> {\n const result = await this.isAuthorized(resource, action, options, request);\n return result.authorized;\n }\n\n /**\n * Check authorization with full result details\n *\n * @example\n * ```ts\n * const result = await grant.isAuthorized('document', 'update', { scope }, request);\n * if (!result.authorized) {\n * console.log('Denied:', result.reason);\n * }\n * ```\n */\n async isAuthorized(\n resource: string,\n action: string,\n options?: PermissionCheckOptions,\n request?: unknown\n ): Promise<AuthorizationResult> {\n try {\n // API expects: { permission: { resource, action }, context: { resource?: any }, scope?: { tenant, id } }\n const response = await this.fetchWithAuth(\n '/api/auth/is-authorized',\n {\n method: 'POST',\n body: JSON.stringify({\n permission: {\n resource,\n action,\n },\n context: {\n resource: options?.context?.resource || null,\n },\n }),\n },\n request\n );\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n return {\n authorized: false,\n reason: error.message || `API error: ${response.status}`,\n };\n }\n\n const json = await response.json();\n // API returns { success: true, data: { authorized, ... } }\n const result: AuthorizationResult = json.data ?? json;\n return result;\n } catch (error) {\n return {\n authorized: false,\n reason: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n /**\n * Extract token from a request object\n * Useful for middleware that needs to extract token before making authorization calls\n */\n async getTokenFromRequest(request: unknown): Promise<string | null> {\n return extractTokenFromRequest(request, this.config);\n }\n\n // ============================================================================\n // Private - HTTP & Authentication\n // ============================================================================\n\n /**\n * Make an authenticated fetch request (token from request; no refresh).\n */\n private async fetchWithAuth(\n url: string,\n init?: RequestInit,\n request?: unknown\n ): Promise<Response> {\n return this.doFetch(url, init, request);\n }\n\n /**\n * Perform the actual fetch request\n */\n private async doFetch(url: string, init?: RequestInit, request?: unknown): Promise<Response> {\n const fetchFn = this.config.fetch ?? globalThis.fetch;\n const fullUrl = url.startsWith('http') ? url : `${this.config.apiUrl}${url}`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(init?.headers as Record<string, string>),\n };\n\n // Add authorization header if token is available\n if (request) {\n const token = await extractTokenFromRequest(request, this.config);\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // Forward cookies if using cookie-based auth\n const req = request as { headers?: { cookie?: string } };\n if (req.headers?.cookie) {\n headers['Cookie'] = req.headers.cookie;\n }\n }\n\n return fetchFn(fullUrl, {\n ...init,\n headers,\n // Include cookies for same-origin requests (supports cookie-based auth)\n credentials: this.config.credentials ?? 'include',\n });\n }\n}\n"],"mappings":"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"}