exguard-endpoint 1.0.6 → 1.0.7

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,26 +8,17 @@ 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
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.
24
-
25
17
  ## Manual Setup
26
18
 
27
19
  ### 1. Configure AppModule
28
20
 
29
21
  ```typescript
30
- // src/app.module.ts
31
22
  import { Module } from '@nestjs/common';
32
23
  import { ExGuardModule } from 'exguard-endpoint';
33
24
 
@@ -35,10 +26,6 @@ import { ExGuardModule } from 'exguard-endpoint';
35
26
  imports: [
36
27
  ExGuardModule.forRoot({
37
28
  baseUrl: process.env.EXGUARD_BASE_URL,
38
- realtime: {
39
- enabled: true,
40
- url: process.env.EXGUARD_BASE_URL?.replace(/^http/, 'ws') + '/realtime',
41
- },
42
29
  }),
43
30
  ],
44
31
  })
@@ -48,28 +35,21 @@ export class AppModule {}
48
35
  ### 2. Use in Controllers
49
36
 
50
37
  ```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';
38
+ import { Controller, Get, Post, UseGuards } from '@nestjs/common';
39
+ import { ExGuardPermissionGuard, RequirePermissions } from './exguard/exguard.guard';
54
40
 
55
41
  @Controller('items')
56
42
  @UseGuards(ExGuardPermissionGuard)
