exguard-backend 1.0.6 ā 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/setup-nestjs-old.cjs +535 -0
- package/scripts/setup-nestjs.cjs +71 -31
package/package.json
CHANGED
|
@@ -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 };
|
package/scripts/setup-nestjs.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Automatic ExGuard Module Setup for NestJS
|
|
4
|
+
* Fixed Automatic ExGuard Module Setup for NestJS
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* npx exguard-backend
|
|
8
8
|
* npx exguard-backend setup-nestjs
|
|
9
9
|
* OR
|
|
10
|
-
* node node_modules/exguard-backend/scripts/setup-nestjs.
|
|
10
|
+
* node node_modules/exguard-backend/scripts/setup-nestjs-fixed.cjs
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
@@ -71,6 +71,18 @@ function checkNestJSProject() {
|
|
|
71
71
|
process.exit(1);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Check if exguard-backend is installed
|
|
75
|
+
if (!dependencies['exguard-backend']) {
|
|
76
|
+
logWarning('exguard-backend not found in dependencies. Installing it...');
|
|
77
|
+
try {
|
|
78
|
+
execSync('npm install exguard-backend@latest', { stdio: 'inherit' });
|
|
79
|
+
logSuccess('exguard-backend installed successfully');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logError('Failed to install exguard-backend. Please install it manually: npm install exguard-backend');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
logSuccess(`NestJS project detected with packages: ${nestPackages.join(', ')}`);
|
|
75
87
|
return true;
|
|
76
88
|
}
|
|
@@ -113,12 +125,12 @@ import { Guard } from 'exguard-backend';
|
|
|
113
125
|
apiUrl: process.env.EXGUARD_API_URL || 'http://localhost:3000',
|
|
114
126
|
cache: {
|
|
115
127
|
enabled: process.env.EXGUARD_CACHE_ENABLED !== 'false',
|
|
116
|
-
ttl: parseInt(process.env.EXGUARD_CACHE_TTL
|
|
128
|
+
ttl: parseInt(process.env.EXGUARD_CACHE_TTL || '300000'), // 5 minutes
|
|
117
129
|
},
|
|
118
130
|
realtime: {
|
|
119
131
|
enabled: process.env.EXGUARD_REALTIME_ENABLED === 'true',
|
|
120
|
-
url: process.env.EXGUARD_REALTIME_URL,
|
|
121
|
-
token: process.env.EXGUARD_SERVICE_TOKEN,
|
|
132
|
+
url: process.env.EXGUARD_REALTIME_URL || undefined,
|
|
133
|
+
token: process.env.EXGUARD_SERVICE_TOKEN || undefined,
|
|
122
134
|
},
|
|
123
135
|
}),
|
|
124
136
|
},
|
|
@@ -148,7 +160,7 @@ import { Guard, GuardContext } from 'exguard-backend';
|
|
|
148
160
|
|
|
149
161
|
@Injectable()
|
|
150
162
|
export class ExGuardNestGuard implements CanActivate {
|
|
151
|
-
constructor(
|
|
163
|
+
constructor(public exGuard: Guard) {}
|
|
152
164
|
|
|
153
165
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
154
166
|
const request = context.switchToHttp().getRequest();
|
|
@@ -173,11 +185,11 @@ export class ExGuardNestGuard implements CanActivate {
|
|
|
173
185
|
return true;
|
|
174
186
|
}
|
|
175
187
|
|
|
176
|
-
|
|
188
|
+
public async checkPermissions(context: GuardContext) {
|
|
177
189
|
return this.exGuard.authenticate(context);
|
|
178
190
|
}
|
|
179
191
|
|
|
180
|
-
|
|
192
|
+
public extractToken(request: any): string | null {
|
|
181
193
|
const authHeader = request.headers?.authorization;
|
|
182
194
|
if (authHeader?.startsWith('Bearer ')) {
|
|
183
195
|
return authHeader.substring(7);
|
|
@@ -188,14 +200,14 @@ export class ExGuardNestGuard implements CanActivate {
|
|
|
188
200
|
|
|
189
201
|
@Injectable()
|
|
190
202
|
export class ExGuardPermissionGuard extends ExGuardNestGuard {
|
|
191
|
-
|
|
203
|
+
public async checkPermissions(context: GuardContext) {
|
|
192
204
|
return this.exGuard.requirePermissions(context, ['read']);
|
|
193
205
|
}
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
@Injectable()
|
|
197
209
|
export class ExGuardRoleGuard extends ExGuardNestGuard {
|
|
198
|
-
|
|
210
|
+
public async checkPermissions(context: GuardContext) {
|
|
199
211
|
return this.exGuard.requireRoles(context, ['Admin']);
|
|
200
212
|
}
|
|
201
213
|
}
|
|
@@ -203,7 +215,7 @@ export class ExGuardRoleGuard extends ExGuardNestGuard {
|
|
|
203
215
|
// Factory functions for dynamic guards
|
|
204
216
|
export function createPermissionGuard(permissions: string[], requireAll = false) {
|
|
205
217
|
return class extends ExGuardNestGuard {
|
|
206
|
-
|
|
218
|
+
public async checkPermissions(context: GuardContext) {
|
|
207
219
|
return this.exGuard.requirePermissions(context, permissions, { requireAll });
|
|
208
220
|
}
|
|
209
221
|
};
|
|
@@ -211,7 +223,7 @@ export function createPermissionGuard(permissions: string[], requireAll = false)
|
|
|
211
223
|
|
|
212
224
|
export function createRoleGuard(roles: string[], requireAll = false) {
|
|
213
225
|
return class extends ExGuardNestGuard {
|
|
214
|
-
|
|
226
|
+
public async checkPermissions(context: GuardContext) {
|
|
215
227
|
return this.exGuard.requireRoles(context, roles, { requireAll });
|
|
216
228
|
}
|
|
217
229
|
};
|
|
@@ -219,7 +231,7 @@ export function createRoleGuard(roles: string[], requireAll = false) {
|
|
|
219
231
|
|
|
220
232
|
export function createModuleGuard(modules: string[], requireAll = false) {
|
|
221
233
|
return class extends ExGuardNestGuard {
|
|
222
|
-
|
|
234
|
+
public async checkPermissions(context: GuardContext) {
|
|
223
235
|
return this.exGuard.requireModules(context, modules, { requireAll });
|
|
224
236
|
}
|
|
225
237
|
};
|
|
@@ -409,23 +421,51 @@ function updateAppModule() {
|
|
|
409
421
|
return;
|
|
410
422
|
}
|
|
411
423
|
|
|
412
|
-
// Add import
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
424
|
+
// Add import at the top - find the last import statement
|
|
425
|
+
const importRegex = /import\s+.*\s+from\s+['"][^'"]*['"];?\s*/g;
|
|
426
|
+
const imports = appModuleContent.match(importRegex);
|
|
427
|
+
|
|
428
|
+
if (imports && imports.length > 0) {
|
|
429
|
+
// Add after the last import
|
|
430
|
+
const lastImport = imports[imports.length - 1];
|
|
431
|
+
const lastImportIndex = appModuleContent.lastIndexOf(lastImport);
|
|
432
|
+
const afterLastImport = appModuleContent.substring(lastImportIndex + lastImport.length);
|
|
433
|
+
|
|
434
|
+
appModuleContent = appModuleContent.substring(0, lastImportIndex + lastImport.length) +
|
|
435
|
+
`import { ExGuardModule } from './exguard/exguard.module';\n` +
|
|
436
|
+
afterLastImport;
|
|
418
437
|
} else {
|
|
419
|
-
// Add
|
|
420
|
-
appModuleContent = `import { ExGuardModule } from './exguard/exguard.module';\n
|
|
438
|
+
// Add at the very top
|
|
439
|
+
appModuleContent = `import { ExGuardModule } from './exguard/exguard.module';\n${appModuleContent}`;
|
|
421
440
|
}
|
|
422
441
|
|
|
423
|
-
//
|
|
424
|
-
const
|
|
425
|
-
if (
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
442
|
+
// Find and update the @Module decorator
|
|
443
|
+
const moduleDecoratorMatch = appModuleContent.match(/@Module\s*\(\s*\{([^}]*)\}/s);
|
|
444
|
+
if (moduleDecoratorMatch) {
|
|
445
|
+
const moduleContent = moduleDecoratorMatch[1];
|
|
446
|
+
|
|
447
|
+
// Check if imports array exists
|
|
448
|
+
const importsArrayMatch = moduleContent.match(/imports:\s*\[([^\]]*)\]/s);
|
|
449
|
+
if (importsArrayMatch) {
|
|
450
|
+
// Add to existing imports array
|
|
451
|
+
const importsContent = importsArrayMatch[1];
|
|
452
|
+
const newImportsContent = importsContent.trim() + ',\n ExGuardModule';
|
|
453
|
+
appModuleContent = appModuleContent.replace(
|
|
454
|
+
importsArrayMatch[0],
|
|
455
|
+
`imports: [${newImportsContent}]`
|
|
456
|
+
);
|
|
457
|
+
} else {
|
|
458
|
+
// Add imports array after @Module({
|
|
459
|
+
const moduleStartMatch = appModuleContent.match(/@Module\s*\(\s*\{/);
|
|
460
|
+
if (moduleStartMatch) {
|
|
461
|
+
const moduleStartIndex = appModuleContent.indexOf(moduleStartMatch[0]);
|
|
462
|
+
const afterModuleStart = appModuleContent.substring(moduleStartIndex + moduleStartMatch[0].length);
|
|
463
|
+
|
|
464
|
+
appModuleContent = appModuleContent.substring(0, moduleStartIndex + moduleStartMatch[0].length) +
|
|
465
|
+
`\n imports: [ExGuardModule],` +
|
|
466
|
+
afterModuleStart;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
429
469
|
}
|
|
430
470
|
|
|
431
471
|
fs.writeFileSync(appModulePath, appModuleContent);
|
|
@@ -464,7 +504,7 @@ EXGUARD_SERVICE_TOKEN=your-service-jwt-token
|
|
|
464
504
|
}
|
|
465
505
|
}
|
|
466
506
|
|
|
467
|
-
// Update package
|
|
507
|
+
// Update package scripts
|
|
468
508
|
function updatePackageScripts() {
|
|
469
509
|
logStep(8, 'Updating Package Scripts');
|
|
470
510
|
|
|
@@ -472,8 +512,8 @@ function updatePackageScripts() {
|
|
|
472
512
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
473
513
|
|
|
474
514
|
const scriptsToAdd = {
|
|
475
|
-
'exguard:setup': 'node node_modules/exguard-backend/scripts/setup-nestjs.
|
|
476
|
-
'exguard:example': 'node node_modules/exguard-backend/scripts/create-example.
|
|
515
|
+
'exguard:setup': 'node node_modules/exguard-backend/scripts/setup-nestjs-fixed.cjs',
|
|
516
|
+
'exguard:example': 'node node_modules/exguard-backend/scripts/create-example.cjs'
|
|
477
517
|
};
|
|
478
518
|
|
|
479
519
|
if (!packageJson.scripts) {
|
|
@@ -495,7 +535,7 @@ function updatePackageScripts() {
|
|
|
495
535
|
|
|
496
536
|
// Main setup function
|
|
497
537
|
async function setupExGuard() {
|
|
498
|
-
log('š ExGuard NestJS Automatic Setup', 'bright');
|
|
538
|
+
log('š ExGuard NestJS Automatic Setup (Fixed)', 'bright');
|
|
499
539
|
log('This script will automatically set up ExGuard in your NestJS project.\n');
|
|
500
540
|
|
|
501
541
|
try {
|