exguard-endpoint 1.0.1 → 1.0.3

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.
3
+ Simple RBAC permission guard for NestJS with realtime support.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,19 +8,42 @@ Simple RBAC permission guard for NestJS.
8
8
  npm install exguard-endpoint
9
9
  ```
10
10
 
11
- ## Quick Setup
11
+ ## Quick Setup (Recommended)
12
+
13
+ Run in your NestJS project root:
14
+
15
+ ```bash
16
+ npx exguard-endpoint setup
17
+ ```
18
+
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
+ ## Manual Setup
12
26
 
13
27
  ### 1. Configure AppModule
14
28
 
15
29
  ```typescript
30
+ // src/app.module.ts
16
31
  import { Module } from '@nestjs/common';
17
32
  import { ExGuardModule } from 'exguard-endpoint';
18
33
 
19
34
  @Module({
20
35
  imports: [
21
36
  ExGuardModule.forRoot({
22
- baseUrl: 'https://api.exguard.com',
37
+ baseUrl: process.env.EXGUARD_BASE_URL || 'https://api.exguard.com',
23
38
  apiKey: process.env.EXGUARD_API_KEY,
39
+ cache: {
40
+ enabled: true,
41
+ ttl: 300000, // 5 minutes
42
+ },
43
+ realtime: {
44
+ enabled: true,
45
+ url: process.env.EXGUARD_REALTIME_URL,
46
+ },
24
47
  }),
25
48
  ],
26
49
  })
@@ -30,11 +53,12 @@ export class AppModule {}
30
53
  ### 2. Use in Controllers
31
54
 
32
55
  ```typescript
33
- import { Controller, Get, Post, UseGuards } from '@nestjs/common';
34
- import { ExGuardPermissionGuard, RequirePermissions } from 'exguard-endpoint';
56
+ // src/items/items.controller.ts
57
+ import { Controller, Get, Post, Patch, Delete, UseGuards } from '@nestjs/common';
58
+ import { ExGuardPermissionGuard, RequirePermissions } from '../exguard/exguard.guard';
35
59
 
36
60
  @Controller('items')
37
- @UseGuards(ExGuardPermissionGuard)
61
+ @UseGuards(ExGuardPermissionGuard) // Apply to all routes in this controller
38
62
  export class ItemsController {
39
63
 
40
64
  // User needs ANY of these permissions
@@ -42,30 +66,72 @@ export class ItemsController {
42
66
  @RequirePermissions(['item:read'])
43
67
  findAll() { }
44
68
 
69
+ // Multiple permissions - needs ALL
45
70
  @Post()
46
- @RequirePermissions(['item:create'])
71
+ @RequirePermissions(['item:create', 'item:admin'], true)
47
72
  create() { }
48
73
 
49
- // Multiple permissions - needs ALL
74
+ @Patch(':id')
75
+ @RequirePermissions(['item:update'])
76
+ update() { }
77
+
50
78
  @Delete(':id')
51
79
  @RequirePermissions(['item:delete', 'admin'], true)
52
80
  delete() { }
53
81
  }
54
82
  ```
55
83
 
56
- ## Decorators
84
+ ## Environment Variables
85
+
86
+ Add to your `.env` file:
87
+
88
+ ```env
89
+ EXGUARD_BASE_URL=https://api.exguard.com
90
+ EXGUARD_API_KEY=your_api_key_here
91
+ EXGUARD_REALTIME_URL=wss://realtime.exguard.com
92
+ ```
93
+
94
+ ## API Reference
95
+
96
+ ### Decorators
57
97
 
58
98
  | Decorator | Description |
59
99
  |-----------|-------------|
