@grantjs/server 1.0.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/LICENSE +21 -0
  2. package/README.md +443 -0
  3. package/dist/debug-C8ibbrE5.js +9 -0
  4. package/dist/debug-C8ibbrE5.js.map +1 -0
  5. package/dist/debug-Crb5gNDM.cjs +8 -0
  6. package/dist/debug-Crb5gNDM.cjs.map +1 -0
  7. package/dist/errors.d.ts +38 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/express/index.d.ts +6 -0
  10. package/dist/express/index.d.ts.map +1 -0
  11. package/dist/express/middleware.d.ts +38 -0
  12. package/dist/express/middleware.d.ts.map +1 -0
  13. package/dist/express.cjs +99 -0
  14. package/dist/express.cjs.map +1 -0
  15. package/dist/express.mjs +101 -0
  16. package/dist/express.mjs.map +1 -0
  17. package/dist/fastify/index.d.ts +6 -0
  18. package/dist/fastify/index.d.ts.map +1 -0
  19. package/dist/fastify/plugin.d.ts +74 -0
  20. package/dist/fastify/plugin.d.ts.map +1 -0
  21. package/dist/fastify.cjs +103 -0
  22. package/dist/fastify.cjs.map +1 -0
  23. package/dist/fastify.mjs +104 -0
  24. package/dist/fastify.mjs.map +1 -0
  25. package/dist/grant-client-D1LZI2f4.js +180 -0
  26. package/dist/grant-client-D1LZI2f4.js.map +1 -0
  27. package/dist/grant-client-DywfJN5P.cjs +179 -0
  28. package/dist/grant-client-DywfJN5P.cjs.map +1 -0
  29. package/dist/grant-client.d.ts +50 -0
  30. package/dist/grant-client.d.ts.map +1 -0
  31. package/dist/index.cjs +50 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.mjs +50 -0
  36. package/dist/index.mjs.map +1 -0
  37. package/dist/nest/grant.decorator.d.ts +38 -0
  38. package/dist/nest/grant.decorator.d.ts.map +1 -0
  39. package/dist/nest/grant.guard.d.ts +29 -0
  40. package/dist/nest/grant.guard.d.ts.map +1 -0
  41. package/dist/nest/grant.module.d.ts +28 -0
  42. package/dist/nest/grant.module.d.ts.map +1 -0
  43. package/dist/nest/index.d.ts +8 -0
  44. package/dist/nest/index.d.ts.map +1 -0
  45. package/dist/nest.cjs +154 -0
  46. package/dist/nest.cjs.map +1 -0
  47. package/dist/nest.mjs +156 -0
  48. package/dist/nest.mjs.map +1 -0
  49. package/dist/next/index.d.ts +6 -0
  50. package/dist/next/index.d.ts.map +1 -0
  51. package/dist/next/with-grant.d.ts +57 -0
  52. package/dist/next/with-grant.d.ts.map +1 -0
  53. package/dist/next.cjs +62 -0
  54. package/dist/next.cjs.map +1 -0
  55. package/dist/next.mjs +63 -0
  56. package/dist/next.mjs.map +1 -0
  57. package/dist/types.d.ts +92 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/utils/debug.d.ts +9 -0
  60. package/dist/utils/debug.d.ts.map +1 -0
  61. package/dist/utils/token-extractor.d.ts +14 -0
  62. package/dist/utils/token-extractor.d.ts.map +1 -0
  63. package/package.json +109 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alejandro Heredia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,443 @@
