exguard-endpoint 1.0.6 → 1.0.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # ExGuard Endpoint
2
2
 
3
- Simple RBAC permission guard for NestJS with realtime support.
3
+ Simple RBAC permission guard for NestJS with built-in caching.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,162 +8,68 @@ Simple RBAC permission guard for NestJS with realtime support.
8
8
  npm install exguard-endpoint
9
9
  ```
10
10
 
11
- ## Quick Setup (Recommended)
12
-
13
- Run in your NestJS project root:
11
+ ## Quick Setup (Auto-Configures Everything)
14
12
 
15
13
  ```bash
16
14
  npx exguard-endpoint setup
17
15
  ```
18
16
 
19
- This creates:
20
- - `src/exguard/exguard.guard.ts` - The permission guard
21
- - `src/exguard/exguard.module.ts` - The module
22
-
23
- Then follow the printed instructions.
17
+ This will:
18
+ 1. Create `src/exguard/exguard.guard.ts`
19
+ 2. Create `src/exguard/exguard.module.ts`
20
+ 3. Update `src/app.module.ts` with ExGuardModule
21
+ 4. Create `.env.example` with EXGUARD_BASE_URL
24
22
 
25
- ## Manual Setup
26
-
27
- ### 1. Configure AppModule
23
+ ## Usage
28
24
 
29
25
  ```typescript
30
- // src/app.module.ts
31
- import { Module } from '@nestjs/common';
32
- import { ExGuardModule } from 'exguard-endpoint';
33
-
34
- @Module({
35
- imports: [
36
- ExGuardModule.forRoot({
37
- baseUrl: process.env.EXGUARD_BASE_URL,
38
- realtime: {
39
- enabled: true,
40
- url: process.env.EXGUARD_BASE_URL?.replace(/^http/, 'ws') + '/realtime',
41
- },
42
- }),
43
- ],
44
- })
45
- export class AppModule {}
46
- ```
26
+ // app.module.ts is auto-configured!
27
+ // Just use in controllers:
47
28
 
48
- ### 2. Use in Controllers
49
-
50
- ```typescript
51
- // src/items/items.controller.ts
52
- import { Controller, Get, Post, Patch, Delete, UseGuards } from '@nestjs/common';
53
- import { ExGuardPermissionGuard, RequirePermissions } from '../exguard/exguard.guard';
29
+ import { ExGuardPermissionGuard, RequirePermissions } from './exguard/exguard.guard';
54
30
 
55
31
  @Controller('items')
56
32
  @UseGuards(ExGuardPermissionGuard)
57
33
  export class ItemsController {
58
34
 
59
- // User needs ANY of these permissions
60
35
  @Get()
61
36
  @RequirePermissions(['item:read'])
62
37
  findAll() { }
63
38
 
64
- // Multiple permissions - needs ALL
65
39
  @Post()
66
- @RequirePermissions(['item:create', 'item:admin'], true)
40
+ @RequirePermissions(['item:create'])
67
41
  create() { }
68
-
69
- @Patch(':id')
70
- @RequirePermissions(['item:update'])
71
- update() { }
72
-
73
- @Delete(':id')
74
- @RequirePermissions(['item:delete', 'admin'], true)
75
- delete() { }
76
42
  }
77
43
  ```
78
44
 
79
- ## Environment Variables
80
-
81
- Add to your `.env` file:
45
+ ## Environment
82
46
 
83
47
  ```env
84
- EXGUARD_BASE_URL=https://your-guard-api.com
48
+ EXGUARD_BASE_URL=http://localhost:3000
85
49
  ```
86
50
 
87
- The package automatically handles:
88
- - REST API at `/guard/verify-token` and `/guard/me`
89
- - Realtime WebSocket at `/realtime` namespace
90
-
91
- ## How It Works
51
+ ## Caching
92
52
 
93
- 1. Extracts Bearer token from request header
94
- 2. Calls `/guard/verify-token` to validate the Cognito token
95
- 3. Calls `/guard/me` to get user access (roles, permissions, modules)
96
- 4. Connects to realtime WebSocket for live updates
97
- 5. Checks if user has required permissions
98
- 6. Returns user object in `request.user`
53
+ - **TTL**: 5 minutes
54
+ - **Clear cache**: `clearExGuardCache()`
99
55
 
100
- ## API Reference
101
-
102
- ### Decorators
56
+ ## Decorators
103
57
 
104
58
  | Decorator | Description |
105
59
  |-----------|-------------|
106
- | `@RequirePermissions(['item:read'])` | User needs ANY of the listed permissions |
107
- | `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL listed permissions |
108
-
109
- ### ExGuardModule.forRoot Options
110
-
111
- | Option | Type | Required | Default | Description |
112
- |--------|------|----------|---------|-------------|
113
- | baseUrl | string | Yes | - | Guard API base URL |
114
- | cache.enabled | boolean | No | true | Enable caching |
115
- | cache.ttl | number | No | 300000 | Cache TTL in ms (5 min) |
116
- | realtime.enabled | boolean | No | false | Enable realtime connection |
117
- | realtime.url | string | No | - | Realtime WebSocket URL |
60
+ | `@RequirePermissions(['item:read'])` | User needs ANY permission |
61
+ | `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL permissions |
118
62
 
119
- ### Token Extraction
120
-
121
- The guard automatically extracts token from:
122
- - `Authorization: Bearer <token>` header
123
- - `x-access-token` header
124
-
125
- ### Request User
126
-
127
- After successful authentication, the user object is available at:
128
- ```typescript
129
- @Request() req: any
130
- console.log(req.user); // { id, username, email, roles, modules, groups, fieldOffices }
131
- ```
132
-
133
- ## User Object Structure
63
+ ## User Object
134
64
 
135
65
  ```typescript