60
- | `@RequirePermissions(['perm1'])` | User needs ANY of the permissions |
61
- | `@RequirePermissions(['perm1', 'perm2'], true)` | User needs ALL permissions |
100
+ | `@RequirePermissions(['item:read'])` | User needs ANY of the listed permissions |
101
+ | `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL listed permissions |
62
102
 
63
- ## Token
103
+ ### ExGuardModule.forRoot Options
64
104
 
65
- The guard extracts token from:
105
+ | Option | Type | Required | Default | Description |
106
+ |--------|------|----------|---------|-------------|
107
+ | baseUrl | string | Yes | - | ExGuard API base URL |
108
+ | apiKey | string | Yes | - | Your API key |
109
+ | cache.enabled | boolean | No | true | Enable caching |
110
+ | cache.ttl | number | No | 300000 | Cache TTL in ms (5 min) |
111
+ | realtime.enabled | boolean | No | false | Enable realtime connection |
112
+ | realtime.url | string | No | - | Realtime WebSocket URL |
113
+
114
+ ### Token Extraction
115
+
116
+ The guard automatically extracts token from:
66
117
  - `Authorization: Bearer <token>` header
67
118
  - `x-access-token` header
68
119
 
120
+ ### Request User
121
+
122
+ After successful authentication, the user object is available at:
123
+ ```typescript
124
+ @Request() req: any
125
+ console.log(req.user); // { id, username, email, roles, modules }
126
+ ```
127
+
128
+ ## Realtime Features
129
+
130
+ When realtime is enabled:
131
+ - Permission changes are detected in real-time
132
+ - Cache is automatically cleared when permissions update
133
+ - Events: `permission:update`, `user:update`
134
+
69
135
  ## License
70
136
 
71
137
  MIT
package/dist/index.cjs CHANGED
@@ -37,7 +37,8 @@ __export(src_exports, {
37
37
  RequirePermissions: () => RequirePermissions,
38
38
  RequireRoles: () => RequireRoles,
39
39
  cache: () => cache,
40
- createExGuardClient: () => createExGuardClient
40
+ createExGuardClient: () => createExGuardClient,
41
+ realtime: () => realtime
41
42
  });
42
43
  module.exports = __toCommonJS(src_exports);
43
44
 
@@ -63,13 +64,96 @@ var ExGuardCache = class {
63
64
  ttl: ttl || this.ttl
64
65
  });
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
+ }
66
80
  };
67
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, 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
+ this.socket = io(url, {
99
+ transports: ["websocket"],
100
+ reconnection: true,
101
+ reconnectionAttempts: 5,
102
+ reconnectionDelay: 1e3
103
+ });
104
+ this.socket.on("connect", () => {
105
+ this.connected = true;
106
+ console.log("[ExGuard] Realtime connected");
107
+ this.events.onConnect?.();
108
+ });
109
+ this.socket.on("disconnect", () => {
110
+ this.connected = false;
111
+ console.log("[ExGuard] Realtime disconnected");
112
+ this.events.onDisconnect?.();
113
+ });
114
+ this.socket.on("permission:update", (data) => {
115
+ console.log("[ExGuard] Permission update received:", data);
116
+ cache.clearUser(data.userId);
117
+ this.events.onPermissionUpdate?.(data);
118
+ });
119
+ this.socket.on("user:update", (data) => {
120
+ console.log("[ExGuard] User update received:", data);
121
+ cache.clearUser(data.userId);
122
+ this.events.onUserUpdate?.(data);
123
+ });
124
+ this.socket.on("connect_error", (err) => {
125
+ console.error("[ExGuard] Realtime connection error:", err.message);
126
+ });
127
+ } catch (error) {
128
+ console.error("[ExGuard] Failed to load socket.io-client:", error);
129
+ }
130
+ }
131
+ disconnect() {
132
+ if (this.socket) {
133
+ this.socket.disconnect();
134
+ this.socket = null;
135
+ this.connected = false;
136
+ }
137
+ }
138
+ isConnected() {
139
+ return this.connected;
140
+ }
141
+ emit(event, data) {
142
+ if (this.socket && this.connected) {
143
+ this.socket.emit(event, data);
144
+ }
145
+ }
146
+ };
147
+ var realtime = new ExGuardRealtime();
68
148
  var ExGuardClient = class {
69
149
  constructor(config) {
70
150
  this.baseUrl = config.baseUrl;
71
151
  this.apiKey = config.apiKey;
72
152
  this.cache = cache;
153
+ this.realtime = realtime;
154
+ if (config.realtime?.enabled && config.realtime?.url) {
155
+ this.realtime.connect(config.realtime.url);
156
+ }
73
157
  }
74
158
  async authenticate(context) {
75
159
  const cacheKey = `auth:${context.token}`;
@@ -104,6 +188,12 @@ var ExGuardClient = class {
104
188
  const userPermissions = result.user.modules?.flatMap((m) => m.permissions) || [];
105
189
  return userPermissions.includes(permission);
106
190
  }
191
+ clearCache() {
192
+ this.cache.clear();
193
+ }
194
+ disconnectRealtime() {
195
+ this.realtime.disconnect();
196
+ }
107
197
  };
108
198
  function createExGuardClient(config) {
109
199
  return new ExGuardClient(config);
@@ -116,7 +206,8 @@ var ExGuardModule = class {
116
206
  const client = new ExGuardClient({
117
207
  baseUrl: options.baseUrl,
118
208
  apiKey: options.apiKey,
119
- cache: options.cache
209
+ cache: options.cache,
210
+ realtime: options.realtime
120
211
  });
121
212
  return {
122
213
  module: ExGuardModule,
@@ -151,7 +242,8 @@ var ExGuardPermissionGuard = class {
151
242
  throw new import_common2.UnauthorizedException("No token provided");
152
243
  }
153
244
  if (!this.client) {
154
- return true;
245
+ console.warn("[ExGuard] Client not configured. Access denied.");
246
+ return false;
155
247
  }
156
248
  const authResult = await this.client.authenticate({ token, request });
157
249
  if (!authResult.allowed) {
@@ -233,6 +325,7 @@ function RequireRoles(roles, requireAll = false) {
233
325
  RequirePermissions,
234
326
  RequireRoles,
235
327
  cache,
236
- createExGuardClient
328
+ createExGuardClient,
329
+ realtime
237
330
  });
238
331
  //# 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 } 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"]}
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 apiKey: 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 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\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}\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, 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 this.socket = io(url, {\n transports: ['websocket'],\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');\n this.events.onConnect?.();\n });\n\n this.socket.on('disconnect', () => {\n this.connected = false;\n console.log('[ExGuard] Realtime disconnected');\n this.events.onDisconnect?.();\n });\n\n this.socket.on('permission:update', (data: any) => {\n console.log('[ExGuard] Permission update received:', data);\n cache.clearUser(data.userId);\n this.events.onPermissionUpdate?.(data);\n });\n\n this.socket.on('user:update', (data: any) => {\n console.log('[ExGuard] User update received:', data);\n cache.clearUser(data.userId);\n this.events.onUserUpdate?.(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 apiKey: string;\n private cache: ExGuardCache;\n private realtime: ExGuardRealtime;\n\n constructor(config: ExGuardConfig) {\n this.baseUrl = config.baseUrl;\n this.apiKey = config.apiKey;\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 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 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 apiKey: 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 apiKey: options.apiKey,\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"]}
package/dist/index.d.cts CHANGED
@@ -8,6 +8,10 @@ interface ExGuardConfig {
8
8
  enabled?: boolean;
9
9
  ttl?: number;
10
10
  };
11
+ realtime?: {
12
+ enabled?: boolean;
13
+ url?: string;
14
+ };
11
15
  }
12
16
  interface User {
13
17
  id: string;
@@ -33,20 +37,49 @@ interface GuardResult {
33
37
  user?: UserAccessResponse;
34
38
  error?: string;
35
39
  }
40
+ interface RealtimeEvents {
41
+ onConnect?: () => void;
42
+ onDisconnect?: () => void;
43
+ onPermissionUpdate?: (data: {
44
+ userId: string;
45
+ permissions: string[];
46
+ }) => void;
47
+ onUserUpdate?: (data: {
48
+ userId: string;
49
+ user: UserAccessResponse;
50
+ }) => void;
51
+ }
36
52
  declare class ExGuardCache {
37
53
  private cache;
38
54
  private ttl;
39
55
  get(key: string): any;
40
56
  set(key: string, data: any, ttl?: number): void;
57
+ delete(key: string): void;
58
+ clear(): void;
59
+ clearUser(userId: string): void;
41
60
  }
42
61
  declare const cache: ExGuardCache;
62
+ declare class ExGuardRealtime {
63
+ private socket;
64
+ private connected;
65
+ private url;
66
+ private events;
67
+ connect(url: string, events?: RealtimeEvents): void;
68
+ disconnect(): void;
69
+ isConnected(): boolean;
70
+ emit(event: string, data: any): void;
71
+ }
72
+ declare const realtime: ExGuardRealtime;
43
73
  declare class ExGuardClient {
44
74
  private baseUrl;
45
75
  private apiKey;
46
76
  private cache;
77
+ private realtime;
47
78
  constructor(config: ExGuardConfig);
48
79
  authenticate(context: GuardContext): Promise<GuardResult>;
49
80
  hasPermission(token: string, permission: string): Promise<boolean>;
81
+ clearCache(): void;
82
+ disconnectRealtime(): void;
50
83
  }
51
84
  declare function createExGuardClient(config: ExGuardConfig): ExGuardClient;
52
85
 
@@ -57,6 +90,10 @@ interface ExGuardModuleOptions {
57
90
  enabled?: boolean;
58
91
  ttl?: number;
59
92
  };
93
+ realtime?: {
94
+ enabled?: boolean;
95
+ url?: string;
96
+ };
60
97
  }
61
98
  declare class ExGuardModule {
62
99
  static forRoot(options: ExGuardModuleOptions): DynamicModule;
@@ -74,4 +111,4 @@ declare class ExGuardPermissionGuard implements CanActivate {
74
111
  declare function RequirePermissions(permissions: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
75
112
  declare function RequireRoles(roles: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
76
113
 
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 };
114
+ export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardClient, type ExGuardConfig, ExGuardModule, type ExGuardModuleOptions, ExGuardPermissionGuard, type GuardContext, type GuardResult, type ModulePermission, type RealtimeEvents, RequirePermissions, RequireRoles, type User, type UserAccessResponse, cache, createExGuardClient, realtime };
package/dist/index.d.ts CHANGED
@@ -8,6 +8,10 @@ interface ExGuardConfig {
8
8
  enabled?: boolean;
9
9
  ttl?: number;
10
10
  };
11
+ realtime?: {
12
+ enabled?: boolean;
13
+ url?: string;
14
+ };
11
15
  }
12
16
  interface User {
13
17
  id: string;
@@ -33,20 +37,49 @@ interface GuardResult {
33
37
  user?: UserAccessResponse;
34
38
  error?: string;
35
39
  }
40
+ interface RealtimeEvents {
41
+ onConnect?: () => void;
42
+ onDisconnect?: () => void;
43
+ onPermissionUpdate?: (data: {
44
+ userId: string;
45
+ permissions: string[];
46
+ }) => void;
47
+ onUserUpdate?: (data: {
48
+ userId: string;
49
+ user: UserAccessResponse;
50
+ }) => void;
51
+ }
36
52
  declare class ExGuardCache {
37
53
  private cache;
38
54
  private ttl;
39
55
  get(key: string): any;
40
56
  set(key: string, data: any, ttl?: number): void;
57
+ delete(key: string): void;
58
+ clear(): void;
59
+ clearUser(userId: string): void;
41
60
  }
42
61
  declare const cache: ExGuardCache;
62
+ declare class ExGuardRealtime {
63
+ private socket;
64
+ private connected;
65
+ private url;
66
+ private events;
67
+ connect(url: string, events?: RealtimeEvents): void;
68
+ disconnect(): void;
69
+ isConnected(): boolean;
70
+ emit(event: string, data: any): void;
71
+ }
72
+ declare const realtime: ExGuardRealtime;
43
73
  declare class ExGuardClient {
44
74
  private baseUrl;
45
75
  private apiKey;
46
76
  private cache;
77
+ private realtime;
47
78
  constructor(config: ExGuardConfig);
48
79
  authenticate(context: GuardContext): Promise<GuardResult>;
49
80
  hasPermission(token: string, permission: string): Promise<boolean>;
81
+ clearCache(): void;
82
+ disconnectRealtime(): void;
50
83
  }
51
84
  declare function createExGuardClient(config: ExGuardConfig): ExGuardClient;
52
85
 
@@ -57,6 +90,10 @@ interface ExGuardModuleOptions {
57
90
  enabled?: boolean;
58
91
  ttl?: number;
59
92
  };
93
+ realtime?: {
94
+ enabled?: boolean;
95
+ url?: string;
96
+ };
60
97
  }
61
98
  declare class ExGuardModule {
62
99
  static forRoot(options: ExGuardModuleOptions): DynamicModule;
@@ -74,4 +111,4 @@ declare class ExGuardPermissionGuard implements CanActivate {
74
111
  declare function RequirePermissions(permissions: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
75
112
  declare function RequireRoles(roles: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
76
113
 
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 };
114
+ export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardClient, type ExGuardConfig, ExGuardModule, type ExGuardModuleOptions, ExGuardPermissionGuard, type GuardContext, type GuardResult, type ModulePermission, type RealtimeEvents, RequirePermissions, RequireRoles, type User, type UserAccessResponse, cache, createExGuardClient, realtime };
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __decorateClass = (decorators, target, key, kind) => {
4
10
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
11
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
@@ -32,13 +38,96 @@ var ExGuardCache = class {
32
38
  ttl: ttl || this.ttl
33
39
  });
34
40
  }
41
+ delete(key) {
42
+ this.cache.delete(key);
43
+ }
44
+ clear() {
45
+ this.cache.clear();
46
+ }
47
+ clearUser(userId) {
48
+ for (const key of this.cache.keys()) {
49
+ if (key.includes(userId)) {
50
+ this.cache.delete(key);
51
+ }
52
+ }
53
+ }
35
54
  };
36
55
  var cache = new ExGuardCache();
56
+ var ExGuardRealtime = class {
57
+ constructor() {
58
+ this.socket = null;
59
+ this.connected = false;
60
+ this.url = "";
61
+ this.events = {};
62
+ }
63
+ connect(url, events = {}) {
64
+ this.url = url;
65
+ this.events = events;
66
+ if (!url) {
67
+ console.log("[ExGuard] Realtime URL not provided, skipping connection");
68
+ return;
69
+ }
70
+ try {
71
+ const { io } = __require("socket.io-client");
72
+ this.socket = io(url, {
73
+ transports: ["websocket"],
74
+ reconnection: true,
75
+ reconnectionAttempts: 5,
76
+ reconnectionDelay: 1e3
77
+ });
78
+ this.socket.on("connect", () => {
79
+ this.connected = true;
80
+ console.log("[ExGuard] Realtime connected");
81
+ this.events.onConnect?.();
82
+ });
83
+ this.socket.on("disconnect", () => {
84
+ this.connected = false;
85
+ console.log("[ExGuard] Realtime disconnected");
86
+ this.events.onDisconnect?.();
87
+ });
88
+ this.socket.on("permission:update", (data) => {
89
+ console.log("[ExGuard] Permission update received:", data);
90
+ cache.clearUser(data.userId);
91
+ this.events.onPermissionUpdate?.(data);
92
+ });
93
+ this.socket.on("user:update", (data) => {
94
+ console.log("[ExGuard] User update received:", data);
95
+ cache.clearUser(data.userId);
96
+ this.events.onUserUpdate?.(data);
97
+ });
98
+ this.socket.on("connect_error", (err) => {
99
+ console.error("[ExGuard] Realtime connection error:", err.message);
100
+ });
101
+ } catch (error) {
102
+ console.error("[ExGuard] Failed to load socket.io-client:", error);
103
+ }
104
+ }
105
+ disconnect() {
106
+ if (this.socket) {
107
+ this.socket.disconnect();
108
+ this.socket = null;
109
+ this.connected = false;
110
+ }
111
+ }
112
+ isConnected() {
113
+ return this.connected;
114
+ }
115
+ emit(event, data) {
116
+ if (this.socket && this.connected) {
117
+ this.socket.emit(event, data);
118
+ }
119
+ }
120
+ };
121
+ var realtime = new ExGuardRealtime();
37
122
  var ExGuardClient = class {
38
123
  constructor(config) {
39
124
  this.baseUrl = config.baseUrl;
40
125
  this.apiKey = config.apiKey;
41
126
  this.cache = cache;
127
+ this.realtime = realtime;
128
+ if (config.realtime?.enabled && config.realtime?.url) {
129
+ this.realtime.connect(config.realtime.url);
130
+ }
42
131
  }
43
132
  async authenticate(context) {
44
133
  const cacheKey = `auth:${context.token}`;
@@ -73,6 +162,12 @@ var ExGuardClient = class {
73
162
  const userPermissions = result.user.modules?.flatMap((m) => m.permissions) || [];
74
163
  return userPermissions.includes(permission);
75
164
  }
165
+ clearCache() {
166
+ this.cache.clear();
167
+ }
168
+ disconnectRealtime() {
169
+ this.realtime.disconnect();
170
+ }
76
171
  };
77
172
  function createExGuardClient(config) {
78
173
  return new ExGuardClient(config);
@@ -85,7 +180,8 @@ var ExGuardModule = class {
85
180
  const client = new ExGuardClient({
86
181
  baseUrl: options.baseUrl,
87
182
  apiKey: options.apiKey,
88
- cache: options.cache
183
+ cache: options.cache,
184
+ realtime: options.realtime
89
185
  });
90
186
  return {
91
187
  module: ExGuardModule,
@@ -120,7 +216,8 @@ var ExGuardPermissionGuard = class {
120
216
  throw new UnauthorizedException("No token provided");
121
217
  }
122
218
  if (!this.client) {
123
- return true;
219
+ console.warn("[ExGuard] Client not configured. Access denied.");
220
+ return false;
124
221
  }
125
222
  const authResult = await this.client.authenticate({ token, request });
126
223
  if (!authResult.allowed) {
@@ -201,6 +298,7 @@ export {
201
298
  RequirePermissions,
202
299
  RequireRoles,
203
300
  cache,
204
- createExGuardClient
301
+ createExGuardClient,
302
+ realtime
205
303
  };
206
304
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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":[]}
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 realtime?: {\n enabled?: boolean;\n url?: string;\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\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}\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, 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 this.socket = io(url, {\n transports: ['websocket'],\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');\n this.events.onConnect?.();\n });\n\n this.socket.on('disconnect', () => {\n this.connected = false;\n console.log('[ExGuard] Realtime disconnected');\n this.events.onDisconnect?.();\n });\n\n this.socket.on('permission:update', (data: any) => {\n console.log('[ExGuard] Permission update received:', data);\n cache.clearUser(data.userId);\n this.events.onPermissionUpdate?.(data);\n });\n\n this.socket.on('user:update', (data: any) => {\n console.log('[ExGuard] User update received:', data);\n cache.clearUser(data.userId);\n this.events.onUserUpdate?.(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 apiKey: string;\n private cache: ExGuardCache;\n private realtime: ExGuardRealtime;\n\n constructor(config: ExGuardConfig) {\n this.baseUrl = config.baseUrl;\n this.apiKey = config.apiKey;\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 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 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 apiKey: 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 apiKey: options.apiKey,\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":[]}
package/dist/setup.cjs ADDED
@@ -0,0 +1,204 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const GUARD_CONTENT = `import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';
5
+ import { Reflector } from '@nestjs/core';
6
+ import { ExGuardClient } from 'exguard-endpoint';
7
+
8
+ export const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';
9
+ export const EXGUARD_ROLES_KEY = 'exguard_roles';
10
+
11
+ @Injectable()
12
+ export class ExGuardPermissionGuard implements CanActivate {
13
+ constructor(
14
+ @Optional() @Inject('EXGUARD_CLIENT') private client: ExGuardClient,
15
+ private reflector: Reflector,
16
+ ) {}
17
+
18
+ async canActivate(context: ExecutionContext): Promise<boolean> {
19
+ const request = context.switchToHttp().getRequest();
20
+ const token = this.extractToken(request);
21
+
22
+ if (!token) {
23
+ throw new UnauthorizedException('No token provided');
24
+ }
25
+
26
+ if (!this.client) {
27
+ console.warn('[ExGuard] Client not configured. Access denied.');
28
+ return false;
29
+ }
30
+
31
+ const authResult = await this.client.authenticate({ token, request });
32
+
33
+ if (!authResult.allowed) {
34
+ throw new ForbiddenException(authResult.error || 'Access denied');
35
+ }
36
+
37
+ if (!authResult.user) {
38
+ throw new ForbiddenException('User not found');
39
+ }
40
+
41
+ const handler = context.getHandler();
42
+ const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);
43
+
44
+ if (permMeta) {
45
+ const { permissions, requireAll } = permMeta;
46
+ const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap(m => m.permissions) : [];
47
+
48
+ if (requireAll) {
49
+ if (!permissions.every(p => userPermissions.includes(p))) {
50
+ throw new ForbiddenException('Insufficient permissions');
51
+ }
52
+ } else {
53
+ if (!permissions.some(p => userPermissions.includes(p))) {
54
+ throw new ForbiddenException('Insufficient permissions');
55
+ }
56
+ }
57
+ }
58
+
59
+ const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);
60
+
61
+ if (roleMeta) {
62
+ const { roles, requireAll } = roleMeta;
63
+ const userRoles = authResult.user.roles || [];
64
+
65
+ if (requireAll) {
66
+ if (!roles.every(r => userRoles.includes(r))) {
67
+ throw new ForbiddenException('Insufficient roles');
68
+ }
69
+ } else {
70
+ if (!roles.some(r => userRoles.includes(r))) {
71
+ throw new ForbiddenException('Insufficient roles');
72
+ }
73
+ }
74
+ }
75
+
76
+ request.user = authResult.user;
77
+ return true;
78
+ }
79
+
80
+ private extractToken(request) {
81
+ const auth = request.headers ? request.headers.authorization : null;
82
+ if (auth && auth.startsWith('Bearer ')) {
83
+ return auth.substring(7);
84
+ }
85
+ return request.headers ? request.headers['x-access-token'] : null;
86
+ }
87
+ }
88
+
89
+ export function RequirePermissions(permissions, requireAll = false) {
90
+ return function (target, propertyKey, descriptor) {
91
+ Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);
92
+ return descriptor;
93
+ };
94
+ }
95
+
96
+ export function RequireRoles(roles, requireAll = false) {
97
+ return function (target, propertyKey, descriptor) {
98
+ Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);
99
+ return descriptor;
100
+ };
101
+ }
102
+ `;
103
+
104
+ const MODULE_CONTENT = `import { Module, Global, DynamicModule } from '@nestjs/common';
105
+ import { ExGuardModule } from 'exguard-endpoint';
106
+
107
+ @Global()
108
+ @Module({})
109
+ export class ExGuardModuleSetup {
110
+ static forRoot(options) {
111
+ return ExGuardModule.forRoot(options);
112
+ }
113
+ }
114
+ `;
115
+
116
+ function ensureDir(dirPath) {
117
+ if (!fs.existsSync(dirPath)) {
118
+ fs.mkdirSync(dirPath, { recursive: true });
119
+ }
120
+ }
121
+
122
+ function writeFile(filePath, content) {
123
+ const dir = path.dirname(filePath);
124
+ ensureDir(dir);
125
+ fs.writeFileSync(filePath, content);
126
+ console.log('Created: ' + filePath);
127
+ }
128
+
129
+ function setup() {
130
+ const srcDir = path.join(process.cwd(), 'src');
131
+ const exguardDir = path.join(srcDir, 'exguard');
132
+
133
+ if (!fs.existsSync(srcDir)) {
134
+ console.error('Error: Run this command in your NestJS project root');
135
+ process.exit(1);
136
+ }
137
+
138
+ ensureDir(exguardDir);
139
+
140
+ writeFile(path.join(exguardDir, 'exguard.guard.ts'), GUARD_CONTENT);
141
+ writeFile(path.join(exguardDir, 'exguard.module.ts'), MODULE_CONTENT);
142
+
143
+ console.log('');
144
+ console.log('===========================================');
145
+ console.log('✅ ExGuard Setup Complete!');
146
+ console.log('===========================================');
147
+ console.log('');
148
+ console.log('Next steps:');
149
+ console.log('');
150
+ console.log('1. Add to app.module.ts:');
151
+ console.log('');
152
+ console.log(' import { ExGuardModule } from "./exguard/exguard.module";');
153
+ console.log('');
154
+ console.log(' @Module({');
155
+ console.log(' imports: [');
156
+ console.log(' ExGuardModule.forRoot({');
157
+ console.log(' baseUrl: process.env.EXGUARD_BASE_URL,');
158
+ console.log(' apiKey: process.env.EXGUARD_API_KEY,');
159
+ console.log(' realtime: {');
160
+ console.log(' enabled: true,');
161
+ console.log(' url: process.env.EXGUARD_REALTIME_URL,');
162
+ console.log(' },');
163
+ console.log(' }),');
164
+ console.log(' ],');
165
+ console.log(' })');
166
+ console.log(' export class AppModule {}');
167
+ console.log('');
168
+ console.log('2. Add environment variables:');
169
+ console.log('');
170
+ console.log(' EXGUARD_BASE_URL=https://api.exguard.com');
171
+ console.log(' EXGUARD_API_KEY=your_api_key');
172
+ console.log(' EXGUARD_REALTIME_URL=wss://realtime.exguard.com');
173
+ console.log('');
174
+ console.log('3. Use in controllers:');
175
+ console.log('');
176
+ console.log(' import { ExGuardPermissionGuard, RequirePermissions } from "./exguard/exguard.guard";');
177
+ console.log('');
178
+ console.log(' @Controller("items")');
179
+ console.log(' @UseGuards(ExGuardPermissionGuard)');
180
+ console.log(' export class ItemsController {');
181
+ console.log(' @Get() @RequirePermissions(["item:read"]) findAll() {}');
182
+ console.log(' @Post() @RequirePermissions(["item:create"]) create() {}');
183
+ console.log(' }');
184
+ console.log('');
185
+ console.log('===========================================');
186
+ }
187
+
188
+ const args = process.argv.slice(2);
189
+
190
+ if (args[0] === 'setup') {
191
+ setup();
192
+ } else {
193
+ console.log('===========================================');
194
+ console.log('ExGuard Endpoint - NestJS RBAC Guard');
195
+ console.log('===========================================');
196
+ console.log('');
197
+ console.log('To setup in your NestJS project:');
198
+ console.log(' npx exguard-endpoint setup');
199
+ console.log('');
200
+ console.log('Or add manually:');
201
+ console.log(' 1. Import ExGuardModule in app.module.ts');
202
+ console.log(' 2. Use ExGuardPermissionGuard in controllers');
203
+ console.log('');
204
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "exguard-endpoint",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "type": "module",
9
- "description": "Simple RBAC permission guard for NestJS",
9
+ "description": "Simple RBAC permission guard for NestJS with realtime support",
10
10
  "main": "./dist/index.cjs",
11
11
  "module": "./dist/index.js",
12
12
  "types": "./dist/index.d.ts",
@@ -17,25 +17,31 @@
17
17
  "require": "./dist/index.cjs"
18
18
  }
19
19
  },
20
+ "bin": {
21
+ "exguard-endpoint": "./dist/setup.cjs"
22
+ },
20
23
  "files": [
21
24
  "dist"
22
25
  ],
23
26
  "scripts": {
24
27
  "build": "tsup",
25
28
  "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
26
- "prebuild": "npm run clean"
29
+ "prebuild": "npm run clean",
30
+ "setup": "node dist/setup.cjs"
27
31
  },
28
32
  "keywords": [
29
33
  "exguard",
30
34
  "rbac",
31
35
  "nestjs",
32
36
  "permissions",
33
- "guards"
37
+ "guards",
38
+ "realtime"
34
39
  ],
35
40
  "author": "EmpowerX",
36
41
  "license": "MIT",
37
42
  "dependencies": {
38
- "axios": "^1.0.0"
43
+ "axios": "^1.0.0",
44
+ "socket.io-client": "^4.8.3"
39
45
  },
40
46
  "devDependencies": {
41
47
  "@nestjs/common": "^10.0.0",