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 +23 -88
- package/dist/index.cjs +62 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -107
- package/dist/index.d.ts +4 -107
- package/dist/index.js +61 -226
- package/dist/index.js.map +1 -1
- package/dist/setup.cjs +78 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ExGuard Endpoint
|
|
2
2
|
|
|
3
|
-
Simple RBAC permission guard for NestJS with
|
|
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
|
|
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
|
-
|
|
52
|
-
import {
|
|
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'
|
|
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
|
-
|
|
88
|
-
- REST API at `/guard/verify-token` and `/guard/me`
|
|
89
|
-
- Realtime WebSocket at `/realtime` namespace
|
|
65
|
+
## Caching (Enabled by Default)
|
|
90
66
|
|
|
91
|
-
|
|
67
|
+
- **TTL**: 5 minutes
|
|
68
|
+
- **Auto-clear**: When permissions change via realtime
|
|
69
|
+
- **Manual clear**: `clearExGuardCache()`
|
|
92
70
|
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
74
|
+
// Call when needed
|
|
75
|
+
clearExGuardCache();
|
|
76
|
+
```
|
|
101
77
|
|
|
102
|
-
|
|
78
|
+
## Decorators
|
|
103
79
|
|
|
104
80
|
| Decorator | Description |
|
|
105
81
|
|-----------|-------------|
|
|
106
|
-
| `@RequirePermissions(['item:read'])` | User needs ANY of the
|
|
107
|
-
| `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
## User Object
|
|
126
91
|
|
|
127
|
-
After successful authentication, the user object is available at:
|
|
128
92
|
```typescript
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
252
|
-
useValue:
|
|
50
|
+
provide: "EXGUARD_BASE_URL",
|
|
51
|
+
useValue: options.baseUrl
|
|
253
52
|
}
|
|
254
53
|
],
|
|
255
|
-
exports: ["
|
|
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(
|
|
270
|
-
this.
|
|
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.
|
|
280
|
-
console.warn("[ExGuard]
|
|
92
|
+
if (!this.baseUrl) {
|
|
93
|
+
console.warn("[ExGuard] Base URL not configured. Access denied.");
|
|
281
94
|
return false;
|
|
282
95
|
}
|
|
283
|
-
const
|
|
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
|
|
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)("
|
|
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
|
-
|
|
363
|
-
createExGuardClient,
|
|
364
|
-
realtime
|
|
202
|
+
clearExGuardCache
|
|
365
203
|
});
|
|
366
204
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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"]}
|