exguard-backend 1.0.39 → 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 CHANGED
@@ -1,1271 +1,183 @@
1
- # ExGuard Backend SDK v2.0
1
+ # ExGuard Backend SDK
2
2
 
3
- 🛡️ **Enterprise-grade RBAC protection for NestJS applications** with intelligent caching and realtime support.
3
+ Simple RBAC/ABAC permission guard for NestJS.
4
4
 
5
- A powerful backend SDK specifically optimized for NestJS, providing seamless role-based access control with decorators, guards, and 95%+ performance improvement through smart caching.
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
- If you prefer manual setup, follow these steps:
11
+ ## Quick Setup
65
12
 
66
- #### Step 1: Install Dependencies
67
-
68
- ```bash
69
- npm install exguard-backend @nestjs/core @nestjs/common @nestjs/platform-express
70
- ```
13
+ ### 1. Copy Guard Files
71
14
 
72
- #### Step 2: Create ExGuard Module
15
+ Create `src/exguard/exguard.guard.ts`:
73
16
 
74
17
  ```typescript
75
- // src/exguard/exguard.module.ts
76
- import { Module, Global } from '@nestjs/common';
77
- import { Guard } from 'exguard-backend';
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
- ```typescript
98
- // src/exguard/exguard.guard.ts
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 ExGuardNestGuard implements CanActivate {
104
- constructor(protected exGuard: Guard) {}
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
- @Post('execute')
286
- @UseGuards(new (class extends ExGuardNestGuard {
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
- ### Dynamic Permission Checking
39
+ const authResult = await this.exGuard.authenticate({ token, request });
303
40
 
304
- ```typescript
305
- // src/dynamic/dynamic.controller.ts
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
- @Get('resource/:resourceType/:resourceId')
316
- async getResource(@Param('resourceType') resourceType: string, @Request() req) {
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 (!result.allowed) {
326
- throw new HttpException(`Access denied to ${resourceType}`, HttpStatus.FORBIDDEN);
327
- }
47
+ if (permMeta) {
48
+ const { permissions, requireAll } = permMeta;
49
+ const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];
328
50
 
329
- return { success: true, data: { resourceType, content: '...' } };
330
- }
331
-
332
- private extractToken(req: any): string {
333
- const authHeader = req.headers?.authorization;
334
- return authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : '';
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
- ### API Versioning
62
+ request.user = authResult.user;
63
+ return true;
64
+ }
889
65
 
890
- ```typescript
891
- @Controller({ path: 'events', version: '1' })
892
- @UseGuards(createPermissionGuard(['events:read:v1']))
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
- @Controller({ path: 'events', version: '2' })
901
- @UseGuards(createPermissionGuard(['events:read:v2']))
902
- export class EventsV2Controller {
903
- @Get()
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
- ## 🔄 Migration from v1.x to v2.x
79
+ Create `src/exguard/exguard.module.ts`:
911
80
 
912
81
  ```typescript
913
- // v1.x (old)
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
- @Injectable()
85
+ @Global()
86
+ @Module({})
922
87
  export class ExGuardModule {
923
- constructor() {
924
- this.exGuard = new Guard({
925
- apiUrl: 'http://localhost:3000',
926
- cache: { enabled: true }, // New: caching
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
- success: true,
998
- data: {
999
- cacheSize: stats.size,
1000
- cacheKeys: stats.keys,
1001
- timestamp: new Date().toISOString(),
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
- ### Logging Integration
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
- @Injectable()
1041
- export class ExGuardNestGuard implements CanActivate {
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
- ```typescript
1064
- @Injectable()
1065
- export class ExGuardNestGuard implements CanActivate {
1066
- async canActivate(context: ExecutionContext): Promise<boolean> {
1067
- try {
1068
- // ... permission checking logic
1069
- } catch (error) {
1070
- console.error('ExGuard error:', error);
1071
-
1072
- // Don't expose internal errors
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
- ## 📊 Performance Benchmarks
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
- new ExGuardBackend(config: ExGuardConfig)
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
- ### Methods
130
+ @Controller('items')
131
+ @UseGuards(ExGuardPermissionGuard)
132
+ export class ItemsController {
133
+
134
+ @Get()
135
+ @RequirePermissions(['item:read'])
136
+ findAll() { }
1120
137
 
1121
- #### `getUserAccess(token: string): Promise<UserAccessResponse>`
138
+ @Post()
139
+ @RequirePermissions(['item:create'])
140
+ create() { }
1122
141
 
1123
- Get complete user access information including roles, permissions, and field offices.
142
+ @Get('drafts')
143
+ @RequirePermissions(['item:read_draft', 'item:admin']) // ANY of these
144
+ findDrafts() { }
1124
145
 
1125
- **Returns:**
1126
- ```typescript
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
- #### `hasPermission(token: string, permission: string): Promise<boolean>`
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
- Get all permissions for a specific module.
154
+ The guard extracts token from:
155
+ - `Authorization: Bearer <token>` header
156
+ - `x-access-token` header
1158
157
 
1159
- **Example:**
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
- #### `getUserFieldOffices(token: string): Promise<string[]>`
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
- Get all user field offices.
167
+ ## Express/Fastify (Non-NestJS)
1178
168
 
1179
- **Example:**
1180
169
  ```typescript
1181
- const fieldOffices = await exGuard.getUserFieldOffices(token);
1182
- // Returns: ['FO-MIMAROPA', 'FO-NCR']
1183
- ```
1184
-
1185
- ## Usage Examples
170
+ import { createExGuardExpress } from 'exguard-backend';
1186
171
 
1187
- ### Express.js Middleware
1188
-
1189
- ```typescript
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
- // Middleware to check permissions
1199
- const requirePermission = (permission: string) => {
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