exguard-endpoint 1.0.1
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 +71 -0
- package/dist/index.cjs +238 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +206 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ExGuard Endpoint
|
|
2
|
+
|
|
3
|
+
Simple RBAC permission guard for NestJS.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install exguard-endpoint
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Setup
|
|
12
|
+
|
|
13
|
+
### 1. Configure AppModule
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Module } from '@nestjs/common';
|
|
17
|
+
import { ExGuardModule } from 'exguard-endpoint';
|
|
18
|
+
|
|
19
|
+
@Module({
|
|
20
|
+
imports: [
|
|
21
|
+
ExGuardModule.forRoot({
|
|
22
|
+
baseUrl: 'https://api.exguard.com',
|
|
23
|
+
apiKey: process.env.EXGUARD_API_KEY,
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
})
|
|
27
|
+
export class AppModule {}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Use in Controllers
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
|
|
34
|
+
import { ExGuardPermissionGuard, RequirePermissions } from 'exguard-endpoint';
|
|
35
|
+
|
|
36
|
+
@Controller('items')
|
|
37
|
+
@UseGuards(ExGuardPermissionGuard)
|
|
38
|
+
export class ItemsController {
|
|
39
|
+
|
|
40
|
+
// User needs ANY of these permissions
|
|
41
|
+
@Get()
|
|
42
|
+
@RequirePermissions(['item:read'])
|
|
43
|
+
findAll() { }
|
|
44
|
+
|
|
45
|
+
@Post()
|
|
46
|
+
@RequirePermissions(['item:create'])
|
|
47
|
+
create() { }
|
|
48
|
+
|
|
49
|
+
// Multiple permissions - needs ALL
|
|
50
|
+
@Delete(':id')
|
|
51
|
+
@RequirePermissions(['item:delete', 'admin'], true)
|
|
52
|
+
delete() { }
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Decorators
|
|
57
|
+
|
|
58
|
+
| Decorator | Description |
|
|
59
|
+
|-----------|-------------|
|
|
60
|
+
| `@RequirePermissions(['perm1'])` | User needs ANY of the permissions |
|
|
61
|
+
| `@RequirePermissions(['perm1', 'perm2'], true)` | User needs ALL permissions |
|
|
62
|
+
|
|
63
|
+
## Token
|
|
64
|
+
|
|
65
|
+
The guard extracts token from:
|
|
66
|
+
- `Authorization: Bearer <token>` header
|
|
67
|
+
- `x-access-token` header
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
21
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
22
|
+
if (decorator = decorators[i])
|
|
23
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
24
|
+
if (kind && result) __defProp(target, key, result);
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var src_exports = {};
|
|
31
|
+
__export(src_exports, {
|
|
32
|
+
EXGUARD_PERMISSIONS_KEY: () => EXGUARD_PERMISSIONS_KEY,
|
|
33
|
+
EXGUARD_ROLES_KEY: () => EXGUARD_ROLES_KEY,
|
|
34
|
+
ExGuardClient: () => ExGuardClient,
|
|
35
|
+
ExGuardModule: () => ExGuardModule,
|
|
36
|
+
ExGuardPermissionGuard: () => ExGuardPermissionGuard,
|
|
37
|
+
RequirePermissions: () => RequirePermissions,
|
|
38
|
+
RequireRoles: () => RequireRoles,
|
|
39
|
+
cache: () => cache,
|
|
40
|
+
createExGuardClient: () => createExGuardClient
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(src_exports);
|
|
43
|
+
|
|
44
|
+
// src/client.ts
|
|
45
|
+
var ExGuardCache = class {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
48
|
+
this.ttl = 3e5;
|
|
49
|
+
}
|
|
50
|
+
get(key) {
|
|
51
|
+
const entry = this.cache.get(key);
|
|
52
|
+
if (!entry) return null;
|
|
53
|
+
if (Date.now() - entry.timestamp > entry.ttl) {
|
|
54
|
+
this.cache.delete(key);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return entry.data;
|
|
58
|
+
}
|
|
59
|
+
set(key, data, ttl) {
|
|
60
|
+
this.cache.set(key, {
|
|
61
|
+
data,
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
ttl: ttl || this.ttl
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var cache = new ExGuardCache();
|
|
68
|
+
var ExGuardClient = class {
|
|
69
|
+
constructor(config) {
|
|
70
|
+
this.baseUrl = config.baseUrl;
|
|
71
|
+
this.apiKey = config.apiKey;
|
|
72
|
+
this.cache = cache;
|
|
73
|
+
}
|
|
74
|
+
async authenticate(context) {
|
|
75
|
+
const cacheKey = `auth:${context.token}`;
|
|
76
|
+
const cached = this.cache.get(cacheKey);
|
|
77
|
+
if (cached) return cached;
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({ token: context.token })
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
return { allowed: false, error: "Invalid token" };
|
|
89
|
+
}
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
const result = {
|
|
92
|
+
allowed: true,
|
|
93
|
+
user: data.user || data.data?.user
|
|
94
|
+
};
|
|
95
|
+
this.cache.set(cacheKey, result);
|
|
96
|
+
return result;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return { allowed: false, error: "Authentication failed" };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async hasPermission(token, permission) {
|
|
102
|
+
const result = await this.authenticate({ token });
|
|
103
|
+
if (!result.allowed || !result.user) return false;
|
|
104
|
+
const userPermissions = result.user.modules?.flatMap((m) => m.permissions) || [];
|
|
105
|
+
return userPermissions.includes(permission);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
function createExGuardClient(config) {
|
|
109
|
+
return new ExGuardClient(config);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/module.ts
|
|
113
|
+
var import_common = require("@nestjs/common");
|
|
114
|
+
var ExGuardModule = class {
|
|
115
|
+
static forRoot(options) {
|
|
116
|
+
const client = new ExGuardClient({
|
|
117
|
+
baseUrl: options.baseUrl,
|
|
118
|
+
apiKey: options.apiKey,
|
|
119
|
+
cache: options.cache
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
module: ExGuardModule,
|
|
123
|
+
providers: [
|
|
124
|
+
{
|
|
125
|
+
provide: "EXGUARD_CLIENT",
|
|
126
|
+
useValue: client
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
exports: ["EXGUARD_CLIENT"]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
ExGuardModule = __decorateClass([
|
|
134
|
+
(0, import_common.Global)(),
|
|
135
|
+
(0, import_common.Module)({})
|
|
136
|
+
], ExGuardModule);
|
|
137
|
+
|
|
138
|
+
// src/guard.ts
|
|
139
|
+
var import_common2 = require("@nestjs/common");
|
|
140
|
+
var EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
141
|
+
var EXGUARD_ROLES_KEY = "exguard_roles";
|
|
142
|
+
var ExGuardPermissionGuard = class {
|
|
143
|
+
constructor(client, reflector) {
|
|
144
|
+
this.client = client;
|
|
145
|
+
this.reflector = reflector;
|
|
146
|
+
}
|
|
147
|
+
async canActivate(context) {
|
|
148
|
+
const request = context.switchToHttp().getRequest();
|
|
149
|
+
const token = this.extractToken(request);
|
|
150
|
+
if (!token) {
|
|
151
|
+
throw new import_common2.UnauthorizedException("No token provided");
|
|
152
|
+
}
|
|
153
|
+
if (!this.client) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
const authResult = await this.client.authenticate({ token, request });
|
|
157
|
+
if (!authResult.allowed) {
|
|
158
|
+
throw new import_common2.ForbiddenException(authResult.error || "Access denied");
|
|
159
|
+
}
|
|
160
|
+
if (!authResult.user) {
|
|
161
|
+
throw new import_common2.ForbiddenException("User not found");
|
|
162
|
+
}
|
|
163
|
+
const handler = context.getHandler();
|
|
164
|
+
const permMeta = this.reflector.get(
|
|
165
|
+
EXGUARD_PERMISSIONS_KEY,
|
|
166
|
+
handler
|
|
167
|
+
);
|
|
168
|
+
if (permMeta) {
|
|
169
|
+
const { permissions, requireAll } = permMeta;
|
|
170
|
+
const userPermissions = authResult.user.modules?.flatMap((m) => m.permissions) || [];
|
|
171
|
+
if (requireAll) {
|
|
172
|
+
if (!permissions.every((p) => userPermissions.includes(p))) {
|
|
173
|
+
throw new import_common2.ForbiddenException("Insufficient permissions");
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
if (!permissions.some((p) => userPermissions.includes(p))) {
|
|
177
|
+
throw new import_common2.ForbiddenException("Insufficient permissions");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const roleMeta = this.reflector.get(
|
|
182
|
+
EXGUARD_ROLES_KEY,
|
|
183
|
+
handler
|
|
184
|
+
);
|
|
185
|
+
if (roleMeta) {
|
|
186
|
+
const { roles, requireAll } = roleMeta;
|
|
187
|
+
const userRoles = authResult.user.roles || [];
|
|
188
|
+
if (requireAll) {
|
|
189
|
+
if (!roles.every((r) => userRoles.includes(r))) {
|
|
190
|
+
throw new import_common2.ForbiddenException("Insufficient roles");
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
if (!roles.some((r) => userRoles.includes(r))) {
|
|
194
|
+
throw new import_common2.ForbiddenException("Insufficient roles");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
request.user = authResult.user;
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
extractToken(request) {
|
|
202
|
+
const auth = request.headers?.authorization;
|
|
203
|
+
if (auth?.startsWith("Bearer ")) {
|
|
204
|
+
return auth.substring(7);
|
|
205
|
+
}
|
|
206
|
+
return request.headers?.["x-access-token"] || null;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
ExGuardPermissionGuard = __decorateClass([
|
|
210
|
+
(0, import_common2.Injectable)(),
|
|
211
|
+
__decorateParam(0, (0, import_common2.Optional)()),
|
|
212
|
+
__decorateParam(0, (0, import_common2.Inject)("EXGUARD_CLIENT"))
|
|
213
|
+
], ExGuardPermissionGuard);
|
|
214
|
+
function RequirePermissions(permissions, requireAll = false) {
|
|
215
|
+
return function(target, propertyKey, descriptor) {
|
|
216
|
+
Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);
|
|
217
|
+
return descriptor;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function RequireRoles(roles, requireAll = false) {
|
|
221
|
+
return function(target, propertyKey, descriptor) {
|
|
222
|
+
Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);
|
|
223
|
+
return descriptor;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
227
|
+
0 && (module.exports = {
|
|
228
|
+
EXGUARD_PERMISSIONS_KEY,
|
|
229
|
+
EXGUARD_ROLES_KEY,
|
|
230
|
+
ExGuardClient,
|
|
231
|
+
ExGuardModule,
|
|
232
|
+
ExGuardPermissionGuard,
|
|
233
|
+
RequirePermissions,
|
|
234
|
+
RequireRoles,
|
|
235
|
+
cache,
|
|
236
|
+
createExGuardClient
|
|
237
|
+
});
|
|
238
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/module.ts","../src/guard.ts"],"sourcesContent":["export { ExGuardClient, createExGuardClient, cache } from './client.js';\nexport type { ExGuardConfig, User, ModulePermission, UserAccessResponse, GuardContext, GuardResult } from './client.js';\nexport { ExGuardModule } from './module.js';\nexport type { ExGuardModuleOptions } from './module.js';\nexport { ExGuardPermissionGuard, RequirePermissions, RequireRoles, EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY } from './guard.js';\n","export interface ExGuardConfig {\n baseUrl: string;\n apiKey: string;\n cache?: {\n enabled?: boolean;\n ttl?: number;\n };\n}\n\nexport interface User {\n id: string;\n username: string;\n email: string;\n}\n\nexport interface ModulePermission {\n key: string;\n name: string;\n permissions: string[];\n}\n\nexport interface UserAccessResponse {\n user: User;\n roles: string[];\n modules: ModulePermission[];\n}\n\nexport interface GuardContext {\n token: string;\n request?: any;\n}\n\nexport interface GuardResult {\n allowed: boolean;\n user?: UserAccessResponse;\n error?: string;\n}\n\nclass ExGuardCache {\n private cache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n private ttl = 300000;\n\n get(key: string): any {\n const entry = this.cache.get(key);\n if (!entry) return null;\n if (Date.now() - entry.timestamp > entry.ttl) {\n this.cache.delete(key);\n return null;\n }\n return entry.data;\n }\n\n set(key: string, data: any, ttl?: number): void {\n this.cache.set(key, {\n data,\n timestamp: Date.now(),\n ttl: ttl || this.ttl,\n });\n }\n}\n\nconst cache = new ExGuardCache();\n\nexport class ExGuardClient {\n private baseUrl: string;\n private apiKey: string;\n private cache: ExGuardCache;\n\n constructor(config: ExGuardConfig) {\n this.baseUrl = config.baseUrl;\n this.apiKey = config.apiKey;\n this.cache = cache;\n }\n\n async authenticate(context: GuardContext): Promise<GuardResult> {\n const cacheKey = `auth:${context.token}`;\n const cached = this.cache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ token: context.token }),\n });\n\n if (!response.ok) {\n return { allowed: false, error: 'Invalid token' };\n }\n\n const data = await response.json();\n const result: GuardResult = {\n allowed: true,\n user: data.user || data.data?.user,\n };\n\n this.cache.set(cacheKey, result);\n return result;\n } catch (error) {\n return { allowed: false, error: 'Authentication failed' };\n }\n }\n\n async hasPermission(token: string, permission: string): Promise<boolean> {\n const result = await this.authenticate({ token });\n if (!result.allowed || !result.user) return false;\n\n const userPermissions = result.user.modules?.flatMap(m => m.permissions) || [];\n return userPermissions.includes(permission);\n }\n}\n\nexport function createExGuardClient(config: ExGuardConfig): ExGuardClient {\n return new ExGuardClient(config);\n}\n\nexport { cache };\n","import { Module, Global, DynamicModule } from '@nestjs/common';\nimport { ExGuardClient } from './client.js';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n apiKey: string;\n cache?: {\n enabled?: boolean;\n ttl?: number;\n };\n}\n\n@Global()\n@Module({})\nexport class ExGuardModule {\n static forRoot(options: ExGuardModuleOptions): DynamicModule {\n const client = new ExGuardClient({\n baseUrl: options.baseUrl,\n apiKey: options.apiKey,\n cache: options.cache,\n });\n\n return {\n module: ExGuardModule,\n providers: [\n {\n provide: 'EXGUARD_CLIENT',\n useValue: client,\n },\n ],\n exports: ['EXGUARD_CLIENT'],\n };\n }\n}\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { ExGuardClient } from './client.js';\n\nexport const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';\nexport const EXGUARD_ROLES_KEY = 'exguard_roles';\n\n@Injectable()\nexport class ExGuardPermissionGuard implements CanActivate {\n constructor(\n @Optional() @Inject('EXGUARD_CLIENT') private client: ExGuardClient,\n private reflector: Reflector,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest();\n const token = this.extractToken(request);\n\n if (!token) {\n throw new UnauthorizedException('No token provided');\n }\n\n if (!this.client) {\n return true;\n }\n\n const authResult = await this.client.authenticate({ token, request });\n\n if (!authResult.allowed) {\n throw new ForbiddenException(authResult.error || 'Access denied');\n }\n\n if (!authResult.user) {\n throw new ForbiddenException('User not found');\n }\n\n const handler = context.getHandler();\n const permMeta = this.reflector.get<{ permissions: string[]; requireAll?: boolean }>(\n EXGUARD_PERMISSIONS_KEY,\n handler,\n );\n\n if (permMeta) {\n const { permissions, requireAll } = permMeta;\n const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];\n\n if (requireAll) {\n if (!permissions.every(p => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n } else {\n if (!permissions.some(p => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n }\n }\n\n const roleMeta = this.reflector.get<{ roles: string[]; requireAll?: boolean }>(\n EXGUARD_ROLES_KEY,\n handler,\n );\n\n if (roleMeta) {\n const { roles, requireAll } = roleMeta;\n const userRoles = authResult.user.roles || [];\n\n if (requireAll) {\n if (!roles.every(r => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n } else {\n if (!roles.some(r => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n }\n }\n\n request.user = authResult.user;\n return true;\n }\n\n private extractToken(request: any): string | null {\n const auth = request.headers?.authorization;\n if (auth?.startsWith('Bearer ')) {\n return auth.substring(7);\n }\n return request.headers?.['x-access-token'] || null;\n }\n}\n\nexport function RequirePermissions(permissions: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function RequireRoles(roles: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n"],"mappings":"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","names":["import_common"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DynamicModule, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
|
|
4
|
+
interface ExGuardConfig {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
cache?: {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
ttl?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
interface User {
|
|
13
|
+
id: string;
|
|
14
|
+
username: string;
|
|
15
|
+
email: string;
|
|
16
|
+
}
|
|
17
|
+
interface ModulePermission {
|
|
18
|
+
key: string;
|
|
19
|
+
name: string;
|
|
20
|
+
permissions: string[];
|
|
21
|
+
}
|
|
22
|
+
interface UserAccessResponse {
|
|
23
|
+
user: User;
|
|
24
|
+
roles: string[];
|
|
25
|
+
modules: ModulePermission[];
|
|
26
|
+
}
|
|
27
|
+
interface GuardContext {
|
|
28
|
+
token: string;
|
|
29
|
+
request?: any;
|
|
30
|
+
}
|
|
31
|
+
interface GuardResult {
|
|
32
|
+
allowed: boolean;
|
|
33
|
+
user?: UserAccessResponse;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
declare class ExGuardCache {
|
|
37
|
+
private cache;
|
|
38
|
+
private ttl;
|
|
39
|
+
get(key: string): any;
|
|
40
|
+
set(key: string, data: any, ttl?: number): void;
|
|
41
|
+
}
|
|
42
|
+
declare const cache: ExGuardCache;
|
|
43
|
+
declare class ExGuardClient {
|
|
44
|
+
private baseUrl;
|
|
45
|
+
private apiKey;
|
|
46
|
+
private cache;
|
|
47
|
+
constructor(config: ExGuardConfig);
|
|
48
|
+
authenticate(context: GuardContext): Promise<GuardResult>;
|
|
49
|
+
hasPermission(token: string, permission: string): Promise<boolean>;
|
|
50
|
+
}
|
|
51
|
+
declare function createExGuardClient(config: ExGuardConfig): ExGuardClient;
|
|
52
|
+
|
|
53
|
+
interface ExGuardModuleOptions {
|
|
54
|
+
baseUrl: string;
|
|
55
|
+
apiKey: string;
|
|
56
|
+
cache?: {
|
|
57
|
+
enabled?: boolean;
|
|
58
|
+
ttl?: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
declare class ExGuardModule {
|
|
62
|
+
static forRoot(options: ExGuardModuleOptions): DynamicModule;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare const EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
66
|
+
declare const EXGUARD_ROLES_KEY = "exguard_roles";
|
|
67
|
+
declare class ExGuardPermissionGuard implements CanActivate {
|
|
68
|
+
private client;
|
|
69
|
+
private reflector;
|
|
70
|
+
constructor(client: ExGuardClient, reflector: Reflector);
|
|
71
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
72
|
+
private extractToken;
|
|
73
|
+
}
|
|
74
|
+
declare function RequirePermissions(permissions: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
75
|
+
declare function RequireRoles(roles: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
76
|
+
|
|
77
|
+
export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardClient, type ExGuardConfig, ExGuardModule, type ExGuardModuleOptions, ExGuardPermissionGuard, type GuardContext, type GuardResult, type ModulePermission, RequirePermissions, RequireRoles, type User, type UserAccessResponse, cache, createExGuardClient };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DynamicModule, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
|
|
4
|
+
interface ExGuardConfig {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
cache?: {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
ttl?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
interface User {
|
|
13
|
+
id: string;
|
|
14
|
+
username: string;
|
|
15
|
+
email: string;
|
|
16
|
+
}
|
|
17
|
+
interface ModulePermission {
|
|
18
|
+
key: string;
|
|
19
|
+
name: string;
|
|
20
|
+
permissions: string[];
|
|
21
|
+
}
|
|
22
|
+
interface UserAccessResponse {
|
|
23
|
+
user: User;
|
|
24
|
+
roles: string[];
|
|
25
|
+
modules: ModulePermission[];
|
|
26
|
+
}
|
|
27
|
+
interface GuardContext {
|
|
28
|
+
token: string;
|
|
29
|
+
request?: any;
|
|
30
|
+
}
|
|
31
|
+
interface GuardResult {
|
|
32
|
+
allowed: boolean;
|
|
33
|
+
user?: UserAccessResponse;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
declare class ExGuardCache {
|
|
37
|
+
private cache;
|
|
38
|
+
private ttl;
|
|
39
|
+
get(key: string): any;
|
|
40
|
+
set(key: string, data: any, ttl?: number): void;
|
|
41
|
+
}
|
|
42
|
+
declare const cache: ExGuardCache;
|
|
43
|
+
declare class ExGuardClient {
|
|
44
|
+
private baseUrl;
|
|
45
|
+
private apiKey;
|
|
46
|
+
private cache;
|
|
47
|
+
constructor(config: ExGuardConfig);
|
|
48
|
+
authenticate(context: GuardContext): Promise<GuardResult>;
|
|
49
|
+
hasPermission(token: string, permission: string): Promise<boolean>;
|
|
50
|
+
}
|
|
51
|
+
declare function createExGuardClient(config: ExGuardConfig): ExGuardClient;
|
|
52
|
+
|
|
53
|
+
interface ExGuardModuleOptions {
|
|
54
|
+
baseUrl: string;
|
|
55
|
+
apiKey: string;
|
|
56
|
+
cache?: {
|
|
57
|
+
enabled?: boolean;
|
|
58
|
+
ttl?: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
declare class ExGuardModule {
|
|
62
|
+
static forRoot(options: ExGuardModuleOptions): DynamicModule;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare const EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
66
|
+
declare const EXGUARD_ROLES_KEY = "exguard_roles";
|
|
67
|
+
declare class ExGuardPermissionGuard implements CanActivate {
|
|
68
|
+
private client;
|
|
69
|
+
private reflector;
|
|
70
|
+
constructor(client: ExGuardClient, reflector: Reflector);
|
|
71
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
72
|
+
private extractToken;
|
|
73
|
+
}
|
|
74
|
+
declare function RequirePermissions(permissions: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
75
|
+
declare function RequireRoles(roles: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
76
|
+
|
|
77
|
+
export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardClient, type ExGuardConfig, ExGuardModule, type ExGuardModuleOptions, ExGuardPermissionGuard, type GuardContext, type GuardResult, type ModulePermission, RequirePermissions, RequireRoles, type User, type UserAccessResponse, cache, createExGuardClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
12
|
+
|
|
13
|
+
// src/client.ts
|
|
14
|
+
var ExGuardCache = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
17
|
+
this.ttl = 3e5;
|
|
18
|
+
}
|
|
19
|
+
get(key) {
|
|
20
|
+
const entry = this.cache.get(key);
|
|
21
|
+
if (!entry) return null;
|
|
22
|
+
if (Date.now() - entry.timestamp > entry.ttl) {
|
|
23
|
+
this.cache.delete(key);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return entry.data;
|
|
27
|
+
}
|
|
28
|
+
set(key, data, ttl) {
|
|
29
|
+
this.cache.set(key, {
|
|
30
|
+
data,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
ttl: ttl || this.ttl
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var cache = new ExGuardCache();
|
|
37
|
+
var ExGuardClient = class {
|
|
38
|
+
constructor(config) {
|
|
39
|
+
this.baseUrl = config.baseUrl;
|
|
40
|
+
this.apiKey = config.apiKey;
|
|
41
|
+
this.cache = cache;
|
|
42
|
+
}
|
|
43
|
+
async authenticate(context) {
|
|
44
|
+
const cacheKey = `auth:${context.token}`;
|
|
45
|
+
const cached = this.cache.get(cacheKey);
|
|
46
|
+
if (cached) return cached;
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({ token: context.token })
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
return { allowed: false, error: "Invalid token" };
|
|
58
|
+
}
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
const result = {
|
|
61
|
+
allowed: true,
|
|
62
|
+
user: data.user || data.data?.user
|
|
63
|
+
};
|
|
64
|
+
this.cache.set(cacheKey, result);
|
|
65
|
+
return result;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return { allowed: false, error: "Authentication failed" };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async hasPermission(token, permission) {
|
|
71
|
+
const result = await this.authenticate({ token });
|
|
72
|
+
if (!result.allowed || !result.user) return false;
|
|
73
|
+
const userPermissions = result.user.modules?.flatMap((m) => m.permissions) || [];
|
|
74
|
+
return userPermissions.includes(permission);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
function createExGuardClient(config) {
|
|
78
|
+
return new ExGuardClient(config);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/module.ts
|
|
82
|
+
import { Module, Global } from "@nestjs/common";
|
|
83
|
+
var ExGuardModule = class {
|
|
84
|
+
static forRoot(options) {
|
|
85
|
+
const client = new ExGuardClient({
|
|
86
|
+
baseUrl: options.baseUrl,
|
|
87
|
+
apiKey: options.apiKey,
|
|
88
|
+
cache: options.cache
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
module: ExGuardModule,
|
|
92
|
+
providers: [
|
|
93
|
+
{
|
|
94
|
+
provide: "EXGUARD_CLIENT",
|
|
95
|
+
useValue: client
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
exports: ["EXGUARD_CLIENT"]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
ExGuardModule = __decorateClass([
|
|
103
|
+
Global(),
|
|
104
|
+
Module({})
|
|
105
|
+
], ExGuardModule);
|
|
106
|
+
|
|
107
|
+
// src/guard.ts
|
|
108
|
+
import { Injectable, UnauthorizedException, ForbiddenException, Inject, Optional } from "@nestjs/common";
|
|
109
|
+
var EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
110
|
+
var EXGUARD_ROLES_KEY = "exguard_roles";
|
|
111
|
+
var ExGuardPermissionGuard = class {
|
|
112
|
+
constructor(client, reflector) {
|
|
113
|
+
this.client = client;
|
|
114
|
+
this.reflector = reflector;
|
|
115
|
+
}
|
|
116
|
+
async canActivate(context) {
|
|
117
|
+
const request = context.switchToHttp().getRequest();
|
|
118
|
+
const token = this.extractToken(request);
|
|
119
|
+
if (!token) {
|
|
120
|
+
throw new UnauthorizedException("No token provided");
|
|
121
|
+
}
|
|
122
|
+
if (!this.client) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const authResult = await this.client.authenticate({ token, request });
|
|
126
|
+
if (!authResult.allowed) {
|
|
127
|
+
throw new ForbiddenException(authResult.error || "Access denied");
|
|
128
|
+
}
|
|
129
|
+
if (!authResult.user) {
|
|
130
|
+
throw new ForbiddenException("User not found");
|
|
131
|
+
}
|
|
132
|
+
const handler = context.getHandler();
|
|
133
|
+
const permMeta = this.reflector.get(
|
|
134
|
+
EXGUARD_PERMISSIONS_KEY,
|
|
135
|
+
handler
|
|
136
|
+
);
|
|
137
|
+
if (permMeta) {
|
|
138
|
+
const { permissions, requireAll } = permMeta;
|
|
139
|
+
const userPermissions = authResult.user.modules?.flatMap((m) => m.permissions) || [];
|
|
140
|
+
if (requireAll) {
|
|
141
|
+
if (!permissions.every((p) => userPermissions.includes(p))) {
|
|
142
|
+
throw new ForbiddenException("Insufficient permissions");
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
if (!permissions.some((p) => userPermissions.includes(p))) {
|
|
146
|
+
throw new ForbiddenException("Insufficient permissions");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const roleMeta = this.reflector.get(
|
|
151
|
+
EXGUARD_ROLES_KEY,
|
|
152
|
+
handler
|
|
153
|
+
);
|
|
154
|
+
if (roleMeta) {
|
|
155
|
+
const { roles, requireAll } = roleMeta;
|
|
156
|
+
const userRoles = authResult.user.roles || [];
|
|
157
|
+
if (requireAll) {
|
|
158
|
+
if (!roles.every((r) => userRoles.includes(r))) {
|
|
159
|
+
throw new ForbiddenException("Insufficient roles");
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
if (!roles.some((r) => userRoles.includes(r))) {
|
|
163
|
+
throw new ForbiddenException("Insufficient roles");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
request.user = authResult.user;
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
extractToken(request) {
|
|
171
|
+
const auth = request.headers?.authorization;
|
|
172
|
+
if (auth?.startsWith("Bearer ")) {
|
|
173
|
+
return auth.substring(7);
|
|
174
|
+
}
|
|
175
|
+
return request.headers?.["x-access-token"] || null;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
ExGuardPermissionGuard = __decorateClass([
|
|
179
|
+
Injectable(),
|
|
180
|
+
__decorateParam(0, Optional()),
|
|
181
|
+
__decorateParam(0, Inject("EXGUARD_CLIENT"))
|
|
182
|
+
], ExGuardPermissionGuard);
|
|
183
|
+
function RequirePermissions(permissions, requireAll = false) {
|
|
184
|
+
return function(target, propertyKey, descriptor) {
|
|
185
|
+
Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);
|
|
186
|
+
return descriptor;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function RequireRoles(roles, requireAll = false) {
|
|
190
|
+
return function(target, propertyKey, descriptor) {
|
|
191
|
+
Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);
|
|
192
|
+
return descriptor;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export {
|
|
196
|
+
EXGUARD_PERMISSIONS_KEY,
|
|
197
|
+
EXGUARD_ROLES_KEY,
|
|
198
|
+
ExGuardClient,
|
|
199
|
+
ExGuardModule,
|
|
200
|
+
ExGuardPermissionGuard,
|
|
201
|
+
RequirePermissions,
|
|
202
|
+
RequireRoles,
|
|
203
|
+
cache,
|
|
204
|
+
createExGuardClient
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/module.ts","../src/guard.ts"],"sourcesContent":["export interface ExGuardConfig {\n baseUrl: string;\n apiKey: string;\n cache?: {\n enabled?: boolean;\n ttl?: number;\n };\n}\n\nexport interface User {\n id: string;\n username: string;\n email: string;\n}\n\nexport interface ModulePermission {\n key: string;\n name: string;\n permissions: string[];\n}\n\nexport interface UserAccessResponse {\n user: User;\n roles: string[];\n modules: ModulePermission[];\n}\n\nexport interface GuardContext {\n token: string;\n request?: any;\n}\n\nexport interface GuardResult {\n allowed: boolean;\n user?: UserAccessResponse;\n error?: string;\n}\n\nclass ExGuardCache {\n private cache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n private ttl = 300000;\n\n get(key: string): any {\n const entry = this.cache.get(key);\n if (!entry) return null;\n if (Date.now() - entry.timestamp > entry.ttl) {\n this.cache.delete(key);\n return null;\n }\n return entry.data;\n }\n\n set(key: string, data: any, ttl?: number): void {\n this.cache.set(key, {\n data,\n timestamp: Date.now(),\n ttl: ttl || this.ttl,\n });\n }\n}\n\nconst cache = new ExGuardCache();\n\nexport class ExGuardClient {\n private baseUrl: string;\n private apiKey: string;\n private cache: ExGuardCache;\n\n constructor(config: ExGuardConfig) {\n this.baseUrl = config.baseUrl;\n this.apiKey = config.apiKey;\n this.cache = cache;\n }\n\n async authenticate(context: GuardContext): Promise<GuardResult> {\n const cacheKey = `auth:${context.token}`;\n const cached = this.cache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ token: context.token }),\n });\n\n if (!response.ok) {\n return { allowed: false, error: 'Invalid token' };\n }\n\n const data = await response.json();\n const result: GuardResult = {\n allowed: true,\n user: data.user || data.data?.user,\n };\n\n this.cache.set(cacheKey, result);\n return result;\n } catch (error) {\n return { allowed: false, error: 'Authentication failed' };\n }\n }\n\n async hasPermission(token: string, permission: string): Promise<boolean> {\n const result = await this.authenticate({ token });\n if (!result.allowed || !result.user) return false;\n\n const userPermissions = result.user.modules?.flatMap(m => m.permissions) || [];\n return userPermissions.includes(permission);\n }\n}\n\nexport function createExGuardClient(config: ExGuardConfig): ExGuardClient {\n return new ExGuardClient(config);\n}\n\nexport { cache };\n","import { Module, Global, DynamicModule } from '@nestjs/common';\nimport { ExGuardClient } from './client.js';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n apiKey: string;\n cache?: {\n enabled?: boolean;\n ttl?: number;\n };\n}\n\n@Global()\n@Module({})\nexport class ExGuardModule {\n static forRoot(options: ExGuardModuleOptions): DynamicModule {\n const client = new ExGuardClient({\n baseUrl: options.baseUrl,\n apiKey: options.apiKey,\n cache: options.cache,\n });\n\n return {\n module: ExGuardModule,\n providers: [\n {\n provide: 'EXGUARD_CLIENT',\n useValue: client,\n },\n ],\n exports: ['EXGUARD_CLIENT'],\n };\n }\n}\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { ExGuardClient } from './client.js';\n\nexport const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';\nexport const EXGUARD_ROLES_KEY = 'exguard_roles';\n\n@Injectable()\nexport class ExGuardPermissionGuard implements CanActivate {\n constructor(\n @Optional() @Inject('EXGUARD_CLIENT') private client: ExGuardClient,\n private reflector: Reflector,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest();\n const token = this.extractToken(request);\n\n if (!token) {\n throw new UnauthorizedException('No token provided');\n }\n\n if (!this.client) {\n return true;\n }\n\n const authResult = await this.client.authenticate({ token, request });\n\n if (!authResult.allowed) {\n throw new ForbiddenException(authResult.error || 'Access denied');\n }\n\n if (!authResult.user) {\n throw new ForbiddenException('User not found');\n }\n\n const handler = context.getHandler();\n const permMeta = this.reflector.get<{ permissions: string[]; requireAll?: boolean }>(\n EXGUARD_PERMISSIONS_KEY,\n handler,\n );\n\n if (permMeta) {\n const { permissions, requireAll } = permMeta;\n const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];\n\n if (requireAll) {\n if (!permissions.every(p => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n } else {\n if (!permissions.some(p => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n }\n }\n\n const roleMeta = this.reflector.get<{ roles: string[]; requireAll?: boolean }>(\n EXGUARD_ROLES_KEY,\n handler,\n );\n\n if (roleMeta) {\n const { roles, requireAll } = roleMeta;\n const userRoles = authResult.user.roles || [];\n\n if (requireAll) {\n if (!roles.every(r => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n } else {\n if (!roles.some(r => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n }\n }\n\n request.user = authResult.user;\n return true;\n }\n\n private extractToken(request: any): string | null {\n const auth = request.headers?.authorization;\n if (auth?.startsWith('Bearer ')) {\n return auth.substring(7);\n }\n return request.headers?.['x-access-token'] || null;\n }\n}\n\nexport function RequirePermissions(permissions: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function RequireRoles(roles: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "exguard-endpoint",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"description": "Simple RBAC permission guard for NestJS",
|
|
10
|
+
"main": "./dist/index.cjs",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
26
|
+
"prebuild": "npm run clean"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"exguard",
|
|
30
|
+
"rbac",
|
|
31
|
+
"nestjs",
|
|
32
|
+
"permissions",
|
|
33
|
+
"guards"
|
|
34
|
+
],
|
|
35
|
+
"author": "EmpowerX",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"axios": "^1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@nestjs/common": "^10.0.0",
|
|
42
|
+
"@nestjs/core": "^10.0.0",
|
|
43
|
+
"@types/node": "^22.10.5",
|
|
44
|
+
"tsup": "^8.3.5",
|
|
45
|
+
"typescript": "^5.7.3"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@nestjs/common": "^10.0.0",
|
|
49
|
+
"@nestjs/core": "^10.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|