@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.
- package/README.md +1 -1
- package/dist/debug-DICR8lwQ.js +15 -0
- package/dist/debug-DICR8lwQ.js.map +1 -0
- package/dist/debug-DInHA4QB.cjs +20 -0
- package/dist/debug-DInHA4QB.cjs.map +1 -0
- package/dist/errors-BUeTn_3G.cjs +91 -0
- package/dist/errors-BUeTn_3G.cjs.map +1 -0
- package/dist/errors-CSC_Z_KP.js +62 -0
- package/dist/errors-CSC_Z_KP.js.map +1 -0
- package/dist/express/index.d.ts +3 -3
- package/dist/express/index.d.ts.map +1 -1
- package/dist/express/middleware.d.ts +2 -2
- package/dist/express/middleware.d.ts.map +1 -1
- package/dist/express.cjs +106 -95
- package/dist/express.cjs.map +1 -1
- package/dist/express.mjs +101 -99
- package/dist/express.mjs.map +1 -1
- package/dist/fastify/index.d.ts +4 -4
- package/dist/fastify/index.d.ts.map +1 -1
- package/dist/fastify/plugin.d.ts +2 -2
- package/dist/fastify/plugin.d.ts.map +1 -1
- package/dist/fastify.cjs +133 -97
- package/dist/fastify.cjs.map +1 -1
- package/dist/fastify.mjs +128 -101
- package/dist/fastify.mjs.map +1 -1
- package/dist/grant-client-f6IKMyk5.cjs +228 -0
- package/dist/grant-client-f6IKMyk5.cjs.map +1 -0
- package/dist/grant-client-t3KadxFo.js +205 -0
- package/dist/grant-client-t3KadxFo.js.map +1 -0
- package/dist/grant-client.d.ts +1 -1
- package/dist/grant-client.d.ts.map +1 -1
- package/dist/index.cjs +10 -49
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +3 -50
- package/dist/nest/grant.guard.d.ts +1 -1
- package/dist/nest/grant.guard.d.ts.map +1 -1
- package/dist/nest/grant.module.d.ts.map +1 -1
- package/dist/nest/index.d.ts +6 -6
- package/dist/nest/index.d.ts.map +1 -1
- package/dist/nest.cjs +163 -143
- package/dist/nest.cjs.map +1 -1
- package/dist/nest.mjs +150 -149
- package/dist/nest.mjs.map +1 -1
- package/dist/next/index.d.ts +4 -4
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/with-grant.d.ts +1 -1
- package/dist/next/with-grant.d.ts.map +1 -1
- package/dist/next.cjs +75 -58
- package/dist/next.cjs.map +1 -1
- package/dist/next.mjs +70 -60
- package/dist/next.mjs.map +1 -1
- package/package.json +17 -17
- package/dist/debug-C8ibbrE5.js +0 -9
- package/dist/debug-C8ibbrE5.js.map +0 -1
- package/dist/debug-Crb5gNDM.cjs +0 -8
- package/dist/debug-Crb5gNDM.cjs.map +0 -1
- package/dist/grant-client-D1LZI2f4.js +0 -180
- package/dist/grant-client-D1LZI2f4.js.map +0 -1
- package/dist/grant-client-DywfJN5P.cjs +0 -179
- package/dist/grant-client-DywfJN5P.cjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/nest.mjs
CHANGED
|
@@ -1,156 +1,157 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { i as extractTokenFromRequest, n as _defineProperty, t as GrantClient } from "./grant-client-t3KadxFo.js";
|
|
2
|
+
import { t as debugGrant } from "./debug-DICR8lwQ.js";
|
|
3
|
+
import { ForbiddenException, Global, Inject, Injectable, Module, NotFoundException, SetMetadata, UnauthorizedException } from "@nestjs/common";
|
|
4
|
+
//#region src/nest/grant.decorator.ts
|
|
5
|
+
/**
|
|
6
|
+
* Metadata key for Grant options (resource, action) on route handlers.
|
|
7
|
+
* Used by GrantGuard when options are not passed in the constructor.
|
|
8
|
+
*/
|
|
9
|
+
var GRANT_OPTIONS_KEY = "grant:options";
|
|
8
10
|
function Grant(resourceOrOptions, action) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
return SetMetadata(GRANT_OPTIONS_KEY, typeof resourceOrOptions === "string" && action !== void 0 ? {
|
|
12
|
+
resource: resourceOrOptions,
|
|
13
|
+
action
|
|
14
|
+
} : resourceOrOptions);
|
|
11
15
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
40
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
41
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
42
|
-
if (decorator = decorators[i])
|
|
43
|
-
result = decorator(result) || result;
|
|
44
|
-
return result;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
|
|
18
|
+
function __decorate(decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/nest/grant.module.ts
|
|
26
|
+
var _GrantModule;
|
|
27
|
+
var GRANT_CLIENT = "GrantClient";
|
|
28
|
+
var GrantModule = _GrantModule = class GrantModule {
|
|
29
|
+
static forRoot(config) {
|
|
30
|
+
const client = new GrantClient(config);
|
|
31
|
+
return {
|
|
32
|
+
module: _GrantModule,
|
|
33
|
+
providers: [{
|
|
34
|
+
provide: GrantClient,
|
|
35
|
+
useValue: client
|
|
36
|
+
}, {
|
|
37
|
+
provide: GRANT_CLIENT,
|
|
38
|
+
useValue: client
|
|
39
|
+
}],
|
|
40
|
+
exports: [GrantClient, GRANT_CLIENT]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
45
43
|
};
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
GrantModule = _GrantModule = __decorate([Global(), Module({})], GrantModule);
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region \0@oxc-project+runtime@0.127.0/helpers/decorateMetadata.js
|
|
47
|
+
function __decorateMetadata(k, v) {
|
|
48
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region \0@oxc-project+runtime@0.127.0/helpers/decorateParam.js
|
|
52
|
+
function __decorateParam(paramIndex, decorator) {
|
|
53
|
+
return function(target, key) {
|
|
54
|
+
decorator(target, key, paramIndex);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/nest/grant.guard.ts
|
|
59
|
+
var _ref;
|
|
60
|
+
/** Injection token for GrantGuard. Use with @UseGuards(GRANT_GUARD) so Nest resolves the guard from the container (with GrantClient injected). */
|
|
61
|
+
var GRANT_GUARD = "GrantGuard";
|
|
62
|
+
/**
|
|
63
|
+
* NestJS guard that checks Grant authorization before allowing access to a route.
|
|
64
|
+
*
|
|
65
|
+
* Use in one of two ways:
|
|
66
|
+
*
|
|
67
|
+
* 1. With decorator (resource/action from metadata):
|
|
68
|
+
* - Import GrantModule.forRoot() in AppModule. In a feature module, add
|
|
69
|
+
* `{ provide: GRANT_GUARD, useClass: GrantGuard }` and use @Grant + @UseGuards(GRANT_GUARD).
|
|
70
|
+
* - Using the GRANT_GUARD token ensures Nest resolves the guard from the container (GrantClient injected).
|
|
71
|
+
*
|
|
72
|
+
* 2. With explicit options (supports resourceResolver):
|
|
73
|
+
* - @UseGuards(new GrantGuard(grantClient, { resource: 'Document', action: 'Update', resourceResolver: async ({ request }) => ... }))
|
|
74
|
+
*
|
|
75
|
+
* On success, the authorization result is attached to the request as request.authorization.
|
|
76
|
+
* On failure, throws UnauthorizedException (401), ForbiddenException (403), or NotFoundException (404).
|
|
77
|
+
*/
|
|
48
78
|
function isGrantGuardOptions(x) {
|
|
49
|
-
|
|
79
|
+
return typeof x === "object" && x !== null && "resource" in x && "action" in x && typeof x.resource === "string" && typeof x.action === "string";
|
|
50
80
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
options.action,
|
|
119
|
-
{
|
|
120
|
-
context: {
|
|
121
|
-
resource: resolvedResource ?? void 0
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
request
|
|
125
|
-
);
|
|
126
|
-
debugGrant("Nest", {
|
|
127
|
-
resource: options.resource,
|
|
128
|
-
action: options.action,
|
|
129
|
-
authorized: result.authorized,
|
|
130
|
-
...result.authorized ? {} : { reason: result.reason }
|
|
131
|
-
});
|
|
132
|
-
if (!result.authorized) {
|
|
133
|
-
throw new ForbiddenException({
|
|
134
|
-
error: "Forbidden",
|
|
135
|
-
code: "FORBIDDEN",
|
|
136
|
-
reason: result.reason
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
request.authorization = result;
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
81
|
+
var GrantGuard = class GrantGuard {
|
|
82
|
+
constructor(client, reflectorOrOptions) {
|
|
83
|
+
_defineProperty(this, "explicitOptions", void 0);
|
|
84
|
+
_defineProperty(this, "reflector", void 0);
|
|
85
|
+
this.client = client;
|
|
86
|
+
if (reflectorOrOptions && isGrantGuardOptions(reflectorOrOptions)) this.explicitOptions = reflectorOrOptions;
|
|
87
|
+
else this.reflector = reflectorOrOptions;
|
|
88
|
+
}
|
|
89
|
+
getOptionsFromContext(context) {
|
|
90
|
+
const handler = context.getHandler();
|
|
91
|
+
const clazz = context.getClass();
|
|
92
|
+
if (this.reflector) {
|
|
93
|
+
const fromHandler = this.reflector.get(GRANT_OPTIONS_KEY, handler);
|
|
94
|
+
const fromClass = this.reflector.get(GRANT_OPTIONS_KEY, clazz);
|
|
95
|
+
if (fromHandler) return fromHandler;
|
|
96
|
+
if (fromClass) return fromClass;
|
|
97
|
+
}
|
|
98
|
+
if (typeof Reflect !== "undefined" && typeof Reflect.getMetadata === "function") {
|
|
99
|
+
const fromHandler = Reflect.getMetadata(GRANT_OPTIONS_KEY, handler);
|
|
100
|
+
const fromClass = Reflect.getMetadata(GRANT_OPTIONS_KEY, clazz);
|
|
101
|
+
if (fromHandler) return fromHandler;
|
|
102
|
+
if (fromClass) return fromClass;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async canActivate(context) {
|
|
106
|
+
const request = context.switchToHttp().getRequest();
|
|
107
|
+
const options = this.explicitOptions ?? this.getOptionsFromContext(context);
|
|
108
|
+
if (options?.resource && options?.action) debugGrant("Nest", {
|
|
109
|
+
resource: options.resource,
|
|
110
|
+
action: options.action,
|
|
111
|
+
hasReflector: !!this.reflector,
|
|
112
|
+
handler: context.getHandler()?.name,
|
|
113
|
+
controller: context.getClass()?.name
|
|
114
|
+
});
|
|
115
|
+
if (!options || !options.resource || !options.action) throw new ForbiddenException("Grant options (resource, action) required. Use @Grant() or pass options to GrantGuard.");
|
|
116
|
+
if (!this.client) throw new ForbiddenException("GrantGuard: GrantClient not injected. Import GrantModule.forRoot() in AppModule and register GrantGuard as a provider.");
|
|
117
|
+
if (!await extractTokenFromRequest(request, this.client.config)) throw new UnauthorizedException({
|
|
118
|
+
error: "Unauthorized",
|
|
119
|
+
code: "UNAUTHENTICATED"
|
|
120
|
+
});
|
|
121
|
+
let resolvedResource = null;
|
|
122
|
+
const opts = options;
|
|
123
|
+
if (opts.resourceResolver) {
|
|
124
|
+
resolvedResource = await opts.resourceResolver({
|
|
125
|
+
resourceSlug: options.resource,
|
|
126
|
+
request
|
|
127
|
+
});
|
|
128
|
+
if (!resolvedResource) throw new NotFoundException({
|
|
129
|
+
error: "Resource not found",
|
|
130
|
+
code: "NOT_FOUND"
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const result = await this.client.isAuthorized(options.resource, options.action, { context: { resource: resolvedResource ?? void 0 } }, request);
|
|
134
|
+
debugGrant("Nest", {
|
|
135
|
+
resource: options.resource,
|
|
136
|
+
action: options.action,
|
|
137
|
+
authorized: result.authorized,
|
|
138
|
+
...result.authorized ? {} : { reason: result.reason }
|
|
139
|
+
});
|
|
140
|
+
if (!result.authorized) throw new ForbiddenException({
|
|
141
|
+
error: "Forbidden",
|
|
142
|
+
code: "FORBIDDEN",
|
|
143
|
+
reason: result.reason
|
|
144
|
+
});
|
|
145
|
+
request.authorization = result;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
142
148
|
};
|
|
143
|
-
GrantGuard =
|
|
144
|
-
|
|
145
|
-
|
|
149
|
+
GrantGuard = __decorate([
|
|
150
|
+
Injectable(),
|
|
151
|
+
__decorateParam(0, Inject(GRANT_CLIENT)),
|
|
152
|
+
__decorateMetadata("design:paramtypes", [typeof (_ref = typeof GrantClient !== "undefined" && GrantClient) === "function" ? _ref : Object, Object])
|
|
146
153
|
], GrantGuard);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Grant,
|
|
152
|
-
GrantClient,
|
|
153
|
-
GrantGuard,
|
|
154
|
-
GrantModule
|
|
155
|
-
};
|
|
156
|
-
//# sourceMappingURL=nest.mjs.map
|
|
154
|
+
//#endregion
|
|
155
|
+
export { GRANT_CLIENT, GRANT_GUARD, GRANT_OPTIONS_KEY, Grant, GrantClient, GrantGuard, GrantModule };
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=nest.mjs.map
|
package/dist/nest.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest.mjs","sources":["../src/nest/grant.decorator.ts","../src/nest/grant.module.ts","../src/nest/grant.guard.ts"],"sourcesContent":["import { SetMetadata } from '@nestjs/common';\n\n/**\n * Metadata key for Grant options (resource, action) on route handlers.\n * Used by GrantGuard when options are not passed in the constructor.\n */\nexport const GRANT_OPTIONS_KEY = 'grant:options';\n\n/**\n * Options for the Grant guard (resource, action; resourceResolver only via guard constructor).\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}\n\n/**\n * Decorator to require a Grant permission on a route.\n * Use with GrantGuard (injected) so the guard reads resource/action from this metadata.\n *\n * @example\n * ```ts\n * @Get()\n * @Grant('Document', 'Query')\n * @UseGuards(GrantGuard)\n * list() { ... }\n *\n * @Post()\n * @Grant('Document', 'Create')\n * @UseGuards(GrantGuard)\n * create(@Body() dto: CreateDocumentDto) { ... }\n * ```\n *\n * For dynamic resource resolution (resourceResolver), use the guard with explicit options:\n * `@UseGuards(new GrantGuard(grantClient, { resource: 'Document', action: 'Update', resourceResolver: ... }))`\n */\nexport function Grant(resource: string, action: string): ReturnType<typeof SetMetadata>;\nexport function Grant(options: GrantOptions): ReturnType<typeof SetMetadata>;\nexport function Grant(\n resourceOrOptions: string | GrantOptions,\n action?: string\n): ReturnType<typeof SetMetadata> {\n const options: GrantOptions =\n typeof resourceOrOptions === 'string' && action !== undefined\n ? { resource: resourceOrOptions, action }\n : (resourceOrOptions as GrantOptions);\n return SetMetadata(GRANT_OPTIONS_KEY, options);\n}\n","import { DynamicModule, Global, Module } from '@nestjs/common';\n\nimport { GrantClient } from '../grant-client';\n\nimport type { GrantServerConfig } from '../types';\n\nexport const GRANT_CLIENT = 'GrantClient';\n\n/**\n * NestJS module that provides GrantClient for injection.\n *\n * @example\n * ```ts\n * // app.module.ts\n * import { GrantModule } from '@grantjs/server/nest';\n *\n * @Module({\n * imports: [\n * GrantModule.forRoot({\n * apiUrl: process.env.GRANT_API_URL!,\n * getToken: (req) => req.headers?.authorization?.replace?.('Bearer ', '') ?? null,\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * Then inject GrantClient in controllers and use with GrantGuard.\n */\n@Global()\n@Module({})\nexport class GrantModule {\n static forRoot(config: GrantServerConfig): DynamicModule {\n const client = new GrantClient(config);\n return {\n module: GrantModule,\n providers: [\n { provide: GrantClient, useValue: client },\n { provide: GRANT_CLIENT, useValue: client },\n ],\n exports: [GrantClient, GRANT_CLIENT],\n };\n }\n}\n","import {\n CanActivate,\n ExecutionContext,\n ForbiddenException,\n Inject,\n Injectable,\n NotFoundException,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\n\nimport { GrantClient } from '../grant-client';\nimport { debugGrant } from '../utils/debug';\nimport { extractTokenFromRequest } from '../utils/token-extractor';\n\nimport { GRANT_OPTIONS_KEY } from './grant.decorator';\nimport { GRANT_CLIENT } from './grant.module';\n\n/** Injection token for GrantGuard. Use with @UseGuards(GRANT_GUARD) so Nest resolves the guard from the container (with GrantClient injected). */\nexport const GRANT_GUARD = 'GrantGuard';\n\nimport type { ResourceResolver, AuthorizationResult } from '../types';\nimport type { GrantOptions } from './grant.decorator';\n\n/**\n * Extended request type with Grant authorization result (for use in controllers).\n */\nexport interface AuthorizedRequest {\n authorization?: AuthorizationResult;\n}\n\n/**\n * Guard options when using explicit options (includes optional resourceResolver).\n */\nexport interface GrantGuardOptions {\n resource: string;\n action: string;\n resourceResolver?: ResourceResolver;\n}\n\n/**\n * NestJS guard that checks Grant authorization before allowing access to a route.\n *\n * Use in one of two ways:\n *\n * 1. With decorator (resource/action from metadata):\n * - Import GrantModule.forRoot() in AppModule. In a feature module, add\n * `{ provide: GRANT_GUARD, useClass: GrantGuard }` and use @Grant + @UseGuards(GRANT_GUARD).\n * - Using the GRANT_GUARD token ensures Nest resolves the guard from the container (GrantClient injected).\n *\n * 2. With explicit options (supports resourceResolver):\n * - @UseGuards(new GrantGuard(grantClient, { resource: 'Document', action: 'Update', resourceResolver: async ({ request }) => ... }))\n *\n * On success, the authorization result is attached to the request as request.authorization.\n * On failure, throws UnauthorizedException (401), ForbiddenException (403), or NotFoundException (404).\n */\nfunction isGrantGuardOptions(x: unknown): x is GrantGuardOptions {\n return (\n typeof x === 'object' &&\n x !== null &&\n 'resource' in x &&\n 'action' in x &&\n typeof (x as GrantGuardOptions).resource === 'string' &&\n typeof (x as GrantGuardOptions).action === 'string'\n );\n}\n\n@Injectable()\nexport class GrantGuard implements CanActivate {\n private readonly explicitOptions?: GrantGuardOptions;\n private readonly reflector?: Reflector;\n\n constructor(\n @Inject(GRANT_CLIENT) private readonly client: GrantClient,\n reflectorOrOptions?: Reflector | GrantGuardOptions\n ) {\n if (reflectorOrOptions && isGrantGuardOptions(reflectorOrOptions)) {\n this.explicitOptions = reflectorOrOptions;\n } else {\n this.reflector = reflectorOrOptions as Reflector | undefined;\n }\n }\n\n private getOptionsFromContext(context: ExecutionContext): GrantOptions | undefined {\n const handler = context.getHandler();\n const clazz = context.getClass();\n // Prefer Reflector when available (Nest DI)\n if (this.reflector) {\n const fromHandler = this.reflector.get<GrantOptions>(GRANT_OPTIONS_KEY, handler);\n const fromClass = this.reflector.get<GrantOptions>(GRANT_OPTIONS_KEY, clazz);\n if (fromHandler) return fromHandler;\n if (fromClass) return fromClass;\n }\n // Fallback: read metadata via Reflect (works when Reflector is not injected, e.g. ESM/CJS boundary)\n if (typeof Reflect !== 'undefined' && typeof Reflect.getMetadata === 'function') {\n const fromHandler = Reflect.getMetadata(GRANT_OPTIONS_KEY, handler) as\n | GrantOptions\n | undefined;\n const fromClass = Reflect.getMetadata(GRANT_OPTIONS_KEY, clazz) as GrantOptions | undefined;\n if (fromHandler) return fromHandler;\n if (fromClass) return fromClass;\n }\n return undefined;\n }\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest();\n\n const options = this.explicitOptions ?? this.getOptionsFromContext(context);\n\n if (options?.resource && options?.action) {\n debugGrant('Nest', {\n resource: options.resource,\n action: options.action,\n hasReflector: !!this.reflector,\n handler: context.getHandler()?.name,\n controller: context.getClass()?.name,\n });\n }\n\n if (!options || !options.resource || !options.action) {\n throw new ForbiddenException(\n 'Grant options (resource, action) required. Use @Grant() or pass options to GrantGuard.'\n );\n }\n\n if (!this.client) {\n throw new ForbiddenException(\n 'GrantGuard: GrantClient not injected. Import GrantModule.forRoot() in AppModule and register GrantGuard as a provider.'\n );\n }\n\n const token = await extractTokenFromRequest(request, this.client.config);\n if (!token) {\n throw new UnauthorizedException({ error: 'Unauthorized', code: 'UNAUTHENTICATED' });\n }\n\n let resolvedResource: Record<string, unknown> | null = null;\n\n const opts = options as GrantGuardOptions;\n if (opts.resourceResolver) {\n resolvedResource = await opts.resourceResolver({\n resourceSlug: options.resource,\n request,\n });\n if (!resolvedResource) {\n throw new NotFoundException({ error: 'Resource not found', code: 'NOT_FOUND' });\n }\n }\n\n const result = await this.client.isAuthorized(\n options.resource,\n options.action,\n {\n context: {\n resource: resolvedResource ?? undefined,\n },\n },\n request\n );\n\n debugGrant('Nest', {\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 throw new ForbiddenException({\n error: 'Forbidden',\n code: 'FORBIDDEN',\n reason: result.reason,\n });\n }\n\n (request as AuthorizedRequest).authorization = result;\n return true;\n }\n}\n"],"names":["__decorateClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nest.mjs","names":[],"sources":["../src/nest/grant.decorator.ts","../src/nest/grant.module.ts","../src/nest/grant.guard.ts"],"sourcesContent":["import { SetMetadata } from '@nestjs/common';\n\n/**\n * Metadata key for Grant options (resource, action) on route handlers.\n * Used by GrantGuard when options are not passed in the constructor.\n */\nexport const GRANT_OPTIONS_KEY = 'grant:options';\n\n/**\n * Options for the Grant guard (resource, action; resourceResolver only via guard constructor).\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}\n\n/**\n * Decorator to require a Grant permission on a route.\n * Use with GrantGuard (injected) so the guard reads resource/action from this metadata.\n *\n * @example\n * ```ts\n * @Get()\n * @Grant('Document', 'Query')\n * @UseGuards(GrantGuard)\n * list() { ... }\n *\n * @Post()\n * @Grant('Document', 'Create')\n * @UseGuards(GrantGuard)\n * create(@Body() dto: CreateDocumentDto) { ... }\n * ```\n *\n * For dynamic resource resolution (resourceResolver), use the guard with explicit options:\n * `@UseGuards(new GrantGuard(grantClient, { resource: 'Document', action: 'Update', resourceResolver: ... }))`\n */\nexport function Grant(resource: string, action: string): ReturnType<typeof SetMetadata>;\nexport function Grant(options: GrantOptions): ReturnType<typeof SetMetadata>;\nexport function Grant(\n resourceOrOptions: string | GrantOptions,\n action?: string\n): ReturnType<typeof SetMetadata> {\n const options: GrantOptions =\n typeof resourceOrOptions === 'string' && action !== undefined\n ? { resource: resourceOrOptions, action }\n : (resourceOrOptions as GrantOptions);\n return SetMetadata(GRANT_OPTIONS_KEY, options);\n}\n","import { DynamicModule, Global, Module } from '@nestjs/common';\n\nimport { GrantClient } from '../grant-client';\nimport type { GrantServerConfig } from '../types';\n\nexport const GRANT_CLIENT = 'GrantClient';\n\n/**\n * NestJS module that provides GrantClient for injection.\n *\n * @example\n * ```ts\n * // app.module.ts\n * import { GrantModule } from '@grantjs/server/nest';\n *\n * @Module({\n * imports: [\n * GrantModule.forRoot({\n * apiUrl: process.env.GRANT_API_URL!,\n * getToken: (req) => req.headers?.authorization?.replace?.('Bearer ', '') ?? null,\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * Then inject GrantClient in controllers and use with GrantGuard.\n */\n@Global()\n@Module({})\nexport class GrantModule {\n static forRoot(config: GrantServerConfig): DynamicModule {\n const client = new GrantClient(config);\n return {\n module: GrantModule,\n providers: [\n { provide: GrantClient, useValue: client },\n { provide: GRANT_CLIENT, useValue: client },\n ],\n exports: [GrantClient, GRANT_CLIENT],\n };\n }\n}\n","import {\n CanActivate,\n ExecutionContext,\n ForbiddenException,\n Inject,\n Injectable,\n NotFoundException,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\n\nimport { GrantClient } from '../grant-client';\nimport { debugGrant } from '../utils/debug';\nimport { extractTokenFromRequest } from '../utils/token-extractor';\nimport { GRANT_OPTIONS_KEY } from './grant.decorator';\nimport { GRANT_CLIENT } from './grant.module';\n\n/** Injection token for GrantGuard. Use with @UseGuards(GRANT_GUARD) so Nest resolves the guard from the container (with GrantClient injected). */\nexport const GRANT_GUARD = 'GrantGuard';\n\nimport type { AuthorizationResult, ResourceResolver } from '../types';\nimport type { GrantOptions } from './grant.decorator';\n\n/**\n * Extended request type with Grant authorization result (for use in controllers).\n */\nexport interface AuthorizedRequest {\n authorization?: AuthorizationResult;\n}\n\n/**\n * Guard options when using explicit options (includes optional resourceResolver).\n */\nexport interface GrantGuardOptions {\n resource: string;\n action: string;\n resourceResolver?: ResourceResolver;\n}\n\n/**\n * NestJS guard that checks Grant authorization before allowing access to a route.\n *\n * Use in one of two ways:\n *\n * 1. With decorator (resource/action from metadata):\n * - Import GrantModule.forRoot() in AppModule. In a feature module, add\n * `{ provide: GRANT_GUARD, useClass: GrantGuard }` and use @Grant + @UseGuards(GRANT_GUARD).\n * - Using the GRANT_GUARD token ensures Nest resolves the guard from the container (GrantClient injected).\n *\n * 2. With explicit options (supports resourceResolver):\n * - @UseGuards(new GrantGuard(grantClient, { resource: 'Document', action: 'Update', resourceResolver: async ({ request }) => ... }))\n *\n * On success, the authorization result is attached to the request as request.authorization.\n * On failure, throws UnauthorizedException (401), ForbiddenException (403), or NotFoundException (404).\n */\nfunction isGrantGuardOptions(x: unknown): x is GrantGuardOptions {\n return (\n typeof x === 'object' &&\n x !== null &&\n 'resource' in x &&\n 'action' in x &&\n typeof (x as GrantGuardOptions).resource === 'string' &&\n typeof (x as GrantGuardOptions).action === 'string'\n );\n}\n\n@Injectable()\nexport class GrantGuard implements CanActivate {\n private readonly explicitOptions?: GrantGuardOptions;\n private readonly reflector?: Reflector;\n\n constructor(\n @Inject(GRANT_CLIENT) private readonly client: GrantClient,\n reflectorOrOptions?: Reflector | GrantGuardOptions\n ) {\n if (reflectorOrOptions && isGrantGuardOptions(reflectorOrOptions)) {\n this.explicitOptions = reflectorOrOptions;\n } else {\n this.reflector = reflectorOrOptions as Reflector | undefined;\n }\n }\n\n private getOptionsFromContext(context: ExecutionContext): GrantOptions | undefined {\n const handler = context.getHandler();\n const clazz = context.getClass();\n // Prefer Reflector when available (Nest DI)\n if (this.reflector) {\n const fromHandler = this.reflector.get<GrantOptions>(GRANT_OPTIONS_KEY, handler);\n const fromClass = this.reflector.get<GrantOptions>(GRANT_OPTIONS_KEY, clazz);\n if (fromHandler) return fromHandler;\n if (fromClass) return fromClass;\n }\n // Fallback: read metadata via Reflect (works when Reflector is not injected, e.g. ESM/CJS boundary)\n if (typeof Reflect !== 'undefined' && typeof Reflect.getMetadata === 'function') {\n const fromHandler = Reflect.getMetadata(GRANT_OPTIONS_KEY, handler) as\n | GrantOptions\n | undefined;\n const fromClass = Reflect.getMetadata(GRANT_OPTIONS_KEY, clazz) as GrantOptions | undefined;\n if (fromHandler) return fromHandler;\n if (fromClass) return fromClass;\n }\n return undefined;\n }\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest();\n\n const options = this.explicitOptions ?? this.getOptionsFromContext(context);\n\n if (options?.resource && options?.action) {\n debugGrant('Nest', {\n resource: options.resource,\n action: options.action,\n hasReflector: !!this.reflector,\n handler: context.getHandler()?.name,\n controller: context.getClass()?.name,\n });\n }\n\n if (!options || !options.resource || !options.action) {\n throw new ForbiddenException(\n 'Grant options (resource, action) required. Use @Grant() or pass options to GrantGuard.'\n );\n }\n\n if (!this.client) {\n throw new ForbiddenException(\n 'GrantGuard: GrantClient not injected. Import GrantModule.forRoot() in AppModule and register GrantGuard as a provider.'\n );\n }\n\n const token = await extractTokenFromRequest(request, this.client.config);\n if (!token) {\n throw new UnauthorizedException({ error: 'Unauthorized', code: 'UNAUTHENTICATED' });\n }\n\n let resolvedResource: Record<string, unknown> | null = null;\n\n const opts = options as GrantGuardOptions;\n if (opts.resourceResolver) {\n resolvedResource = await opts.resourceResolver({\n resourceSlug: options.resource,\n request,\n });\n if (!resolvedResource) {\n throw new NotFoundException({ error: 'Resource not found', code: 'NOT_FOUND' });\n }\n }\n\n const result = await this.client.isAuthorized(\n options.resource,\n options.action,\n {\n context: {\n resource: resolvedResource ?? undefined,\n },\n },\n request\n );\n\n debugGrant('Nest', {\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 throw new ForbiddenException({\n error: 'Forbidden',\n code: 'FORBIDDEN',\n reason: result.reason,\n });\n }\n\n (request as AuthorizedRequest).authorization = result;\n return true;\n }\n}\n"],"mappings":";;;;;;;;AAMA,IAAa,oBAAoB;AAkCjC,SAAgB,MACd,mBACA,QACgC;AAKhC,QAAO,YAAY,mBAHjB,OAAO,sBAAsB,YAAY,WAAW,KAAA,IAChD;EAAE,UAAU;EAAmB;EAAQ,GACtC,kBACuC;;;;;;;;;;;;;AC3ChD,IAAa,eAAe;AAyBrB,IAAA,cAAA,eAAA,MAAM,YAAY;CACvB,OAAO,QAAQ,QAA0C;EACvD,MAAM,SAAS,IAAI,YAAY,OAAO;AACtC,SAAO;GACL,QAAA;GACA,WAAW,CACT;IAAE,SAAS;IAAa,UAAU;IAAQ,EAC1C;IAAE,SAAS;IAAc,UAAU;IAAQ,CAC5C;GACD,SAAS,CAAC,aAAa,aAAa;GACrC;;;yCAZJ,QAAQ,EACR,OAAO,EAAE,CAAC,CAAA,EAAA,YAAA;;;;;;;;;;;;;;;;;ACXX,IAAa,cAAc;;;;;;;;;;;;;;;;;AAqC3B,SAAS,oBAAoB,GAAoC;AAC/D,QACE,OAAO,MAAM,YACb,MAAM,QACN,cAAc,KACd,YAAY,KACZ,OAAQ,EAAwB,aAAa,YAC7C,OAAQ,EAAwB,WAAW;;AAKxC,IAAA,aAAA,MAAM,WAAkC;CAI7C,YACE,QACA,oBACA;wBANF,mBAAA,KAAA,EAAiB;wBACjB,aAAA,KAAA,EAAiB;AAGwB,OAAA,SAAA;AAGvC,MAAI,sBAAsB,oBAAoB,mBAAmB,CAC/D,MAAK,kBAAkB;MAEvB,MAAK,YAAY;;CAIrB,sBAA8B,SAAqD;EACjF,MAAM,UAAU,QAAQ,YAAY;EACpC,MAAM,QAAQ,QAAQ,UAAU;AAEhC,MAAI,KAAK,WAAW;GAClB,MAAM,cAAc,KAAK,UAAU,IAAkB,mBAAmB,QAAQ;GAChF,MAAM,YAAY,KAAK,UAAU,IAAkB,mBAAmB,MAAM;AAC5E,OAAI,YAAa,QAAO;AACxB,OAAI,UAAW,QAAO;;AAGxB,MAAI,OAAO,YAAY,eAAe,OAAO,QAAQ,gBAAgB,YAAY;GAC/E,MAAM,cAAc,QAAQ,YAAY,mBAAmB,QAAQ;GAGnE,MAAM,YAAY,QAAQ,YAAY,mBAAmB,MAAM;AAC/D,OAAI,YAAa,QAAO;AACxB,OAAI,UAAW,QAAO;;;CAK1B,MAAM,YAAY,SAA6C;EAC7D,MAAM,UAAU,QAAQ,cAAc,CAAC,YAAY;EAEnD,MAAM,UAAU,KAAK,mBAAmB,KAAK,sBAAsB,QAAQ;AAE3E,MAAI,SAAS,YAAY,SAAS,OAChC,YAAW,QAAQ;GACjB,UAAU,QAAQ;GAClB,QAAQ,QAAQ;GAChB,cAAc,CAAC,CAAC,KAAK;GACrB,SAAS,QAAQ,YAAY,EAAE;GAC/B,YAAY,QAAQ,UAAU,EAAE;GACjC,CAAC;AAGJ,MAAI,CAAC,WAAW,CAAC,QAAQ,YAAY,CAAC,QAAQ,OAC5C,OAAM,IAAI,mBACR,yFACD;AAGH,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,mBACR,yHACD;AAIH,MAAI,CAAC,MADe,wBAAwB,SAAS,KAAK,OAAO,OAAO,CAEtE,OAAM,IAAI,sBAAsB;GAAE,OAAO;GAAgB,MAAM;GAAmB,CAAC;EAGrF,IAAI,mBAAmD;EAEvD,MAAM,OAAO;AACb,MAAI,KAAK,kBAAkB;AACzB,sBAAmB,MAAM,KAAK,iBAAiB;IAC7C,cAAc,QAAQ;IACtB;IACD,CAAC;AACF,OAAI,CAAC,iBACH,OAAM,IAAI,kBAAkB;IAAE,OAAO;IAAsB,MAAM;IAAa,CAAC;;EAInF,MAAM,SAAS,MAAM,KAAK,OAAO,aAC/B,QAAQ,UACR,QAAQ,QACR,EACE,SAAS,EACP,UAAU,oBAAoB,KAAA,GAC/B,EACF,EACD,QACD;AAED,aAAW,QAAQ;GACjB,UAAU,QAAQ;GAClB,QAAQ,QAAQ;GAChB,YAAY,OAAO;GACnB,GAAI,OAAO,aAAa,EAAE,GAAG,EAAE,QAAQ,OAAO,QAAQ;GACvD,CAAC;AAEF,MAAI,CAAC,OAAO,WACV,OAAM,IAAI,mBAAmB;GAC3B,OAAO;GACP,MAAM;GACN,QAAQ,OAAO;GAChB,CAAC;AAGH,UAA8B,gBAAgB;AAC/C,SAAO;;;;CA9GV,YAAY;oBAMR,OAAO,aAAa,CAAA"}
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { withGrant } from './with-grant';
|
|
2
|
-
export type { GrantOptions, GrantRouteHandler, WithGrantContext } from './with-grant';
|
|
3
|
-
export type { GrantServerConfig, AuthorizationResult, PermissionCheckOptions, Scope, ResourceResolver, } from '../types';
|
|
4
|
-
export { GrantClient } from '../grant-client';
|
|
5
1
|
export { AuthenticationError, AuthorizationError, BadRequestError, NotFoundError } from '../errors';
|
|
2
|
+
export { GrantClient } from '../grant-client';
|
|
3
|
+
export type { AuthorizationResult, GrantServerConfig, PermissionCheckOptions, ResourceResolver, Scope, } from '../types';
|
|
4
|
+
export type { GrantOptions, GrantRouteHandler, WithGrantContext } from './with-grant';
|
|
5
|
+
export { withGrant } from './with-grant';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/next/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,KAAK,GACN,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest } from 'next/server';
|
|
2
2
|
import { GrantClient } from '../grant-client';
|
|
3
|
-
import {
|
|
3
|
+
import { AuthorizationResult, ResourceResolver } from '../types';
|
|
4
4
|
/**
|
|
5
5
|
* Options for Next.js route handler guard
|
|
6
6
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-grant.d.ts","sourceRoot":"","sources":["../../src/next/with-grant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"with-grant.d.ts","sourceRoot":"","sources":["../../src/next/with-grant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAItE;;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,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,mBAAmB,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,gBAAgB,KACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,iBAAiB,GACzB,CACD,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,KACnD,OAAO,CAAC,QAAQ,CAAC,CA0DrB"}
|
package/dist/next.cjs
CHANGED
|
@@ -1,62 +1,79 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
2
|
+
const require_grant_client = require("./grant-client-f6IKMyk5.cjs");
|
|
3
|
+
const require_errors = require("./errors-BUeTn_3G.cjs");
|
|
4
|
+
const require_debug = require("./debug-DInHA4QB.cjs");
|
|
5
|
+
let next_server = require("next/server");
|
|
6
|
+
//#region src/next/with-grant.ts
|
|
7
|
+
/**
|
|
8
|
+
* Wrap a Next.js App Router route handler with Grant authorization.
|
|
9
|
+
* Use for GET, POST, PUT, PATCH, DELETE in app/api/.../route.ts.
|
|
10
|
+
*
|
|
11
|
+
* Compatible with Next.js 13–16. In Next 15+, the route context's `params` is a Promise;
|
|
12
|
+
* this wrapper does not use params (only `request` is passed to the resolver/handler).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // app/api/documents/route.ts
|
|
17
|
+
* import { withGrant } from '@grantjs/server/next';
|
|
18
|
+
* import { GrantClient } from '@grantjs/server';
|
|
19
|
+
*
|
|
20
|
+
* const grantClient = new GrantClient({ apiUrl: process.env.GRANT_API_URL! });
|
|
21
|
+
*
|
|
22
|
+
* export const GET = withGrant(
|
|
23
|
+
* grantClient,
|
|
24
|
+
* { resource: 'Document', action: 'Query' },
|
|
25
|
+
* async (request) => NextResponse.json({ data: [] })
|
|
26
|
+
* );
|
|
27
|
+
*
|
|
28
|
+
* export const POST = withGrant(
|
|
29
|
+
* grantClient,
|
|
30
|
+
* { resource: 'Document', action: 'Create' },
|
|
31
|
+
* async (request) => { ... }
|
|
32
|
+
* );
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
7
35
|
function withGrant(client, options, handler) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
if (!result.authorized) {
|
|
44
|
-
return server.NextResponse.json(
|
|
45
|
-
{
|
|
46
|
-
error: "Forbidden",
|
|
47
|
-
code: "FORBIDDEN",
|
|
48
|
-
reason: result.reason
|
|
49
|
-
},
|
|
50
|
-
{ status: 403 }
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
return handler(request, { authorization: result });
|
|
54
|
-
};
|
|
36
|
+
return async (request, _routeContext) => {
|
|
37
|
+
require_debug.debugGrant("Next", {
|
|
38
|
+
resource: options.resource,
|
|
39
|
+
action: options.action
|
|
40
|
+
});
|
|
41
|
+
if (!await require_grant_client.extractTokenFromRequest(request, client.config)) return next_server.NextResponse.json({
|
|
42
|
+
error: "Unauthorized",
|
|
43
|
+
code: "UNAUTHENTICATED"
|
|
44
|
+
}, { status: 401 });
|
|
45
|
+
let resolvedResource = null;
|
|
46
|
+
if (options.resourceResolver) {
|
|
47
|
+
resolvedResource = await options.resourceResolver({
|
|
48
|
+
resourceSlug: options.resource,
|
|
49
|
+
request
|
|
50
|
+
});
|
|
51
|
+
if (!resolvedResource) return next_server.NextResponse.json({
|
|
52
|
+
error: "Resource not found",
|
|
53
|
+
code: "NOT_FOUND"
|
|
54
|
+
}, { status: 404 });
|
|
55
|
+
}
|
|
56
|
+
const result = await client.isAuthorized(options.resource, options.action, { context: { resource: resolvedResource ?? void 0 } }, request);
|
|
57
|
+
require_debug.debugGrant("Next", {
|
|
58
|
+
resource: options.resource,
|
|
59
|
+
action: options.action,
|
|
60
|
+
authorized: result.authorized,
|
|
61
|
+
...result.authorized ? {} : { reason: result.reason }
|
|
62
|
+
});
|
|
63
|
+
if (!result.authorized) return next_server.NextResponse.json({
|
|
64
|
+
error: "Forbidden",
|
|
65
|
+
code: "FORBIDDEN",
|
|
66
|
+
reason: result.reason
|
|
67
|
+
}, { status: 403 });
|
|
68
|
+
return handler(request, { authorization: result });
|
|
69
|
+
};
|
|
55
70
|
}
|
|
56
|
-
|
|
57
|
-
exports.AuthenticationError =
|
|
58
|
-
exports.AuthorizationError =
|
|
59
|
-
exports.BadRequestError =
|
|
60
|
-
exports.
|
|
71
|
+
//#endregion
|
|
72
|
+
exports.AuthenticationError = require_errors.AuthenticationError;
|
|
73
|
+
exports.AuthorizationError = require_errors.AuthorizationError;
|
|
74
|
+
exports.BadRequestError = require_errors.BadRequestError;
|
|
75
|
+
exports.GrantClient = require_grant_client.GrantClient;
|
|
76
|
+
exports.NotFoundError = require_errors.NotFoundError;
|
|
61
77
|
exports.withGrant = withGrant;
|
|
62
|
-
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=next.cjs.map
|
package/dist/next.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.cjs","sources":["../src/next/with-grant.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\n\nimport { GrantClient } from '../grant-client';\nimport {
|
|
1
|
+
{"version":3,"file":"next.cjs","names":[],"sources":["../src/next/with-grant.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\n\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 * Options for Next.js route handler guard\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 * Context passed to the handler after authorization (for downstream use)\n */\nexport interface WithGrantContext {\n authorization: AuthorizationResult;\n}\n\n/**\n * Next.js App Router route handler type (GET, POST, etc.)\n * Second argument is optional context; params are in the request URL for resolver use.\n */\nexport type GrantRouteHandler = (\n request: NextRequest,\n context?: WithGrantContext\n) => Promise<Response> | Response;\n\n/**\n * Wrap a Next.js App Router route handler with Grant authorization.\n * Use for GET, POST, PUT, PATCH, DELETE in app/api/.../route.ts.\n *\n * Compatible with Next.js 13–16. In Next 15+, the route context's `params` is a Promise;\n * this wrapper does not use params (only `request` is passed to the resolver/handler).\n *\n * @example\n * ```ts\n * // app/api/documents/route.ts\n * import { withGrant } from '@grantjs/server/next';\n * import { GrantClient } from '@grantjs/server';\n *\n * const grantClient = new GrantClient({ apiUrl: process.env.GRANT_API_URL! });\n *\n * export const GET = withGrant(\n * grantClient,\n * { resource: 'Document', action: 'Query' },\n * async (request) => NextResponse.json({ data: [] })\n * );\n *\n * export const POST = withGrant(\n * grantClient,\n * { resource: 'Document', action: 'Create' },\n * async (request) => { ... }\n * );\n * ```\n */\nexport function withGrant(\n client: GrantClient,\n options: GrantOptions,\n handler: GrantRouteHandler\n): (\n request: NextRequest,\n context?: { params?: Promise<Record<string, string>> }\n) => Promise<Response> {\n return async (\n request: NextRequest,\n _routeContext?: { params?: Promise<Record<string, string>> }\n ): Promise<Response> => {\n debugGrant('Next', { resource: options.resource, action: options.action });\n\n const token = await extractTokenFromRequest(request, client.config);\n if (!token) {\n return NextResponse.json({ error: 'Unauthorized', code: 'UNAUTHENTICATED' }, { status: 401 });\n }\n\n let resolvedResource: Record<string, unknown> | null = null;\n\n if (options.resourceResolver) {\n resolvedResource = await options.resourceResolver({\n resourceSlug: options.resource,\n request,\n });\n if (!resolvedResource) {\n return NextResponse.json(\n { error: 'Resource not found', code: 'NOT_FOUND' },\n { status: 404 }\n );\n }\n }\n\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('Next', {\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 return NextResponse.json(\n {\n error: 'Forbidden',\n code: 'FORBIDDEN',\n reason: result.reason,\n },\n { status: 403 }\n );\n }\n\n return handler(request, { authorization: result });\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,UACd,QACA,SACA,SAIqB;AACrB,QAAO,OACL,SACA,kBACsB;AACtB,gBAAA,WAAW,QAAQ;GAAE,UAAU,QAAQ;GAAU,QAAQ,QAAQ;GAAQ,CAAC;AAG1E,MAAI,CAAC,MADe,qBAAA,wBAAwB,SAAS,OAAO,OAAO,CAEjE,QAAO,YAAA,aAAa,KAAK;GAAE,OAAO;GAAgB,MAAM;GAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;EAG/F,IAAI,mBAAmD;AAEvD,MAAI,QAAQ,kBAAkB;AAC5B,sBAAmB,MAAM,QAAQ,iBAAiB;IAChD,cAAc,QAAQ;IACtB;IACD,CAAC;AACF,OAAI,CAAC,iBACH,QAAO,YAAA,aAAa,KAClB;IAAE,OAAO;IAAsB,MAAM;IAAa,EAClD,EAAE,QAAQ,KAAK,CAChB;;EAIL,MAAM,SAAS,MAAM,OAAO,aAC1B,QAAQ,UACR,QAAQ,QACR,EACE,SAAS,EACP,UAAU,oBAAoB,KAAA,GAC/B,EACF,EACD,QACD;AAED,gBAAA,WAAW,QAAQ;GACjB,UAAU,QAAQ;GAClB,QAAQ,QAAQ;GAChB,YAAY,OAAO;GACnB,GAAI,OAAO,aAAa,EAAE,GAAG,EAAE,QAAQ,OAAO,QAAQ;GACvD,CAAC;AAEF,MAAI,CAAC,OAAO,WACV,QAAO,YAAA,aAAa,KAClB;GACE,OAAO;GACP,MAAM;GACN,QAAQ,OAAO;GAChB,EACD,EAAE,QAAQ,KAAK,CAChB;AAGH,SAAO,QAAQ,SAAS,EAAE,eAAe,QAAQ,CAAC"}
|