exguard-backend 1.0.38 → 1.1.0
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 +108 -1196
- package/dist/index.cjs +16 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +16 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/setup-nestjs-module.cjs +37 -0
- package/scripts/setup-nestjs-simple.cjs +199 -0
- package/scripts/setup-nestjs.cjs +26 -14
package/README.md
CHANGED
|
@@ -1,1271 +1,183 @@
|
|
|
1
|
-
# ExGuard Backend SDK
|
|
1
|
+
# ExGuard Backend SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Simple RBAC/ABAC permission guard for NestJS.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 🚀 Why ExGuard for NestJS?
|
|
8
|
-
|
|
9
|
-
- **🎯 NestJS-Native Design** - Built specifically for NestJS with decorators and guards
|
|
10
|
-
- **⚡ 95% Performance Boost** - Smart caching eliminates redundant API calls
|
|
11
|
-
- **🔄 Realtime Updates** - Automatic cache invalidation on RBAC changes
|
|
12
|
-
- **🛡️ Comprehensive Protection** - Permissions, roles, modules, and field offices
|
|
13
|
-
- **📊 Performance Monitoring** - Built-in cache statistics and optimization
|
|
14
|
-
- **🔧 Zero Configuration** - Works out of the box with sensible defaults
|
|
15
|
-
|
|
16
|
-
## 📦 Installation
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm install exguard-backend
|
|
20
|
-
# or
|
|
21
|
-
yarn add exguard-backend
|
|
22
|
-
# or
|
|
23
|
-
pnpm add exguard-backend
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## 🚀 Quick Start - NestJS Integration
|
|
27
|
-
|
|
28
|
-
### 🎯 Option 1: Automatic Setup (Recommended)
|
|
29
|
-
|
|
30
|
-
**One-command setup for NestJS projects:**
|
|
5
|
+
## Installation
|
|
31
6
|
|
|
32
7
|
```bash
|
|
33
|
-
# Install exguard-backend
|
|
34
8
|
npm install exguard-backend
|
|
35
|
-
|
|
36
|
-
# Run automatic setup (overwrites existing files)
|
|
37
|
-
npx exguard-backend
|
|
38
|
-
# OR
|
|
39
|
-
npm run setup-nestjs
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Quick Start - NestJS Integration:**
|
|
43
|
-
|
|
44
|
-
After running the setup script, you can quickly start using ExGuard:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# Start development server
|
|
48
|
-
npm run start:dev
|
|
49
|
-
|
|
50
|
-
# The setup script automatically:
|
|
51
|
-
# ✅ Creates src/exguard/ directory with all guards and decorators
|
|
52
|
-
# ✅ Updates src/app.module.ts with ExGuardModule import
|
|
53
|
-
# ✅ Creates src/events/events.controller.ts with working examples
|
|
54
|
-
# ✅ Adds npm scripts for easy access
|
|
55
9
|
```
|
|
56
|
-
✅ **Improved module detection** - More reliable NestJS project detection
|
|
57
|
-
✅ **Enhanced file creation** - Better file path handling
|
|
58
|
-
✅ **TypeScript error fixes** - All generated code is type-safe
|
|
59
|
-
✅ **Complete imports** - All factory functions properly imported
|
|
60
|
-
✅ **Working permission checks** - Guards actually enforce permissions
|
|
61
|
-
|
|
62
|
-
### 📋 Option 2: Manual Setup
|
|
63
10
|
|
|
64
|
-
|
|
11
|
+
## Quick Setup
|
|
65
12
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npm install exguard-backend @nestjs/core @nestjs/common @nestjs/platform-express
|
|
70
|
-
```
|
|
13
|
+
### 1. Copy Guard Files
|
|
71
14
|
|
|
72
|
-
|
|
15
|
+
Create `src/exguard/exguard.guard.ts`:
|
|
73
16
|
|
|
74
17
|
```typescript
|
|
75
|
-
|
|
76
|
-
import {
|
|
77
|
-
import {
|
|
78
|
-
|
|
79
|
-
@Global()
|
|
80
|
-
@Module({
|
|
81
|
-
providers: [
|
|
82
|
-
{
|
|
83
|
-
provide: Guard,
|
|
84
|
-
useFactory: () => new Guard({
|
|
85
|
-
apiUrl: process.env.EXGUARD_API_URL || 'http://localhost:3000',
|
|
86
|
-
cache: { enabled: true, ttl: 300000 }, // 5 minutes cache
|
|
87
|
-
}),
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
exports: [Guard],
|
|
91
|
-
})
|
|
92
|
-
export class ExGuardModule {}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
#### Step 3: Create Custom Guards
|
|
18
|
+
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';
|
|
19
|
+
import { Reflector } from '@nestjs/core';
|
|
20
|
+
import { ExGuardBackend } from 'exguard-backend';
|
|
96
21
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
|
|
100
|
-
import { Guard, GuardContext } from 'exguard-backend';
|
|
22
|
+
export const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';
|
|
23
|
+
export const EXGUARD_ROLES_KEY = 'exguard_roles';
|
|
101
24
|
|
|
102
25
|
@Injectable()
|
|
103
|
-
export class
|
|
104
|
-
constructor(
|
|
26
|
+
export class ExGuardPermissionGuard implements CanActivate {
|
|
27
|
+
constructor(
|
|
28
|
+
@Optional() @Inject('EXGUARD_INSTANCE') private exGuard: ExGuardBackend,
|
|
29
|
+
private reflector: Reflector,
|
|
30
|
+
) {}
|
|
105
31
|
|
|
106
32
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
107
33
|
const request = context.switchToHttp().getRequest();
|
|
108
34
|
const token = this.extractToken(request);
|
|
109
|
-
|
|
110
|
-
const guardContext: GuardContext = { token, request };
|
|
111
|
-
const result = await this.exGuard.authenticate(guardContext);
|
|
112
|
-
|
|
113
|
-
if (!result.allowed) {
|
|
114
|
-
throw new ForbiddenException(result.error);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
request.user = result.user;
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
protected extractToken(request: any): string | null {
|
|
122
|
-
const authHeader = request.headers?.authorization;
|
|
123
|
-
return authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Permission-specific guard
|
|
128
|
-
@Injectable()
|
|
129
|
-
export class ExGuardPermissionGuard extends ExGuardNestGuard {
|
|
130
|
-
protected async checkPermissions(context: GuardContext) {
|
|
131
|
-
return this.exGuard.requirePermissions(context, ['read']);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Factory functions for dynamic guards
|
|
136
|
-
export function createPermissionGuard(permissions: string[], requireAll = false) {
|
|
137
|
-
return class extends ExGuardNestGuard {
|
|
138
|
-
protected async checkPermissions(context: GuardContext) {
|
|
139
|
-
return this.exGuard.requirePermissions(context, permissions, { requireAll });
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
#### Step 4: Protect Your Controllers
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
// src/events/events.controller.ts
|
|
149
|
-
import { Controller, Get, Post, Body, UseGuards, Request } from '@nestjs/common';
|
|
150
|
-
import { createPermissionGuard } from '../exguard/exguard.guard';
|
|
151
|
-
|
|
152
|
-
@Controller('events')
|
|
153
|
-
@UseGuards(ExGuardPermissionGuard) // Requires 'read' permission
|
|
154
|
-
export class EventsController {
|
|
155
|
-
@Get()
|
|
156
|
-
async getEvents(@Request() req) {
|
|
157
|
-
console.log('User:', req.user);
|
|
158
|
-
return { success: true, data: [] };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
@Post()
|
|
162
|
-
@UseGuards(createPermissionGuard(['events:create']))
|
|
163
|
-
async createEvent(@Body() createEventDto: any, @Request() req) {
|
|
164
|
-
return { success: true, data: createEventDto };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
@Put(':id')
|
|
168
|
-
@UseGuards(createPermissionGuard(['events:update', 'events:admin']))
|
|
169
|
-
async updateEvent(@Param('id') id: string, @Body() updateDto: any) {
|
|
170
|
-
return { success: true, data: { id, ...updateDto } };
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
#### Step 5: Update App Module
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
// src/app.module.ts
|
|
179
|
-
import { Module } from '@nestjs/common';
|
|
180
|
-
import { ExGuardModule } from './exguard/exguard.module';
|
|
181
|
-
import { EventsController } from './events/events.controller';
|
|
182
|
-
|
|
183
|
-
@Module({
|
|
184
|
-
imports: [ExGuardModule],
|
|
185
|
-
controllers: [EventsController],
|
|
186
|
-
})
|
|
187
|
-
export class AppModule {}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### 🎯 Automatic Setup Features
|
|
191
|
-
|
|
192
|
-
The automatic setup creates a complete NestJS integration:
|
|
193
|
-
|
|
194
|
-
#### **Generated Files:**
|
|
195
|
-
```
|
|
196
|
-
src/
|
|
197
|
-
├── exguard/
|
|
198
|
-
│ ├── exguard.module.ts # Global ExGuard module
|
|
199
|
-
│ ├── exguard.guard.ts # Custom guards
|
|
200
|
-
│ ├── exguard.decorators.ts # Permission decorators
|
|
201
|
-
│ └── guards/ # Additional guard files
|
|
202
|
-
├── events/
|
|
203
|
-
│ └── events.controller.ts # Example controller
|
|
204
|
-
└── .env # Environment configuration
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
#### **Generated Decorators:**
|
|
208
|
-
```typescript
|
|
209
|
-
@RequirePermissions(['events:read'])
|
|
210
|
-
@RequireRoles(['Admin'])
|
|
211
|
-
@RequireModules(['reporting'])
|
|
212
|
-
@RequireFieldOffices(['FO-MANILA'])
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
#### **Generated Package Scripts:**
|
|
216
|
-
```json
|
|
217
|
-
{
|
|
218
|
-
"scripts": {
|
|
219
|
-
"exguard:setup": "node node_modules/exguard-backend/scripts/setup-nestjs.js",
|
|
220
|
-
"exguard:example": "node node_modules/exguard-backend/scripts/create-example.js"
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## 🎯 Advanced NestJS Examples
|
|
226
|
-
|
|
227
|
-
### Role-Based Protection
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
// src/admin/admin.controller.ts
|
|
231
|
-
import { Controller, Get, UseGuards } from '@nestjs/common';
|
|
232
|
-
import { createRoleGuard } from '../exguard/exguard.guard';
|
|
233
|
-
|
|
234
|
-
@Controller('admin')
|
|
235
|
-
@UseGuards(createRoleGuard(['Admin'])) // Requires 'Admin' role
|
|
236
|
-
export class AdminController {
|
|
237
|
-
@Get('users')
|
|
238
|
-
async getUsers() {
|
|
239
|
-
return { success: true, data: [] };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
@Get('stats')
|
|
243
|
-
@UseGuards(createRoleGuard(['Admin', 'SuperAdmin'])) // Admin OR SuperAdmin
|
|
244
|
-
async getStats() {
|
|
245
|
-
return { success: true, data: { totalUsers: 100 } };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Module-Based Protection
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
// src/reports/reports.controller.ts
|
|
254
|
-
import { Controller, Get, UseGuards } from '@nestjs/common';
|
|
255
|
-
import { createModuleGuard } from '../exguard/exguard.guard';
|
|
256
|
-
|
|
257
|
-
@Controller('reports')
|
|
258
|
-
@UseGuards(createModuleGuard(['reporting'])) // Requires access to 'reporting' module
|
|
259
|
-
export class ReportsController {
|
|
260
|
-
@Get()
|
|
261
|
-
async getReports() {
|
|
262
|
-
return { success: true, data: [] };
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
@Get('analytics')
|
|
266
|
-
@UseGuards(createModuleGuard(['analytics', 'reporting'])) // analytics OR reporting
|
|
267
|
-
async getAnalytics() {
|
|
268
|
-
return { success: true, data: { pageViews: 10000 } };
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Complex Multi-Requirement Protection
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
// src/sensitive/sensitive.controller.ts
|
|
277
|
-
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
|
|
278
|
-
import { Guard } from 'exguard-backend';
|
|
279
|
-
import { ExGuardNestGuard } from '../exguard/exguard.guard';
|
|
280
|
-
|
|
281
|
-
@Controller('sensitive')
|
|
282
|
-
export class SensitiveController {
|
|
283
|
-
constructor(private exGuard: Guard) {}
|
|
284
35
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
protected async checkPermissions(context: any) {
|
|
288
|
-
return this.exGuard.require(context, {
|
|
289
|
-
permissions: ['sensitive:execute'],
|
|
290
|
-
roles: ['Manager'],
|
|
291
|
-
modules: ['operations'],
|
|
292
|
-
requireAll: true // Must satisfy ALL conditions
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
})(this.exGuard))
|
|
296
|
-
async executeSensitiveOperation(@Body() operation: any) {
|
|
297
|
-
return { success: true, operation };
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
```
|
|
36
|
+
if (!token) throw new UnauthorizedException('No token provided');
|
|
37
|
+
if (!this.exGuard) return true;
|
|
301
38
|
|
|
302
|
-
|
|
39
|
+
const authResult = await this.exGuard.authenticate({ token, request });
|
|
303
40
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
import { Controller, Get, Param, UseGuards, HttpException, HttpStatus } from '@nestjs/common';
|
|
307
|
-
import { Guard } from 'exguard-backend';
|
|
308
|
-
import { ExGuardNestGuard } from '../exguard/exguard.guard';
|
|
309
|
-
|
|
310
|
-
@Controller('dynamic')
|
|
311
|
-
@UseGuards(ExGuardNestGuard) // Only authentication, no specific permission
|
|
312
|
-
export class DynamicController {
|
|
313
|
-
constructor(private exGuard: Guard) {}
|
|
41
|
+
if (!authResult.allowed) throw new ForbiddenException(authResult.error || 'Access denied');
|
|
42
|
+
if (!authResult.user) throw new ForbiddenException('User not found');
|
|
314
43
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// Dynamic permission based on resource type
|
|
318
|
-
const permission = `${resourceType}:read`;
|
|
319
|
-
|
|
320
|
-
const result = await this.exGuard.requirePermissions(
|
|
321
|
-
{ token: this.extractToken(req) },
|
|
322
|
-
[permission]
|
|
323
|
-
);
|
|
44
|
+
const handler = context.getHandler();
|
|
45
|
+
const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);
|
|
324
46
|
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
47
|
+
if (permMeta) {
|
|
48
|
+
const { permissions, requireAll } = permMeta;
|
|
49
|
+
const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];
|
|
328
50
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
## 🔧 Environment Configuration
|
|
340
|
-
|
|
341
|
-
### Development Environment
|
|
342
|
-
|
|
343
|
-
```env
|
|
344
|
-
# .env.development
|
|
345
|
-
EXGUARD_API_URL=http://localhost:3000
|
|
346
|
-
EXGUARD_CACHE_ENABLED=true
|
|
347
|
-
EXGUARD_CACHE_TTL=60000
|
|
348
|
-
EXGUARD_REALTIME_ENABLED=false
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### Production Environment
|
|
352
|
-
|
|
353
|
-
```env
|
|
354
|
-
# .env.production
|
|
355
|
-
EXGUARD_API_URL=https://api.your-domain.com
|
|
356
|
-
EXGUARD_CACHE_ENABLED=true
|
|
357
|
-
EXGUARD_CACHE_TTL=300000
|
|
358
|
-
# Optional: Enable realtime for automatic cache invalidation
|
|
359
|
-
# EXGUARD_REALTIME_ENABLED=true
|
|
360
|
-
# EXGUARD_REALTIME_URL=wss://api.your-domain.com/realtime
|
|
361
|
-
# EXGUARD_SERVICE_TOKEN=your-service-token
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### ⚠️ Troubleshooting
|
|
365
|
-
|
|
366
|
-
**502 WebSocket Error:**
|
|
367
|
-
- This means the realtime server is not running or the URL is incorrect
|
|
368
|
-
- **Solution:** Set `EXGUARD_REALTIME_ENABLED=false` in your .env file
|
|
369
|
-
- The SDK will work without realtime (cache invalidation will be manual)
|
|
370
|
-
|
|
371
|
-
```env
|
|
372
|
-
EXGUARD_REALTIME_ENABLED=false
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
**To enable realtime later:**
|
|
376
|
-
1. Make sure your WebSocket server is running
|
|
377
|
-
2. Set correct URL: `EXGUARD_REALTIME_URL=ws://your-server:3000/realtime`
|
|
378
|
-
3. Enable: `EXGUARD_REALTIME_ENABLED=true`
|
|
379
|
-
|
|
380
|
-
## 📊 Performance Benefits
|
|
381
|
-
|
|
382
|
-
| Operation | Without Cache | With Cache | Improvement |
|
|
383
|
-
|-----------|---------------|------------|-------------|
|
|
384
|
-
| Single Permission Check | ~100ms | ~5ms | **95% faster** |
|
|
385
|
-
| 10 Permission Checks | ~1000ms | ~10ms | **99% faster** |
|
|
386
|
-
| 100 Concurrent Requests | ~10s | ~0.5s | **95% faster** |
|
|
387
|
-
|
|
388
|
-
## 🧪 Testing NestJS Integration
|
|
389
|
-
|
|
390
|
-
### Unit Test Example
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
// test/exguard.guard.spec.ts
|
|
394
|
-
import { Test, TestingModule } from '@nestjs/testing';
|
|
395
|
-
import { Guard } from 'exguard-backend';
|
|
396
|
-
import { ExGuardNestGuard } from '../src/exguard/exguard.guard';
|
|
397
|
-
|
|
398
|
-
describe('ExGuardNestGuard', () => {
|
|
399
|
-
let guard: ExGuardNestGuard;
|
|
400
|
-
let exGuard: jest.Mocked<Guard>;
|
|
401
|
-
|
|
402
|
-
beforeEach(async () => {
|
|
403
|
-
const module: TestingModule = await Test.createTestingModule({
|
|
404
|
-
providers: [
|
|
405
|
-
ExGuardNestGuard,
|
|
406
|
-
{
|
|
407
|
-
provide: Guard,
|
|
408
|
-
useValue: {
|
|
409
|
-
authenticate: jest.fn(),
|
|
410
|
-
requirePermissions: jest.fn(),
|
|
411
|
-
requireRoles: jest.fn(),
|
|
412
|
-
},
|
|
413
|
-
},
|
|
414
|
-
],
|
|
415
|
-
}).compile();
|
|
416
|
-
|
|
417
|
-
guard = module.get<ExGuardNestGuard>(ExGuardNestGuard);
|
|
418
|
-
exGuard = module.get(Guard);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it('should allow access with valid token', async () => {
|
|
422
|
-
const mockContext = {
|
|
423
|
-
switchToHttp: () => ({
|
|
424
|
-
getRequest: () => ({
|
|
425
|
-
headers: { authorization: 'Bearer valid-token' },
|
|
426
|
-
}),
|
|
427
|
-
}),
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
exGuard.authenticate.mockResolvedValue({
|
|
431
|
-
allowed: true,
|
|
432
|
-
user: { id: '1', roles: ['User'] },
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
const result = await guard.canActivate(mockContext as any);
|
|
436
|
-
expect(result).toBe(true);
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### Integration Test Example
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
// test/app.e2e-spec.ts
|
|
445
|
-
import { Test, TestingModule } from '@nestjs/testing';
|
|
446
|
-
import { INestApplication } from '@nestjs/common';
|
|
447
|
-
import * as request from 'supertest';
|
|
448
|
-
import { AppModule } from '../src/app.module';
|
|
449
|
-
|
|
450
|
-
describe('AppController (e2e)', () => {
|
|
451
|
-
let app: INestApplication;
|
|
452
|
-
|
|
453
|
-
beforeEach(async () => {
|
|
454
|
-
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
455
|
-
imports: [AppModule],
|
|
456
|
-
}).compile();
|
|
457
|
-
|
|
458
|
-
app = moduleFixture.createNestApplication();
|
|
459
|
-
await app.init();
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it('should protect endpoints', () => {
|
|
463
|
-
return request(app.getHttpServer())
|
|
464
|
-
.get('/events')
|
|
465
|
-
.expect(401); // No token provided
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it('should allow access with valid token', () => {
|
|
469
|
-
return request(app.getHttpServer())
|
|
470
|
-
.get('/events')
|
|
471
|
-
.set('Authorization', 'Bearer valid-token')
|
|
472
|
-
.expect(200);
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
## 🚀 Quick Implementation Guide
|
|
478
|
-
|
|
479
|
-
### **Step 1: Install and Setup**
|
|
480
|
-
```bash
|
|
481
|
-
# Install latest version
|
|
482
|
-
npm install exguard-backend
|
|
483
|
-
|
|
484
|
-
# Run automatic setup (overwrites existing files)
|
|
485
|
-
npx exguard-backend
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### **Step 2: Controller Implementation**
|
|
489
|
-
```typescript
|
|
490
|
-
import { Controller, Get, Post, UseGuards, Request, Body } from '@nestjs/common';
|
|
491
|
-
import { createPermissionGuard, createRoleGuard, createModuleGuard } from '../exguard/exguard.guard';
|
|
492
|
-
|
|
493
|
-
@Controller('your-controller')
|
|
494
|
-
export class YourController {
|
|
495
|
-
@Get()
|
|
496
|
-
@UseGuards(createPermissionGuard(['your-resource:read']))
|
|
497
|
-
async getAll(@Request() req) {
|
|
498
|
-
return { success: true, data: [] };
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
@Post()
|
|
502
|
-
@UseGuards(createPermissionGuard(['your-resource:create']))
|
|
503
|
-
async create(@Body() createDto: any, @Request() req) {
|
|
504
|
-
return { success: true, data: createDto };
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
### **Step 3: Test Your Implementation**
|
|
510
|
-
```bash
|
|
511
|
-
# Test with valid permissions
|
|
512
|
-
curl http://localhost:3000/your-controller -H "Authorization: Bearer YOUR_TOKEN"
|
|
513
|
-
|
|
514
|
-
# Test with invalid permissions (should return 403)
|
|
515
|
-
curl http://localhost:3000/your-controller -H "Authorization: Bearer TOKEN_WITHOUT_PERMISSIONS"
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
### **Important Notes:**
|
|
519
|
-
- ✅ **File Overwriting**: The setup script now overwrites existing files to ensure you get the latest fixes
|
|
520
|
-
- ✅ **Permission Checking**: Guards now actually check and enforce permissions
|
|
521
|
-
- ✅ **TypeScript Safe**: All generated code is TypeScript compliant
|
|
522
|
-
- ✅ **Working Examples**: The generated controller demonstrates all protection patterns
|
|
523
|
-
|
|
524
|
-
## 🎯 Controller Implementation Examples
|
|
525
|
-
|
|
526
|
-
### **Basic Permission Protection**
|
|
527
|
-
|
|
528
|
-
#### **Simple Permission Guard**
|
|
529
|
-
```typescript
|
|
530
|
-
import { Controller, Get, Post, UseGuards, Request, Body } from '@nestjs/common';
|
|
531
|
-
import { createPermissionGuard } from '../exguard/exguard.guard';
|
|
532
|
-
|
|
533
|
-
@Controller('events')
|
|
534
|
-
export class EventsController {
|
|
535
|
-
@Get()
|
|
536
|
-
@UseGuards(createPermissionGuard(['events:read']))
|
|
537
|
-
async getEvents(@Request() req) {
|
|
538
|
-
console.log('User accessing events:', req.user);
|
|
539
|
-
return { success: true, data: [] };
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
@Post()
|
|
543
|
-
@UseGuards(createPermissionGuard(['events:create']))
|
|
544
|
-
async createEvent(@Body() createEventDto: any, @Request() req) {
|
|
545
|
-
console.log('User creating event:', req.user);
|
|
546
|
-
return { success: true, data: createEventDto };
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
#### **Multiple Permissions (Require All)**
|
|
552
|
-
```typescript
|
|
553
|
-
@Get('admin')
|
|
554
|
-
@UseGuards(createPermissionGuard(['admin:access', 'events:manage'], true))
|
|
555
|
-
async getAdminEvents(@Request() req) {
|
|
556
|
-
return { success: true, data: [], message: 'Admin events' };
|
|
557
|
-
}
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
#### **Multiple Permissions (Require Any)**
|
|
561
|
-
```typescript
|
|
562
|
-
@Get('reports')
|
|
563
|
-
@UseGuards(createPermissionGuard(['reports:view', 'events:read'], false))
|
|
564
|
-
async getReports(@Request() req) {
|
|
565
|
-
return { success: true, data: [], message: 'Reports data' };
|
|
566
|
-
}
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
### **Role-Based Protection**
|
|
570
|
-
|
|
571
|
-
#### **Role Guards**
|
|
572
|
-
```typescript
|
|
573
|
-
import { createRoleGuard } from '../exguard/exguard.guard';
|
|
574
|
-
|
|
575
|
-
@Controller('admin')
|
|
576
|
-
export class AdminController {
|
|
577
|
-
@Get()
|
|
578
|
-
@UseGuards(createRoleGuard(['Admin']))
|
|
579
|
-
async getAdminData(@Request() req) {
|
|
580
|
-
return { success: true, data: [], message: 'Admin data' };
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
@Get('super')
|
|
584
|
-
@UseGuards(createRoleGuard(['SuperAdmin', 'Admin'], false))
|
|
585
|
-
async getSuperData(@Request() req) {
|
|
586
|
-
return { success: true, data: [], message: 'Super admin data' };
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
### **Module-Based Protection**
|
|
592
|
-
|
|
593
|
-
#### **Module Guards**
|
|
594
|
-
```typescript
|
|
595
|
-
import { createModuleGuard } from '../exguard/exguard.guard';
|
|
596
|
-
|
|
597
|
-
@Controller('reporting')
|
|
598
|
-
export class ReportingController {
|
|
599
|
-
@Get()
|
|
600
|
-
@UseGuards(createModuleGuard(['reporting']))
|
|
601
|
-
async getReports(@Request() req) {
|
|
602
|
-
return { success: true, data: [], message: 'Reports data' };
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
@Get('finance')
|
|
606
|
-
@UseGuards(createModuleGuard(['finance', 'reporting'], true))
|
|
607
|
-
async getFinanceReports(@Request() req) {
|
|
608
|
-
return { success: true, data: [], message: 'Finance reports' };
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
### **Decorator-Based Protection**
|
|
614
|
-
|
|
615
|
-
#### **Using Decorators with Base Guard**
|
|
616
|
-
```typescript
|
|
617
|
-
import {
|
|
618
|
-
RequirePermissions,
|
|
619
|
-
RequireRoles,
|
|
620
|
-
RequireModules
|
|
621
|
-
} from '../exguard/exguard.decorators';
|
|
622
|
-
import { ExGuardPermissionGuard } from '../exguard/exguard.guard';
|
|
623
|
-
|
|
624
|
-
@Controller('users')
|
|
625
|
-
@UseGuards(ExGuardPermissionGuard) // Base guard for authentication
|
|
626
|
-
export class UsersController {
|
|
627
|
-
@Get()
|
|
628
|
-
@RequirePermissions(['users:read'])
|
|
629
|
-
async getUsers(@Request() req) {
|
|
630
|
-
return { success: true, data: [] };
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
@Post()
|
|
634
|
-
@RequirePermissions(['users:create'])
|
|
635
|
-
async createUser(@Body() createUserDto: any, @Request() req) {
|
|
636
|
-
return { success: true, data: createUserDto };
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
@Get('admin')
|
|
640
|
-
@RequireRoles(['Admin'])
|
|
641
|
-
async getAdminUsers(@Request() req) {
|
|
642
|
-
return { success: true, data: [], message: 'Admin users' };
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
@Get('management')
|
|
646
|
-
@RequireModules(['user-management'])
|
|
647
|
-
async getManagementData(@Request() req) {
|
|
648
|
-
return { success: true, data: [], message: 'Management data' };
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
### **Complex Combined Protection**
|
|
654
|
-
|
|
655
|
-
#### **Using Combined Decorators**
|
|
656
|
-
```typescript
|
|
657
|
-
import { Require } from '../exguard/exguard.decorators';
|
|
658
|
-
|
|
659
|
-
@Controller('critical')
|
|
660
|
-
@UseGuards(ExGuardPermissionGuard)
|
|
661
|
-
export class CriticalController {
|
|
662
|
-
@Get('data')
|
|
663
|
-
@Require({
|
|
664
|
-
permissions: ['critical:access'],
|
|
665
|
-
roles: ['Admin'],
|
|
666
|
-
modules: ['security'],
|
|
667
|
-
requireAll: true // Must have ALL of the above
|
|
668
|
-
})
|
|
669
|
-
async getCriticalData(@Request() req) {
|
|
670
|
-
return { success: true, data: [], message: 'Critical data' };
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
@Get('flexible')
|
|
674
|
-
@Require({
|
|
675
|
-
permissions: ['basic:access'],
|
|
676
|
-
roles: ['User', 'Manager'],
|
|
677
|
-
requireAll: false // Can have ANY of the above
|
|
678
|
-
})
|
|
679
|
-
async getFlexibleData(@Request() req) {
|
|
680
|
-
return { success: true, data: [], message: 'Flexible data' };
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
#### **Alternative: Separate Decorators (Recommended for clarity)**
|
|
686
|
-
```typescript
|
|
687
|
-
@Controller('alternative')
|
|
688
|
-
@UseGuards(ExGuardPermissionGuard)
|
|
689
|
-
export class AlternativeController {
|
|
690
|
-
@Get('data')
|
|
691
|
-
@RequirePermissions(['critical:access'])
|
|
692
|
-
@RequireRoles(['Admin'])
|
|
693
|
-
@RequireModules(['security'])
|
|
694
|
-
async getCriticalData(@Request() req) {
|
|
695
|
-
return { success: true, data: [], message: 'Critical data' };
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
@Get('flexible')
|
|
699
|
-
@RequirePermissions(['basic:access'])
|
|
700
|
-
@RequireRoles(['User', 'Manager'], false) // Any role
|
|
701
|
-
async getFlexibleData(@Request() req) {
|
|
702
|
-
return { success: true, data: [], message: 'Flexible data' };
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
### **Custom Guard Implementation**
|
|
708
|
-
|
|
709
|
-
#### **Extending Base Guard**
|
|
710
|
-
```typescript
|
|
711
|
-
import { Injectable } from '@nestjs/common';
|
|
712
|
-
import { ExGuardNestGuard } from '../exguard/exguard.guard';
|
|
713
|
-
|
|
714
|
-
@Injectable()
|
|
715
|
-
export class BusinessHoursGuard extends ExGuardNestGuard {
|
|
716
|
-
public async checkPermissions(context: GuardContext) {
|
|
717
|
-
const user = context.request.user;
|
|
718
|
-
|
|
719
|
-
// Only allow access during business hours (9 AM - 5 PM)
|
|
720
|
-
const hour = new Date().getHours();
|
|
721
|
-
if (hour < 9 || hour > 17) {
|
|
722
|
-
return {
|
|
723
|
-
allowed: false,
|
|
724
|
-
error: 'Access only allowed during business hours (9 AM - 5 PM)'
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Check basic permissions
|
|
729
|
-
return this.exGuard.requirePermissions(context, ['business:access']);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Usage
|
|
734
|
-
@Controller('business')
|
|
735
|
-
@UseGuards(BusinessHoursGuard)
|
|
736
|
-
export class BusinessController {
|
|
737
|
-
@Get()
|
|
738
|
-
async getBusinessData(@Request() req) {
|
|
739
|
-
return { success: true, data: [], message: 'Business data' };
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
### **Dynamic Resource Protection**
|
|
745
|
-
|
|
746
|
-
#### **Resource-Specific Permissions**
|
|
747
|
-
```typescript
|
|
748
|
-
@Controller('resources')
|
|
749
|
-
export class ResourceController {
|
|
750
|
-
@Get(':resource')
|
|
751
|
-
@UseGuards(createPermissionGuard(['resources:read']))
|
|
752
|
-
async getResource(@Param('resource') resource: string, @Request() req) {
|
|
753
|
-
// Check if user has permission for this specific resource
|
|
754
|
-
const hasPermission = req.user?.permissions?.includes(`${resource}:read`);
|
|
755
|
-
|
|
756
|
-
if (!hasPermission) {
|
|
757
|
-
return { success: false, error: 'No access to this resource' };
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
return { success: true, data: { resource } };
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
@Post(':resource')
|
|
764
|
-
@UseGuards(createPermissionGuard(['resources:create']))
|
|
765
|
-
async createResource(
|
|
766
|
-
@Param('resource') resource: string,
|
|
767
|
-
@Body() createDto: any,
|
|
768
|
-
@Request() req
|
|
769
|
-
) {
|
|
770
|
-
return {
|
|
771
|
-
success: true,
|
|
772
|
-
data: { resource, ...createDto },
|
|
773
|
-
message: `${resource} created successfully`
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
### **Field Office Protection**
|
|
780
|
-
|
|
781
|
-
#### **Location-Based Access**
|
|
782
|
-
```typescript
|
|
783
|
-
@Controller('field-offices')
|
|
784
|
-
export class FieldOfficeController {
|
|
785
|
-
@Get()
|
|
786
|
-
@UseGuards(createPermissionGuard(['field-offices:read']))
|
|
787
|
-
async getFieldOffices(@Request() req) {
|
|
788
|
-
// Only show field offices user has access to
|
|
789
|
-
const userFieldOffices = req.user?.fieldOffices || [];
|
|
790
|
-
return { success: true, data: userFieldOffices };
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
@Get(':officeId')
|
|
794
|
-
@UseGuards(createPermissionGuard(['field-offices:manage']))
|
|
795
|
-
async getFieldOffice(
|
|
796
|
-
@Param('officeId') officeId: string,
|
|
797
|
-
@Request() req
|
|
798
|
-
) {
|
|
799
|
-
// Check if user has access to this specific field office
|
|
800
|
-
const userFieldOffices = req.user?.fieldOffices || [];
|
|
801
|
-
const hasAccess = userFieldOffices.includes(officeId);
|
|
802
|
-
|
|
803
|
-
if (!hasAccess) {
|
|
804
|
-
return { success: false, error: 'No access to this field office' };
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return { success: true, data: { officeId } };
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
### **API Response Format**
|
|
813
|
-
|
|
814
|
-
#### **Standardized Responses**
|
|
815
|
-
```typescript
|
|
816
|
-
@Controller('api')
|
|
817
|
-
export class ApiController {
|
|
818
|
-
@Get('protected')
|
|
819
|
-
@UseGuards(createPermissionGuard(['api:access']))
|
|
820
|
-
async getProtectedData(@Request() req) {
|
|
821
|
-
return {
|
|
822
|
-
success: true,
|
|
823
|
-
data: {
|
|
824
|
-
message: 'Protected data accessed successfully',
|
|
825
|
-
user: req.user,
|
|
826
|
-
timestamp: new Date().toISOString()
|
|
827
|
-
},
|
|
828
|
-
meta: {
|
|
829
|
-
permissions: req.user?.permissions,
|
|
830
|
-
roles: req.user?.roles,
|
|
831
|
-
fieldOffices: req.user?.fieldOffices
|
|
51
|
+
if (requireAll) {
|
|
52
|
+
if (!permissions.every(p => userPermissions.includes(p))) {
|
|
53
|
+
throw new ForbiddenException('Insufficient permissions');
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
if (!permissions.some(p => userPermissions.includes(p))) {
|
|
57
|
+
throw new ForbiddenException('Insufficient permissions');
|
|
58
|
+
}
|
|
832
59
|
}
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
@Get('forbidden')
|
|
837
|
-
async getForbiddenData() {
|
|
838
|
-
// This endpoint is not protected - for testing
|
|
839
|
-
return {
|
|
840
|
-
success: false,
|
|
841
|
-
error: 'This endpoint requires authentication',
|
|
842
|
-
code: 'AUTH_REQUIRED'
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
## 📚 Documentation
|
|
849
|
-
|
|
850
|
-
- **[NestJS Setup Guide](./examples/NESTJS-SETUP.md)** - Complete NestJS integration guide
|
|
851
|
-
- **[Backend Protection Guide](./README-BACKEND-PROTECTION.md)** - Complete usage guide
|
|
852
|
-
- **[Integration & Publishing](./README-INTEGRATION.md)** - Setup, integration, and publishing instructions
|
|
853
|
-
- **[Enhanced Features](./README-ENHANCED.md)** - Caching and realtime features
|
|
854
|
-
|
|
855
|
-
## 🎯 Common NestJS Use Cases
|
|
856
|
-
|
|
857
|
-
### Admin Panel Protection
|
|
858
|
-
|
|
859
|
-
```typescript
|
|
860
|
-
@Controller('admin')
|
|
861
|
-
@UseGuards(createRoleGuard(['Admin']))
|
|
862
|
-
export class AdminController {
|
|
863
|
-
@Get('users')
|
|
864
|
-
async getUsers() {
|
|
865
|
-
return { success: true, data: [] };
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
### Multi-tenant Applications
|
|
871
|
-
|
|
872
|
-
```typescript
|
|
873
|
-
@Controller('field-offices')
|
|
874
|
-
export class FieldOfficesController {
|
|
875
|
-
@Get(':officeId/data')
|
|
876
|
-
@UseGuards(new (class extends ExGuardNestGuard {
|
|
877
|
-
protected async checkPermissions(context: any) {
|
|
878
|
-
const officeId = context.request.params.officeId;
|
|
879
|
-
return this.exGuard.requireFieldOffices(context, [officeId]);
|
|
880
60
|
}
|
|
881
|
-
})(this.exGuard))
|
|
882
|
-
async getOfficeData(@Param('officeId') officeId: string) {
|
|
883
|
-
return { success: true, data: { officeId } };
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
```
|
|
887
61
|
|
|
888
|
-
|
|
62
|
+
request.user = authResult.user;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
889
65
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
export class EventsV1Controller {
|
|
894
|
-
@Get()
|
|
895
|
-
async getEventsV1() {
|
|
896
|
-
return { success: true, data: [], version: 1 };
|
|
66
|
+
private extractToken(request: any): string | null {
|
|
67
|
+
const auth = request.headers?.authorization;
|
|
68
|
+
return auth?.startsWith('Bearer ') ? auth.substring(7) : request.headers?.['x-access-token'] || null;
|
|
897
69
|
}
|
|
898
70
|
}
|
|
899
71
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
async getEventsV2() {
|
|
905
|
-
return { success: true, data: [], version: 2 };
|
|
906
|
-
}
|
|
72
|
+
export function RequirePermissions(permissions: string[], requireAll = false) {
|
|
73
|
+
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
74
|
+
Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);
|
|
75
|
+
};
|
|
907
76
|
}
|
|
908
77
|
```
|
|
909
78
|
|
|
910
|
-
|
|
79
|
+
Create `src/exguard/exguard.module.ts`:
|
|
911
80
|
|
|
912
81
|
```typescript
|
|
913
|
-
|
|
82
|
+
import { Module, Global, DynamicModule } from '@nestjs/common';
|
|
914
83
|
import { ExGuardBackend } from 'exguard-backend';
|
|
915
|
-
const guard = new ExGuardBackend({ apiUrl: 'http://localhost:3000' });
|
|
916
|
-
|
|
917
|
-
// v2.x (new) - NestJS Integration
|
|
918
|
-
import { Guard } from 'exguard-backend';
|
|
919
|
-
import { ExGuardNestGuard } from './exguard.guard';
|
|
920
84
|
|
|
921
|
-
@
|
|
85
|
+
@Global()
|
|
86
|
+
@Module({})
|
|
922
87
|
export class ExGuardModule {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
88
|
+
static forRoot(options: { baseUrl: string; apiKey: string; cache?: { enabled?: boolean; ttl?: number } }): DynamicModule {
|
|
89
|
+
const exGuard = new ExGuardBackend({
|
|
90
|
+
baseUrl: options.baseUrl,
|
|
91
|
+
apiKey: options.apiKey,
|
|
92
|
+
cache: options.cache || { enabled: true, ttl: 300000 },
|
|
927
93
|
});
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
@Controller('events')
|
|
932
|
-
@UseGuards(ExGuardNestGuard)
|
|
933
|
-
export class EventsController {
|
|
934
|
-
// Now protected with decorators!
|
|
935
|
-
}
|
|
936
|
-
```
|
|
937
|
-
|
|
938
|
-
## 🚀 Deployment & Production
|
|
939
|
-
|
|
940
|
-
### Docker Configuration
|
|
941
|
-
|
|
942
|
-
```dockerfile
|
|
943
|
-
FROM node:18-alpine
|
|
944
|
-
|
|
945
|
-
WORKDIR /app
|
|
946
|
-
|
|
947
|
-
COPY package*.json ./
|
|
948
|
-
RUN npm ci --only=production
|
|
949
|
-
|
|
950
|
-
COPY dist/ ./dist/
|
|
951
|
-
|
|
952
|
-
# Environment variables
|
|
953
|
-
ENV EXGUARD_CACHE_ENABLED=true
|
|
954
|
-
ENV EXGUARD_CACHE_TTL=300000
|
|
955
94
|
|
|
956
|
-
EXPOSE 3000
|
|
957
|
-
|
|
958
|
-
CMD ["node", "dist/main.js"]
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
### Kubernetes Deployment
|
|
962
|
-
|
|
963
|
-
```yaml
|
|
964
|
-
apiVersion: apps/v1
|
|
965
|
-
kind: Deployment
|
|
966
|
-
metadata:
|
|
967
|
-
name: nestjs-app
|
|
968
|
-
spec:
|
|
969
|
-
template:
|
|
970
|
-
spec:
|
|
971
|
-
containers:
|
|
972
|
-
- name: nestjs-app
|
|
973
|
-
image: your-registry/nestjs-app:latest
|
|
974
|
-
env:
|
|
975
|
-
- name: EXGUARD_API_URL
|
|
976
|
-
value: "https://api.your-domain.com"
|
|
977
|
-
- name: EXGUARD_CACHE_ENABLED
|
|
978
|
-
value: "true"
|
|
979
|
-
- name: EXGUARD_CACHE_TTL
|
|
980
|
-
value: "300000"
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
## 📈 Monitoring & Debugging
|
|
984
|
-
|
|
985
|
-
### Cache Statistics Endpoint
|
|
986
|
-
|
|
987
|
-
```typescript
|
|
988
|
-
@Controller('admin')
|
|
989
|
-
@UseGuards(createRoleGuard(['Admin']))
|
|
990
|
-
export class AdminController {
|
|
991
|
-
constructor(private exGuard: Guard) {}
|
|
992
|
-
|
|
993
|
-
@Get('cache-stats')
|
|
994
|
-
async getCacheStats() {
|
|
995
|
-
const stats = this.exGuard.getExGuard().getCacheStats();
|
|
996
95
|
return {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
},
|
|
96
|
+
module: ExGuardModule,
|
|
97
|
+
providers: [
|
|
98
|
+
{ provide: 'EXGUARD_INSTANCE', useValue: exGuard },
|
|
99
|
+
],
|
|
100
|
+
exports: ['EXGUARD_INSTANCE'],
|
|
1003
101
|
};
|
|
1004
102
|
}
|
|
1005
103
|
}
|
|
1006
104
|
```
|
|
1007
105
|
|
|
1008
|
-
###
|
|
1009
|
-
|
|
1010
|
-
```typescript
|
|
1011
|
-
import { Logger } from '@nestjs/common';
|
|
1012
|
-
|
|
1013
|
-
@Injectable()
|
|
1014
|
-
export class ExGuardNestGuard implements CanActivate {
|
|
1015
|
-
private readonly logger = new Logger(ExGuardNestGuard.name);
|
|
1016
|
-
|
|
1017
|
-
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
1018
|
-
const request = context.switchToHttp().getRequest();
|
|
1019
|
-
|
|
1020
|
-
this.logger.log(`Checking access for ${request.method} ${request.url}`);
|
|
1021
|
-
|
|
1022
|
-
const result = await this.checkPermissions(guardContext);
|
|
1023
|
-
|
|
1024
|
-
if (result.allowed) {
|
|
1025
|
-
this.logger.log(`Access granted for user ${result.user?.user?.id}`);
|
|
1026
|
-
} else {
|
|
1027
|
-
this.logger.warn(`Access denied: ${result.error}`);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
return result.allowed;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
```
|
|
1034
|
-
|
|
1035
|
-
## � Security Best Practices
|
|
1036
|
-
|
|
1037
|
-
### Token Validation
|
|
106
|
+
### 2. Configure AppModule
|
|
1038
107
|
|
|
1039
108
|
```typescript
|
|
1040
|
-
@
|
|
1041
|
-
|
|
1042
|
-
protected extractToken(request: any): string | null {
|
|
1043
|
-
const authHeader = request.headers?.authorization;
|
|
1044
|
-
|
|
1045
|
-
if (!authHeader?.startsWith('Bearer ')) {
|
|
1046
|
-
return null;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
const token = authHeader.substring(7);
|
|
1050
|
-
|
|
1051
|
-
// Additional validation
|
|
1052
|
-
if (token.length < 10) {
|
|
1053
|
-
return null;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
return token;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
```
|
|
1060
|
-
|
|
1061
|
-
### Error Handling
|
|
109
|
+
import { Module } from '@nestjs/common';
|
|
110
|
+
import { ExGuardModule } from './exguard/exguard.module';
|
|
1062
111
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
throw new ForbiddenException('Access check failed');
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
112
|
+
@Module({
|
|
113
|
+
imports: [
|
|
114
|
+
ExGuardModule.forRoot({
|
|
115
|
+
baseUrl: 'https://api.exguard.com',
|
|
116
|
+
apiKey: process.env.EXGUARD_API_KEY,
|
|
117
|
+
cache: { enabled: true, ttl: 300000 },
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
120
|
+
})
|
|
121
|
+
export class AppModule {}
|
|
1077
122
|
```
|
|
1078
123
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
Real-world performance metrics with NestJS:
|
|
1082
|
-
|
|
1083
|
-
| Scenario | Requests/sec | Avg Response Time | Cache Hit Rate |
|
|
1084
|
-
|----------|--------------|-------------------|----------------|
|
|
1085
|
-
| Single Permission | 2,000 | 5ms | 95% |
|
|
1086
|
-
| Multiple Permissions | 1,800 | 8ms | 92% |
|
|
1087
|
-
| Role Checks | 2,200 | 4ms | 96% |
|
|
1088
|
-
| Complex Requirements | 1,500 | 12ms | 88% |
|
|
1089
|
-
|
|
1090
|
-
## 🎉 Summary
|
|
1091
|
-
|
|
1092
|
-
Your NestJS application now has:
|
|
1093
|
-
|
|
1094
|
-
✅ **Enterprise-grade RBAC protection** with decorators
|
|
1095
|
-
✅ **95%+ performance improvement** through intelligent caching
|
|
1096
|
-
✅ **Realtime cache invalidation** for data consistency
|
|
1097
|
-
✅ **Comprehensive testing support** with Jest
|
|
1098
|
-
✅ **Production-ready deployment** configurations
|
|
1099
|
-
✅ **Detailed monitoring and debugging** capabilities
|
|
1100
|
-
|
|
1101
|
-
**Perfect for production NestJS applications!** 🚀
|
|
1102
|
-
|
|
1103
|
-
---
|
|
1104
|
-
|
|
1105
|
-
**Need help? Check out our [NestJS Setup Guide](./examples/NESTJS-SETUP.md) for detailed instructions.**
|
|
1106
|
-
|
|
1107
|
-
## API Reference
|
|
1108
|
-
|
|
1109
|
-
### Constructor
|
|
124
|
+
### 3. Use in Controllers
|
|
1110
125
|
|
|
1111
126
|
```typescript
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
**Config:**
|
|
1116
|
-
- `apiUrl` (string): Base URL of your Guard API
|
|
1117
|
-
- `timeout` (number, optional): Request timeout in milliseconds (default: 10000)
|
|
127
|
+
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
|
|
128
|
+
import { ExGuardPermissionGuard, RequirePermissions } from '@/exguard/exguard.guard';
|
|
1118
129
|
|
|
1119
|
-
|
|
130
|
+
@Controller('items')
|
|
131
|
+
@UseGuards(ExGuardPermissionGuard)
|
|
132
|
+
export class ItemsController {
|
|
133
|
+
|
|
134
|
+
@Get()
|
|
135
|
+
@RequirePermissions(['item:read'])
|
|
136
|
+
findAll() { }
|
|
1120
137
|
|
|
1121
|
-
|
|
138
|
+
@Post()
|
|
139
|
+
@RequirePermissions(['item:create'])
|
|
140
|
+
create() { }
|
|
1122
141
|
|
|
1123
|
-
Get
|
|
142
|
+
@Get('drafts')
|
|
143
|
+
@RequirePermissions(['item:read_draft', 'item:admin']) // ANY of these
|
|
144
|
+
findDrafts() { }
|
|
1124
145
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
{
|
|
1128
|
-
user: User,
|
|
1129
|
-
groups: string[],
|
|
1130
|
-
roles: string[],
|
|
1131
|
-
module: string[],
|
|
1132
|
-
modules: ModulePermission[],
|
|
1133
|
-
fieldOffices: string[]
|
|
146
|
+
@Delete(':id')
|
|
147
|
+
@RequirePermissions(['item:delete', 'admin'], true) // ALL of these
|
|
148
|
+
delete() { }
|
|
1134
149
|
}
|
|
1135
150
|
```
|
|
1136
151
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
Check if user has a specific permission.
|
|
1140
|
-
|
|
1141
|
-
**Example:**
|
|
1142
|
-
```typescript
|
|
1143
|
-
const canCreateEvent = await exGuard.hasPermission(token, 'events:create');
|
|
1144
|
-
```
|
|
1145
|
-
|
|
1146
|
-
#### `hasRole(token: string, role: string): Promise<boolean>`
|
|
1147
|
-
|
|
1148
|
-
Check if user has a specific role.
|
|
1149
|
-
|
|
1150
|
-
**Example:**
|
|
1151
|
-
```typescript
|
|
1152
|
-
const isEventManager = await exGuard.hasRole(token, 'Event Manager');
|
|
1153
|
-
```
|
|
1154
|
-
|
|
1155
|
-
#### `getModulePermissions(token: string, moduleKey: string): Promise<string[]>`
|
|
152
|
+
## Token Format
|
|
1156
153
|
|
|
1157
|
-
|
|
154
|
+
The guard extracts token from:
|
|
155
|
+
- `Authorization: Bearer <token>` header
|
|
156
|
+
- `x-access-token` header
|
|
1158
157
|
|
|
1159
|
-
|
|
1160
|
-
```typescript
|
|
1161
|
-
const eventPermissions = await exGuard.getModulePermissions(token, 'events');
|
|
1162
|
-
// Returns: ['events:create', 'events:read', 'events:update', 'events:delete']
|
|
1163
|
-
```
|
|
1164
|
-
|
|
1165
|
-
#### `getUserRoles(token: string): Promise<string[]>`
|
|
1166
|
-
|
|
1167
|
-
Get all user roles.
|
|
1168
|
-
|
|
1169
|
-
**Example:**
|
|
1170
|
-
```typescript
|
|
1171
|
-
const roles = await exGuard.getUserRoles(token);
|
|
1172
|
-
// Returns: ['Event Manager', 'Viewer']
|
|
1173
|
-
```
|
|
158
|
+
## Configuration
|
|
1174
159
|
|
|
1175
|
-
|
|
160
|
+
| Option | Type | Default | Description |
|
|
161
|
+
|--------|------|---------|-------------|
|
|
162
|
+
| baseUrl | string | required | ExGuard API URL |
|
|
163
|
+
| apiKey | string | required | Your API key |
|
|
164
|
+
| cache.enabled | boolean | true | Enable caching |
|
|
165
|
+
| cache.ttl | number | 300000 | Cache TTL in ms (5 min) |
|
|
1176
166
|
|
|
1177
|
-
|
|
167
|
+
## Express/Fastify (Non-NestJS)
|
|
1178
168
|
|
|
1179
|
-
**Example:**
|
|
1180
169
|
```typescript
|
|
1181
|
-
|
|
1182
|
-
// Returns: ['FO-MIMAROPA', 'FO-NCR']
|
|
1183
|
-
```
|
|
1184
|
-
|
|
1185
|
-
## Usage Examples
|
|
170
|
+
import { createExGuardExpress } from 'exguard-backend';
|
|
1186
171
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
import express from 'express';
|
|
1191
|
-
import { ExGuardBackend } from 'exguard-backend';
|
|
1192
|
-
|
|
1193
|
-
const app = express();
|
|
1194
|
-
const exGuard = new ExGuardBackend({
|
|
1195
|
-
apiUrl: process.env.GUARD_API_URL || 'http://localhost:3001'
|
|
172
|
+
const guard = createExGuardExpress({
|
|
173
|
+
baseUrl: 'https://api.exguard.com',
|
|
174
|
+
apiKey: process.env.EXGUARD_API_KEY,
|
|
1196
175
|
});
|
|
1197
176
|
|
|
1198
|
-
//
|
|
1199
|
-
|
|
1200
|
-
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
1201
|
-
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
1202
|
-
|
|
1203
|
-
if (!token) {
|
|
1204
|
-
return res.status(401).json({ error: 'No token provided' });
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
try {
|
|
1208
|
-
const hasPermission = await exGuard.hasPermission(token, permission);
|
|
1209
|
-
if (!hasPermission) {
|
|
1210
|
-
return res.status(403).json({ error: 'Insufficient permissions' });
|
|
1211
|
-
}
|
|
1212
|
-
next();
|
|
1213
|
-
} catch (error) {
|
|
1214
|
-
return res.status(401).json({ error: 'Invalid token' });
|
|
1215
|
-
}
|
|
1216
|
-
};
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
// Use in routes
|
|
1220
|
-
app.post('/events', requirePermission('events:create'), (req, res) => {
|
|
1221
|
-
// Your route logic here
|
|
1222
|
-
});
|
|
177
|
+
// Use as middleware
|
|
178
|
+
app.use('/api', guard.requirePermissions(['item:read']));
|
|
1223
179
|
```
|
|
1224
180
|
|
|
1225
|
-
### NestJS Guard
|
|
1226
|
-
|
|
1227
|
-
```typescript
|
|
1228
|
-
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
1229
|
-
import { ExGuardBackend } from 'exguard-backend';
|
|
1230
|
-
|
|
1231
|
-
@Injectable()
|
|
1232
|
-
export class PermissionGuard implements CanActivate {
|
|
1233
|
-
private exGuard = new ExGuardBackend({
|
|
1234
|
-
apiUrl: process.env.GUARD_API_URL
|
|
1235
|
-
});
|
|
1236
|
-
|
|
1237
|
-
constructor(private requiredPermission: string) {}
|
|
1238
|
-
|
|
1239
|
-
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
1240
|
-
const request = context.switchToHttp().getRequest();
|
|
1241
|
-
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
1242
|
-
|
|
1243
|
-
if (!token) {
|
|
1244
|
-
return false;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
return await this.exGuard.hasPermission(token, this.requiredPermission);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// Usage in controller
|
|
1252
|
-
@Get('events')
|
|
1253
|
-
@UseGuards(new PermissionGuard('events:read'))
|
|
1254
|
-
async getEvents() {
|
|
1255
|
-
// Your logic here
|
|
1256
|
-
}
|
|
1257
|
-
```
|
|
1258
|
-
|
|
1259
|
-
## Error Handling
|
|
1260
|
-
|
|
1261
|
-
The SDK throws specific errors for different scenarios:
|
|
1262
|
-
|
|
1263
|
-
- **Unauthorized**: Invalid or expired token (401)
|
|
1264
|
-
- **API Error**: General API errors with detailed messages
|
|
1265
|
-
- **Network Error**: Connection issues
|
|
1266
|
-
|
|
1267
|
-
Always wrap SDK calls in try-catch blocks for proper error handling.
|
|
1268
|
-
|
|
1269
181
|
## License
|
|
1270
182
|
|
|
1271
183
|
MIT
|