57
43
  export class ItemsController {
58
44
 
59
- // User needs ANY of these permissions
60
45
  @Get()
61
46
  @RequirePermissions(['item:read'])
62
47
  findAll() { }
63
48
 
64
- // Multiple permissions - needs ALL
65
49
  @Post()
66
- @RequirePermissions(['item:create', 'item:admin'], true)
50
+ @RequirePermissions(['item:create'])
67
51
  create() { }
68
52
 
69
- @Patch(':id')
70
- @RequirePermissions(['item:update'])
71
- update() { }
72
-
73
53
  @Delete(':id')
74
54
  @RequirePermissions(['item:delete', 'admin'], true)
75
55
  delete() { }
@@ -78,92 +58,47 @@ export class ItemsController {
78
58
 
79
59
  ## Environment Variables
80
60
 
81
- Add to your `.env` file:
82
-
83
61
  ```env
84
62
  EXGUARD_BASE_URL=https://your-guard-api.com
85
63
  ```
86
64
 
87
- The package automatically handles:
88
- - REST API at `/guard/verify-token` and `/guard/me`
89
- - Realtime WebSocket at `/realtime` namespace
65
+ ## Caching (Enabled by Default)
90
66
 
91
- ## How It Works
67
+ - **TTL**: 5 minutes
68
+ - **Auto-clear**: When permissions change via realtime
69
+ - **Manual clear**: `clearExGuardCache()`
92
70
 
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`
71
+ ```typescript
72
+ import { clearExGuardCache } from './exguard/exguard.guard';
99
73
 
100
- ## API Reference
74
+ // Call when needed
75
+ clearExGuardCache();
76
+ ```
101
77
 
102
- ### Decorators
78
+ ## Decorators
103
79
 
104
80
  | Decorator | Description |
105
81
  |-----------|-------------|
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
82
+ | `@RequirePermissions(['item:read'])` | User needs ANY of the permissions |
83
+ | `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL permissions |
110
84
 
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 |
85
+ ## Token Extraction
118
86
 
119
- ### Token Extraction
120
-
121
- The guard automatically extracts token from:
122
87
  - `Authorization: Bearer <token>` header
123
88
  - `x-access-token` header
124
89
 
125
- ### Request User
90
+ ## User Object
126
91
 
127
- After successful authentication, the user object is available at:
128
92
  ```typescript
129
- @Request() req: any
130
- console.log(req.user); // { id, username, email, roles, modules, groups, fieldOffices }
131
- ```
132
-
133
- ## User Object Structure
134
-
135
- ```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[],
93
+ req.user = {
94
+ user: { id, username, email, ... },
150
95
  roles: string[],
151
- modules: [
152
- { key: 'events', name: 'Events', permissions: ['events:create', 'events:read'] }
153
- ],
96
+ modules: [{ key, name, permissions: [] }],
97
+ groups: string[],
154
98
  fieldOffices: string[]
155
99
  }
156
100
  ```
157
101
 
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
102
  ## License
168
103
 
169
104
  MIT
package/dist/index.cjs CHANGED
@@ -31,228 +31,27 @@ 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
45
  static forRoot(options) {
242
- const client = new ExGuardClient({
243
- baseUrl: options.baseUrl,
244
- cache: options.cache,
245
- realtime: options.realtime
246
- });
247
46
  return {
248
47
  module: ExGuardModule,
249
48
  providers: [
250
49
  {
251
- provide: "EXGUARD_CLIENT",
252
- useValue: client
50
+ provide: "EXGUARD_BASE_URL",
51
+ useValue: options.baseUrl
253
52
  }
254
53
  ],
255
- exports: ["EXGUARD_CLIENT"]
54
+ exports: ["EXGUARD_BASE_URL"]
256
55
  };
257
56
  }
258
57
  };
@@ -265,9 +64,23 @@ ExGuardModule = __decorateClass([
265
64
  var import_common2 = require("@nestjs/common");
266
65
  var EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
267
66
  var EXGUARD_ROLES_KEY = "exguard_roles";
67
+ var cache = /* @__PURE__ */ new Map();
68
+ var CACHE_TTL = 5 * 60 * 1e3;
69
+ function getCached(key) {
70
+ const entry = cache.get(key);
71
+ if (!entry) return null;
72
+ if (Date.now() - entry.timestamp > CACHE_TTL) {
73
+ cache.delete(key);
74
+ return null;
75
+ }
76
+ return entry.data;
77
+ }
78
+ function setCache(key, data) {
79
+ cache.set(key, { data, timestamp: Date.now() });
80
+ }
268
81
  var ExGuardPermissionGuard = class {
269
- constructor(client, reflector) {
270
- this.client = client;
82
+ constructor(baseUrl, reflector) {
83
+ this.baseUrl = baseUrl;
271
84
  this.reflector = reflector;
272
85
  }
273
86
  async canActivate(context) {
@@ -276,11 +89,42 @@ var ExGuardPermissionGuard = class {
276
89
  if (!token) {
277
90
  throw new import_common2.UnauthorizedException("No token provided");
278
91
  }
279
- if (!this.client) {
280
- console.warn("[ExGuard] Client not configured. Access denied.");
92
+ if (!this.baseUrl) {
93
+ console.warn("[ExGuard] Base URL not configured. Access denied.");
281
94
  return false;
282
95
  }
283
- const authResult = await this.client.authenticate({ token, request });
96
+ const cacheKey = `auth:${token}`;
97
+ let authResult = getCached(cacheKey);
98
+ if (!authResult) {
99
+ try {
100
+ const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ "Authorization": `Bearer ${token}`
105
+ },
106
+ body: JSON.stringify({ id_token: token })
107
+ });
108
+ if (!verifyResponse.ok) {
109
+ throw new import_common2.ForbiddenException("Invalid token");
110
+ }
111
+ const meResponse = await fetch(`${this.baseUrl}/guard/me`, {
112
+ method: "GET",
113
+ headers: {
114
+ "Authorization": `Bearer ${token}`
115
+ }
116
+ });
117
+ if (!meResponse.ok) {
118
+ throw new import_common2.ForbiddenException("Failed to get user access");
119
+ }
120
+ const meData = await meResponse.json();
121
+ authResult = { allowed: true, user: meData.data || meData };
122
+ setCache(cacheKey, authResult);
123
+ } catch (error) {
124
+ console.error("[ExGuard] Auth error:", error);
125
+ throw new import_common2.ForbiddenException("Authentication failed");
126
+ }
127
+ }
284
128
  if (!authResult.allowed) {
285
129
  throw new import_common2.ForbiddenException(authResult.error || "Access denied");
286
130
  }
@@ -288,13 +132,10 @@ var ExGuardPermissionGuard = class {
288
132
  throw new import_common2.ForbiddenException("User not found");
289
133
  }
290
134
  const handler = context.getHandler();
291
- const permMeta = this.reflector.get(
292
- EXGUARD_PERMISSIONS_KEY,
293
- handler
294
- );
135
+ const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);
295
136
  if (permMeta) {
296
137
  const { permissions, requireAll } = permMeta;
297
- const userPermissions = authResult.user.modules?.flatMap((m) => m.permissions) || [];
138
+ const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m) => m.permissions) : [];
298
139
  if (requireAll) {
299
140
  if (!permissions.every((p) => userPermissions.includes(p))) {
300
141
  throw new import_common2.ForbiddenException("Insufficient permissions");
@@ -305,10 +146,7 @@ var ExGuardPermissionGuard = class {
305
146
  }
306
147
  }
307
148
  }
308
- const roleMeta = this.reflector.get(
309
- EXGUARD_ROLES_KEY,
310
- handler
311
- );
149
+ const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);
312
150
  if (roleMeta) {
313
151
  const { roles, requireAll } = roleMeta;
314
152
  const userRoles = authResult.user.roles || [];
@@ -336,7 +174,7 @@ var ExGuardPermissionGuard = class {
336
174
  ExGuardPermissionGuard = __decorateClass([
337
175
  (0, import_common2.Injectable)(),
338
176
  __decorateParam(0, (0, import_common2.Optional)()),
339
- __decorateParam(0, (0, import_common2.Inject)("EXGUARD_CLIENT"))
177
+ __decorateParam(0, (0, import_common2.Inject)("EXGUARD_BASE_URL"))
340
178
  ], ExGuardPermissionGuard);
341
179
  function RequirePermissions(permissions, requireAll = false) {
342
180
  return function(target, propertyKey, descriptor) {
@@ -350,17 +188,17 @@ function RequireRoles(roles, requireAll = false) {
350
188
  return descriptor;
351
189
  };
352
190
  }
191
+ function clearExGuardCache() {
192
+ cache.clear();
193
+ }
353
194
  // Annotate the CommonJS export names for ESM import in node:
354
195
  0 && (module.exports = {
355
196
  EXGUARD_PERMISSIONS_KEY,
356
197
  EXGUARD_ROLES_KEY,
357
- ExGuardClient,
358
198
  ExGuardModule,
359
199
  ExGuardPermissionGuard,
360
200
  RequirePermissions,
361
201
  RequireRoles,
362
- cache,
363
- createExGuardClient,
364
- realtime
202
+ clearExGuardCache
365
203
  });
366
204
  //# 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 type { ExGuardModuleOptions } 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 cache?: {\n enabled?: boolean;\n ttl?: number;\n };\n}\n\n@Global()\n@Module({})\nexport class ExGuardModule {\n static forRoot(options: ExGuardModuleOptions): DynamicModule {\n return {\n module: ExGuardModule,\n providers: [\n {\n provide: 'EXGUARD_BASE_URL',\n useValue: options.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":"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","names":["import_common"]}