1
+ # @grantjs/server
2
+
3
+ Server SDK for Grant authorization platform. Provides middleware and guards for **Express**, **Fastify**, **NestJS**, and **Next.js** applications.
4
+
5
+ **Documentation:** [Server SDK](https://github.com/logusgraphics/grant/blob/main/docs/integration/server-sdk.md) in the official docs.
6
+
7
+ ## Features
8
+
9
+ - **REST-based API** – Uses native `fetch`, no GraphQL client required
10
+ - **Framework integrations** – Express (`grant()`), Fastify (`grant()`), NestJS (`@Grant` + `GrantGuard`), Next.js (`withGrant()`)
11
+ - **Token extraction** – Supports Authorization header and cookies
12
+ - **Resource resolvers** – Optional resource resolution for condition evaluation
13
+ - **JWT-based scope** – Scope is taken from token claims (API-key / client-secret flows); session-style scope from request is not used
14
+ - **TypeScript** – Full type safety with types from `@grantjs/schema`
15
+ - **Generic** – Works with any permission model (uses plain strings for resource/action)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @grantjs/server
21
+ # or
22
+ pnpm add @grantjs/server
23
+ # or
24
+ yarn add @grantjs/server
25
+ ```
26
+
27
+ ## Examples
28
+
29
+ Minimal runnable apps for each framework live in **`examples/`** and share the same CRUD surface (documents: GET, POST, PUT, PATCH, DELETE). From the repo root:
30
+
31
+ ```bash
32
+ pnpm install
33
+ pnpm --filter @grantjs/server build
34
+ cd packages/@grantjs/server/examples/express # or fastify, nextjs, nestjs
35
+ cp .env.example .env # set GRANT_API_URL and optionally GRANT_TOKEN
36
+ pnpm start
37
+ ```
38
+
39
+ | Example | Framework | Usage |
40
+ | --------- | --------- | ---------------------------------------------------------------- |
41
+ | `express` | Express | `grant(client, { resource, action })` middleware |
42
+ | `fastify` | Fastify | `grant(client, { resource, action })` preHandler |
43
+ | `nextjs` | Next.js | `withGrant(client, { resource, action }, handler)` on API routes |
44
+ | `nestjs` | NestJS | `@Grant(resource, action)` + `GrantGuard`, `GrantModule` |
45
+
46
+ See **[examples/README.md](./examples/README.md)** for prerequisites, routes, and curl commands.
47
+
48
+ ## Quick Start
49
+
50
+ ### 1. Create the Client
51
+
52
+ ```typescript
53
+ import { GrantClient } from '@grantjs/server';
54
+
55
+ // Basic configuration (uses Authorization header or cookies)
56
+ const grantClient = new GrantClient({
57
+ apiUrl: 'https://api.grant.com',
58
+ });
59
+
60
+ // With cookie-based auth
61
+ const grantClient = new GrantClient({
62
+ apiUrl: 'https://api.grant.com',
63
+ cookieName: 'grant-access-token', // Default: 'grant-access-token'
64
+ });
65
+
66
+ // With custom token extraction
67
+ const grantClient = new GrantClient({
68
+ apiUrl: 'https://api.grant.com',
69
+ getToken: (request) => {
70
+ // Custom logic to extract token from request
71
+ const req = request as { headers?: { 'x-api-key'?: string } };
72
+ return req.headers?.['x-api-key'] || null;
73
+ },
74
+ });
75
+ ```
76
+
77
+ ### 2. Express Middleware
78
+
79
+ ```typescript
80
+ import express from 'express';
81
+ import { grant } from '@grantjs/server/express';
82
+
83
+ const app = express();
84
+ const grantClient = new GrantClient({ apiUrl: 'https://api.grant.com' });
85
+
86
+ // Basic usage
87
+ app.get(
88
+ '/organizations',
89
+ grant(grantClient, {
90
+ resource: 'Organization',
91
+ action: 'Query',
92
+ }),
93
+ async (req, res) => {
94
+ // User is authorized, proceed with handler
95
+ res.json({ organizations: [] });
96
+ }
97
+ );
98
+
99
+ // With resource resolver for condition evaluation
100
+ app.patch(
101
+ '/projects/:id',
102
+ grant(grantClient, {
103
+ resource: 'Project',
104
+ action: 'Update',
105
+ resourceResolver: async ({ resourceSlug, request }) => {
106
+ // Resolve the project resource for condition evaluation
107
+ const projectId = (request as any).params.id;
108
+ const project = await getProjectById(projectId);
109
+ return project ? { id: project.id, ownerId: project.ownerId } : null;
110
+ },
111
+ }),
112
+ async (req, res) => {
113
+ // User is authorized, proceed with handler
114
+ res.json({ success: true });
115
+ }
116
+ );
117
+ ```
118
+
119
+ ### 3. Fastify Plugin
120
+
121
+ ```typescript
122
+ import Fastify from 'fastify';
123
+ import { grantPlugin, grant } from '@grantjs/server/fastify';
124
+ import { GrantClient } from '@grantjs/server';
125
+
126
+ const fastify = Fastify();
127
+
128
+ // Register the plugin (decorates fastify.grant)
129
+ await fastify.register(grantPlugin, {
130
+ apiUrl: 'https://api.grant.com',
131
+ cookieName: 'grant-access-token',
132
+ });
133
+
134
+ // Use preHandler hook
135
+ fastify.get(
136
+ '/organizations',
137
+ {
138
+ preHandler: grant(fastify.grant, {
139
+ resource: 'Organization',
140
+ action: 'Query',
141
+ }),
142
+ },
143
+ async (request, reply) => {
144
+ return { organizations: [] };
145
+ }
146
+ );
147
+
148
+ // With resource resolver
149
+ fastify.patch(
150
+ '/projects/:id',
151
+ {
152
+ preHandler: grant(fastify.grant, {
153
+ resource: 'Project',
154
+ action: 'Update',
155
+ resourceResolver: async ({ resourceSlug, request }) => {
156
+ const projectId = (request.params as { id: string }).id;
157
+ const project = await getProjectById(projectId);
158
+ return project ? { id: project.id, ownerId: project.ownerId } : null;
159
+ },
160
+ }),
161
+ },
162
+ async (request, reply) => {
163
+ return { success: true };
164
+ }
165
+ );
166
+ ```
167
+
168
+ ### 4. Next.js (App Router)
169
+
170
+ ```typescript
171
+ // app/api/documents/route.ts
172
+ import { NextResponse } from 'next/server';
173
+ import { withGrant } from '@grantjs/server/next';
174
+ import { GrantClient } from '@grantjs/server';
175
+
176
+ const grantClient = new GrantClient({ apiUrl: process.env.GRANT_API_URL! });
177
+
178
+ export const GET = withGrant(grantClient, { resource: 'Document', action: 'Query' }, async () =>
179
+ NextResponse.json({ data: [] })
180
+ );
181
+
182
+ export const POST = withGrant(
183
+ grantClient,
184
+ { resource: 'Document', action: 'Create' },
185
+ async (request) => {
186
+ const body = await request.json();
187
+ return NextResponse.json({ data: { title: body?.title ?? 'Untitled' } }, { status: 201 });
188
+ }
189
+ );
190
+ ```
191
+
192
+ ### 5. NestJS
193
+
194
+ ```typescript
195
+ // app.module.ts
196
+ import { GrantModule } from '@grantjs/server/nest';
197
+
198
+ @Module({
199
+ imports: [
200
+ GrantModule.forRoot({
201
+ apiUrl: process.env.GRANT_API_URL!,
202
+ getToken: (req: any) => req.headers?.authorization?.replace?.('Bearer ', '') ?? null,
203
+ }),
204
+ ],
205
+ })
206
+ export class AppModule {}
207
+
208
+ // documents.controller.ts
209
+ import { Grant, GrantGuard } from '@grantjs/server/nest';
210
+
211
+ @Controller('documents')
212
+ export class DocumentsController {
213
+ @Get()
214
+ @Grant('Document', 'Query')
215
+ @UseGuards(GrantGuard)
216
+ list() {
217
+ return { data: [] };
218
+ }
219
+
220
+ @Post()
221
+ @Grant('Document', 'Create')
222
+ @UseGuards(GrantGuard)
223
+ create(@Body() body: { title?: string }) {
224
+ return { data: { title: body?.title ?? 'Untitled' } };
225
+ }
226
+ }
227
+ ```
228
+
229
+ Register `GrantGuard` as a provider in your feature module (e.g. `providers: [GrantGuard]`) so Nest injects `GrantClient`. See `examples/nestjs` for a full app.
230
+
231
+ ## API Reference
232
+
233
+ ### GrantClient
234
+
235
+ ```typescript
236
+ const grantClient = new GrantClient(config: GrantServerConfig);
237
+ ```
238
+
239
+ #### Configuration
240
+
241
+ ```typescript
242
+ interface GrantServerConfig {
243
+ // Required
244
+ apiUrl: string;
245
+
246
+ // Optional
247
+ cookieName?: string; // Default: 'grant-access-token'
248
+ getToken?: (request: unknown) => string | null | Promise<string | null>;
249
+ fetch?: typeof fetch;
250
+ credentials?: RequestCredentials;
251
+ }
252
+ ```
253
+
254
+ #### Methods
255
+
256
+ ```typescript
257
+ // Permission checks
258
+ grantClient.isGranted(resource, action, options?, request?): Promise<boolean>
259
+ grantClient.isAuthorized(resource, action, options?, request?): Promise<AuthorizationResult>
260
+
261
+ // Token extraction
262
+ grantClient.getTokenFromRequest(request): Promise<string | null>
263
+ ```
264
+
265
+ ### Express Middleware
266
+
267
+ #### `grant(client, options)`
268
+
269
+ Creates Express middleware that checks authorization before proceeding.
270
+
271
+ **Options:**
272
+
273
+ ```typescript
274
+ interface GrantOptions {
275
+ resource: string; // Resource slug (e.g., "Organization", "Project", "Document")
276
+ action: string; // Action name (e.g., "Query", "Create", "Update", "Delete")
277
+ resourceResolver?: ResourceResolver; // Resource resolution for conditions
278
+ }
279
+ ```
280
+
281
+ **Behavior:**
282
+
283
+ - Returns `401 Unauthorized` if no token is found
284
+ - Returns `404 Not Found` if resource resolver returns null
285
+ - Returns `403 Forbidden` if user lacks permission
286
+ - Calls `next()` if authorized
287
+ - Attaches `authorization` result to `req.authorization` for downstream use
288
+ - Scope for authorization is taken from the JWT (e.g. API-key / client-secret tokens); it is not extracted from the request
289
+
290
+ ### Fastify Plugin
291
+
292
+ #### `grantPlugin(fastify, options)`
293
+
294
+ Registers a Fastify plugin that decorates the instance with `fastify.grant` (GrantClient).
295
+
296
+ **Options:**
297
+
298
+ Same as `GrantServerConfig` - see [GrantClient Configuration](#grantclient) above.
299
+
300
+ **Usage:**
301
+
302
+ ```typescript
303
+ await fastify.register(grantPlugin, {
304
+ apiUrl: 'https://api.grant.com',
305
+ cookieName: 'grant-access-token',
306
+ });
307
+
308
+ // Now fastify.grant is available
309
+ const canEdit = await fastify.grant.isGranted('Document', 'Update', undefined, request);
310
+ ```
311
+
312
+ #### `grant(client, options)`
313
+
314
+ Creates a Fastify `preHandler` hook that checks authorization before proceeding.
315
+
316
+ **Options:**
317
+
318
+ Same as Express `GrantOptions` - see above.
319
+
320
+ **Behavior:**
321
+
322
+ - Returns `401 Unauthorized` if no token is found
323
+ - Returns `404 Not Found` if resource resolver returns null
324
+ - Returns `403 Forbidden` if user lacks permission
325
+ - Attaches `authorization` result to `request.authorization` for downstream use
326
+ - Scope for authorization is taken from the JWT; it is not extracted from the request
327
+
328
+ ### Next.js (App Router)
329
+
330
+ #### `withGrant(client, options, handler)`
331
+
332
+ Wraps an App Router route handler with Grant authorization. Use for `GET`, `POST`, `PUT`, `PATCH`, `DELETE` in `app/api/.../route.ts`.
333
+
334
+ **Options:** Same as Express `GrantOptions` (resource, action, optional resourceResolver).
335
+
336
+ **Behavior:** Returns `401` / `404` / `403` responses on failure; calls your handler with `(request, { authorization })` on success. Compatible with Next.js 13–16.
337
+
338
+ ### NestJS
339
+
340
+ #### `GrantModule.forRoot(config)`
341
+
342
+ Global module that provides `GrantClient` for injection. Import in `AppModule`.
343
+
344
+ #### `@Grant(resource, action)` / `Grant(resource, action)`
345
+
346
+ Decorator that sets resource/action metadata for `GrantGuard`. Use with `@UseGuards(GrantGuard)` on controller methods.
347
+
348
+ #### `GrantGuard`
349
+
350
+ Guard that reads options from `@Grant()` metadata (or explicit constructor options with optional `resourceResolver`). Register as a provider (e.g. `providers: [GrantGuard]`) so Nest injects `GrantClient` and `Reflector`. Exports `GRANT_CLIENT` and `GRANT_OPTIONS_KEY` for advanced use.
351
+
352
+ ## Token Extraction
353
+
354
+ The client supports multiple token extraction methods (in order of precedence):
355
+
356
+ 1. **Custom `getToken` function** (if provided)
357
+ 2. **Authorization header**: `Authorization: Bearer <token>`
358
+ 3. **Cookies**: Cookie named by `cookieName` config (default: `grant-access-token`)
359
+
360
+ ## Resource Resolvers
361
+
362
+ Resource resolvers are optional functions that resolve resource data for condition evaluation. Scope is not passed from the middleware (it is taken from the JWT on the API side).
363
+
364
+ ```typescript
365
+ const resourceResolver: ResourceResolver = async ({ resourceSlug, request }) => {
366
+ // Fetch resource from database
367
+ const resource = await getResource(resourceSlug, request);
368
+
369
+ // Return resource data for condition evaluation
370
+ // e.g., { id: '...', ownerId: '...', status: 'active' }
371
+ return resource ? { id: resource.id, ownerId: resource.ownerId } : null;
372
+ };
373
+ ```
374
+
375
+ If the resolver returns `null`, the middleware returns `404 Not Found`.
376
+
377
+ ## Development / Debug
378
+
379
+ Set **`DEBUG_GRANT=1`** in your environment (e.g. in `.env`) to enable request/outcome logs for all integrations (Express, Fastify, Next, Nest). Logs include resource, action, and authorized/denied with reason.
380
+
381
+ ## Error Handling
382
+
383
+ The package exports error classes for graceful error handling:
384
+
385
+ ```typescript
386
+ import {
387
+ AuthenticationError, // 401
388
+ AuthorizationError, // 403
389
+ BadRequestError, // 400
390
+ NotFoundError, // 404
391
+ } from '@grantjs/server';
392
+
393
+ try {
394
+ await grantClient.isAuthorized('resource', 'action', undefined, request);
395
+ } catch (error) {
396
+ if (error instanceof AuthenticationError) {
397
+ // Handle 401
398
+ } else if (error instanceof AuthorizationError) {
399
+ // Handle 403
400
+ }
401
+ }
402
+ ```
403
+
404
+ ## TypeScript
405
+
406
+ Full type definitions are included:
407
+
408
+ ```typescript
409
+ import type {
410
+ GrantServerConfig,
411
+ AuthorizationResult,
412
+ PermissionCheckOptions,
413
+ Scope,
414
+ ResourceResolver,
415
+ } from '@grantjs/server';
416
+
417
+ import { Tenant } from '@grantjs/schema';
418
+ ```
419
+
420
+ ## Comparison with @grantjs/client
421
+
422
+ The server package (`@grantjs/server`) is designed for **server-side** Node.js applications, while the client package (`@grantjs/client`) is for **browser** applications.
423
+
424
+ **Key Differences:**
425
+
426
+ | Feature | @grantjs/server | @grantjs/client |
427
+ | --------------------- | ------------------------------------------------------------------ | --------------------------- |
428
+ | **Target** | Node.js servers | Browser apps |
429
+ | **Caching** | No (handled by API) | Yes (5min TTL) |
430
+ | **Token Source** | Request object | Callback functions |
431
+ | **Framework Support** | Express, Fastify, NestJS, Next.js | React, Vue, Svelte, Angular |
432
+ | **API** | `grant()` (Express/Fastify), `withGrant()` (Next), `@Grant` (Nest) | `useGrant()` hook |
433
+ | **Error Handling** | HTTP status codes | Boolean returns |
434
+ | **Resource/Action** | Plain strings (generic) | Plain strings (generic) |
435
+
436
+ **When to Use:**
437
+
438
+ - **@grantjs/server**: Protecting API routes, GraphQL resolvers, server-side middleware
439
+ - **@grantjs/client**: Conditional UI rendering, client-side permission checks
440
+
441
+ ## License
442
+
443
+ MIT
@@ -0,0 +1,9 @@
1
+ const DEBUG_GRANT_ENV = "DEBUG_GRANT";
2
+ function debugGrant(integration, data) {
3
+ if (process.env[DEBUG_GRANT_ENV] !== "1") return;
4
+ console.debug(`[Grant ${integration}]`, data);
5
+ }
6
+ export {
7
+ debugGrant as d
8
+ };
9
+ //# sourceMappingURL=debug-C8ibbrE5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-C8ibbrE5.js","sources":["../src/utils/debug.ts"],"sourcesContent":["const DEBUG_GRANT_ENV = 'DEBUG_GRANT';\n\n/** Whether Grant debug logging is enabled (set DEBUG_GRANT=1 in development). */\nexport function isDebugGrant(): boolean {\n return process.env[DEBUG_GRANT_ENV] === '1';\n}\n\n/**\n * Log Grant integration debug info when DEBUG_GRANT=1.\n * Used by Express middleware, Fastify grant hook, Next withGrant, and Nest GrantGuard.\n * Set DEBUG_GRANT=1 in .env or the environment to see resource, action, and outcome in development.\n */\nexport function debugGrant(integration: string, data: Record<string, unknown>): void {\n if (process.env[DEBUG_GRANT_ENV] !== '1') return;\n console.debug(`[Grant ${integration}]`, data);\n}\n"],"names":[],"mappings":"AAAA,MAAM,kBAAkB;AAYjB,SAAS,WAAW,aAAqB,MAAqC;AACnF,MAAI,QAAQ,IAAI,eAAe,MAAM,IAAK;AAC1C,UAAQ,MAAM,UAAU,WAAW,KAAK,IAAI;AAC9C;"}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ const DEBUG_GRANT_ENV = "DEBUG_GRANT";
3
+ function debugGrant(integration, data) {
4
+ if (process.env[DEBUG_GRANT_ENV] !== "1") return;
5
+ console.debug(`[Grant ${integration}]`, data);
6
+ }
7
+ exports.debugGrant = debugGrant;
8
+ //# sourceMappingURL=debug-Crb5gNDM.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-Crb5gNDM.cjs","sources":["../src/utils/debug.ts"],"sourcesContent":["const DEBUG_GRANT_ENV = 'DEBUG_GRANT';\n\n/** Whether Grant debug logging is enabled (set DEBUG_GRANT=1 in development). */\nexport function isDebugGrant(): boolean {\n return process.env[DEBUG_GRANT_ENV] === '1';\n}\n\n/**\n * Log Grant integration debug info when DEBUG_GRANT=1.\n * Used by Express middleware, Fastify grant hook, Next withGrant, and Nest GrantGuard.\n * Set DEBUG_GRANT=1 in .env or the environment to see resource, action, and outcome in development.\n */\nexport function debugGrant(integration: string, data: Record<string, unknown>): void {\n if (process.env[DEBUG_GRANT_ENV] !== '1') return;\n console.debug(`[Grant ${integration}]`, data);\n}\n"],"names":[],"mappings":";AAAA,MAAM,kBAAkB;AAYjB,SAAS,WAAW,aAAqB,MAAqC;AACnF,MAAI,QAAQ,IAAI,eAAe,MAAM,IAAK;AAC1C,UAAQ,MAAM,UAAU,WAAW,KAAK,IAAI;AAC9C;;"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Base error class for Grant server errors
3
+ */
4
+ export declare class GrantServerError extends Error {
5
+ readonly code: string;
6
+ readonly statusCode: number;
7
+ constructor(message: string, code: string, statusCode?: number);
8
+ }
9
+ /**
10
+ * Authentication error (401)
11
+ * Thrown when user is not authenticated
12
+ */
13
+ export declare class AuthenticationError extends GrantServerError {
14
+ constructor(message?: string, code?: string);
15
+ }
16
+ /**
17
+ * Authorization error (403)
18
+ * Thrown when user is authenticated but lacks required permission
19
+ */
20
+ export declare class AuthorizationError extends GrantServerError {
21
+ readonly reason?: string | undefined;
22
+ constructor(message?: string, code?: string, reason?: string | undefined);
23
+ }
24
+ /**
25
+ * Bad request error (400)
26
+ * Thrown when request is malformed or missing required data
27
+ */
28
+ export declare class BadRequestError extends GrantServerError {
29
+ constructor(message?: string, code?: string);
30
+ }
31
+ /**
32
+ * Not found error (404)
33
+ * Thrown when resource is not found
34
+ */
35
+ export declare class NotFoundError extends GrantServerError {
36
+ constructor(message?: string, code?: string);
37
+ }
38
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,IAAI,EAAE,MAAM;aACZ,UAAU,EAAE,MAAM;gBAFlC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAAY;CAM3C;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,gBAAgB;gBAC3C,OAAO,GAAE,MAAuB,EAAE,IAAI,GAAE,MAA0B;CAK/E;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,gBAAgB;aAIpC,MAAM,CAAC,EAAE,MAAM;gBAF/B,OAAO,GAAE,MAAoB,EAC7B,IAAI,GAAE,MAAoB,EACV,MAAM,CAAC,EAAE,MAAM,YAAA;CAMlC;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,gBAAgB;gBACvC,OAAO,GAAE,MAAsB,EAAE,IAAI,GAAE,MAAsB;CAK1E;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,gBAAgB;gBACrC,OAAO,GAAE,MAAoB,EAAE,IAAI,GAAE,MAAoB;CAKtE"}
@@ -0,0 +1,6 @@
1
+ export { grant } from './middleware';
2
+ export type { GrantOptions, AuthorizedRequest } from './middleware';
3
+ export type { GrantServerConfig, AuthorizationResult, PermissionCheckOptions, Scope, ResourceResolver, } from '../types';
4
+ export { GrantServerError, AuthenticationError, AuthorizationError, BadRequestError, NotFoundError, } from '../errors';
5
+ export { GrantClient } from '../grant-client';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/express/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGpE,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,EACL,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { GrantClient } from '../grant-client';
2
+ import { ResourceResolver, AuthorizationResult } from '../types';
3
+ import { Request, Response, NextFunction } from 'express';
4
+ /**
5
+ * Extended Express Request with authorization result
6
+ */
7
+ export interface AuthorizedRequest extends Request {
8
+ authorization?: AuthorizationResult;
9
+ }
10
+ /**
11
+ * Options for Express middleware
12
+ */
13
+ export interface GrantOptions {
14
+ /** The resource slug to check (e.g., "Organization", "Project", "Document") */
15
+ resource: string;
16
+ /** The action to check (e.g., "Query", "Create", "Update", "Delete") */
17
+ action: string;
18
+ /** Custom resource resolver for condition evaluation */
19
+ resourceResolver?: ResourceResolver;
20
+ }
21
+ /**
22
+ * Create Express middleware to check if user is granted permission
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { grant } from '@grantjs/server/express';
27
+ * import { GrantClient } from '@grantjs/server';
28
+ *
29
+ * const grantClient = new GrantClient({ apiUrl: 'https://api.grant.com' });
30
+ *
31
+ * router.get('/organizations', grant(grantClient, {
32
+ * resource: 'Organization',
33
+ * action: 'Query',
34
+ * }), handler);
35
+ * ```
36
+ */
37
+ export declare function grant(client: GrantClient, options: GrantOptions): (req: AuthorizedRequest, res: Response, next: NextFunction) => Promise<void>;
38
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/express/middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAI9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO;IAChD,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,KAAK,CACnB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,YAAY,GACpB,CAAC,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAyG9E"}
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const index = require("./index.cjs");
4
+ const debug = require("./debug-Crb5gNDM.cjs");
5
+ const grantClient = require("./grant-client-DywfJN5P.cjs");
6
+ function grant(client, options) {
7
+ return async (req, res, next) => {
8
+ try {
9
+ debug.debugGrant("Express", { resource: options.resource, action: options.action });
10
+ const token = await grantClient.extractTokenFromRequest(req, client.config);
11
+ if (!token) {
12
+ res.status(401).json({
13
+ error: "Unauthorized",
14
+ code: "UNAUTHENTICATED"
15
+ });
16
+ return;
17
+ }
18
+ let resolvedResource = null;
19
+ if (options.resourceResolver) {
20
+ resolvedResource = await options.resourceResolver({
21
+ resourceSlug: options.resource,
22
+ request: req
23
+ });
24
+ if (!resolvedResource) {
25
+ res.status(404).json({
26
+ error: "Resource not found",
27
+ code: "NOT_FOUND"
28
+ });
29
+ return;
30
+ }
31
+ }
32
+ const result = await client.isAuthorized(
33
+ options.resource,
34
+ options.action,
35
+ {
36
+ context: {
37
+ resource: resolvedResource || void 0
38
+ }
39
+ },
40
+ req
41
+ );
42
+ debug.debugGrant("Express", {
43
+ resource: options.resource,
44
+ action: options.action,
45
+ authorized: result.authorized,
46
+ ...result.authorized ? {} : { reason: result.reason }
47
+ });
48
+ if (!result.authorized) {
49
+ res.status(403).json({
50
+ error: "Forbidden",
51
+ code: "FORBIDDEN",
52
+ reason: result.reason
53
+ });
54
+ return;
55
+ }
56
+ req.authorization = result;
57
+ next();
58
+ } catch (error) {
59
+ if (error instanceof index.AuthenticationError) {
60
+ res.status(401).json({
61
+ error: error.message,
62
+ code: error.code
63
+ });
64
+ return;
65
+ }
66
+ if (error instanceof index.AuthorizationError) {
67
+ res.status(403).json({
68
+ error: error.message,
69
+ code: error.code,
70
+ reason: error.reason
71
+ });
72
+ return;
73
+ }
74
+ if (error instanceof index.BadRequestError) {
75
+ res.status(400).json({
76
+ error: error.message,
77
+ code: error.code
78
+ });
79
+ return;
80
+ }
81
+ if (error instanceof index.NotFoundError) {
82
+ res.status(404).json({
83
+ error: error.message,
84
+ code: error.code
85
+ });
86
+ return;
87
+ }
88
+ next(error);
89
+ }
90
+ };
91
+ }
92
+ exports.AuthenticationError = index.AuthenticationError;
93
+ exports.AuthorizationError = index.AuthorizationError;
94
+ exports.BadRequestError = index.BadRequestError;
95
+ exports.GrantServerError = index.GrantServerError;
96
+ exports.NotFoundError = index.NotFoundError;
97
+ exports.GrantClient = grantClient.GrantClient;
98
+ exports.grant = grant;
99
+ //# sourceMappingURL=express.cjs.map