exguard-backend 1.0.3 → 1.0.5

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
@@ -25,13 +25,40 @@ pnpm add exguard-backend
25
25
 
26
26
  ## šŸš€ Quick Start - NestJS Integration
27
27
 
28
- ### Step 1: Install Dependencies
28
+ ### šŸŽÆ Option 1: Automatic Setup (Recommended)
29
+
30
+ **One-command setup for NestJS projects:**
31
+
32
+ ```bash
33
+ # Install exguard-backend
34
+ npm install exguard-backend
35
+
36
+ # Run automatic setup
37
+ npx exguard-backend setup-nestjs
38
+ # OR
39
+ npm run setup-nestjs
40
+ ```
41
+
42
+ **What the automatic setup creates:**
43
+
44
+ āœ… **ExGuard Module** - Global module with Guard provider
45
+ āœ… **Custom Guards** - ExGuardNestGuard, PermissionGuard, RoleGuard
46
+ āœ… **Decorators** - @RequirePermissions, @RequireRoles, @RequireModules
47
+ āœ… **Example Controller** - Complete usage examples
48
+ āœ… **Environment Variables** - .env configuration
49
+ āœ… **Package Scripts** - Helper scripts for development
50
+
51
+ ### šŸ“‹ Option 2: Manual Setup
52
+
53
+ If you prefer manual setup, follow these steps:
54
+
55
+ #### Step 1: Install Dependencies
29
56
 
30
57
  ```bash
31
58
  npm install exguard-backend @nestjs/core @nestjs/common @nestjs/platform-express
32
59
  ```
33
60
 
34
- ### Step 2: Create ExGuard Module
61
+ #### Step 2: Create ExGuard Module
35
62
 
36
63
  ```typescript
37
64
  // src/exguard/exguard.module.ts
@@ -54,7 +81,7 @@ import { Guard } from 'exguard-backend';
54
81
  export class ExGuardModule {}
55
82
  ```
56
83
 
57
- ### Step 3: Create Custom Guards
84
+ #### Step 3: Create Custom Guards
58
85
 
59
86
  ```typescript
60
87
  // src/exguard/exguard.guard.ts
@@ -104,7 +131,7 @@ export function createPermissionGuard(permissions: string[], requireAll = false)
104
131
  }
105
132
  ```
106
133
 
107
- ### Step 4: Protect Your Controllers
134
+ #### Step 4: Protect Your Controllers
108
135
 
109
136
  ```typescript
110
137
  // src/events/events.controller.ts
@@ -134,7 +161,7 @@ export class EventsController {
134
161
  }
135
162
  ```
136
163
 
137
- ### Step 5: Update App Module
164
+ #### Step 5: Update App Module
138
165
 
139
166
  ```typescript
140
167
  // src/app.module.ts
@@ -149,6 +176,41 @@ import { EventsController } from './events/events.controller';
149
176
  export class AppModule {}
150
177
  ```
151
178
 
179
+ ### šŸŽÆ Automatic Setup Features
180
+
181
+ The automatic setup creates a complete NestJS integration:
182
+
183
+ #### **Generated Files:**
184
+ ```
185
+ src/
186
+ ā”œā”€ā”€ exguard/
187
+ │ ā”œā”€ā”€ exguard.module.ts # Global ExGuard module
188
+ │ ā”œā”€ā”€ exguard.guard.ts # Custom guards
189
+ │ ā”œā”€ā”€ exguard.decorators.ts # Permission decorators
190
+ │ └── guards/ # Additional guard files
191
+ ā”œā”€ā”€ events/
192
+ │ └── events.controller.ts # Example controller
193
+ └── .env # Environment configuration
194
+ ```
195
+
196
+ #### **Generated Decorators:**
197
+ ```typescript
198
+ @RequirePermissions(['events:read'])
199
+ @RequireRoles(['Admin'])
200
+ @RequireModules(['reporting'])
201
+ @RequireFieldOffices(['FO-MANILA'])
202
+ ```
203
+
204
+ #### **Generated Package Scripts:**
205
+ ```json
206
+ {
207
+ "scripts": {
208
+ "exguard:setup": "node node_modules/exguard-backend/scripts/setup-nestjs.js",
209
+ "exguard:example": "node node_modules/exguard-backend/scripts/create-example.js"
210
+ }
211
+ }
212
+ ```
213
+
152
214
  ## šŸŽÆ Advanced NestJS Examples
153
215
 
154
216
  ### Role-Based Protection
package/dist/index.d.cts CHANGED
@@ -51,17 +51,17 @@ interface ExGuardEnhancedConfig$1 extends ExGuardConfig {
51
51
  cache?: ExGuardCacheConfig;
52
52
  realtime?: ExGuardRealtimeConfig;
53
53
  }
