exguard-endpoint 1.0.5 → 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 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -103
- package/dist/index.d.ts +4 -103
- package/dist/index.js +61 -200
- package/dist/index.js.map +1 -1
- package/dist/setup.cjs +78 -20
- 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,14 +26,6 @@ import { ExGuardModule } from 'exguard-endpoint';
|
|
|
35
26
|
imports: [
|
|
36
27
|
ExGuardModule.forRoot({
|
|
37
28
|
baseUrl: process.env.EXGUARD_BASE_URL,
|
|
38
|
-
cache: {
|
|
39
|
-
enabled: true,
|
|
40
|
-
ttl: 300000, // 5 minutes
|
|
41
|
-
},
|
|
42
|
-
realtime: {
|
|
43
|
-
enabled: true,
|
|
44
|
-
url: process.env.EXGUARD_REALTIME_URL,
|
|
45
|
-
},
|
|
46
29
|
}),
|
|
47
30
|
],
|
|
48
31
|
})
|
|
@@ -52,28 +35,21 @@ export class AppModule {}
|
|
|
52
35
|
### 2. Use in Controllers
|
|
53
36
|
|
|
54
37
|
```typescript
|
|
55
|
-
|
|
56
|
-
import {
|
|
57
|
-
import { ExGuardPermissionGuard, RequirePermissions } from '../exguard/exguard.guard';
|
|
38
|
+
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
|
|
39
|
+
import { ExGuardPermissionGuard, RequirePermissions } from './exguard/exguard.guard';
|
|
58
40
|
|
|
59
41
|
@Controller('items')
|
|
60
42
|
@UseGuards(ExGuardPermissionGuard)
|
|
61
43
|
export class ItemsController {
|
|
62
44
|
|
|
63
|
-
// User needs ANY of these permissions
|
|
64
45
|
@Get()
|
|
65
46
|
@RequirePermissions(['item:read'])
|
|
66
47
|
findAll() { }
|
|
67
48
|
|
|
68
|
-
// Multiple permissions - needs ALL
|
|
69
49
|
@Post()
|
|
70
|
-
@RequirePermissions(['item:create'
|
|
50
|
+
@RequirePermissions(['item:create'])
|
|
71
51
|
create() { }
|
|
72
52
|
|
|
73
|
-
@Patch(':id')
|
|
74
|
-
@RequirePermissions(['item:update'])
|
|
75
|
-
update() { }
|
|
76
|
-
|
|
77
53
|
@Delete(':id')
|
|
78
54
|
@RequirePermissions(['item:delete', 'admin'], true)
|
|
79
55
|
delete() { }
|
|
@@ -82,88 +58,47 @@ export class ItemsController {
|
|
|
82
58
|
|
|
83
59
|
## Environment Variables
|
|
84
60
|
|
|
85
|
-
Add to your `.env` file:
|
|
86
|
-
|
|
87
61
|
```env
|
|
88
62
|
EXGUARD_BASE_URL=https://your-guard-api.com
|
|
89
|
-
EXGUARD_REALTIME_URL=wss://your-realtime.com
|
|
90
63
|
```
|
|
91
64
|
|
|
92
|
-
|
|
65
|
+
## Caching (Enabled by Default)
|
|
93
66
|
|
|
94
|
-
|
|
67
|
+
- **TTL**: 5 minutes
|
|
68
|
+
- **Auto-clear**: When permissions change via realtime
|
|
69
|
+
- **Manual clear**: `clearExGuardCache()`
|
|
95
70
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
3. Calls `/guard/me` to get user access (roles, permissions, modules)
|
|
99
|
-
4. Checks if user has required permissions
|
|
100
|
-
5. Returns user object in `request.user`
|
|
71
|
+
```typescript
|
|
72
|
+
import { clearExGuardCache } from './exguard/exguard.guard';
|
|
101
73
|
|
|
102
|
-
|
|
74
|
+
// Call when needed
|
|
75
|
+
clearExGuardCache();
|
|
76
|
+
```
|
|
103
77
|
|
|
104
|
-
|
|
78
|
+
## Decorators
|
|
105
79
|
|
|
106
80
|
| Decorator | Description |
|
|
107
81
|
|-----------|-------------|
|
|
108
|
-
| `@RequirePermissions(['item:read'])` | User needs ANY of the
|
|
109
|
-
| `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL
|
|
110
|
-
|
|
111
|
-
### ExGuardModule.forRoot Options
|
|
112
|
-
|
|
113
|
-
| Option | Type | Required | Default | Description |
|
|
114
|
-
|--------|------|----------|---------|-------------|
|
|
115
|
-
| baseUrl | string | Yes | - | Guard API base URL |
|
|
116
|
-
| cache.enabled | boolean | No | true | Enable caching |
|
|
117
|
-
| cache.ttl | number | No | 300000 | Cache TTL in ms (5 min) |
|
|
118
|
-
| realtime.enabled | boolean | No | false | Enable realtime connection |
|
|
119
|
-
| realtime.url | string | No | - | Realtime WebSocket URL |
|
|
82
|
+
| `@RequirePermissions(['item:read'])` | User needs ANY of the permissions |
|
|
83
|
+
| `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL permissions |
|
|
120
84
|
|
|
121
|
-
|
|
85
|
+
## Token Extraction
|
|
122
86
|
|
|
123
|
-
The guard automatically extracts token from:
|
|
124
87
|
- `Authorization: Bearer <token>` header
|
|
125
88
|
- `x-access-token` header
|
|
126
89
|
|
|
127
|
-
|
|
90
|
+
## User Object
|
|
128
91
|
|
|
129
|
-
After successful authentication, the user object is available at:
|
|
130
92
|
```typescript
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## User Object Structure
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
{
|
|
139
|
-
user: {
|
|
140
|
-
id: string,
|
|
141
|
-
cognitoSubId: string,
|
|
142
|
-
username: string,
|
|
143
|
-
email: string,
|
|
144
|
-
emailVerified: boolean,
|
|
145
|
-
givenName: string,
|
|
146
|
-
familyName: string,
|
|
147
|
-
employeeNumber: string,
|
|
148
|
-
regionId: string,
|
|
149
|
-
fieldOffice: { id, code, name }
|
|
150
|
-
},
|
|
151
|
-
groups: string[],
|
|
93
|
+
req.user = {
|
|
94
|
+
user: { id, username, email, ... },
|
|
152
95
|
roles: string[],
|
|
153
|
-
modules: [
|
|
154
|
-
|
|
155
|
-
],
|
|
96
|
+
modules: [{ key, name, permissions: [] }],
|
|
97
|
+
groups: string[],
|
|
156
98
|
fieldOffices: string[]
|
|
157
99
|
}
|
|
158
100
|
```
|
|
159
101
|
|
|
160
|
-
## Realtime Features
|
|
161
|
-
|
|
162
|
-
When realtime is enabled:
|
|
163
|
-
- Permission changes are detected in real-time
|
|
164
|
-
- Cache is automatically cleared when permissions update
|
|
165
|
-
- Events: `permission:update`, `user:update`
|
|
166
|
-
|
|
167
102
|
## License
|
|
168
103
|
|
|
169
104
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -31,202 +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, 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();
|
|
148
|
-
var ExGuardClient = class {
|
|
149
|
-
constructor(config) {
|
|
150
|
-
this.baseUrl = config.baseUrl;
|
|
151
|
-
this.cache = cache;
|
|
152
|
-
this.realtime = realtime;
|
|
153
|
-
if (config.realtime?.enabled && config.realtime?.url) {
|
|
154
|
-
this.realtime.connect(config.realtime.url);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
async authenticate(context) {
|
|
158
|
-
const cacheKey = `auth:${context.token}`;
|
|
159
|
-
const cached = this.cache.get(cacheKey);
|
|
160
|
-
if (cached) return cached;
|
|
161
|
-
try {
|
|
162
|
-
const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {
|
|
163
|
-
method: "POST",
|
|
164
|
-
headers: {
|
|
165
|
-
"Content-Type": "application/json",
|
|
166
|
-
"Authorization": `Bearer ${context.token}`
|
|
167
|
-
},
|
|
168
|
-
body: JSON.stringify({ id_token: context.token })
|
|
169
|
-
});
|
|
170
|
-
if (!verifyResponse.ok) {
|
|
171
|
-
const errorData = await verifyResponse.json().catch(() => ({}));
|
|
172
|
-
return { allowed: false, error: errorData.message || "Invalid token" };
|
|
173
|
-
}
|
|
174
|
-
const meResponse = await fetch(`${this.baseUrl}/guard/me`, {
|
|
175
|
-
method: "GET",
|
|
176
|
-
headers: {
|
|
177
|
-
"Authorization": `Bearer ${context.token}`
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
if (!meResponse.ok) {
|
|
181
|
-
return { allowed: false, error: "Failed to get user access" };
|
|
182
|
-
}
|
|
183
|
-
const meData = await meResponse.json();
|
|
184
|
-
const result = {
|
|
185
|
-
allowed: true,
|
|
186
|
-
user: meData.data || meData
|
|
187
|
-
};
|
|
188
|
-
this.cache.set(cacheKey, result);
|
|
189
|
-
return result;
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.error("[ExGuard] Authentication error:", error);
|
|
192
|
-
return { allowed: false, error: "Authentication failed" };
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
async hasPermission(token, permission) {
|
|
196
|
-
const result = await this.authenticate({ token });
|
|
197
|
-
if (!result.allowed || !result.user) return false;
|
|
198
|
-
const userPermissions = result.user.modules?.flatMap((m) => m.permissions) || [];
|
|
199
|
-
return userPermissions.includes(permission);
|
|
200
|
-
}
|
|
201
|
-
clearCache() {
|
|
202
|
-
this.cache.clear();
|
|
203
|
-
}
|
|
204
|
-
disconnectRealtime() {
|
|
205
|
-
this.realtime.disconnect();
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
function createExGuardClient(config) {
|
|
209
|
-
return new ExGuardClient(config);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
42
|
// src/module.ts
|
|
213
43
|
var import_common = require("@nestjs/common");
|
|
214
44
|
var ExGuardModule = class {
|
|
215
45
|
static forRoot(options) {
|
|
216
|
-
const client = new ExGuardClient({
|
|
217
|
-
baseUrl: options.baseUrl,
|
|
218
|
-
cache: options.cache,
|
|
219
|
-
realtime: options.realtime
|
|
220
|
-
});
|
|
221
46
|
return {
|
|
222
47
|
module: ExGuardModule,
|
|
223
48
|
providers: [
|
|
224
49
|
{
|
|
225
|
-
provide: "
|
|
226
|
-
useValue:
|
|
50
|
+
provide: "EXGUARD_BASE_URL",
|
|
51
|
+
useValue: options.baseUrl
|
|
227
52
|
}
|
|
228
53
|
],
|
|
229
|
-
exports: ["
|
|
54
|
+
exports: ["EXGUARD_BASE_URL"]
|
|
230
55
|
};
|
|
231
56
|
}
|
|
232
57
|
};
|
|
@@ -239,9 +64,23 @@ ExGuardModule = __decorateClass([
|
|
|
239
64
|
var import_common2 = require("@nestjs/common");
|
|
240
65
|
var EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
241
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
|
+
}
|
|
242
81
|
var ExGuardPermissionGuard = class {
|
|
243
|
-
constructor(
|
|
244
|
-
this.
|
|
82
|
+
constructor(baseUrl, reflector) {
|
|
83
|
+
this.baseUrl = baseUrl;
|
|
245
84
|
this.reflector = reflector;
|
|
246
85
|
}
|
|
247
86
|
async canActivate(context) {
|
|
@@ -250,11 +89,42 @@ var ExGuardPermissionGuard = class {
|
|
|
250
89
|
if (!token) {
|
|
251
90
|
throw new import_common2.UnauthorizedException("No token provided");
|
|
252
91
|
}
|
|
253
|
-
if (!this.
|
|
254
|
-
console.warn("[ExGuard]
|
|
92
|
+
if (!this.baseUrl) {
|
|
93
|
+
console.warn("[ExGuard] Base URL not configured. Access denied.");
|
|
255
94
|
return false;
|
|
256
95
|
}
|
|
257
|
-
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
|
+
}
|
|
258
128
|
if (!authResult.allowed) {
|
|
259
129
|
throw new import_common2.ForbiddenException(authResult.error || "Access denied");
|
|
260
130
|
}
|
|
@@ -262,13 +132,10 @@ var ExGuardPermissionGuard = class {
|
|
|
262
132
|
throw new import_common2.ForbiddenException("User not found");
|
|
263
133
|
}
|
|
264
134
|
const handler = context.getHandler();
|
|
265
|
-
const permMeta = this.reflector.get(
|
|
266
|
-
EXGUARD_PERMISSIONS_KEY,
|
|
267
|
-
handler
|
|
268
|
-
);
|
|
135
|
+
const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);
|
|
269
136
|
if (permMeta) {
|
|
270
137
|
const { permissions, requireAll } = permMeta;
|
|
271
|
-
const userPermissions = authResult.user.modules
|
|
138
|
+
const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m) => m.permissions) : [];
|
|
272
139
|
if (requireAll) {
|
|
273
140
|
if (!permissions.every((p) => userPermissions.includes(p))) {
|
|
274
141
|
throw new import_common2.ForbiddenException("Insufficient permissions");
|
|
@@ -279,10 +146,7 @@ var ExGuardPermissionGuard = class {
|
|
|
279
146
|
}
|
|
280
147
|
}
|
|
281
148
|
}
|
|
282
|
-
const roleMeta = this.reflector.get(
|
|
283
|
-
EXGUARD_ROLES_KEY,
|
|
284
|
-
handler
|
|
285
|
-
);
|
|
149
|
+
const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);
|
|
286
150
|
if (roleMeta) {
|
|
287
151
|
const { roles, requireAll } = roleMeta;
|
|
288
152
|
const userRoles = authResult.user.roles || [];
|
|
@@ -310,7 +174,7 @@ var ExGuardPermissionGuard = class {
|
|
|
310
174
|
ExGuardPermissionGuard = __decorateClass([
|
|
311
175
|
(0, import_common2.Injectable)(),
|
|
312
176
|
__decorateParam(0, (0, import_common2.Optional)()),
|
|
313
|
-
__decorateParam(0, (0, import_common2.Inject)("
|
|
177
|
+
__decorateParam(0, (0, import_common2.Inject)("EXGUARD_BASE_URL"))
|
|
314
178
|
], ExGuardPermissionGuard);
|
|
315
179
|
function RequirePermissions(permissions, requireAll = false) {
|
|
316
180
|
return function(target, propertyKey, descriptor) {
|
|
@@ -324,17 +188,17 @@ function RequireRoles(roles, requireAll = false) {
|
|
|
324
188
|
return descriptor;
|
|
325
189
|
};
|
|
326
190
|
}
|
|
191
|
+
function clearExGuardCache() {
|
|
192
|
+
cache.clear();
|
|
193
|
+
}
|
|
327
194
|
// Annotate the CommonJS export names for ESM import in node:
|
|
328
195
|
0 && (module.exports = {
|
|
329
196
|
EXGUARD_PERMISSIONS_KEY,
|
|
330
197
|
EXGUARD_ROLES_KEY,
|
|
331
|
-
ExGuardClient,
|
|
332
198
|
ExGuardModule,
|
|
333
199
|
ExGuardPermissionGuard,
|
|
334
200
|
RequirePermissions,
|
|
335
201
|
RequireRoles,
|
|
336
|
-
|
|
337
|
-
createExGuardClient,
|
|
338
|
-
realtime
|
|
202
|
+
clearExGuardCache
|
|
339
203
|
});
|
|
340
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}\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 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 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"]}
|