exguard-endpoint 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -119
- package/dist/index.cjs +64 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -111
- package/dist/index.d.ts +5 -111
- package/dist/index.js +63 -227
- package/dist/index.js.map +1 -1
- package/dist/setup.cjs +150 -48
- 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,162 +8,68 @@ Simple RBAC permission guard for NestJS with realtime support.
|
|
|
8
8
|
npm install exguard-endpoint
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## Quick Setup (
|
|
12
|
-
|
|
13
|
-
Run in your NestJS project root:
|
|
11
|
+
## Quick Setup (Auto-Configures Everything)
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
14
|
npx exguard-endpoint setup
|
|
17
15
|
```
|
|
18
16
|
|
|
19
|
-
This
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
This will:
|
|
18
|
+
1. Create `src/exguard/exguard.guard.ts`
|
|
19
|
+
2. Create `src/exguard/exguard.module.ts`
|
|
20
|
+
3. Update `src/app.module.ts` with ExGuardModule
|
|
21
|
+
4. Create `.env.example` with EXGUARD_BASE_URL
|
|
24
22
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
### 1. Configure AppModule
|
|
23
|
+
## Usage
|
|
28
24
|
|
|
29
25
|
```typescript
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
import { ExGuardModule } from 'exguard-endpoint';
|
|
33
|
-
|
|
34
|
-
@Module({
|
|
35
|
-
imports: [
|
|
36
|
-
ExGuardModule.forRoot({
|
|
37
|
-
baseUrl: process.env.EXGUARD_BASE_URL,
|
|
38
|
-
realtime: {
|
|
39
|
-
enabled: true,
|
|
40
|
-
url: process.env.EXGUARD_BASE_URL?.replace(/^http/, 'ws') + '/realtime',
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
],
|
|
44
|
-
})
|
|
45
|
-
export class AppModule {}
|
|
46
|
-
```
|
|
26
|
+
// app.module.ts is auto-configured!
|
|
27
|
+
// Just use in controllers:
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
// src/items/items.controller.ts
|
|
52
|
-
import { Controller, Get, Post, Patch, Delete, UseGuards } from '@nestjs/common';
|
|
53
|
-
import { ExGuardPermissionGuard, RequirePermissions } from '../exguard/exguard.guard';
|
|
29
|
+
import { ExGuardPermissionGuard, RequirePermissions } from './exguard/exguard.guard';
|
|
54
30
|
|
|
55
31
|
@Controller('items')
|
|
56
32
|
@UseGuards(ExGuardPermissionGuard)
|
|
57
33
|
export class ItemsController {
|
|
58
34
|
|
|
59
|
-
// User needs ANY of these permissions
|
|
60
35
|
@Get()
|
|
61
36
|
@RequirePermissions(['item:read'])
|
|
62
37
|
findAll() { }
|
|
63
38
|
|
|
64
|
-
// Multiple permissions - needs ALL
|
|
65
39
|
@Post()
|
|
66
|
-
@RequirePermissions(['item:create'
|
|
40
|
+
@RequirePermissions(['item:create'])
|
|
67
41
|
create() { }
|
|
68
|
-
|
|
69
|
-
@Patch(':id')
|
|
70
|
-
@RequirePermissions(['item:update'])
|
|
71
|
-
update() { }
|
|
72
|
-
|
|
73
|
-
@Delete(':id')
|
|
74
|
-
@RequirePermissions(['item:delete', 'admin'], true)
|
|
75
|
-
delete() { }
|
|
76
42
|
}
|
|
77
43
|
```
|
|
78
44
|
|
|
79
|
-
## Environment
|
|
80
|
-
|
|
81
|
-
Add to your `.env` file:
|
|
45
|
+
## Environment
|
|
82
46
|
|
|
83
47
|
```env
|
|
84
|
-
EXGUARD_BASE_URL=
|
|
48
|
+
EXGUARD_BASE_URL=http://localhost:3000
|
|
85
49
|
```
|
|
86
50
|
|
|
87
|
-
|
|
88
|
-
- REST API at `/guard/verify-token` and `/guard/me`
|
|
89
|
-
- Realtime WebSocket at `/realtime` namespace
|
|
90
|
-
|
|
91
|
-
## How It Works
|
|
51
|
+
## Caching
|
|
92
52
|
|
|
93
|
-
|
|
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`
|
|
53
|
+
- **TTL**: 5 minutes
|
|
54
|
+
- **Clear cache**: `clearExGuardCache()`
|
|
99
55
|
|
|
100
|
-
##
|
|
101
|
-
|
|
102
|
-
### Decorators
|
|
56
|
+
## Decorators
|
|
103
57
|
|
|
104
58
|
| Decorator | Description |
|
|
105
59
|
|-----------|-------------|
|
|
106
|
-
| `@RequirePermissions(['item:read'])` | User needs ANY
|
|
107
|
-
| `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL
|
|
108
|
-
|
|
109
|
-
### ExGuardModule.forRoot Options
|
|
110
|
-
|
|
111
|
-
| Option | Type | Required | Default | Description |
|
|
112
|
-
|--------|------|----------|---------|-------------|
|
|
113
|
-
| baseUrl | string | Yes | - | Guard API base URL |
|
|
114
|
-
| cache.enabled | boolean | No | true | Enable caching |
|
|
115
|
-
| cache.ttl | number | No | 300000 | Cache TTL in ms (5 min) |
|
|
116
|
-
| realtime.enabled | boolean | No | false | Enable realtime connection |
|
|
117
|
-
| realtime.url | string | No | - | Realtime WebSocket URL |
|
|
60
|
+
| `@RequirePermissions(['item:read'])` | User needs ANY permission |
|
|
61
|
+
| `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL permissions |
|
|
118
62
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
The guard automatically extracts token from:
|
|
122
|
-
- `Authorization: Bearer <token>` header
|
|
123
|
-
- `x-access-token` header
|
|
124
|
-
|
|
125
|
-
### Request User
|
|
126
|
-
|
|
127
|
-
After successful authentication, the user object is available at:
|
|
128
|
-
```typescript
|
|
129
|
-
@Request() req: any
|
|
130
|
-
console.log(req.user); // { id, username, email, roles, modules, groups, fieldOffices }
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## User Object Structure
|
|
63
|
+
## User Object
|
|
134
64
|
|
|
135
65
|
```typescript
|
|
136
|
-
{
|
|
137
|
-
user: {
|
|
138
|
-
id: string,
|
|
139
|
-
cognitoSubId: string,
|
|
140
|
-
username: string,
|
|
141
|
-
email: string,
|
|
142
|
-
emailVerified: boolean,
|
|
143
|
-
givenName: string,
|
|
144
|
-
familyName: string,
|
|
145
|
-
employeeNumber: string,
|
|
146
|
-
regionId: string,
|
|
147
|
-
fieldOffice: { id, code, name }
|
|
148
|
-
},
|
|
149
|
-
groups: string[],
|
|
66
|
+
req.user = {
|
|
67
|
+
user: { id, username, email, ... },
|
|
150
68
|
roles: string[],
|
|
151
|
-
modules: [
|
|
152
|
-
|
|
153
|
-
],
|
|
69
|
+
modules: [{ key, name, permissions: [] }],
|
|
70
|
+
groups: string[],
|
|
154
71
|
fieldOffices: string[]
|
|
155
72
|
}
|
|
156
73
|
```
|
|
157
74
|
|
|
158
|
-
## Realtime Events
|
|
159
|
-
|
|
160
|
-
The guard listens to these events from the WebSocket:
|
|
161
|
-
- `permission:updated` - Permission changes
|
|
162
|
-
- `role:updated` - Role changes
|
|
163
|
-
- `user:updated` - User changes
|
|
164
|
-
|
|
165
|
-
When received, the cache is automatically cleared for that user.
|
|
166
|
-
|
|
167
|
-
## License
|
|
168
|
-
|
|
169
75
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -31,228 +31,28 @@ var src_exports = {};
|
|
|
31
31
|
__export(src_exports, {
|
|
32
32
|
EXGUARD_PERMISSIONS_KEY: () => EXGUARD_PERMISSIONS_KEY,
|
|
33
33
|
EXGUARD_ROLES_KEY: () => EXGUARD_ROLES_KEY,
|
|
34
|
-
ExGuardClient: () => ExGuardClient,
|
|
35
34
|
ExGuardModule: () => ExGuardModule,
|
|
36
35
|
ExGuardPermissionGuard: () => ExGuardPermissionGuard,
|
|
37
36
|
RequirePermissions: () => RequirePermissions,
|
|
38
37
|
RequireRoles: () => RequireRoles,
|
|
39
|
-
|
|
40
|
-
createExGuardClient: () => createExGuardClient,
|
|
41
|
-
realtime: () => realtime
|
|
38
|
+
clearExGuardCache: () => clearExGuardCache
|
|
42
39
|
});
|
|
43
40
|
module.exports = __toCommonJS(src_exports);
|
|
44
41
|
|
|
45
|
-
// src/client.ts
|
|
46
|
-
var ExGuardCache = class {
|
|
47
|
-
constructor() {
|
|
48
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
49
|
-
this.ttl = 3e5;
|
|
50
|
-
}
|
|
51
|
-
get(key) {
|
|
52
|
-
const entry = this.cache.get(key);
|
|
53
|
-
if (!entry) return null;
|
|
54
|
-
if (Date.now() - entry.timestamp > entry.ttl) {
|
|
55
|
-
this.cache.delete(key);
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
return entry.data;
|
|
59
|
-
}
|
|
60
|
-
set(key, data, ttl) {
|
|
61
|
-
this.cache.set(key, {
|
|
62
|
-
data,
|
|
63
|
-
timestamp: Date.now(),
|
|
64
|
-
ttl: ttl || this.ttl
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
delete(key) {
|
|
68
|
-
this.cache.delete(key);
|
|
69
|
-
}
|
|
70
|
-
clear() {
|
|
71
|
-
this.cache.clear();
|
|
72
|
-
}
|
|
73
|
-
clearUser(userId) {
|
|
74
|
-
for (const key of this.cache.keys()) {
|
|
75
|
-
if (key.includes(userId)) {
|
|
76
|
-
this.cache.delete(key);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
var cache = new ExGuardCache();
|
|
82
|
-
var ExGuardRealtime = class {
|
|
83
|
-
constructor() {
|
|
84
|
-
this.socket = null;
|
|
85
|
-
this.connected = false;
|
|
86
|
-
this.url = "";
|
|
87
|
-
this.events = {};
|
|
88
|
-
}
|
|
89
|
-
connect(url, token, events = {}) {
|
|
90
|
-
this.url = url;
|
|
91
|
-
this.events = events;
|
|
92
|
-
if (!url) {
|
|
93
|
-
console.log("[ExGuard] Realtime URL not provided, skipping connection");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const { io } = require("socket.io-client");
|
|
98
|
-
let wsUrl = url;
|
|
99
|
-
if (url.startsWith("http://")) {
|
|
100
|
-
wsUrl = url.replace("http://", "ws://");
|
|
101
|
-
} else if (url.startsWith("https://")) {
|
|
102
|
-
wsUrl = url.replace("https://", "wss://");
|
|
103
|
-
}
|
|
104
|
-
if (!wsUrl.includes("/realtime")) {
|
|
105
|
-
wsUrl = wsUrl.replace(/\/$/, "") + "/realtime";
|
|
106
|
-
}
|
|
107
|
-
this.socket = io(wsUrl, {
|
|
108
|
-
auth: { token },
|
|
109
|
-
transports: ["websocket", "polling"],
|
|
110
|
-
reconnection: true,
|
|
111
|
-
reconnectionAttempts: 5,
|
|
112
|
-
reconnectionDelay: 1e3
|
|
113
|
-
});
|
|
114
|
-
this.socket.on("connect", () => {
|
|
115
|
-
this.connected = true;
|
|
116
|
-
console.log("[ExGuard] Realtime connected to", wsUrl);
|
|
117
|
-
this.events.onConnect?.();
|
|
118
|
-
this.socket.emit("subscribe:permissions");
|
|
119
|
-
});
|
|
120
|
-
this.socket.on("disconnect", () => {
|
|
121
|
-
this.connected = false;
|
|
122
|
-
console.log("[ExGuard] Realtime disconnected");
|
|
123
|
-
this.events.onDisconnect?.();
|
|
124
|
-
});
|
|
125
|
-
this.socket.on("permission:updated", (data) => {
|
|
126
|
-
console.log("[ExGuard] Permission update received:", data);
|
|
127
|
-
if (data.userId) {
|
|
128
|
-
cache.clearUser(data.userId);
|
|
129
|
-
}
|
|
130
|
-
this.events.onPermissionUpdate?.(data);
|
|
131
|
-
});
|
|
132
|
-
this.socket.on("user:updated", (data) => {
|
|
133
|
-
console.log("[ExGuard] User update received:", data);
|
|
134
|
-
if (data.userId) {
|
|
135
|
-
cache.clearUser(data.userId);
|
|
136
|
-
}
|
|
137
|
-
this.events.onUserUpdate?.(data);
|
|
138
|
-
});
|
|
139
|
-
this.socket.on("role:updated", (data) => {
|
|
140
|
-
console.log("[ExGuard] Role update received:", data);
|
|
141
|
-
if (data.userId) {
|
|
142
|
-
cache.clearUser(data.userId);
|
|
143
|
-
}
|
|
144
|
-
this.events.onRoleUpdate?.(data);
|
|
145
|
-
});
|
|
146
|
-
this.socket.on("connect_error", (err) => {
|
|
147
|
-
console.error("[ExGuard] Realtime connection error:", err.message);
|
|
148
|
-
});
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error("[ExGuard] Failed to load socket.io-client:", error);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
disconnect() {
|
|
154
|
-
if (this.socket) {
|
|
155
|
-
this.socket.disconnect();
|
|
156
|
-
this.socket = null;
|
|
157
|
-
this.connected = false;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
isConnected() {
|
|
161
|
-
return this.connected;
|
|
162
|
-
}
|
|
163
|
-
emit(event, data) {
|
|
164
|
-
if (this.socket && this.connected) {
|
|
165
|
-
this.socket.emit(event, data);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
var realtime = new ExGuardRealtime();
|
|
170
|
-
var ExGuardClient = class {
|
|
171
|
-
constructor(config) {
|
|
172
|
-
this.baseUrl = config.baseUrl;
|
|
173
|
-
this.cache = cache;
|
|
174
|
-
this.realtime = realtime;
|
|
175
|
-
if (config.realtime?.enabled && config.realtime?.url) {
|
|
176
|
-
this.realtime.connect(config.realtime.url);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
async authenticate(context) {
|
|
180
|
-
const cacheKey = `auth:${context.token}`;
|
|
181
|
-
const cached = this.cache.get(cacheKey);
|
|
182
|
-
if (cached) return cached;
|
|
183
|
-
try {
|
|
184
|
-
const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {
|
|
185
|
-
method: "POST",
|
|
186
|
-
headers: {
|
|
187
|
-
"Content-Type": "application/json",
|
|
188
|
-
"Authorization": `Bearer ${context.token}`
|
|
189
|
-
},
|
|
190
|
-
body: JSON.stringify({ id_token: context.token })
|
|
191
|
-
});
|
|
192
|
-
if (!verifyResponse.ok) {
|
|
193
|
-
const errorData = await verifyResponse.json().catch(() => ({}));
|
|
194
|
-
return { allowed: false, error: errorData.message || "Invalid token" };
|
|
195
|
-
}
|
|
196
|
-
const meResponse = await fetch(`${this.baseUrl}/guard/me`, {
|
|
197
|
-
method: "GET",
|
|
198
|
-
headers: {
|
|
199
|
-
"Authorization": `Bearer ${context.token}`
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
if (!meResponse.ok) {
|
|
203
|
-
return { allowed: false, error: "Failed to get user access" };
|
|
204
|
-
}
|
|
205
|
-
const meData = await meResponse.json();
|
|
206
|
-
const result = {
|
|
207
|
-
allowed: true,
|
|
208
|
-
user: meData.data || meData
|
|
209
|
-
};
|
|
210
|
-
this.cache.set(cacheKey, result);
|
|
211
|
-
if (this.realtime && !this.realtime.isConnected()) {
|
|
212
|
-
const realtimeUrl = this.baseUrl.replace(/^http/, "ws");
|
|
213
|
-
this.realtime.connect(realtimeUrl, context.token);
|
|
214
|
-
}
|
|
215
|
-
return result;
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.error("[ExGuard] Authentication error:", error);
|
|
218
|
-
return { allowed: false, error: "Authentication failed" };
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
async hasPermission(token, permission) {
|
|
222
|
-
const result = await this.authenticate({ token });
|
|
223
|
-
if (!result.allowed || !result.user) return false;
|
|
224
|
-
const userPermissions = result.user.modules?.flatMap((m) => m.permissions) || [];
|
|
225
|
-
return userPermissions.includes(permission);
|
|
226
|
-
}
|
|
227
|
-
clearCache() {
|
|
228
|
-
this.cache.clear();
|
|
229
|
-
}
|
|
230
|
-
disconnectRealtime() {
|
|
231
|
-
this.realtime.disconnect();
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
function createExGuardClient(config) {
|
|
235
|
-
return new ExGuardClient(config);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
42
|
// src/module.ts
|
|
239
43
|
var import_common = require("@nestjs/common");
|
|
240
44
|
var ExGuardModule = class {
|
|
241
|
-
static forRoot(
|
|
242
|
-
const
|
|
243
|
-
baseUrl: options.baseUrl,
|
|
244
|
-
cache: options.cache,
|
|
245
|
-
realtime: options.realtime
|
|
246
|
-
});
|
|
45
|
+
static forRoot(optionsOrUrl) {
|
|
46
|
+
const baseUrl = typeof optionsOrUrl === "string" ? optionsOrUrl : optionsOrUrl.baseUrl;
|
|
247
47
|
return {
|
|
248
48
|
module: ExGuardModule,
|
|
249
49
|
providers: [
|
|
250
50
|
{
|
|
251
|
-
provide: "
|
|
252
|
-
useValue:
|
|
51
|
+
provide: "EXGUARD_BASE_URL",
|
|
52
|
+
useValue: baseUrl
|
|
253
53
|
}
|
|
254
54
|
],
|
|
255
|
-
exports: ["
|
|
55
|
+
exports: ["EXGUARD_BASE_URL"]
|
|
256
56
|
};
|
|
257
57
|
}
|
|
258
58
|
};
|
|
@@ -265,9 +65,23 @@ ExGuardModule = __decorateClass([
|
|
|
265
65
|
var import_common2 = require("@nestjs/common");
|
|
266
66
|
var EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
267
67
|
var EXGUARD_ROLES_KEY = "exguard_roles";
|
|
68
|
+
var cache = /* @__PURE__ */ new Map();
|
|
69
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
70
|
+
function getCached(key) {
|
|
71
|
+
const entry = cache.get(key);
|
|
72
|
+
if (!entry) return null;
|
|
73
|
+
if (Date.now() - entry.timestamp > CACHE_TTL) {
|
|
74
|
+
cache.delete(key);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return entry.data;
|
|
78
|
+
}
|
|
79
|
+
function setCache(key, data) {
|
|
80
|
+
cache.set(key, { data, timestamp: Date.now() });
|
|
81
|
+
}
|
|
268
82
|
var ExGuardPermissionGuard = class {
|
|
269
|
-
constructor(
|
|
270
|
-
this.
|
|
83
|
+
constructor(baseUrl, reflector) {
|
|
84
|
+
this.baseUrl = baseUrl;
|
|
271
85
|
this.reflector = reflector;
|
|
272
86
|
}
|
|
273
87
|
async canActivate(context) {
|
|
@@ -276,11 +90,42 @@ var ExGuardPermissionGuard = class {
|
|
|
276
90
|
if (!token) {
|
|
277
91
|
throw new import_common2.UnauthorizedException("No token provided");
|
|
278
92
|
}
|
|
279
|
-
if (!this.
|
|
280
|
-
console.warn("[ExGuard]
|
|
93
|
+
if (!this.baseUrl) {
|
|
94
|
+
console.warn("[ExGuard] Base URL not configured. Access denied.");
|
|
281
95
|
return false;
|
|
282
96
|
}
|
|
283
|
-
const
|
|
97
|
+
const cacheKey = `auth:${token}`;
|
|
98
|
+
let authResult = getCached(cacheKey);
|
|
99
|
+
if (!authResult) {
|
|
100
|
+
try {
|
|
101
|
+
const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: {
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"Authorization": `Bearer ${token}`
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({ id_token: token })
|
|
108
|
+
});
|
|
109
|
+
if (!verifyResponse.ok) {
|
|
110
|
+
throw new import_common2.ForbiddenException("Invalid token");
|
|
111
|
+
}
|
|
112
|
+
const meResponse = await fetch(`${this.baseUrl}/guard/me`, {
|
|
113
|
+
method: "GET",
|
|
114
|
+
headers: {
|
|
115
|
+
"Authorization": `Bearer ${token}`
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
if (!meResponse.ok) {
|
|
119
|
+
throw new import_common2.ForbiddenException("Failed to get user access");
|
|
120
|
+
}
|
|
121
|
+
const meData = await meResponse.json();
|
|
122
|
+
authResult = { allowed: true, user: meData.data || meData };
|
|
123
|
+
setCache(cacheKey, authResult);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("[ExGuard] Auth error:", error);
|
|
126
|
+
throw new import_common2.ForbiddenException("Authentication failed");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
284
129
|
if (!authResult.allowed) {
|
|
285
130
|
throw new import_common2.ForbiddenException(authResult.error || "Access denied");
|
|
286
131
|
}
|
|
@@ -288,13 +133,10 @@ var ExGuardPermissionGuard = class {
|
|
|
288
133
|
throw new import_common2.ForbiddenException("User not found");
|
|
289
134
|
}
|
|
290
135
|
const handler = context.getHandler();
|
|
291
|
-
const permMeta = this.reflector.get(
|
|
292
|
-
EXGUARD_PERMISSIONS_KEY,
|
|
293
|
-
handler
|
|
294
|
-
);
|
|
136
|
+
const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);
|
|
295
137
|
if (permMeta) {
|
|
296
138
|
const { permissions, requireAll } = permMeta;
|
|
297
|
-
const userPermissions = authResult.user.modules
|
|
139
|
+
const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m) => m.permissions) : [];
|
|
298
140
|
if (requireAll) {
|
|
299
141
|
if (!permissions.every((p) => userPermissions.includes(p))) {
|
|
300
142
|
throw new import_common2.ForbiddenException("Insufficient permissions");
|
|
@@ -305,10 +147,7 @@ var ExGuardPermissionGuard = class {
|
|
|
305
147
|
}
|
|
306
148
|
}
|
|
307
149
|
}
|
|
308
|
-
const roleMeta = this.reflector.get(
|
|
309
|
-
EXGUARD_ROLES_KEY,
|
|
310
|
-
handler
|
|
311
|
-
);
|
|
150
|
+
const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);
|
|
312
151
|
if (roleMeta) {
|
|
313
152
|
const { roles, requireAll } = roleMeta;
|
|
314
153
|
const userRoles = authResult.user.roles || [];
|
|
@@ -336,7 +175,7 @@ var ExGuardPermissionGuard = class {
|
|
|
336
175
|
ExGuardPermissionGuard = __decorateClass([
|
|
337
176
|
(0, import_common2.Injectable)(),
|
|
338
177
|
__decorateParam(0, (0, import_common2.Optional)()),
|
|
339
|
-
__decorateParam(0, (0, import_common2.Inject)("
|
|
178
|
+
__decorateParam(0, (0, import_common2.Inject)("EXGUARD_BASE_URL"))
|
|
340
179
|
], ExGuardPermissionGuard);
|
|
341
180
|
function RequirePermissions(permissions, requireAll = false) {
|
|
342
181
|
return function(target, propertyKey, descriptor) {
|
|
@@ -350,17 +189,17 @@ function RequireRoles(roles, requireAll = false) {
|
|
|
350
189
|
return descriptor;
|
|
351
190
|
};
|
|
352
191
|
}
|
|
192
|
+
function clearExGuardCache() {
|
|
193
|
+
cache.clear();
|
|
194
|
+
}
|
|
353
195
|
// Annotate the CommonJS export names for ESM import in node:
|
|
354
196
|
0 && (module.exports = {
|
|
355
197
|
EXGUARD_PERMISSIONS_KEY,
|
|
356
198
|
EXGUARD_ROLES_KEY,
|
|
357
|
-
ExGuardClient,
|
|
358
199
|
ExGuardModule,
|
|
359
200
|
ExGuardPermissionGuard,
|
|
360
201
|
RequirePermissions,
|
|
361
202
|
RequireRoles,
|
|
362
|
-
|
|
363
|
-
createExGuardClient,
|
|
364
|
-
realtime
|
|
203
|
+
clearExGuardCache
|
|
365
204
|
});
|
|
366
205
|
//# 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiEA,IAAM,eAAN,MAAmB;AAAA,EAAnB;AACE,SAAQ,QAAQ,oBAAI,IAA2D;AAC/E,SAAQ,MAAM;AAAA;AAAA,EAEd,IAAI,KAAkB;AACpB,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,KAAK,IAAI,IAAI,MAAM,YAAY,MAAM,KAAK;AAC5C,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,IAAI,KAAa,MAAW,KAAoB;AAC9C,SAAK,MAAM,IAAI,KAAK;AAAA,MAClB;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,MACpB,KAAK,OAAO,KAAK;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAEA,OAAO,KAAmB;AACxB,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,UAAU,QAAsB;AAC9B,eAAW,OAAO,KAAK,MAAM,KAAK,GAAG;AACnC,UAAI,IAAI,SAAS,MAAM,GAAG;AACxB,aAAK,MAAM,OAAO,GAAG;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,QAAQ,IAAI,aAAa;AAE/B,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACE,SAAQ,SAAc;AACtB,SAAQ,YAAY;AACpB,SAAQ,MAAc;AACtB,SAAQ,SAAyB,CAAC;AAAA;AAAA,EAElC,QAAQ,KAAa,OAAgB,SAAyB,CAAC,GAAS;AACtE,SAAK,MAAM;AACX,SAAK,SAAS;AAEd,QAAI,CAAC,KAAK;AACR,cAAQ,IAAI,0DAA0D;AACtE;AAAA,IACF;AAEA,QAAI;AACF,YAAM,EAAE,GAAG,IAAI,QAAQ,kBAAkB;AAGzC,UAAI,QAAQ;AACZ,UAAI,IAAI,WAAW,SAAS,GAAG;AAC7B,gBAAQ,IAAI,QAAQ,WAAW,OAAO;AAAA,MACxC,WAAW,IAAI,WAAW,UAAU,GAAG;AACrC,gBAAQ,IAAI,QAAQ,YAAY,QAAQ;AAAA,MAC1C;AAGA,UAAI,CAAC,MAAM,SAAS,WAAW,GAAG;AAChC,gBAAQ,MAAM,QAAQ,OAAO,EAAE,IAAI;AAAA,MACrC;AAEA,WAAK,SAAS,GAAG,OAAO;AAAA,QACtB,MAAM,EAAE,MAAM;AAAA,QACd,YAAY,CAAC,aAAa,SAAS;AAAA,QACnC,cAAc;AAAA,QACd,sBAAsB;AAAA,QACtB,mBAAmB;AAAA,MACrB,CAAC;AAED,WAAK,OAAO,GAAG,WAAW,MAAM;AAC9B,aAAK,YAAY;AACjB,gBAAQ,IAAI,mCAAmC,KAAK;AACpD,aAAK,OAAO,YAAY;AAGxB,aAAK,OAAO,KAAK,uBAAuB;AAAA,MAC1C,CAAC;AAED,WAAK,OAAO,GAAG,cAAc,MAAM;AACjC,aAAK,YAAY;AACjB,gBAAQ,IAAI,iCAAiC;AAC7C,aAAK,OAAO,eAAe;AAAA,MAC7B,CAAC;AAGD,WAAK,OAAO,GAAG,sBAAsB,CAAC,SAAc;AAClD,gBAAQ,IAAI,yCAAyC,IAAI;AACzD,YAAI,KAAK,QAAQ;AACf,gBAAM,UAAU,KAAK,MAAM;AAAA,QAC7B;AACA,aAAK,OAAO,qBAAqB,IAAI;AAAA,MACvC,CAAC;AAED,WAAK,OAAO,GAAG,gBAAgB,CAAC,SAAc;AAC5C,gBAAQ,IAAI,mCAAmC,IAAI;AACnD,YAAI,KAAK,QAAQ;AACf,gBAAM,UAAU,KAAK,MAAM;AAAA,QAC7B;AACA,aAAK,OAAO,eAAe,IAAI;AAAA,MACjC,CAAC;AAED,WAAK,OAAO,GAAG,gBAAgB,CAAC,SAAc;AAC5C,gBAAQ,IAAI,mCAAmC,IAAI;AACnD,YAAI,KAAK,QAAQ;AACf,gBAAM,UAAU,KAAK,MAAM;AAAA,QAC7B;AACA,aAAK,OAAO,eAAe,IAAI;AAAA,MACjC,CAAC;AAED,WAAK,OAAO,GAAG,iBAAiB,CAAC,QAAa;AAC5C,gBAAQ,MAAM,wCAAwC,IAAI,OAAO;AAAA,MACnE,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,8CAA8C,KAAK;AAAA,IACnE;AAAA,EACF;AAAA,EAEA,aAAmB;AACjB,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,WAAW;AACvB,WAAK,SAAS;AACd,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,OAAe,MAAiB;AACnC,QAAI,KAAK,UAAU,KAAK,WAAW;AACjC,WAAK,OAAO,KAAK,OAAO,IAAI;AAAA,IAC9B;AAAA,EACF;AACF;AAEA,IAAM,WAAW,IAAI,gBAAgB;AAE9B,IAAM,gBAAN,MAAoB;AAAA,EAKzB,YAAY,QAAuB;AACjC,SAAK,UAAU,OAAO;AACtB,SAAK,QAAQ;AACb,SAAK,WAAW;AAEhB,QAAI,OAAO,UAAU,WAAW,OAAO,UAAU,KAAK;AACpD,WAAK,SAAS,QAAQ,OAAO,SAAS,GAAG;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,SAA6C;AAC9D,UAAM,WAAW,QAAQ,QAAQ,KAAK;AACtC,UAAM,SAAS,KAAK,MAAM,IAAI,QAAQ;AACtC,QAAI,OAAQ,QAAO;AAEnB,QAAI;AAEF,YAAM,iBAAiB,MAAM,MAAM,GAAG,KAAK,OAAO,uBAAuB;AAAA,QACvE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB,UAAU,QAAQ,KAAK;AAAA,QAC1C;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,UAAU,QAAQ,MAAM,CAAC;AAAA,MAClD,CAAC;AAED,UAAI,CAAC,eAAe,IAAI;AACtB,cAAM,YAAY,MAAM,eAAe,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9D,eAAO,EAAE,SAAS,OAAO,OAAO,UAAU,WAAW,gBAAgB;AAAA,MACvE;AAGA,YAAM,aAAa,MAAM,MAAM,GAAG,KAAK,OAAO,aAAa;AAAA,QACzD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,iBAAiB,UAAU,QAAQ,KAAK;AAAA,QAC1C;AAAA,MACF,CAAC;AAED,UAAI,CAAC,WAAW,IAAI;AAClB,eAAO,EAAE,SAAS,OAAO,OAAO,4BAA4B;AAAA,MAC9D;AAEA,YAAM,SAAS,MAAM,WAAW,KAAK;AAErC,YAAM,SAAsB;AAAA,QAC1B,SAAS;AAAA,QACT,MAAM,OAAO,QAAQ;AAAA,MACvB;AAEA,WAAK,MAAM,IAAI,UAAU,MAAM;AAG/B,UAAI,KAAK,YAAY,CAAC,KAAK,SAAS,YAAY,GAAG;AACjD,cAAM,cAAc,KAAK,QAAQ,QAAQ,SAAS,IAAI;AACtD,aAAK,SAAS,QAAQ,aAAa,QAAQ,KAAK;AAAA,MAClD;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,mCAAmC,KAAK;AACtD,aAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB;AAAA,IAC1D;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,OAAe,YAAsC;AACvE,UAAM,SAAS,MAAM,KAAK,aAAa,EAAE,MAAM,CAAC;AAChD,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,KAAM,QAAO;AAE5C,UAAM,kBAAkB,OAAO,KAAK,SAAS,QAAQ,OAAK,EAAE,WAAW,KAAK,CAAC;AAC7E,WAAO,gBAAgB,SAAS,UAAU;AAAA,EAC5C;AAAA,EAEA,aAAmB;AACjB,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,qBAA2B;AACzB,SAAK,SAAS,WAAW;AAAA,EAC3B;AACF;AAEO,SAAS,oBAAoB,QAAsC;AACxE,SAAO,IAAI,cAAc,MAAM;AACjC;;;AC/SA,oBAA8C;AAiBvC,IAAM,gBAAN,MAAoB;AAAA,EACzB,OAAO,QAAQ,SAA8C;AAC3D,UAAM,SAAS,IAAI,cAAc;AAAA,MAC/B,SAAS,QAAQ;AAAA,MACjB,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,IACpB,CAAC;AAED,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW;AAAA,QACT;AAAA,UACE,SAAS;AAAA,UACT,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,SAAS,CAAC,gBAAgB;AAAA,IAC5B;AAAA,EACF;AACF;AAnBa,gBAAN;AAAA,MAFN,sBAAO;AAAA,MACP,sBAAO,CAAC,CAAC;AAAA,GACG;;;ACjBb,IAAAA,iBAAuH;AAIhH,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAG1B,IAAM,yBAAN,MAAoD;AAAA,EACzD,YACgD,QACtC,WACR;AAF8C;AACtC;AAAA,EACP;AAAA,EAEH,MAAM,YAAY,SAA6C;AAC7D,UAAM,UAAU,QAAQ,aAAa,EAAE,WAAW;AAClD,UAAM,QAAQ,KAAK,aAAa,OAAO;AAEvC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,qCAAsB,mBAAmB;AAAA,IACrD;AAEA,QAAI,CAAC,KAAK,QAAQ;AAChB,cAAQ,KAAK,iDAAiD;AAC9D,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,MAAM,KAAK,OAAO,aAAa,EAAE,OAAO,QAAQ,CAAC;AAEpE,QAAI,CAAC,WAAW,SAAS;AACvB,YAAM,IAAI,kCAAmB,WAAW,SAAS,eAAe;AAAA,IAClE;AAEA,QAAI,CAAC,WAAW,MAAM;AACpB,YAAM,IAAI,kCAAmB,gBAAgB;AAAA,IAC/C;AAEA,UAAM,UAAU,QAAQ,WAAW;AACnC,UAAM,WAAW,KAAK,UAAU;AAAA,MAC9B;AAAA,MACA;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,YAAM,EAAE,aAAa,WAAW,IAAI;AACpC,YAAM,kBAAkB,WAAW,KAAK,SAAS,QAAQ,OAAK,EAAE,WAAW,KAAK,CAAC;AAEjF,UAAI,YAAY;AACd,YAAI,CAAC,YAAY,MAAM,OAAK,gBAAgB,SAAS,CAAC,CAAC,GAAG;AACxD,gBAAM,IAAI,kCAAmB,0BAA0B;AAAA,QACzD;AAAA,MACF,OAAO;AACL,YAAI,CAAC,YAAY,KAAK,OAAK,gBAAgB,SAAS,CAAC,CAAC,GAAG;AACvD,gBAAM,IAAI,kCAAmB,0BAA0B;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,UAAU;AAAA,MAC9B;AAAA,MACA;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,YAAM,EAAE,OAAO,WAAW,IAAI;AAC9B,YAAM,YAAY,WAAW,KAAK,SAAS,CAAC;AAE5C,UAAI,YAAY;AACd,YAAI,CAAC,MAAM,MAAM,OAAK,UAAU,SAAS,CAAC,CAAC,GAAG;AAC5C,gBAAM,IAAI,kCAAmB,oBAAoB;AAAA,QACnD;AAAA,MACF,OAAO;AACL,YAAI,CAAC,MAAM,KAAK,OAAK,UAAU,SAAS,CAAC,CAAC,GAAG;AAC3C,gBAAM,IAAI,kCAAmB,oBAAoB;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,OAAO,WAAW;AAC1B,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,SAA6B;AAChD,UAAM,OAAO,QAAQ,SAAS;AAC9B,QAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,aAAO,KAAK,UAAU,CAAC;AAAA,IACzB;AACA,WAAO,QAAQ,UAAU,gBAAgB,KAAK;AAAA,EAChD;AACF;AAjFa,yBAAN;AAAA,MADN,2BAAW;AAAA,EAGP,gDAAS;AAAA,EAAG,8CAAO,gBAAgB;AAAA,GAF3B;AAmFN,SAAS,mBAAmB,aAAuB,aAAa,OAAO;AAC5E,SAAO,SAAU,QAAa,aAAqB,YAAgC;AACjF,YAAQ,eAAe,yBAAyB,EAAE,aAAa,WAAW,GAAG,WAAW,KAAK;AAC7F,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,OAAiB,aAAa,OAAO;AAChE,SAAO,SAAU,QAAa,aAAqB,YAAgC;AACjF,YAAQ,eAAe,mBAAmB,EAAE,OAAO,WAAW,GAAG,WAAW,KAAK;AACjF,WAAO;AAAA,EACT;AACF;","names":["import_common"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/module.ts","../src/guard.ts"],"sourcesContent":["export { ExGuardModule } from './module.js';\nexport { ExGuardPermissionGuard, RequirePermissions, RequireRoles, clearExGuardCache, EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY } from './guard.js';\n","import { Module, Global, DynamicModule } from '@nestjs/common';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n}\n\n@Global()\n@Module({})\nexport class ExGuardModule {\n static forRoot(options: ExGuardModuleOptions): DynamicModule;\n static forRoot(baseUrl: string): DynamicModule;\n static forRoot(optionsOrUrl: ExGuardModuleOptions | string): DynamicModule {\n const baseUrl = typeof optionsOrUrl === 'string' ? optionsOrUrl : optionsOrUrl.baseUrl;\n \n return {\n module: ExGuardModule,\n providers: [\n {\n provide: 'EXGUARD_BASE_URL',\n useValue: baseUrl,\n },\n ],\n exports: ['EXGUARD_BASE_URL'],\n };\n }\n}\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\n\nexport const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';\nexport const EXGUARD_ROLES_KEY = 'exguard_roles';\n\nconst cache = new Map<string, { data: any; timestamp: number }>();\nconst CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\nfunction getCached(key: string): any {\n const entry = cache.get(key);\n if (!entry) return null;\n if (Date.now() - entry.timestamp > CACHE_TTL) {\n cache.delete(key);\n return null;\n }\n return entry.data;\n}\n\nfunction setCache(key: string, data: any): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n@Injectable()\nexport class ExGuardPermissionGuard implements CanActivate {\n constructor(\n @Optional() @Inject('EXGUARD_BASE_URL') private baseUrl: string,\n private reflector: Reflector,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest();\n const token = this.extractToken(request);\n\n if (!token) {\n throw new UnauthorizedException('No token provided');\n }\n\n if (!this.baseUrl) {\n console.warn('[ExGuard] Base URL not configured. Access denied.');\n return false;\n }\n\n // Check cache first\n const cacheKey = `auth:${token}`;\n let authResult = getCached(cacheKey);\n\n if (!authResult) {\n try {\n // Verify token\n const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${token}`,\n },\n body: JSON.stringify({ id_token: token }),\n });\n\n if (!verifyResponse.ok) {\n throw new ForbiddenException('Invalid token');\n }\n\n // Get user access\n const meResponse = await fetch(`${this.baseUrl}/guard/me`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n },\n });\n\n if (!meResponse.ok) {\n throw new ForbiddenException('Failed to get user access');\n }\n\n const meData = await meResponse.json();\n authResult = { allowed: true, user: meData.data || meData };\n \n // Cache the result\n setCache(cacheKey, authResult);\n } catch (error) {\n console.error('[ExGuard] Auth error:', error);\n throw new ForbiddenException('Authentication failed');\n }\n }\n\n if (!authResult.allowed) {\n throw new ForbiddenException(authResult.error || 'Access denied');\n }\n\n if (!authResult.user) {\n throw new ForbiddenException('User not found');\n }\n\n const handler = context.getHandler();\n const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);\n\n if (permMeta) {\n const { permissions, requireAll } = permMeta;\n const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m: any) => m.permissions) : [];\n\n if (requireAll) {\n if (!permissions.every((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n } else {\n if (!permissions.some((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n }\n }\n\n const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);\n\n if (roleMeta) {\n const { roles, requireAll } = roleMeta;\n const userRoles = authResult.user.roles || [];\n\n if (requireAll) {\n if (!roles.every((r: string) => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n } else {\n if (!roles.some((r: string) => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n }\n }\n\n request.user = authResult.user;\n return true;\n }\n\n private extractToken(request: any): string | null {\n const auth = request.headers?.authorization;\n if (auth?.startsWith('Bearer ')) {\n return auth.substring(7);\n }\n return request.headers?.['x-access-token'] || null;\n }\n}\n\nexport function RequirePermissions(permissions: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function RequireRoles(roles: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function clearExGuardCache(): void {\n cache.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAA8C;AAQvC,IAAM,gBAAN,MAAoB;AAAA,EAGzB,OAAO,QAAQ,cAA4D;AACzE,UAAM,UAAU,OAAO,iBAAiB,WAAW,eAAe,aAAa;AAE/E,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW;AAAA,QACT;AAAA,UACE,SAAS;AAAA,UACT,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,SAAS,CAAC,kBAAkB;AAAA,IAC9B;AAAA,EACF;AACF;AAjBa,gBAAN;AAAA,MAFN,sBAAO;AAAA,MACP,sBAAO,CAAC,CAAC;AAAA,GACG;;;ACRb,IAAAA,iBAAuH;AAGhH,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAEjC,IAAM,QAAQ,oBAAI,IAA8C;AAChE,IAAM,YAAY,IAAI,KAAK;AAE3B,SAAS,UAAU,KAAkB;AACnC,QAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,KAAK,IAAI,IAAI,MAAM,YAAY,WAAW;AAC5C,UAAM,OAAO,GAAG;AAChB,WAAO;AAAA,EACT;AACA,SAAO,MAAM;AACf;AAEA,SAAS,SAAS,KAAa,MAAiB;AAC9C,QAAM,IAAI,KAAK,EAAE,MAAM,WAAW,KAAK,IAAI,EAAE,CAAC;AAChD;AAGO,IAAM,yBAAN,MAAoD;AAAA,EACzD,YACkD,SACxC,WACR;AAFgD;AACxC;AAAA,EACP;AAAA,EAEH,MAAM,YAAY,SAA6C;AAC7D,UAAM,UAAU,QAAQ,aAAa,EAAE,WAAW;AAClD,UAAM,QAAQ,KAAK,aAAa,OAAO;AAEvC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,qCAAsB,mBAAmB;AAAA,IACrD;AAEA,QAAI,CAAC,KAAK,SAAS;AACjB,cAAQ,KAAK,mDAAmD;AAChE,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,QAAQ,KAAK;AAC9B,QAAI,aAAa,UAAU,QAAQ;AAEnC,QAAI,CAAC,YAAY;AACf,UAAI;AAEF,cAAM,iBAAiB,MAAM,MAAM,GAAG,KAAK,OAAO,uBAAuB;AAAA,UACvE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,iBAAiB,UAAU,KAAK;AAAA,UAClC;AAAA,UACA,MAAM,KAAK,UAAU,EAAE,UAAU,MAAM,CAAC;AAAA,QAC1C,CAAC;AAED,YAAI,CAAC,eAAe,IAAI;AACtB,gBAAM,IAAI,kCAAmB,eAAe;AAAA,QAC9C;AAGA,cAAM,aAAa,MAAM,MAAM,GAAG,KAAK,OAAO,aAAa;AAAA,UACzD,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,iBAAiB,UAAU,KAAK;AAAA,UAClC;AAAA,QACF,CAAC;AAED,YAAI,CAAC,WAAW,IAAI;AAClB,gBAAM,IAAI,kCAAmB,2BAA2B;AAAA,QAC1D;AAEA,cAAM,SAAS,MAAM,WAAW,KAAK;AACrC,qBAAa,EAAE,SAAS,MAAM,MAAM,OAAO,QAAQ,OAAO;AAG1D,iBAAS,UAAU,UAAU;AAAA,MAC/B,SAAS,OAAO;AACd,gBAAQ,MAAM,yBAAyB,KAAK;AAC5C,cAAM,IAAI,kCAAmB,uBAAuB;AAAA,MACtD;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,SAAS;AACvB,YAAM,IAAI,kCAAmB,WAAW,SAAS,eAAe;AAAA,IAClE;AAEA,QAAI,CAAC,WAAW,MAAM;AACpB,YAAM,IAAI,kCAAmB,gBAAgB;AAAA,IAC/C;AAEA,UAAM,UAAU,QAAQ,WAAW;AACnC,UAAM,WAAW,KAAK,UAAU,IAAI,yBAAyB,OAAO;AAEpE,QAAI,UAAU;AACZ,YAAM,EAAE,aAAa,WAAW,IAAI;AACpC,YAAM,kBAAkB,WAAW,KAAK,UAAU,WAAW,KAAK,QAAQ,QAAQ,CAAC,MAAW,EAAE,WAAW,IAAI,CAAC;AAEhH,UAAI,YAAY;AACd,YAAI,CAAC,YAAY,MAAM,CAAC,MAAc,gBAAgB,SAAS,CAAC,CAAC,GAAG;AAClE,gBAAM,IAAI,kCAAmB,0BAA0B;AAAA,QACzD;AAAA,MACF,OAAO;AACL,YAAI,CAAC,YAAY,KAAK,CAAC,MAAc,gBAAgB,SAAS,CAAC,CAAC,GAAG;AACjE,gBAAM,IAAI,kCAAmB,0BAA0B;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,UAAU,IAAI,mBAAmB,OAAO;AAE9D,QAAI,UAAU;AACZ,YAAM,EAAE,OAAO,WAAW,IAAI;AAC9B,YAAM,YAAY,WAAW,KAAK,SAAS,CAAC;AAE5C,UAAI,YAAY;AACd,YAAI,CAAC,MAAM,MAAM,CAAC,MAAc,UAAU,SAAS,CAAC,CAAC,GAAG;AACtD,gBAAM,IAAI,kCAAmB,oBAAoB;AAAA,QACnD;AAAA,MACF,OAAO;AACL,YAAI,CAAC,MAAM,KAAK,CAAC,MAAc,UAAU,SAAS,CAAC,CAAC,GAAG;AACrD,gBAAM,IAAI,kCAAmB,oBAAoB;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,OAAO,WAAW;AAC1B,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,SAA6B;AAChD,UAAM,OAAO,QAAQ,SAAS;AAC9B,QAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,aAAO,KAAK,UAAU,CAAC;AAAA,IACzB;AACA,WAAO,QAAQ,UAAU,gBAAgB,KAAK;AAAA,EAChD;AACF;AApHa,yBAAN;AAAA,MADN,2BAAW;AAAA,EAGP,gDAAS;AAAA,EAAG,8CAAO,kBAAkB;AAAA,GAF7B;AAsHN,SAAS,mBAAmB,aAAuB,aAAa,OAAO;AAC5E,SAAO,SAAU,QAAa,aAAqB,YAAgC;AACjF,YAAQ,eAAe,yBAAyB,EAAE,aAAa,WAAW,GAAG,WAAW,KAAK;AAC7F,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,OAAiB,aAAa,OAAO;AAChE,SAAO,SAAU,QAAa,aAAqB,YAAgC;AACjF,YAAQ,eAAe,mBAAmB,EAAE,OAAO,WAAW,GAAG,WAAW,KAAK;AACjF,WAAO;AAAA,EACT;AACF;AAEO,SAAS,oBAA0B;AACxC,QAAM,MAAM;AACd;","names":["import_common"]}
|