54
- interface GuardContext$1 {
54
+ interface GuardContext {
55
55
  token: string;
56
56
  request?: any;
57
57
  }
58
- interface GuardResult$1 {
58
+ interface GuardResult {
59
59
  allowed: boolean;
60
60
  user?: UserAccessResponse;
61
61
  error?: string;
62
62
  statusCode?: number;
63
63
  }
64
- interface GuardOptions$1 {
64
+ interface GuardOptions {
65
65
  permissions?: string[];
66
66
  roles?: string[];
67
67
  modules?: string[];
@@ -292,23 +292,6 @@ declare const realtime: ExGuardRealtime;
292
292
  * Framework-agnostic guards for protecting backend endpoints
293
293
  */
294
294
 
295
- interface GuardContext {
296
- token: string;
297
- request?: any;
298
- }
299
- interface GuardResult {
300
- allowed: boolean;
301
- user?: UserAccessResponse;
302
- error?: string;
303
- statusCode?: number;
304
- }
305
- interface GuardOptions {
306
- permissions?: string[];
307
- roles?: string[];
308
- modules?: string[];
309
- fieldOffices?: string[];
310
- requireAll?: boolean;
311
- }
312
295
  /**
313
296
  * Framework-agnostic guard class for endpoint protection
314
297
  */
@@ -480,4 +463,4 @@ declare function createExGuardFastify(config: any): {
480
463
  getGuard(): ExGuardBackend;
481
464
  };
482
465
 
483
- export { type ApiResponse, ExGuardBackend$1 as ExGuardBackend, ExGuardBackendEnhanced, ExGuardCache, type ExGuardCacheConfig, type ExGuardConfig, type ExGuardEnhancedConfig$1 as ExGuardEnhancedConfig, ExGuardRealtime, type ExGuardRealtimeConfig, ExGuardBackend as Guard, type GuardContext$1 as GuardContext, type GuardOptions$1 as GuardOptions, type GuardResult$1 as GuardResult, type UserAccessResponse, cache, createExGuardExpress, createExGuardFastify, realtime };
466
+ export { type ApiResponse, ExGuardBackend$1 as ExGuardBackend, ExGuardBackendEnhanced, ExGuardCache, type ExGuardCacheConfig, type ExGuardConfig, type ExGuardEnhancedConfig$1 as ExGuardEnhancedConfig, ExGuardRealtime, type ExGuardRealtimeConfig, ExGuardBackend as Guard, type GuardContext, type GuardOptions, type GuardResult, type UserAccessResponse, cache, createExGuardExpress, createExGuardFastify, realtime };
package/dist/index.d.ts CHANGED
@@ -51,17 +51,17 @@ interface ExGuardEnhancedConfig$1 extends ExGuardConfig {
51
51
  cache?: ExGuardCacheConfig;
52
52
  realtime?: ExGuardRealtimeConfig;
53
53
  }
54
- interface GuardContext$1 {
54
+ interface GuardContext {
55
55
  token: string;
56
56
  request?: any;
57
57
  }
58
- interface GuardResult$1 {
58
+ interface GuardResult {
59
59
  allowed: boolean;
60
60
  user?: UserAccessResponse;
61
61
  error?: string;
62
62
  statusCode?: number;
63
63
  }
64
- interface GuardOptions$1 {
64
+ interface GuardOptions {
65
65
  permissions?: string[];
66
66
  roles?: string[];
67
67
  modules?: string[];
@@ -292,23 +292,6 @@ declare const realtime: ExGuardRealtime;
292
292
  * Framework-agnostic guards for protecting backend endpoints
293
293
  */
294
294
 
295
- interface GuardContext {
296
- token: string;
297
- request?: any;
298
- }
299
- interface GuardResult {
300
- allowed: boolean;
301
- user?: UserAccessResponse;
302
- error?: string;
303
- statusCode?: number;
304
- }
305
- interface GuardOptions {
306
- permissions?: string[];
307
- roles?: string[];
308
- modules?: string[];
309
- fieldOffices?: string[];
310
- requireAll?: boolean;
311
- }
312
295
  /**
313
296
  * Framework-agnostic guard class for endpoint protection
314
297
  */
@@ -480,4 +463,4 @@ declare function createExGuardFastify(config: any): {
480
463
  getGuard(): ExGuardBackend;
481
464
  };
482
465
 
483
- export { type ApiResponse, ExGuardBackend$1 as ExGuardBackend, ExGuardBackendEnhanced, ExGuardCache, type ExGuardCacheConfig, type ExGuardConfig, type ExGuardEnhancedConfig$1 as ExGuardEnhancedConfig, ExGuardRealtime, type ExGuardRealtimeConfig, ExGuardBackend as Guard, type GuardContext$1 as GuardContext, type GuardOptions$1 as GuardOptions, type GuardResult$1 as GuardResult, type UserAccessResponse, cache, createExGuardExpress, createExGuardFastify, realtime };
466
+ export { type ApiResponse, ExGuardBackend$1 as ExGuardBackend, ExGuardBackendEnhanced, ExGuardCache, type ExGuardCacheConfig, type ExGuardConfig, type ExGuardEnhancedConfig$1 as ExGuardEnhancedConfig, ExGuardRealtime, type ExGuardRealtimeConfig, ExGuardBackend as Guard, type GuardContext, type GuardOptions, type GuardResult, type UserAccessResponse, cache, createExGuardExpress, createExGuardFastify, realtime };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exguard-backend",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,8 +19,12 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
+ "scripts",
22
23
  "README.md"
23
24
  ],
25
+ "bin": {
26
+ "exguard-backend": "scripts/setup-nestjs.js"
27
+ },
24
28
  "keywords": [
25
29
  "exguard",
26
30
  "rbac",
@@ -46,6 +50,8 @@
46
50
  "test": "node --test",
47
51
  "test:watch": "node --test --watch",
48
52
  "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
49
- "prebuild": "npm run clean"
53
+ "prebuild": "npm run clean",
54
+ "setup-nestjs": "node scripts/setup-nestjs.js",
55
+ "create-example": "node scripts/create-example.js"
50
56
  }
51
57
  }
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Create Example Controller for ExGuard
5
+ *
6
+ * Usage:
7
+ * npx exguard-backend create-example
8
+ * OR
9
+ * node node_modules/exguard-backend/scripts/create-example.js
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // ANSI colors
16
+ const colors = {
17
+ reset: '\x1b[0m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ cyan: '\x1b[36m'
21
+ };
22
+
23
+ function log(message, color = 'reset') {
24
+ console.log(`${colors[color]}${message}${colors.reset}`);
25
+ }
26
+
27
+ function logSuccess(message) {
28
+ log(`āœ… ${message}`, 'green');
29
+ }
30
+
31
+ function logInfo(message) {
32
+ log(`ā„¹ļø ${message}`, 'cyan');
33
+ }
34
+
35
+ // Create example controller
36
+ function createExampleController() {
37
+ const controllerContent = `import {
38
+ Controller,
39
+ Get,
40
+ Post,
41
+ Put,
42
+ Delete,
43
+ Body,
44
+ Param,
45
+ UseGuards,
46
+ Request
47
+ } from '@nestjs/common';
48
+ import {
49
+ RequirePermissions,
50
+ RequireRoles,
51
+ RequireModules
52
+ } from '../exguard/exguard.decorators';
53
+ import {
54
+ ExGuardPermissionGuard,
55
+ createPermissionGuard,
56
+ createRoleGuard,
57
+ createModuleGuard
58
+ } from '../exguard/exguard.guard';
59
+
60
+ @Controller('api/example')
61
+ export class ExampleController {
62
+ @Get()
63
+ @UseGuards(ExGuardPermissionGuard) // Requires 'read' permission
64
+ async getItems(@Request() req) {
65
+ return {
66
+ success: true,
67
+ data: [],
68
+ user: req.user,
69
+ message: 'Items retrieved successfully'
70
+ };
71
+ }
72
+
73
+ @Post()
74
+ @RequirePermissions(['items:create']) // Using decorator
75
+ @UseGuards(createPermissionGuard(['items:create']))
76
+ async createItem(@Body() createDto: any, @Request() req) {
77
+ return {
78
+ success: true,
79
+ data: createDto,
80
+ user: req.user,
81
+ message: 'Item created successfully'
82
+ };
83
+ }
84
+
85
+ @Put(':id')
86
+ @UseGuards(createPermissionGuard(['items:update'], true)) // Require both permissions
87
+ async updateItem(@Param('id') id: string, @Body() updateDto: any, @Request() req) {
88
+ return {
89
+ success: true,
90
+ data: { id, ...updateDto },
91
+ user: req.user,
92
+ message: 'Item updated successfully'
93
+ };
94
+ }
95
+
96
+ @Delete(':id')
97
+ @RequireRoles(['Admin', 'Manager']) // Using decorator
98
+ @UseGuards(createRoleGuard(['Admin', 'Manager']))
99
+ async deleteItem(@Param('id') id: string, @Request() req) {
100
+ return {
101
+ success: true,
102
+ message: \`Item \${id} deleted successfully\`,
103
+ user: req.user
104
+ };
105
+ }
106
+
107
+ @Get('reports')
108
+ @RequireModules(['reporting']) // Using decorator
109
+ @UseGuards(createModuleGuard(['reporting']))
110
+ async getReports(@Request() req) {
111
+ return {
112
+ success: true,
113
+ data: { reports: [], total: 0 },
114
+ user: req.user,
115
+ message: 'Reports retrieved successfully'
116
+ };
117
+ }
118
+
119
+ @Get('admin/dashboard')
120
+ @UseGuards(createRoleGuard(['Admin']))
121
+ async getAdminDashboard(@Request() req) {
122
+ return {
123
+ success: true,
124
+ data: {
125
+ totalUsers: 100,
126
+ activeUsers: 85,
127
+ totalItems: 500
128
+ },
129
+ user: req.user,
130
+ message: 'Admin dashboard data retrieved'
131
+ };
132
+ }
133
+
134
+ @Get('complex')
135
+ @UseGuards(new (class extends ExGuardPermissionGuard {
136
+ protected async checkPermissions(context: any) {
137
+ return this.exGuard.require(context, {
138
+ permissions: ['complex:access'],
139
+ roles: ['Manager'],
140
+ modules: ['analytics'],
141
+ requireAll: true // Must satisfy ALL conditions
142
+ });
143
+ }
144
+ })(this.exGuard))
145
+ async getComplexData(@Request() req) {
146
+ return {
147
+ success: true,
148
+ data: { complexData: '...' },
149
+ user: req.user,
150
+ message: 'Complex data accessed successfully'
151
+ };
152
+ }
153
+ }
154
+ `;
155
+
156
+ const controllerPath = path.join(process.cwd(), 'src/api/example/example.controller.ts');
157
+
158
+ // Create directories if they don't exist
159
+ const apiDir = path.join(process.cwd(), 'src/api');
160
+ const exampleDir = path.join(process.cwd(), 'src/api/example');
161
+
162
+ if (!fs.existsSync(apiDir)) {
163
+ fs.mkdirSync(apiDir, { recursive: true });
164
+ logSuccess('Created api directory');
165
+ }
166
+
167
+ if (!fs.existsSync(exampleDir)) {
168
+ fs.mkdirSync(exampleDir, { recursive: true });
169
+ logSuccess('Created example directory');
170
+ }
171
+
172
+ if (fs.existsSync(controllerPath)) {
173
+ log('āš ļø Example controller already exists. Skipping creation.', 'yellow');
174
+ return;
175
+ }
176
+
177
+ fs.writeFileSync(controllerPath, controllerContent);
178
+ logSuccess('Created example controller at src/api/example/example.controller.ts');
179
+
180
+ logInfo('\nšŸ“‹ Example controller features:');
181
+ logInfo('• Basic CRUD operations with permission protection');
182
+ logInfo('• Role-based protection for admin operations');
183
+ logInfo('• Module-based protection for reports');
184
+ logInfo('• Complex multi-requirement protection');
185
+ logInfo('• Decorator usage examples');
186
+ logInfo('• Dynamic guard factory usage');
187
+
188
+ logInfo('\nšŸŽÆ Next steps:');
189
+ logInfo('1. Update your app.module.ts to import ExampleController');
190
+ logInfo('2. Start your application: npm run start:dev');
191
+ logInfo('3. Test the endpoints with different user permissions');
192
+ logInfo('4. Check the user data attached to requests');
193
+ }
194
+
195
+ // Run if called directly
196
+ if (require.main === module) {
197
+ log('šŸš€ Creating ExGuard Example Controller', 'cyan');
198
+ createExampleController();
199
+ }
200
+
201
+ module.exports = { createExampleController };
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Automatic ExGuard Module Setup for NestJS
5
+ *
6
+ * Usage:
7
+ * npx exguard-backend
8
+ * npx exguard-backend setup-nestjs
9
+ * OR
10
+ * node node_modules/exguard-backend/scripts/setup-nestjs.js
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { execSync } = require('child_process');
16
+
17
+ // ANSI colors for better output
18
+ const colors = {
19
+ reset: '\x1b[0m',
20
+ bright: '\x1b[1m',
21
+ red: '\x1b[31m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ magenta: '\x1b[35m',
26
+ cyan: '\x1b[36m'
27
+ };
28
+
29
+ function log(message, color = 'reset') {
30
+ console.log(`${colors[color]}${message}${colors.reset}`);
31
+ }
32
+
33
+ function logStep(step, message) {
34
+ log(`\nšŸ”§ Step ${step}: ${message}`, 'cyan');
35
+ }
36
+
37
+ function logSuccess(message) {
38
+ log(`āœ… ${message}`, 'green');
39
+ }
40
+
41
+ function logWarning(message) {
42
+ log(`āš ļø ${message}`, 'yellow');
43
+ }
44
+
45
+ function logError(message) {
46
+ log(`āŒ ${message}`, 'red');
47
+ }
48
+
49
+ function logInfo(message) {
50
+ log(`ā„¹ļø ${message}`, 'blue');
51
+ }
52
+
53
+ // Check if we're in a NestJS project
54
+ function checkNestJSProject() {
55
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
56
+
57
+ if (!fs.existsSync(packageJsonPath)) {
58
+ logError('package.json not found. Please run this command in a NestJS project root.');
59
+ process.exit(1);
60
+ }
61
+
62
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
63
+ const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
64
+
65
+ const nestPackages = Object.keys(dependencies).filter(dep =>
66
+ dep.startsWith('@nestjs/') || dep === 'nestjs'
67
+ );
68
+
69
+ if (nestPackages.length === 0) {
70
+ logError('No NestJS packages found. Please run this command in a NestJS project.');
71
+ process.exit(1);
72
+ }
73
+
74
+ logSuccess(`NestJS project detected with packages: ${nestPackages.join(', ')}`);
75
+ return true;
76
+ }
77
+
78
+ // Create exguard directory structure
79
+ function createDirectoryStructure() {
80
+ logStep(1, 'Creating ExGuard directory structure');
81
+
82
+ const directories = [
83
+ 'src/exguard',
84
+ 'src/exguard/guards',
85
+ 'src/exguard/decorators',
86
+ 'src/exguard/interceptors'
87
+ ];
88
+
89
+ directories.forEach(dir => {
90
+ const fullPath = path.join(process.cwd(), dir);
91
+ if (!fs.existsSync(fullPath)) {
92
+ fs.mkdirSync(fullPath, { recursive: true });
93
+ logSuccess(`Created directory: ${dir}`);
94
+ } else {
95
+ logWarning(`Directory already exists: ${dir}`);
96
+ }
97
+ });
98
+ }
99
+
100
+ // Create ExGuard Module
101
+ function createExGuardModule() {
102
+ logStep(2, 'Creating ExGuard Module');
103
+
104
+ const moduleContent = `import { Module, Global } from '@nestjs/common';
105
+ import { Guard } from 'exguard-backend';
106
+
107
+ @Global()
108
+ @Module({
109
+ providers: [
110
+ {
111
+ provide: Guard,
112
+ useFactory: () => new Guard({
113
+ apiUrl: process.env.EXGUARD_API_URL || 'http://localhost:3000',
114
+ cache: {
115
+ enabled: process.env.EXGUARD_CACHE_ENABLED !== 'false',
116
+ ttl: parseInt(process.env.EXGUARD_CACHE_TTL) || 300000, // 5 minutes
117
+ },
118
+ realtime: {
119
+ enabled: process.env.EXGUARD_REALTIME_ENABLED === 'true',
120
+ url: process.env.EXGUARD_REALTIME_URL,
121
+ token: process.env.EXGUARD_SERVICE_TOKEN,
122
+ },
123
+ }),
124
+ },
125
+ ],
126
+ exports: [Guard],
127
+ })
128
+ export class ExGuardModule {}
129
+ `;
130
+
131
+ const modulePath = path.join(process.cwd(), 'src/exguard/exguard.module.ts');
132
+
133
+ if (fs.existsSync(modulePath)) {
134
+ logWarning('ExGuardModule already exists. Skipping creation.');
135
+ return;
136
+ }
137
+
138
+ fs.writeFileSync(modulePath, moduleContent);
139
+ logSuccess('Created ExGuardModule');
140
+ }
141
+
142
+ // Create ExGuard Guards
143
+ function createExGuardGuards() {
144
+ logStep(3, 'Creating ExGuard Guards');
145
+
146
+ const guardContent = `import { Injectable, CanActivate, ExecutionContext, ForbiddenException, UnauthorizedException } from '@nestjs/common';
147
+ import { Guard, GuardContext } from 'exguard-backend';
148
+
149
+ @Injectable()
150
+ export class ExGuardNestGuard implements CanActivate {
151
+ constructor(protected exGuard: Guard) {}
152
+
153
+ async canActivate(context: ExecutionContext): Promise<boolean> {
154
+ const request = context.switchToHttp().getRequest();
155
+ const token = this.extractToken(request);
156
+
157
+ if (!token) {
158
+ throw new UnauthorizedException('No authentication token provided');
159
+ }
160
+
161
+ const guardContext: GuardContext = {
162
+ token,
163
+ request,
164
+ };
165
+
166
+ const result = await this.checkPermissions(guardContext);
167
+
168
+ if (!result.allowed) {
169
+ throw new ForbiddenException(result.error || 'Access denied');
170
+ }
171
+
172
+ request.user = result.user;
173
+ return true;
174
+ }
175
+
176
+ protected async checkPermissions(context: GuardContext) {
177
+ return this.exGuard.authenticate(context);
178
+ }
179
+
180
+ protected extractToken(request: any): string | null {
181
+ const authHeader = request.headers?.authorization;
182
+ if (authHeader?.startsWith('Bearer ')) {
183
+ return authHeader.substring(7);
184
+ }
185
+ return null;
186
+ }
187
+ }
188
+
189
+ @Injectable()
190
+ export class ExGuardPermissionGuard extends ExGuardNestGuard {
191
+ protected async checkPermissions(context: GuardContext) {
192
+ return this.exGuard.requirePermissions(context, ['read']);
193
+ }
194
+ }
195
+
196
+ @Injectable()
197
+ export class ExGuardRoleGuard extends ExGuardNestGuard {
198
+ protected async checkPermissions(context: GuardContext) {
199
+ return this.exGuard.requireRoles(context, ['Admin']);
200
+ }
201
+ }
202
+
203
+ // Factory functions for dynamic guards
204
+ export function createPermissionGuard(permissions: string[], requireAll = false) {
205
+ return class extends ExGuardNestGuard {
206
+ protected async checkPermissions(context: GuardContext) {
207
+ return this.exGuard.requirePermissions(context, permissions, { requireAll });
208
+ }
209
+ };
210
+ }
211
+
212
+ export function createRoleGuard(roles: string[], requireAll = false) {
213
+ return class extends ExGuardNestGuard {
214
+ protected async checkPermissions(context: GuardContext) {
215
+ return this.exGuard.requireRoles(context, roles, { requireAll });
216
+ }
217
+ };
218
+ }
219
+
220
+ export function createModuleGuard(modules: string[], requireAll = false) {
221
+ return class extends ExGuardNestGuard {
222
+ protected async checkPermissions(context: GuardContext) {
223
+ return this.exGuard.requireModules(context, modules, { requireAll });
224
+ }
225
+ };
226
+ }
227
+ `;
228
+
229
+ const guardPath = path.join(process.cwd(), 'src/exguard/exguard.guard.ts');
230
+
231
+ if (fs.existsSync(guardPath)) {
232
+ logWarning('ExGuard guards already exist. Skipping creation.');
233
+ return;
234
+ }
235
+
236
+ fs.writeFileSync(guardPath, guardContent);
237
+ logSuccess('Created ExGuard guards');
238
+ }
239
+
240
+ // Create ExGuard Decorators
241
+ function createExGuardDecorators() {
242
+ logStep(4, 'Creating ExGuard Decorators');
243
+
244
+ const decoratorContent = `import { SetMetadata } from '@nestjs/common';
245
+ import { createPermissionGuard, createRoleGuard, createModuleGuard } from './exguard.guard';
246
+
247
+ // Metadata keys
248
+ export const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';
249
+ export const EXGUARD_ROLES_KEY = 'exguard_roles';
250
+ export const EXGUARD_MODULES_KEY = 'exguard_modules';
251
+ export const EXGUARD_FIELD_OFFICES_KEY = 'exguard_field_offices';
252
+
253
+ // Permission decorator
254
+ export const RequirePermissions = (permissions: string[], requireAll = false) => {
255
+ return SetMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll });
256
+ };
257
+
258
+ // Role decorator
259
+ export const RequireRoles = (roles: string[], requireAll = false) => {
260
+ return SetMetadata(EXGUARD_ROLES_KEY, { roles, requireAll });
261
+ };
262
+
263
+ // Module decorator
264
+ export const RequireModules = (modules: string[], requireAll = false) => {
265
+ return SetMetadata(EXGUARD_MODULES_KEY, { modules, requireAll });
266
+ };
267
+
268
+ // Field office decorator
269
+ export const RequireFieldOffices = (fieldOffices: string[], requireAll = false) => {
270
+ return SetMetadata(EXGUARD_FIELD_OFFICES_KEY, { fieldOffices, requireAll });
271
+ };
272
+
273
+ // Combined decorator for complex requirements
274
+ export const Require = (requirements: {
275
+ permissions?: string[];
276
+ roles?: string[];
277
+ modules?: string[];
278
+ fieldOffices?: string[];
279
+ requireAll?: boolean;
280
+ }) => {
281
+ return (target: any) => {
282
+ if (requirements.permissions) {
283
+ SetMetadata(EXGUARD_PERMISSIONS_KEY, {
284
+ permissions: requirements.permissions,
285
+ requireAll: requirements.requireAll || false
286
+ })(target);
287
+ }
288
+ if (requirements.roles) {
289
+ SetMetadata(EXGUARD_ROLES_KEY, {
290
+ roles: requirements.roles,
291
+ requireAll: requirements.requireAll || false
292
+ })(target);
293
+ }
294
+ if (requirements.modules) {
295
+ SetMetadata(EXGUARD_MODULES_KEY, {
296
+ modules: requirements.modules,
297
+ requireAll: requirements.requireAll || false
298
+ })(target);
299
+ }
300
+ if (requirements.fieldOffices) {
301
+ SetMetadata(EXGUARD_FIELD_OFFICES_KEY, {
302
+ fieldOffices: requirements.fieldOffices,
303
+ requireAll: requirements.requireAll || false
304
+ })(target);
305
+ }
306
+ };
307
+ };
308
+ `;
309
+
310
+ const decoratorPath = path.join(process.cwd(), 'src/exguard/exguard.decorators.ts');
311
+
312
+ if (fs.existsSync(decoratorPath)) {
313
+ logWarning('ExGuard decorators already exist. Skipping creation.');
314
+ return;
315
+ }
316
+
317
+ fs.writeFileSync(decoratorPath, decoratorContent);
318
+ logSuccess('Created ExGuard decorators');
319
+ }
320
+
321
+ // Create example controller
322
+ function createExampleController() {
323
+ logStep(5, 'Creating Example Controller');
324
+
325
+ const controllerContent = `import {
326
+ Controller,
327
+ Get,
328
+ Post,
329
+ Body,
330
+ UseGuards,
331
+ Request
332
+ } from '@nestjs/common';
333
+ import {
334
+ RequirePermissions,
335
+ RequireRoles,
336
+ RequireModules
337
+ } from '../exguard/exguard.decorators';
338
+ import {
339
+ ExGuardPermissionGuard,
340
+ createPermissionGuard
341
+ } from '../exguard/exguard.guard';
342
+
343
+ @Controller('events')
344
+ @UseGuards(ExGuardPermissionGuard) // Requires 'read' permission
345
+ export class EventsController {
346
+ @Get()
347
+ async getEvents(@Request() req) {
348
+ console.log('User accessing events:', req.user);
349
+ return { success: true, data: [] };
350
+ }
351
+
352
+ @Post()
353
+ @UseGuards(createPermissionGuard(['events:create']))
354
+ async createEvent(@Body() createEventDto: any, @Request() req) {
355
+ console.log('User creating event:', req.user);
356
+ return { success: true, data: createEventDto };
357
+ }
358
+
359
+ @Get('admin')
360
+ @RequireRoles(['Admin']) // Using decorator
361
+ @UseGuards(createRoleGuard(['Admin']))
362
+ async getAdminEvents(@Request() req) {
363
+ return { success: true, data: [], message: 'Admin events' };
364
+ }
365
+
366
+ @Get('reports')
367
+ @RequireModules(['reporting']) // Using decorator
368
+ @UseGuards(createModuleGuard(['reporting']))
369
+ async getReports(@Request() req) {
370
+ return { success: true, data: [], message: 'Reports data' };
371
+ }
372
+ }
373
+ `;
374
+
375
+ const controllerPath = path.join(process.cwd(), 'src/events/events.controller.ts');
376
+
377
+ // Check if events directory exists
378
+ const eventsDir = path.join(process.cwd(), 'src/events');
379
+ if (!fs.existsSync(eventsDir)) {
380
+ fs.mkdirSync(eventsDir, { recursive: true });
381
+ logSuccess('Created events directory');
382
+ }
383
+
384
+ if (fs.existsSync(controllerPath)) {
385
+ logWarning('Example controller already exists. Skipping creation.');
386
+ return;
387
+ }
388
+
389
+ fs.writeFileSync(controllerPath, controllerContent);
390
+ logSuccess('Created example controller');
391
+ }
392
+
393
+ // Update app module
394
+ function updateAppModule() {
395
+ logStep(6, 'Updating App Module');
396
+
397
+ const appModulePath = path.join(process.cwd(), 'src/app.module.ts');
398
+
399
+ if (!fs.existsSync(appModulePath)) {
400
+ logWarning('app.module.ts not found. Please manually import ExGuardModule.');
401
+ return;
402
+ }
403
+
404
+ let appModuleContent = fs.readFileSync(appModulePath, 'utf8');
405
+
406
+ // Check if ExGuardModule is already imported
407
+ if (appModuleContent.includes('ExGuardModule')) {
408
+ logWarning('ExGuardModule already imported in app.module.ts');
409
+ return;
410
+ }
411
+
412
+ // Add import
413
+ const importMatch = appModuleContent.match(/import\s*\{[^}]*\}\s*from\s*['"][^'"]*['"];?\s*/m);
414
+ if (importMatch) {
415
+ const lastImport = importMatch[0];
416
+ const newImport = lastImport.replace(/;?\s*$/, '') + ',\\n ExGuardModule from \\'./exguard/exguard.module\\';\\n';
417
+ appModuleContent = appModuleContent.replace(lastImport, newImport);
418
+ } else {
419
+ // Add import at the top
420
+ appModuleContent = `import { ExGuardModule } from './exguard/exguard.module';\\n\\n${appModuleContent}`;
421
+ }
422
+
423
+ // Add to imports array
424
+ const importsMatch = appModuleContent.match(/@Module\s*\(\s*\{\s*imports:\s*\[([^\]]*)\]/m);
425
+ if (importsMatch) {
426
+ const importsContent = importsMatch[1];
427
+ const newImportsContent = importsContent.trim() + ',\\n ExGuardModule';
428
+ appModuleContent = appModuleContent.replace(importsMatch[0], `@Module({\\n imports: [${newImportsContent}]`);
429
+ }
430
+
431
+ fs.writeFileSync(appModulePath, appModuleContent);
432
+ logSuccess('Updated app.module.ts with ExGuardModule');
433
+ }
434
+
435
+ // Create environment variables
436
+ function createEnvironmentFile() {
437
+ logStep(7, 'Creating Environment Variables');
438
+
439
+ const envContent = `# ExGuard Configuration
440
+ EXGUARD_API_URL=http://localhost:3000
441
+ EXGUARD_CACHE_ENABLED=true
442
+ EXGUARD_CACHE_TTL=300000
443
+
444
+ # Optional Realtime Configuration
445
+ EXGUARD_REALTIME_ENABLED=false
446
+ EXGUARD_REALTIME_URL=ws://localhost:3000/realtime
447
+ EXGUARD_SERVICE_TOKEN=your-service-jwt-token
448
+ `;
449
+
450
+ const envPath = path.join(process.cwd(), '.env');
451
+
452
+ if (fs.existsSync(envPath)) {
453
+ // Append if not already present
454
+ const existingEnv = fs.readFileSync(envPath, 'utf8');
455
+ if (!existingEnv.includes('EXGUARD_API_URL')) {
456
+ fs.appendFileSync(envPath, '\\n\\n# ExGuard Configuration\\n' + envContent);
457
+ logSuccess('Appended ExGuard configuration to .env');
458
+ } else {
459
+ logWarning('ExGuard configuration already exists in .env');
460
+ }
461
+ } else {
462
+ fs.writeFileSync(envPath, envContent);
463
+ logSuccess('Created .env with ExGuard configuration');
464
+ }
465
+ }
466
+
467
+ // Update package.json scripts
468
+ function updatePackageScripts() {
469
+ logStep(8, 'Updating Package Scripts');
470
+
471
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
472
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
473
+
474
+ const scriptsToAdd = {
475
+ 'exguard:setup': 'node node_modules/exguard-backend/scripts/setup-nestjs.js',
476
+ 'exguard:example': 'node node_modules/exguard-backend/scripts/create-example.js'
477
+ };
478
+
479
+ if (!packageJson.scripts) {
480
+ packageJson.scripts = {};
481
+ }
482
+
483
+ Object.entries(scriptsToAdd).forEach(([script, command]) => {
484
+ if (!packageJson.scripts[script]) {
485
+ packageJson.scripts[script] = command;
486
+ logSuccess(`Added script: ${script}`);
487
+ } else {
488
+ logWarning(`Script already exists: ${script}`);
489
+ }
490
+ });
491
+
492
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
493
+ logSuccess('Updated package.json scripts');
494
+ }
495
+
496
+ // Main setup function
497
+ async function setupExGuard() {
498
+ log('šŸš€ ExGuard NestJS Automatic Setup', 'bright');
499
+ log('This script will automatically set up ExGuard in your NestJS project.\\n');
500
+
501
+ try {
502
+ checkNestJSProject();
503
+ createDirectoryStructure();
504
+ createExGuardModule();
505
+ createExGuardGuards();
506
+ createExGuardDecorators();
507
+ createExampleController();
508
+ updateAppModule();
509
+ createEnvironmentFile();
510
+ updatePackageScripts();
511
+
512
+ log('\\nšŸŽ‰ ExGuard setup completed successfully!', 'green');
513
+ log('\\nšŸ“‹ Next steps:', 'cyan');
514
+ log('1. Review the generated files in src/exguard/', 'blue');
515
+ log('2. Check the example controller in src/events/', 'blue');
516
+ log('3. Update your .env file with your ExGuard API URL', 'blue');
517
+ log('4. Start your application: npm run start:dev', 'blue');
518
+ log('5. Test the protected endpoints', 'blue');
519
+
520
+ log('\\nšŸ“š Documentation:', 'cyan');
521
+ log('- Main README: node_modules/exguard-backend/README.md', 'blue');
522
+ log('- NestJS Guide: node_modules/exguard-backend/examples/NESTJS-SETUP.md', 'blue');
523
+
524
+ } catch (error) {
525
+ logError(`Setup failed: ${error.message}`);
526
+ process.exit(1);
527
+ }
528
+ }
529
+
530
+ // Run setup if called directly
531
+ if (require.main === module) {
532
+ setupExGuard();
533
+ }
534
+
535
+ module.exports = { setupExGuard };