136
- {
137
- user: {
138
- id: string,
139
- cognitoSubId: string,
140
- username: string,
141
- email: string,
142
- emailVerified: boolean,
143
- givenName: string,
144
- familyName: string,
145
- employeeNumber: string,
146
- regionId: string,
147
- fieldOffice: { id, code, name }
148
- },
149
- groups: string[],
66
+ req.user = {
67
+ user: { id, username, email, ... },
150
68
  roles: string[],
151
- modules: [
152
- { key: 'events', name: 'Events', permissions: ['events:create', 'events:read'] }
153
- ],
69
+ modules: [{ key, name, permissions: [] }],
70
+ groups: string[],
154
71
  fieldOffices: string[]
155
72
  }
156
73
  ```
157
74
 
158
- ## Realtime Events
159
-
160
- The guard listens to these events from the WebSocket:
161
- - `permission:updated` - Permission changes
162
- - `role:updated` - Role changes
163
- - `user:updated` - User changes
164
-
165
- When received, the cache is automatically cleared for that user.
166
-
167
- ## License
168
-
169
75
  MIT
package/dist/index.cjs CHANGED
@@ -31,228 +31,28 @@ var src_exports = {};
31
31
  __export(src_exports, {
32
32
  EXGUARD_PERMISSIONS_KEY: () => EXGUARD_PERMISSIONS_KEY,
33
33
  EXGUARD_ROLES_KEY: () => EXGUARD_ROLES_KEY,
34
- ExGuardClient: () => ExGuardClient,
35
34
  ExGuardModule: () => ExGuardModule,
36
35
  ExGuardPermissionGuard: () => ExGuardPermissionGuard,
37
36
  RequirePermissions: () => RequirePermissions,
38
37
  RequireRoles: () => RequireRoles,
39
- cache: () => cache,
40
- createExGuardClient: () => createExGuardClient,
41
- realtime: () => realtime
38
+ clearExGuardCache: () => clearExGuardCache
42
39
  });
43
40
  module.exports = __toCommonJS(src_exports);
44
41
 
45
- // src/client.ts
46
- var ExGuardCache = class {
47
- constructor() {
48
- this.cache = /* @__PURE__ */ new Map();
49
- this.ttl = 3e5;
50
- }
51
- get(key) {
52
- const entry = this.cache.get(key);
53
- if (!entry) return null;
54
- if (Date.now() - entry.timestamp > entry.ttl) {
55
- this.cache.delete(key);
56
- return null;
57
- }
58
- return entry.data;
59
- }
60
- set(key, data, ttl) {
61
- this.cache.set(key, {
62
- data,
63
- timestamp: Date.now(),
64
- ttl: ttl || this.ttl
65
- });
66
- }
67
- delete(key) {
68
- this.cache.delete(key);
69
- }
70
- clear() {
71
- this.cache.clear();
72
- }
73
- clearUser(userId) {
74
- for (const key of this.cache.keys()) {
75
- if (key.includes(userId)) {
76
- this.cache.delete(key);
77
- }
78
- }
79
- }
80
- };
81
- var cache = new ExGuardCache();
82
- var ExGuardRealtime = class {
83
- constructor() {
84
- this.socket = null;
85
- this.connected = false;
86
- this.url = "";
87
- this.events = {};
88
- }
89
- connect(url, token, events = {}) {
90
- this.url = url;
91
- this.events = events;
92
- if (!url) {
93
- console.log("[ExGuard] Realtime URL not provided, skipping connection");
94
- return;
95
- }
96
- try {
97
- const { io } = require("socket.io-client");
98
- let wsUrl = url;
99
- if (url.startsWith("http://")) {
100
- wsUrl = url.replace("http://", "ws://");
101
- } else if (url.startsWith("https://")) {
102
- wsUrl = url.replace("https://", "wss://");
103
- }
104
- if (!wsUrl.includes("/realtime")) {
105
- wsUrl = wsUrl.replace(/\/$/, "") + "/realtime";
106
- }
107
- this.socket = io(wsUrl, {
108
- auth: { token },
109
- transports: ["websocket", "polling"],
110
- reconnection: true,
111
- reconnectionAttempts: 5,
112
- reconnectionDelay: 1e3
113
- });
114
- this.socket.on("connect", () => {
115
- this.connected = true;
116
- console.log("[ExGuard] Realtime connected to", wsUrl);
117
- this.events.onConnect?.();
118
- this.socket.emit("subscribe:permissions");
119
- });
120
- this.socket.on("disconnect", () => {
121
- this.connected = false;
122
- console.log("[ExGuard] Realtime disconnected");
123
- this.events.onDisconnect?.();
124
- });
125
- this.socket.on("permission:updated", (data) => {
126
- console.log("[ExGuard] Permission update received:", data);
127
- if (data.userId) {
128
- cache.clearUser(data.userId);
129
- }
130
- this.events.onPermissionUpdate?.(data);
131
- });
132
- this.socket.on("user:updated", (data) => {
133
- console.log("[ExGuard] User update received:", data);
134
- if (data.userId) {
135
- cache.clearUser(data.userId);
136
- }
137
- this.events.onUserUpdate?.(data);
138
- });
139
- this.socket.on("role:updated", (data) => {
140
- console.log("[ExGuard] Role update received:", data);
141
- if (data.userId) {
142
- cache.clearUser(data.userId);
143
- }
144
- this.events.onRoleUpdate?.(data);
145
- });
146
- this.socket.on("connect_error", (err) => {
147
- console.error("[ExGuard] Realtime connection error:", err.message);
148
- });
149
- } catch (error) {
150
- console.error("[ExGuard] Failed to load socket.io-client:", error);
151
- }
152
- }
153
- disconnect() {
154
- if (this.socket) {
155
- this.socket.disconnect();
156
- this.socket = null;
157
- this.connected = false;
158
- }
159
- }
160
- isConnected() {
161
- return this.connected;
162
- }
163
- emit(event, data) {
164
- if (this.socket && this.connected) {
165
- this.socket.emit(event, data);
166
- }
167
- }
168
- };
169
- var realtime = new ExGuardRealtime();
170
- var ExGuardClient = class {
171
- constructor(config) {
172
- this.baseUrl = config.baseUrl;
173
- this.cache = cache;
174
- this.realtime = realtime;
175
- if (config.realtime?.enabled && config.realtime?.url) {
176
- this.realtime.connect(config.realtime.url);
177
- }
178
- }
179
- async authenticate(context) {
180
- const cacheKey = `auth:${context.token}`;
181
- const cached = this.cache.get(cacheKey);
182
- if (cached) return cached;
183
- try {
184
- const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {
185
- method: "POST",
186
- headers: {
187
- "Content-Type": "application/json",
188
- "Authorization": `Bearer ${context.token}`
189
- },
190
- body: JSON.stringify({ id_token: context.token })
191
- });
192
- if (!verifyResponse.ok) {
193
- const errorData = await verifyResponse.json().catch(() => ({}));
194
- return { allowed: false, error: errorData.message || "Invalid token" };
195
- }
196
- const meResponse = await fetch(`${this.baseUrl}/guard/me`, {
197
- method: "GET",
198
- headers: {
199
- "Authorization": `Bearer ${context.token}`
200
- }
201
- });
202
- if (!meResponse.ok) {
203
- return { allowed: false, error: "Failed to get user access" };
204
- }
205
- const meData = await meResponse.json();
206
- const result = {
207
- allowed: true,
208
- user: meData.data || meData
209
- };
210
- this.cache.set(cacheKey, result);
211
- if (this.realtime && !this.realtime.isConnected()) {
212
- const realtimeUrl = this.baseUrl.replace(/^http/, "ws");
213
- this.realtime.connect(realtimeUrl, context.token);
214
- }
215
- return result;
216
- } catch (error) {
217
- console.error("[ExGuard] Authentication error:", error);
218
- return { allowed: false, error: "Authentication failed" };
219
- }
220
- }
221
- async hasPermission(token, permission) {
222
- const result = await this.authenticate({ token });
223
- if (!result.allowed || !result.user) return false;
224
- const userPermissions = result.user.modules?.flatMap((m) => m.permissions) || [];
225
- return userPermissions.includes(permission);
226
- }
227
- clearCache() {
228
- this.cache.clear();
229
- }
230
- disconnectRealtime() {
231
- this.realtime.disconnect();
232
- }
233
- };
234
- function createExGuardClient(config) {
235
- return new ExGuardClient(config);
236
- }
237
-
238
42
  // src/module.ts
239
43
  var import_common = require("@nestjs/common");
240
44
  var ExGuardModule = class {
241
- static forRoot(options) {
242
- const client = new ExGuardClient({
243
- baseUrl: options.baseUrl,
244
- cache: options.cache,
245
- realtime: options.realtime
246
- });
45
+ static forRoot(optionsOrUrl) {
46
+ const baseUrl = typeof optionsOrUrl === "string" ? optionsOrUrl : optionsOrUrl.baseUrl;
247
47
  return {
248
48
  module: ExGuardModule,
249
49
  providers: [
250
50
  {
251
- provide: "EXGUARD_CLIENT",
252
- useValue: client
51
+ provide: "EXGUARD_BASE_URL",
52
+ useValue: baseUrl
253
53
  }
254
54
  ],
255
- exports: ["EXGUARD_CLIENT"]
55
+ exports: ["EXGUARD_BASE_URL"]
256
56
  };
257
57
  }
258
58
  };
@@ -265,9 +65,23 @@ ExGuardModule = __decorateClass([
265
65
  var import_common2 = require("@nestjs/common");
266
66
  var EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
267
67
  var EXGUARD_ROLES_KEY = "exguard_roles";
68
+ var cache = /* @__PURE__ */ new Map();
69
+ var CACHE_TTL = 5 * 60 * 1e3;
70
+ function getCached(key) {
71
+ const entry = cache.get(key);
72
+ if (!entry) return null;
73
+ if (Date.now() - entry.timestamp > CACHE_TTL) {
74
+ cache.delete(key);
75
+ return null;
76
+ }
77
+ return entry.data;
78
+ }
79
+ function setCache(key, data) {
80
+ cache.set(key, { data, timestamp: Date.now() });
81
+ }
268
82
  var ExGuardPermissionGuard = class {
269
- constructor(client, reflector) {
270
- this.client = client;
83
+ constructor(baseUrl, reflector) {
84
+ this.baseUrl = baseUrl;
271
85
  this.reflector = reflector;
272
86
  }
273
87
  async canActivate(context) {
@@ -276,11 +90,42 @@ var ExGuardPermissionGuard = class {
276
90
  if (!token) {
277
91
  throw new import_common2.UnauthorizedException("No token provided");
278
92
  }
279
- if (!this.client) {
280
- console.warn("[ExGuard] Client not configured. Access denied.");
93
+ if (!this.baseUrl) {
94
+ console.warn("[ExGuard] Base URL not configured. Access denied.");
281
95
  return false;
282
96
  }
283
- const authResult = await this.client.authenticate({ token, request });
97
+ const cacheKey = `auth:${token}`;
98
+ let authResult = getCached(cacheKey);
99
+ if (!authResult) {
100
+ try {
101
+ const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {
102
+ method: "POST",
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ "Authorization": `Bearer ${token}`
106
+ },
107
+ body: JSON.stringify({ id_token: token })
108
+ });
109
+ if (!verifyResponse.ok) {
110
+ throw new import_common2.ForbiddenException("Invalid token");
111
+ }
112
+ const meResponse = await fetch(`${this.baseUrl}/guard/me`, {
113
+ method: "GET",
114
+ headers: {
115
+ "Authorization": `Bearer ${token}`
116
+ }
117
+ });
118
+ if (!meResponse.ok) {
119
+ throw new import_common2.ForbiddenException("Failed to get user access");
120
+ }
121
+ const meData = await meResponse.json();
122
+ authResult = { allowed: true, user: meData.data || meData };
123
+ setCache(cacheKey, authResult);
124
+ } catch (error) {
125
+ console.error("[ExGuard] Auth error:", error);
126
+ throw new import_common2.ForbiddenException("Authentication failed");
127
+ }
128
+ }
284
129
  if (!authResult.allowed) {
285
130
  throw new import_common2.ForbiddenException(authResult.error || "Access denied");
286
131
  }
@@ -288,13 +133,10 @@ var ExGuardPermissionGuard = class {
288
133
  throw new import_common2.ForbiddenException("User not found");
289
134
  }
290
135
  const handler = context.getHandler();
291
- const permMeta = this.reflector.get(
292
- EXGUARD_PERMISSIONS_KEY,
293
- handler
294
- );
136
+ const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);
295
137
  if (permMeta) {
296
138
  const { permissions, requireAll } = permMeta;
297
- const userPermissions = authResult.user.modules?.flatMap((m) => m.permissions) || [];
139
+ const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m) => m.permissions) : [];
298
140
  if (requireAll) {
299
141
  if (!permissions.every((p) => userPermissions.includes(p))) {
300
142
  throw new import_common2.ForbiddenException("Insufficient permissions");
@@ -305,10 +147,7 @@ var ExGuardPermissionGuard = class {
305
147
  }
306
148
  }
307
149
  }
308
- const roleMeta = this.reflector.get(
309
- EXGUARD_ROLES_KEY,
310
- handler
311
- );
150
+ const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);
312
151
  if (roleMeta) {
313
152
  const { roles, requireAll } = roleMeta;
314
153
  const userRoles = authResult.user.roles || [];
@@ -336,7 +175,7 @@ var ExGuardPermissionGuard = class {
336
175
  ExGuardPermissionGuard = __decorateClass([
337
176
  (0, import_common2.Injectable)(),
338
177
  __decorateParam(0, (0, import_common2.Optional)()),
339
- __decorateParam(0, (0, import_common2.Inject)("EXGUARD_CLIENT"))
178
+ __decorateParam(0, (0, import_common2.Inject)("EXGUARD_BASE_URL"))
340
179
  ], ExGuardPermissionGuard);
341
180
  function RequirePermissions(permissions, requireAll = false) {
342
181
  return function(target, propertyKey, descriptor) {
@@ -350,17 +189,17 @@ function RequireRoles(roles, requireAll = false) {
350
189
  return descriptor;
351
190
  };
352
191
  }
192
+ function clearExGuardCache() {
193
+ cache.clear();
194
+ }
353
195
  // Annotate the CommonJS export names for ESM import in node:
354
196
  0 && (module.exports = {
355
197
  EXGUARD_PERMISSIONS_KEY,
356
198
  EXGUARD_ROLES_KEY,
357
- ExGuardClient,
358
199
  ExGuardModule,
359
200
  ExGuardPermissionGuard,
360
201
  RequirePermissions,
361
202
  RequireRoles,
362
- cache,
363
- createExGuardClient,
364
- realtime
203
+ clearExGuardCache
365
204
  });
366
205
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/module.ts","../src/guard.ts"],"sourcesContent":["export { ExGuardClient, createExGuardClient, cache, realtime } from './client.js';\nexport type { ExGuardConfig, User, ModulePermission, UserAccessResponse, GuardContext, GuardResult, RealtimeEvents } 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 cache?: {\n enabled?: boolean;\n ttl?: number;\n };\n realtime?: {\n enabled?: boolean;\n url?: string;\n };\n}\n\nexport interface User {\n id: string;\n cognitoSubId: string;\n username: string;\n email: string;\n emailVerified: boolean;\n givenName: string;\n familyName: string;\n employeeNumber: string;\n regionId: string;\n createdAt: string;\n updatedAt: string;\n lastLoginAt: string;\n fieldOffice: {\n id: string;\n code: string;\n name: string;\n };\n}\n\nexport interface ModulePermission {\n key: string;\n name: string;\n permissions: string[];\n}\n\nexport interface UserAccessResponse {\n user: User;\n groups: string[];\n roles: string[];\n modules: ModulePermission[];\n fieldOffices: string[];\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\nexport interface RealtimeEvents {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onPermissionUpdate?: (data: { userId: string; permissions: string[] }) => void;\n onUserUpdate?: (data: { userId: string; user: UserAccessResponse }) => void;\n onRoleUpdate?: (data: { userId: string; roles: string[] }) => void;\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 delete(key: string): void {\n this.cache.delete(key);\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n clearUser(userId: string): void {\n for (const key of this.cache.keys()) {\n if (key.includes(userId)) {\n this.cache.delete(key);\n }\n }\n }\n}\n\nconst cache = new ExGuardCache();\n\nclass ExGuardRealtime {\n private socket: any = null;\n private connected = false;\n private url: string = '';\n private events: RealtimeEvents = {};\n\n connect(url: string, token?: string, events: RealtimeEvents = {}): void {\n this.url = url;\n this.events = events;\n \n if (!url) {\n console.log('[ExGuard] Realtime URL not provided, skipping connection');\n return;\n }\n\n try {\n const { io } = require('socket.io-client');\n \n // Convert http(s) to ws(s) if needed\n let wsUrl = url;\n if (url.startsWith('http://')) {\n wsUrl = url.replace('http://', 'ws://');\n } else if (url.startsWith('https://')) {\n wsUrl = url.replace('https://', 'wss://');\n }\n \n // Add namespace\n if (!wsUrl.includes('/realtime')) {\n wsUrl = wsUrl.replace(/\\/$/, '') + '/realtime';\n }\n\n this.socket = io(wsUrl, {\n auth: { token },\n transports: ['websocket', 'polling'],\n reconnection: true,\n reconnectionAttempts: 5,\n reconnectionDelay: 1000,\n });\n\n this.socket.on('connect', () => {\n this.connected = true;\n console.log('[ExGuard] Realtime connected to', wsUrl);\n this.events.onConnect?.();\n \n // Subscribe to permission updates\n this.socket.emit('subscribe:permissions');\n });\n\n this.socket.on('disconnect', () => {\n this.connected = false;\n console.log('[ExGuard] Realtime disconnected');\n this.events.onDisconnect?.();\n });\n\n // Listen for permission updates\n this.socket.on('permission:updated', (data: any) => {\n console.log('[ExGuard] Permission update received:', data);\n if (data.userId) {\n cache.clearUser(data.userId);\n }\n this.events.onPermissionUpdate?.(data);\n });\n\n this.socket.on('user:updated', (data: any) => {\n console.log('[ExGuard] User update received:', data);\n if (data.userId) {\n cache.clearUser(data.userId);\n }\n this.events.onUserUpdate?.(data);\n });\n\n this.socket.on('role:updated', (data: any) => {\n console.log('[ExGuard] Role update received:', data);\n if (data.userId) {\n cache.clearUser(data.userId);\n }\n this.events.onRoleUpdate?.(data);\n });\n\n this.socket.on('connect_error', (err: any) => {\n console.error('[ExGuard] Realtime connection error:', err.message);\n });\n } catch (error) {\n console.error('[ExGuard] Failed to load socket.io-client:', error);\n }\n }\n\n disconnect(): void {\n if (this.socket) {\n this.socket.disconnect();\n this.socket = null;\n this.connected = false;\n }\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n emit(event: string, data: any): void {\n if (this.socket && this.connected) {\n this.socket.emit(event, data);\n }\n }\n}\n\nconst realtime = new ExGuardRealtime();\n\nexport class ExGuardClient {\n private baseUrl: string;\n private cache: ExGuardCache;\n private realtime: ExGuardRealtime;\n\n constructor(config: ExGuardConfig) {\n this.baseUrl = config.baseUrl;\n this.cache = cache;\n this.realtime = realtime;\n\n if (config.realtime?.enabled && config.realtime?.url) {\n this.realtime.connect(config.realtime.url);\n }\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 // First verify the token\n const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${context.token}`,\n },\n body: JSON.stringify({ id_token: context.token }),\n });\n\n if (!verifyResponse.ok) {\n const errorData = await verifyResponse.json().catch(() => ({}));\n return { allowed: false, error: errorData.message || 'Invalid token' };\n }\n\n // Then get user access info\n const meResponse = await fetch(`${this.baseUrl}/guard/me`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${context.token}`,\n },\n });\n\n if (!meResponse.ok) {\n return { allowed: false, error: 'Failed to get user access' };\n }\n\n const meData = await meResponse.json();\n \n const result: GuardResult = {\n allowed: true,\n user: meData.data || meData,\n };\n\n this.cache.set(cacheKey, result);\n \n // Connect to realtime with token\n if (this.realtime && !this.realtime.isConnected()) {\n const realtimeUrl = this.baseUrl.replace(/^http/, 'ws');\n this.realtime.connect(realtimeUrl, context.token);\n }\n \n return result;\n } catch (error) {\n console.error('[ExGuard] Authentication error:', 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 clearCache(): void {\n this.cache.clear();\n }\n\n disconnectRealtime(): void {\n this.realtime.disconnect();\n }\n}\n\nexport function createExGuardClient(config: ExGuardConfig): ExGuardClient {\n return new ExGuardClient(config);\n}\n\nexport { cache, realtime };\n","import { Module, Global, DynamicModule } from '@nestjs/common';\nimport { ExGuardClient } from './client.js';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n cache?: {\n enabled?: boolean;\n ttl?: number;\n };\n realtime?: {\n enabled?: boolean;\n url?: string;\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 cache: options.cache,\n realtime: options.realtime,\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 console.warn('[ExGuard] Client not configured. Access denied.');\n return false;\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"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/module.ts","../src/guard.ts"],"sourcesContent":["export { ExGuardModule } from './module.js';\nexport { ExGuardPermissionGuard, RequirePermissions, RequireRoles, clearExGuardCache, EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY } from './guard.js';\n","import { Module, Global, DynamicModule } from '@nestjs/common';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n}\n\n@Global()\n@Module({})\nexport class ExGuardModule {\n static forRoot(options: ExGuardModuleOptions): DynamicModule;\n static forRoot(baseUrl: string): DynamicModule;\n static forRoot(optionsOrUrl: ExGuardModuleOptions | string): DynamicModule {\n const baseUrl = typeof optionsOrUrl === 'string' ? optionsOrUrl : optionsOrUrl.baseUrl;\n \n return {\n module: ExGuardModule,\n providers: [\n {\n provide: 'EXGUARD_BASE_URL',\n useValue: baseUrl,\n },\n ],\n exports: ['EXGUARD_BASE_URL'],\n };\n }\n}\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\n\nexport const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';\nexport const EXGUARD_ROLES_KEY = 'exguard_roles';\n\nconst cache = new Map<string, { data: any; timestamp: number }>();\nconst CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\nfunction getCached(key: string): any {\n const entry = cache.get(key);\n if (!entry) return null;\n if (Date.now() - entry.timestamp > CACHE_TTL) {\n cache.delete(key);\n return null;\n }\n return entry.data;\n}\n\nfunction setCache(key: string, data: any): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n@Injectable()\nexport class ExGuardPermissionGuard implements CanActivate {\n constructor(\n @Optional() @Inject('EXGUARD_BASE_URL') private baseUrl: string,\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.baseUrl) {\n console.warn('[ExGuard] Base URL not configured. Access denied.');\n return false;\n }\n\n // Check cache first\n const cacheKey = `auth:${token}`;\n let authResult = getCached(cacheKey);\n\n if (!authResult) {\n try {\n // Verify token\n const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${token}`,\n },\n body: JSON.stringify({ id_token: token }),\n });\n\n if (!verifyResponse.ok) {\n throw new ForbiddenException('Invalid token');\n }\n\n // Get user access\n const meResponse = await fetch(`${this.baseUrl}/guard/me`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n },\n });\n\n if (!meResponse.ok) {\n throw new ForbiddenException('Failed to get user access');\n }\n\n const meData = await meResponse.json();\n authResult = { allowed: true, user: meData.data || meData };\n \n // Cache the result\n setCache(cacheKey, authResult);\n } catch (error) {\n console.error('[ExGuard] Auth error:', error);\n throw new ForbiddenException('Authentication failed');\n }\n }\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(EXGUARD_PERMISSIONS_KEY, handler);\n\n if (permMeta) {\n const { permissions, requireAll } = permMeta;\n const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m: any) => m.permissions) : [];\n\n if (requireAll) {\n if (!permissions.every((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n } else {\n if (!permissions.some((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n }\n }\n\n const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);\n\n if (roleMeta) {\n const { roles, requireAll } = roleMeta;\n const userRoles = authResult.user.roles || [];\n\n if (requireAll) {\n if (!roles.every((r: string) => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n } else {\n if (!roles.some((r: string) => 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\nexport function clearExGuardCache(): void {\n cache.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAA8C;AAQvC,IAAM,gBAAN,MAAoB;AAAA,EAGzB,OAAO,QAAQ,cAA4D;AACzE,UAAM,UAAU,OAAO,iBAAiB,WAAW,eAAe,aAAa;AAE/E,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW;AAAA,QACT;AAAA,UACE,SAAS;AAAA,UACT,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,SAAS,CAAC,kBAAkB;AAAA,IAC9B;AAAA,EACF;AACF;AAjBa,gBAAN;AAAA,MAFN,sBAAO;AAAA,MACP,sBAAO,CAAC,CAAC;AAAA,GACG;;;ACRb,IAAAA,iBAAuH;AAGhH,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAEjC,IAAM,QAAQ,oBAAI,IAA8C;AAChE,IAAM,YAAY,IAAI,KAAK;AAE3B,SAAS,UAAU,KAAkB;AACnC,QAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,KAAK,IAAI,IAAI,MAAM,YAAY,WAAW;AAC5C,UAAM,OAAO,GAAG;AAChB,WAAO;AAAA,EACT;AACA,SAAO,MAAM;AACf;AAEA,SAAS,SAAS,KAAa,MAAiB;AAC9C,QAAM,IAAI,KAAK,EAAE,MAAM,WAAW,KAAK,IAAI,EAAE,CAAC;AAChD;AAGO,IAAM,yBAAN,MAAoD;AAAA,EACzD,YACkD,SACxC,WACR;AAFgD;AACxC;AAAA,EACP;AAAA,EAEH,MAAM,YAAY,SAA6C;AAC7D,UAAM,UAAU,QAAQ,aAAa,EAAE,WAAW;AAClD,UAAM,QAAQ,KAAK,aAAa,OAAO;AAEvC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,qCAAsB,mBAAmB;AAAA,IACrD;AAEA,QAAI,CAAC,KAAK,SAAS;AACjB,cAAQ,KAAK,mDAAmD;AAChE,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,QAAQ,KAAK;AAC9B,QAAI,aAAa,UAAU,QAAQ;AAEnC,QAAI,CAAC,YAAY;AACf,UAAI;AAEF,cAAM,iBAAiB,MAAM,MAAM,GAAG,KAAK,OAAO,uBAAuB;AAAA,UACvE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,iBAAiB,UAAU,KAAK;AAAA,UAClC;AAAA,UACA,MAAM,KAAK,UAAU,EAAE,UAAU,MAAM,CAAC;AAAA,QAC1C,CAAC;AAED,YAAI,CAAC,eAAe,IAAI;AACtB,gBAAM,IAAI,kCAAmB,eAAe;AAAA,QAC9C;AAGA,cAAM,aAAa,MAAM,MAAM,GAAG,KAAK,OAAO,aAAa;AAAA,UACzD,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,iBAAiB,UAAU,KAAK;AAAA,UAClC;AAAA,QACF,CAAC;AAED,YAAI,CAAC,WAAW,IAAI;AAClB,gBAAM,IAAI,kCAAmB,2BAA2B;AAAA,QAC1D;AAEA,cAAM,SAAS,MAAM,WAAW,KAAK;AACrC,qBAAa,EAAE,SAAS,MAAM,MAAM,OAAO,QAAQ,OAAO;AAG1D,iBAAS,UAAU,UAAU;AAAA,MAC/B,SAAS,OAAO;AACd,gBAAQ,MAAM,yBAAyB,KAAK;AAC5C,cAAM,IAAI,kCAAmB,uBAAuB;AAAA,MACtD;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,SAAS;AACvB,YAAM,IAAI,kCAAmB,WAAW,SAAS,eAAe;AAAA,IAClE;AAEA,QAAI,CAAC,WAAW,MAAM;AACpB,YAAM,IAAI,kCAAmB,gBAAgB;AAAA,IAC/C;AAEA,UAAM,UAAU,QAAQ,WAAW;AACnC,UAAM,WAAW,KAAK,UAAU,IAAI,yBAAyB,OAAO;AAEpE,QAAI,UAAU;AACZ,YAAM,EAAE,aAAa,WAAW,IAAI;AACpC,YAAM,kBAAkB,WAAW,KAAK,UAAU,WAAW,KAAK,QAAQ,QAAQ,CAAC,MAAW,EAAE,WAAW,IAAI,CAAC;AAEhH,UAAI,YAAY;AACd,YAAI,CAAC,YAAY,MAAM,CAAC,MAAc,gBAAgB,SAAS,CAAC,CAAC,GAAG;AAClE,gBAAM,IAAI,kCAAmB,0BAA0B;AAAA,QACzD;AAAA,MACF,OAAO;AACL,YAAI,CAAC,YAAY,KAAK,CAAC,MAAc,gBAAgB,SAAS,CAAC,CAAC,GAAG;AACjE,gBAAM,IAAI,kCAAmB,0BAA0B;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,UAAU,IAAI,mBAAmB,OAAO;AAE9D,QAAI,UAAU;AACZ,YAAM,EAAE,OAAO,WAAW,IAAI;AAC9B,YAAM,YAAY,WAAW,KAAK,SAAS,CAAC;AAE5C,UAAI,YAAY;AACd,YAAI,CAAC,MAAM,MAAM,CAAC,MAAc,UAAU,SAAS,CAAC,CAAC,GAAG;AACtD,gBAAM,IAAI,kCAAmB,oBAAoB;AAAA,QACnD;AAAA,MACF,OAAO;AACL,YAAI,CAAC,MAAM,KAAK,CAAC,MAAc,UAAU,SAAS,CAAC,CAAC,GAAG;AACrD,gBAAM,IAAI,kCAAmB,oBAAoB;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,OAAO,WAAW;AAC1B,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,SAA6B;AAChD,UAAM,OAAO,QAAQ,SAAS;AAC9B,QAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,aAAO,KAAK,UAAU,CAAC;AAAA,IACzB;AACA,WAAO,QAAQ,UAAU,gBAAgB,KAAK;AAAA,EAChD;AACF;AApHa,yBAAN;AAAA,MADN,2BAAW;AAAA,EAGP,gDAAS;AAAA,EAAG,8CAAO,kBAAkB;AAAA,GAF7B;AAsHN,SAAS,mBAAmB,aAAuB,aAAa,OAAO;AAC5E,SAAO,SAAU,QAAa,aAAqB,YAAgC;AACjF,YAAQ,eAAe,yBAAyB,EAAE,aAAa,WAAW,GAAG,WAAW,KAAK;AAC7F,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,OAAiB,aAAa,OAAO;AAChE,SAAO,SAAU,QAAa,aAAqB,YAAgC;AACjF,YAAQ,eAAe,mBAAmB,EAAE,OAAO,WAAW,GAAG,WAAW,KAAK;AACjF,WAAO;AAAA,EACT;AACF;AAEO,SAAS,oBAA0B;AACxC,QAAM,MAAM;AACd;","names":["import_common"]}