@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.
- package/LICENSE +21 -0
- package/README.md +443 -0
- package/dist/debug-C8ibbrE5.js +9 -0
- package/dist/debug-C8ibbrE5.js.map +1 -0
- package/dist/debug-Crb5gNDM.cjs +8 -0
- package/dist/debug-Crb5gNDM.cjs.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/express/index.d.ts +6 -0
- package/dist/express/index.d.ts.map +1 -0
- package/dist/express/middleware.d.ts +38 -0
- package/dist/express/middleware.d.ts.map +1 -0
- package/dist/express.cjs +99 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.mjs +101 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify/index.d.ts +6 -0
- package/dist/fastify/index.d.ts.map +1 -0
- package/dist/fastify/plugin.d.ts +74 -0
- package/dist/fastify/plugin.d.ts.map +1 -0
- package/dist/fastify.cjs +103 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.mjs +104 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/grant-client-D1LZI2f4.js +180 -0
- package/dist/grant-client-D1LZI2f4.js.map +1 -0
- package/dist/grant-client-DywfJN5P.cjs +179 -0
- package/dist/grant-client-DywfJN5P.cjs.map +1 -0
- package/dist/grant-client.d.ts +50 -0
- package/dist/grant-client.d.ts.map +1 -0
- package/dist/index.cjs +50 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +50 -0
- package/dist/index.mjs.map +1 -0
- package/dist/nest/grant.decorator.d.ts +38 -0
- package/dist/nest/grant.decorator.d.ts.map +1 -0
- package/dist/nest/grant.guard.d.ts +29 -0
- package/dist/nest/grant.guard.d.ts.map +1 -0
- package/dist/nest/grant.module.d.ts +28 -0
- package/dist/nest/grant.module.d.ts.map +1 -0
- package/dist/nest/index.d.ts +8 -0
- package/dist/nest/index.d.ts.map +1 -0
- package/dist/nest.cjs +154 -0
- package/dist/nest.cjs.map +1 -0
- package/dist/nest.mjs +156 -0
- package/dist/nest.mjs.map +1 -0
- package/dist/next/index.d.ts +6 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/with-grant.d.ts +57 -0
- package/dist/next/with-grant.d.ts.map +1 -0
- package/dist/next.cjs +62 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.mjs +63 -0
- package/dist/next.mjs.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/debug.d.ts +9 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/token-extractor.d.ts +14 -0
- package/dist/utils/token-extractor.d.ts.map +1 -0
- package/package.json +109 -0
package/dist/nest.cjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
|
+
const common = require("@nestjs/common");
|
|
7
|
+
const debug = require("./debug-Crb5gNDM.cjs");
|
|
8
|
+
const grantClient = require("./grant-client-DywfJN5P.cjs");
|
|
9
|
+
const GRANT_OPTIONS_KEY = "grant:options";
|
|
10
|
+
function Grant(resourceOrOptions, action) {
|
|
11
|
+
const options = typeof resourceOrOptions === "string" && action !== void 0 ? { resource: resourceOrOptions, action } : resourceOrOptions;
|
|
12
|
+
return common.SetMetadata(GRANT_OPTIONS_KEY, options);
|
|
13
|
+
}
|
|
14
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
15
|
+
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
16
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
17
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
18
|
+
if (decorator = decorators[i])
|
|
19
|
+
result = decorator(result) || result;
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
const GRANT_CLIENT = "GrantClient";
|
|
23
|
+
exports.GrantModule = class GrantModule {
|
|
24
|
+
static forRoot(config) {
|
|
25
|
+
const client = new grantClient.GrantClient(config);
|
|
26
|
+
return {
|
|
27
|
+
module: exports.GrantModule,
|
|
28
|
+
providers: [
|
|
29
|
+
{ provide: grantClient.GrantClient, useValue: client },
|
|
30
|
+
{ provide: GRANT_CLIENT, useValue: client }
|
|
31
|
+
],
|
|
32
|
+
exports: [grantClient.GrantClient, GRANT_CLIENT]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
exports.GrantModule = __decorateClass$1([
|
|
37
|
+
common.Global(),
|
|
38
|
+
common.Module({})
|
|
39
|
+
], exports.GrantModule);
|
|
40
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
41
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
42
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
43
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
44
|
+
if (decorator = decorators[i])
|
|
45
|
+
result = decorator(result) || result;
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
49
|
+
const GRANT_GUARD = "GrantGuard";
|
|
50
|
+
function isGrantGuardOptions(x) {
|
|
51
|
+
return typeof x === "object" && x !== null && "resource" in x && "action" in x && typeof x.resource === "string" && typeof x.action === "string";
|
|
52
|
+
}
|
|
53
|
+
exports.GrantGuard = class GrantGuard {
|
|
54
|
+
constructor(client, reflectorOrOptions) {
|
|
55
|
+
__publicField(this, "explicitOptions");
|
|
56
|
+
__publicField(this, "reflector");
|
|
57
|
+
this.client = client;
|
|
58
|
+
if (reflectorOrOptions && isGrantGuardOptions(reflectorOrOptions)) {
|
|
59
|
+
this.explicitOptions = reflectorOrOptions;
|
|
60
|
+
} else {
|
|
61
|
+
this.reflector = reflectorOrOptions;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
getOptionsFromContext(context) {
|
|
65
|
+
const handler = context.getHandler();
|
|
66
|
+
const clazz = context.getClass();
|
|
67
|
+
if (this.reflector) {
|
|
68
|
+
const fromHandler = this.reflector.get(GRANT_OPTIONS_KEY, handler);
|
|
69
|
+
const fromClass = this.reflector.get(GRANT_OPTIONS_KEY, clazz);
|
|
70
|
+
if (fromHandler) return fromHandler;
|
|
71
|
+
if (fromClass) return fromClass;
|
|
72
|
+
}
|
|
73
|
+
if (typeof Reflect !== "undefined" && typeof Reflect.getMetadata === "function") {
|
|
74
|
+
const fromHandler = Reflect.getMetadata(GRANT_OPTIONS_KEY, handler);
|
|
75
|
+
const fromClass = Reflect.getMetadata(GRANT_OPTIONS_KEY, clazz);
|
|
76
|
+
if (fromHandler) return fromHandler;
|
|
77
|
+
if (fromClass) return fromClass;
|
|
78
|
+
}
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
async canActivate(context) {
|
|
82
|
+
const request = context.switchToHttp().getRequest();
|
|
83
|
+
const options = this.explicitOptions ?? this.getOptionsFromContext(context);
|
|
84
|
+
if (options?.resource && options?.action) {
|
|
85
|
+
debug.debugGrant("Nest", {
|
|
86
|
+
resource: options.resource,
|
|
87
|
+
action: options.action,
|
|
88
|
+
hasReflector: !!this.reflector,
|
|
89
|
+
handler: context.getHandler()?.name,
|
|
90
|
+
controller: context.getClass()?.name
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (!options || !options.resource || !options.action) {
|
|
94
|
+
throw new common.ForbiddenException(
|
|
95
|
+
"Grant options (resource, action) required. Use @Grant() or pass options to GrantGuard."
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (!this.client) {
|
|
99
|
+
throw new common.ForbiddenException(
|
|
100
|
+
"GrantGuard: GrantClient not injected. Import GrantModule.forRoot() in AppModule and register GrantGuard as a provider."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const token = await grantClient.extractTokenFromRequest(request, this.client.config);
|
|
104
|
+
if (!token) {
|
|
105
|
+
throw new common.UnauthorizedException({ error: "Unauthorized", code: "UNAUTHENTICATED" });
|
|
106
|
+
}
|
|
107
|
+
let resolvedResource = null;
|
|
108
|
+
const opts = options;
|
|
109
|
+
if (opts.resourceResolver) {
|
|
110
|
+
resolvedResource = await opts.resourceResolver({
|
|
111
|
+
resourceSlug: options.resource,
|
|
112
|
+
request
|
|
113
|
+
});
|
|
114
|
+
if (!resolvedResource) {
|
|
115
|
+
throw new common.NotFoundException({ error: "Resource not found", code: "NOT_FOUND" });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const result = await this.client.isAuthorized(
|
|
119
|
+
options.resource,
|
|
120
|
+
options.action,
|
|
121
|
+
{
|
|
122
|
+
context: {
|
|
123
|
+
resource: resolvedResource ?? void 0
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
request
|
|
127
|
+
);
|
|
128
|
+
debug.debugGrant("Nest", {
|
|
129
|
+
resource: options.resource,
|
|
130
|
+
action: options.action,
|
|
131
|
+
authorized: result.authorized,
|
|
132
|
+
...result.authorized ? {} : { reason: result.reason }
|
|
133
|
+
});
|
|
134
|
+
if (!result.authorized) {
|
|
135
|
+
throw new common.ForbiddenException({
|
|
136
|
+
error: "Forbidden",
|
|
137
|
+
code: "FORBIDDEN",
|
|
138
|
+
reason: result.reason
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
request.authorization = result;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
exports.GrantGuard = __decorateClass([
|
|
146
|
+
common.Injectable(),
|
|
147
|
+
__decorateParam(0, common.Inject(GRANT_CLIENT))
|
|
148
|
+
], exports.GrantGuard);
|
|
149
|
+
exports.GrantClient = grantClient.GrantClient;
|
|
150
|
+
exports.GRANT_CLIENT = GRANT_CLIENT;
|
|
151
|
+
exports.GRANT_GUARD = GRANT_GUARD;
|
|
152
|
+
exports.GRANT_OPTIONS_KEY = GRANT_OPTIONS_KEY;
|
|
153
|
+
exports.Grant = Grant;
|
|
154
|
+
//# sourceMappingURL=nest.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest.cjs","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":["SetMetadata","GrantModule","GrantClient","__decorateClass","Global","Module","GrantGuard","debugGrant","ForbiddenException","extractTokenFromRequest","UnauthorizedException","NotFoundException","Injectable"],"mappings":"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"}
|
package/dist/nest.mjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { SetMetadata, Global, Module, ForbiddenException, UnauthorizedException, NotFoundException, Injectable, Inject } from "@nestjs/common";
|
|
5
|
+
import { d as debugGrant } from "./debug-C8ibbrE5.js";
|
|
6
|
+
import { G as GrantClient, e as extractTokenFromRequest } from "./grant-client-D1LZI2f4.js";
|
|
7
|
+
const GRANT_OPTIONS_KEY = "grant:options";
|
|
8
|
+
function Grant(resourceOrOptions, action) {
|
|
9
|
+
const options = typeof resourceOrOptions === "string" && action !== void 0 ? { resource: resourceOrOptions, action } : resourceOrOptions;
|
|
10
|
+
return SetMetadata(GRANT_OPTIONS_KEY, options);
|
|
11
|
+
}
|
|
12
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
14
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
15
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
16
|
+
if (decorator = decorators[i])
|
|
17
|
+
result = decorator(result) || result;
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
const GRANT_CLIENT = "GrantClient";
|
|
21
|
+
let GrantModule = class {
|
|
22
|
+
static forRoot(config) {
|
|
23
|
+
const client = new GrantClient(config);
|
|
24
|
+
return {
|
|
25
|
+
module: GrantModule,
|
|
26
|
+
providers: [
|
|
27
|
+
{ provide: GrantClient, useValue: client },
|
|
28
|
+
{ provide: GRANT_CLIENT, useValue: client }
|
|
29
|
+
],
|
|
30
|
+
exports: [GrantClient, GRANT_CLIENT]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
GrantModule = __decorateClass$1([
|
|
35
|
+
Global(),
|
|
36
|
+
Module({})
|
|
37
|
+
], GrantModule);
|
|
38
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
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;
|
|
45
|
+
};
|
|
46
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
47
|
+
const GRANT_GUARD = "GrantGuard";
|
|
48
|
+
function isGrantGuardOptions(x) {
|
|
49
|
+
return typeof x === "object" && x !== null && "resource" in x && "action" in x && typeof x.resource === "string" && typeof x.action === "string";
|
|
50
|
+
}
|
|
51
|
+
let GrantGuard = class {
|
|
52
|
+
constructor(client, reflectorOrOptions) {
|
|
53
|
+
__publicField(this, "explicitOptions");
|
|
54
|
+
__publicField(this, "reflector");
|
|
55
|
+
this.client = client;
|
|
56
|
+
if (reflectorOrOptions && isGrantGuardOptions(reflectorOrOptions)) {
|
|
57
|
+
this.explicitOptions = reflectorOrOptions;
|
|
58
|
+
} else {
|
|
59
|
+
this.reflector = reflectorOrOptions;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
getOptionsFromContext(context) {
|
|
63
|
+
const handler = context.getHandler();
|
|
64
|
+
const clazz = context.getClass();
|
|
65
|
+
if (this.reflector) {
|
|
66
|
+
const fromHandler = this.reflector.get(GRANT_OPTIONS_KEY, handler);
|
|
67
|
+
const fromClass = this.reflector.get(GRANT_OPTIONS_KEY, clazz);
|
|
68
|
+
if (fromHandler) return fromHandler;
|
|
69
|
+
if (fromClass) return fromClass;
|
|
70
|
+
}
|
|
71
|
+
if (typeof Reflect !== "undefined" && typeof Reflect.getMetadata === "function") {
|
|
72
|
+
const fromHandler = Reflect.getMetadata(GRANT_OPTIONS_KEY, handler);
|
|
73
|
+
const fromClass = Reflect.getMetadata(GRANT_OPTIONS_KEY, clazz);
|
|
74
|
+
if (fromHandler) return fromHandler;
|
|
75
|
+
if (fromClass) return fromClass;
|
|
76
|
+
}
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
async canActivate(context) {
|
|
80
|
+
const request = context.switchToHttp().getRequest();
|
|
81
|
+
const options = this.explicitOptions ?? this.getOptionsFromContext(context);
|
|
82
|
+
if (options?.resource && options?.action) {
|
|
83
|
+
debugGrant("Nest", {
|
|
84
|
+
resource: options.resource,
|
|
85
|
+
action: options.action,
|
|
86
|
+
hasReflector: !!this.reflector,
|
|
87
|
+
handler: context.getHandler()?.name,
|
|
88
|
+
controller: context.getClass()?.name
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (!options || !options.resource || !options.action) {
|
|
92
|
+
throw new ForbiddenException(
|
|
93
|
+
"Grant options (resource, action) required. Use @Grant() or pass options to GrantGuard."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (!this.client) {
|
|
97
|
+
throw new ForbiddenException(
|
|
98
|
+
"GrantGuard: GrantClient not injected. Import GrantModule.forRoot() in AppModule and register GrantGuard as a provider."
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const token = await extractTokenFromRequest(request, this.client.config);
|
|
102
|
+
if (!token) {
|
|
103
|
+
throw new UnauthorizedException({ error: "Unauthorized", code: "UNAUTHENTICATED" });
|
|
104
|
+
}
|
|
105
|
+
let resolvedResource = null;
|
|
106
|
+
const opts = options;
|
|
107
|
+
if (opts.resourceResolver) {
|
|
108
|
+
resolvedResource = await opts.resourceResolver({
|
|
109
|
+
resourceSlug: options.resource,
|
|
110
|
+
request
|
|
111
|
+
});
|
|
112
|
+
if (!resolvedResource) {
|
|
113
|
+
throw new NotFoundException({ error: "Resource not found", code: "NOT_FOUND" });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const result = await this.client.isAuthorized(
|
|
117
|
+
options.resource,
|
|
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
|
+
}
|
|
142
|
+
};
|
|
143
|
+
GrantGuard = __decorateClass([
|
|
144
|
+
Injectable(),
|
|
145
|
+
__decorateParam(0, Inject(GRANT_CLIENT))
|
|
146
|
+
], GrantGuard);
|
|
147
|
+
export {
|
|
148
|
+
GRANT_CLIENT,
|
|
149
|
+
GRANT_GUARD,
|
|
150
|
+
GRANT_OPTIONS_KEY,
|
|
151
|
+
Grant,
|
|
152
|
+
GrantClient,
|
|
153
|
+
GrantGuard,
|
|
154
|
+
GrantModule
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=nest.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +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
|
+
export { AuthenticationError, AuthorizationError, BadRequestError, NotFoundError } from '../errors';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEtF,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,EACL,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { GrantClient } from '../grant-client';
|
|
3
|
+
import { ResourceResolver, AuthorizationResult } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Options for Next.js route handler guard
|
|
6
|
+
*/
|
|
7
|
+
export interface GrantOptions {
|
|
8
|
+
/** The resource slug to check (e.g., "Organization", "Project", "Document") */
|
|
9
|
+
resource: string;
|
|
10
|
+
/** The action to check (e.g., "Query", "Create", "Update", "Delete") */
|
|
11
|
+
action: string;
|
|
12
|
+
/** Custom resource resolver for condition evaluation */
|
|
13
|
+
resourceResolver?: ResourceResolver;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Context passed to the handler after authorization (for downstream use)
|
|
17
|
+
*/
|
|
18
|
+
export interface WithGrantContext {
|
|
19
|
+
authorization: AuthorizationResult;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Next.js App Router route handler type (GET, POST, etc.)
|
|
23
|
+
* Second argument is optional context; params are in the request URL for resolver use.
|
|
24
|
+
*/
|
|
25
|
+
export type GrantRouteHandler = (request: NextRequest, context?: WithGrantContext) => Promise<Response> | Response;
|
|
26
|
+
/**
|
|
27
|
+
* Wrap a Next.js App Router route handler with Grant authorization.
|
|
28
|
+
* Use for GET, POST, PUT, PATCH, DELETE in app/api/.../route.ts.
|
|
29
|
+
*
|
|
30
|
+
* Compatible with Next.js 13–16. In Next 15+, the route context's `params` is a Promise;
|
|
31
|
+
* this wrapper does not use params (only `request` is passed to the resolver/handler).
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* // app/api/documents/route.ts
|
|
36
|
+
* import { withGrant } from '@grantjs/server/next';
|
|
37
|
+
* import { GrantClient } from '@grantjs/server';
|
|
38
|
+
*
|
|
39
|
+
* const grantClient = new GrantClient({ apiUrl: process.env.GRANT_API_URL! });
|
|
40
|
+
*
|
|
41
|
+
* export const GET = withGrant(
|
|
42
|
+
* grantClient,
|
|
43
|
+
* { resource: 'Document', action: 'Query' },
|
|
44
|
+
* async (request) => NextResponse.json({ data: [] })
|
|
45
|
+
* );
|
|
46
|
+
*
|
|
47
|
+
* export const POST = withGrant(
|
|
48
|
+
* grantClient,
|
|
49
|
+
* { resource: 'Document', action: 'Create' },
|
|
50
|
+
* async (request) => { ... }
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function withGrant(client: GrantClient, options: GrantOptions, handler: GrantRouteHandler): (request: NextRequest, context?: {
|
|
55
|
+
params?: Promise<Record<string, string>>;
|
|
56
|
+
}) => Promise<Response>;
|
|
57
|
+
//# sourceMappingURL=with-grant.d.ts.map
|
|
@@ -0,0 +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;AAI9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAEtE;;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
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const server = require("next/server");
|
|
4
|
+
const debug = require("./debug-Crb5gNDM.cjs");
|
|
5
|
+
const grantClient = require("./grant-client-DywfJN5P.cjs");
|
|
6
|
+
const index = require("./index.cjs");
|
|
7
|
+
function withGrant(client, options, handler) {
|
|
8
|
+
return async (request, _routeContext) => {
|
|
9
|
+
debug.debugGrant("Next", { resource: options.resource, action: options.action });
|
|
10
|
+
const token = await grantClient.extractTokenFromRequest(request, client.config);
|
|
11
|
+
if (!token) {
|
|
12
|
+
return server.NextResponse.json({ error: "Unauthorized", code: "UNAUTHENTICATED" }, { status: 401 });
|
|
13
|
+
}
|
|
14
|
+
let resolvedResource = null;
|
|
15
|
+
if (options.resourceResolver) {
|
|
16
|
+
resolvedResource = await options.resourceResolver({
|
|
17
|
+
resourceSlug: options.resource,
|
|
18
|
+
request
|
|
19
|
+
});
|
|
20
|
+
if (!resolvedResource) {
|
|
21
|
+
return server.NextResponse.json(
|
|
22
|
+
{ error: "Resource not found", code: "NOT_FOUND" },
|
|
23
|
+
{ status: 404 }
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const result = await client.isAuthorized(
|
|
28
|
+
options.resource,
|
|
29
|
+
options.action,
|
|
30
|
+
{
|
|
31
|
+
context: {
|
|
32
|
+
resource: resolvedResource ?? void 0
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
request
|
|
36
|
+
);
|
|
37
|
+
debug.debugGrant("Next", {
|
|
38
|
+
resource: options.resource,
|
|
39
|
+
action: options.action,
|
|
40
|
+
authorized: result.authorized,
|
|
41
|
+
...result.authorized ? {} : { reason: result.reason }
|
|
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
|
+
};
|
|
55
|
+
}
|
|
56
|
+
exports.GrantClient = grantClient.GrantClient;
|
|
57
|
+
exports.AuthenticationError = index.AuthenticationError;
|
|
58
|
+
exports.AuthorizationError = index.AuthorizationError;
|
|
59
|
+
exports.BadRequestError = index.BadRequestError;
|
|
60
|
+
exports.NotFoundError = index.NotFoundError;
|
|
61
|
+
exports.withGrant = withGrant;
|
|
62
|
+
//# sourceMappingURL=next.cjs.map
|
|
@@ -0,0 +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 { debugGrant } from '../utils/debug';\nimport { extractTokenFromRequest } from '../utils/token-extractor';\n\nimport type { ResourceResolver, AuthorizationResult } from '../types';\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"],"names":["debugGrant","extractTokenFromRequest","NextResponse"],"mappings":";;;;;;AAgEO,SAAS,UACd,QACA,SACA,SAIqB;AACrB,SAAO,OACL,SACA,kBACsB;AACtBA,qBAAW,QAAQ,EAAE,UAAU,QAAQ,UAAU,QAAQ,QAAQ,QAAQ;AAEzE,UAAM,QAAQ,MAAMC,YAAAA,wBAAwB,SAAS,OAAO,MAAM;AAClE,QAAI,CAAC,OAAO;AACV,aAAOC,oBAAa,KAAK,EAAE,OAAO,gBAAgB,MAAM,qBAAqB,EAAE,QAAQ,KAAK;AAAA,IAC9F;AAEA,QAAI,mBAAmD;AAEvD,QAAI,QAAQ,kBAAkB;AAC5B,yBAAmB,MAAM,QAAQ,iBAAiB;AAAA,QAChD,cAAc,QAAQ;AAAA,QACtB;AAAA,MAAA,CACD;AACD,UAAI,CAAC,kBAAkB;AACrB,eAAOA,OAAAA,aAAa;AAAA,UAClB,EAAE,OAAO,sBAAsB,MAAM,YAAA;AAAA,UACrC,EAAE,QAAQ,IAAA;AAAA,QAAI;AAAA,MAElB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,UAAU,oBAAoB;AAAA,QAAA;AAAA,MAChC;AAAA,MAEF;AAAA,IAAA;AAGFF,UAAAA,WAAW,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB,GAAI,OAAO,aAAa,CAAA,IAAK,EAAE,QAAQ,OAAO,OAAA;AAAA,IAAO,CACtD;AAED,QAAI,CAAC,OAAO,YAAY;AACtB,aAAOE,OAAAA,aAAa;AAAA,QAClB;AAAA,UACE,OAAO;AAAA,UACP,MAAM;AAAA,UACN,QAAQ,OAAO;AAAA,QAAA;AAAA,QAEjB,EAAE,QAAQ,IAAA;AAAA,MAAI;AAAA,IAElB;AAEA,WAAO,QAAQ,SAAS,EAAE,eAAe,QAAQ;AAAA,EACnD;AACF;;;;;;;"}
|
package/dist/next.mjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { d as debugGrant } from "./debug-C8ibbrE5.js";
|
|
3
|
+
import { e as extractTokenFromRequest } from "./grant-client-D1LZI2f4.js";
|
|
4
|
+
import { G } from "./grant-client-D1LZI2f4.js";
|
|
5
|
+
import { AuthenticationError, AuthorizationError, BadRequestError, NotFoundError } from "./index.mjs";
|
|
6
|
+
function withGrant(client, options, handler) {
|
|
7
|
+
return async (request, _routeContext) => {
|
|
8
|
+
debugGrant("Next", { resource: options.resource, action: options.action });
|
|
9
|
+
const token = await extractTokenFromRequest(request, client.config);
|
|
10
|
+
if (!token) {
|
|
11
|
+
return NextResponse.json({ error: "Unauthorized", code: "UNAUTHENTICATED" }, { status: 401 });
|
|
12
|
+
}
|
|
13
|
+
let resolvedResource = null;
|
|
14
|
+
if (options.resourceResolver) {
|
|
15
|
+
resolvedResource = await options.resourceResolver({
|
|
16
|
+
resourceSlug: options.resource,
|
|
17
|
+
request
|
|
18
|
+
});
|
|
19
|
+
if (!resolvedResource) {
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ error: "Resource not found", code: "NOT_FOUND" },
|
|
22
|
+
{ status: 404 }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const result = await client.isAuthorized(
|
|
27
|
+
options.resource,
|
|
28
|
+
options.action,
|
|
29
|
+
{
|
|
30
|
+
context: {
|
|
31
|
+
resource: resolvedResource ?? void 0
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
request
|
|
35
|
+
);
|
|
36
|
+
debugGrant("Next", {
|
|
37
|
+
resource: options.resource,
|
|
38
|
+
action: options.action,
|
|
39
|
+
authorized: result.authorized,
|
|
40
|
+
...result.authorized ? {} : { reason: result.reason }
|
|
41
|
+
});
|
|
42
|
+
if (!result.authorized) {
|
|
43
|
+
return NextResponse.json(
|
|
44
|
+
{
|
|
45
|
+
error: "Forbidden",
|
|
46
|
+
code: "FORBIDDEN",
|
|
47
|
+
reason: result.reason
|
|
48
|
+
},
|
|
49
|
+
{ status: 403 }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return handler(request, { authorization: result });
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
AuthenticationError,
|
|
57
|
+
AuthorizationError,
|
|
58
|
+
BadRequestError,
|
|
59
|
+
G as GrantClient,
|
|
60
|
+
NotFoundError,
|
|
61
|
+
withGrant
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=next.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.mjs","sources":["../src/next/with-grant.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\n\nimport { GrantClient } from '../grant-client';\nimport { debugGrant } from '../utils/debug';\nimport { extractTokenFromRequest } from '../utils/token-extractor';\n\nimport type { ResourceResolver, AuthorizationResult } from '../types';\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"],"names":[],"mappings":";;;;;AAgEO,SAAS,UACd,QACA,SACA,SAIqB;AACrB,SAAO,OACL,SACA,kBACsB;AACtB,eAAW,QAAQ,EAAE,UAAU,QAAQ,UAAU,QAAQ,QAAQ,QAAQ;AAEzE,UAAM,QAAQ,MAAM,wBAAwB,SAAS,OAAO,MAAM;AAClE,QAAI,CAAC,OAAO;AACV,aAAO,aAAa,KAAK,EAAE,OAAO,gBAAgB,MAAM,qBAAqB,EAAE,QAAQ,KAAK;AAAA,IAC9F;AAEA,QAAI,mBAAmD;AAEvD,QAAI,QAAQ,kBAAkB;AAC5B,yBAAmB,MAAM,QAAQ,iBAAiB;AAAA,QAChD,cAAc,QAAQ;AAAA,QACtB;AAAA,MAAA,CACD;AACD,UAAI,CAAC,kBAAkB;AACrB,eAAO,aAAa;AAAA,UAClB,EAAE,OAAO,sBAAsB,MAAM,YAAA;AAAA,UACrC,EAAE,QAAQ,IAAA;AAAA,QAAI;AAAA,MAElB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,QACE,SAAS;AAAA,UACP,UAAU,oBAAoB;AAAA,QAAA;AAAA,MAChC;AAAA,MAEF;AAAA,IAAA;AAGF,eAAW,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB,GAAI,OAAO,aAAa,CAAA,IAAK,EAAE,QAAQ,OAAO,OAAA;AAAA,IAAO,CACtD;AAED,QAAI,CAAC,OAAO,YAAY;AACtB,aAAO,aAAa;AAAA,QAClB;AAAA,UACE,OAAO;AAAA,UACP,MAAM;AAAA,UACN,QAAQ,OAAO;AAAA,QAAA;AAAA,QAEjB,EAAE,QAAQ,IAAA;AAAA,MAAI;AAAA,IAElB;AAEA,WAAO,QAAQ,SAAS,EAAE,eAAe,QAAQ;AAAA,EACnD;AACF;"}
|