exguard-endpoint 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -43
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -5
- package/dist/index.d.ts +2 -5
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/setup.cjs +77 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,34 +8,24 @@ Simple RBAC permission guard for NestJS with built-in caching.
|
|
|
8
8
|
npm install exguard-endpoint
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## Quick Setup
|
|
11
|
+
## Quick Setup (Auto-Configures Everything)
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npx exguard-endpoint setup
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
This will:
|
|
18
|
+
1. Create `src/exguard/exguard.guard.ts`
|
|
19
|
+
2. Create `src/exguard/exguard.module.ts`
|
|
20
|
+
3. Update `src/app.module.ts` with ExGuardModule
|
|
21
|
+
4. Create `.env.example` with EXGUARD_BASE_URL
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
## Usage
|
|
20
24
|
|
|
21
25
|
```typescript
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@Module({
|
|
26
|
-
imports: [
|
|
27
|
-
ExGuardModule.forRoot({
|
|
28
|
-
baseUrl: process.env.EXGUARD_BASE_URL,
|
|
29
|
-
}),
|
|
30
|
-
],
|
|
31
|
-
})
|
|
32
|
-
export class AppModule {}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### 2. Use in Controllers
|
|
26
|
+
// app.module.ts is auto-configured!
|
|
27
|
+
// Just use in controllers:
|
|
36
28
|
|
|
37
|
-
```typescript
|
|
38
|
-
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
|
|
39
29
|
import { ExGuardPermissionGuard, RequirePermissions } from './exguard/exguard.guard';
|
|
40
30
|
|
|
41
31
|
@Controller('items')
|
|
@@ -49,44 +39,27 @@ export class ItemsController {
|
|
|
49
39
|
@Post()
|
|
50
40
|
@RequirePermissions(['item:create'])
|
|
51
41
|
create() { }
|
|
52
|
-
|
|
53
|
-
@Delete(':id')
|
|
54
|
-
@RequirePermissions(['item:delete', 'admin'], true)
|
|
55
|
-
delete() { }
|
|
56
42
|
}
|
|
57
43
|
```
|
|
58
44
|
|
|
59
|
-
## Environment
|
|
45
|
+
## Environment
|
|
60
46
|
|
|
61
47
|
```env
|
|
62
|
-
EXGUARD_BASE_URL=
|
|
48
|
+
EXGUARD_BASE_URL=http://localhost:3000
|
|
63
49
|
```
|
|
64
50
|
|
|
65
|
-
## Caching
|
|
51
|
+
## Caching
|
|
66
52
|
|
|
67
53
|
- **TTL**: 5 minutes
|
|
68
|
-
- **
|
|
69
|
-
- **Manual clear**: `clearExGuardCache()`
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import { clearExGuardCache } from './exguard/exguard.guard';
|
|
73
|
-
|
|
74
|
-
// Call when needed
|
|
75
|
-
clearExGuardCache();
|
|
76
|
-
```
|
|
54
|
+
- **Clear cache**: `clearExGuardCache()`
|
|
77
55
|
|
|
78
56
|
## Decorators
|
|
79
57
|
|
|
80
58
|
| Decorator | Description |
|
|
81
59
|
|-----------|-------------|
|
|
82
|
-
| `@RequirePermissions(['item:read'])` | User needs ANY
|
|
60
|
+
| `@RequirePermissions(['item:read'])` | User needs ANY permission |
|
|
83
61
|
| `@RequirePermissions(['item:delete', 'admin'], true)` | User needs ALL permissions |
|
|
84
62
|
|
|
85
|
-
## Token Extraction
|
|
86
|
-
|
|
87
|
-
- `Authorization: Bearer <token>` header
|
|
88
|
-
- `x-access-token` header
|
|
89
|
-
|
|
90
63
|
## User Object
|
|
91
64
|
|
|
92
65
|
```typescript
|
|
@@ -99,6 +72,4 @@ req.user = {
|
|
|
99
72
|
}
|
|
100
73
|
```
|
|
101
74
|
|
|
102
|
-
## License
|
|
103
|
-
|
|
104
75
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -42,13 +42,14 @@ module.exports = __toCommonJS(src_exports);
|
|
|
42
42
|
// src/module.ts
|
|
43
43
|
var import_common = require("@nestjs/common");
|
|
44
44
|
var ExGuardModule = class {
|
|
45
|
-
static forRoot(
|
|
45
|
+
static forRoot(optionsOrUrl) {
|
|
46
|
+
const baseUrl = typeof optionsOrUrl === "string" ? optionsOrUrl : optionsOrUrl.baseUrl;
|
|
46
47
|
return {
|
|
47
48
|
module: ExGuardModule,
|
|
48
49
|
providers: [
|
|
49
50
|
{
|
|
50
51
|
provide: "EXGUARD_BASE_URL",
|
|
51
|
-
useValue:
|
|
52
|
+
useValue: baseUrl
|
|
52
53
|
}
|
|
53
54
|
],
|
|
54
55
|
exports: ["EXGUARD_BASE_URL"]
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/module.ts","../src/guard.ts"],"sourcesContent":["export { ExGuardModule } from './module.js';\nexport
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/module.ts","../src/guard.ts"],"sourcesContent":["export { ExGuardModule } from './module.js';\nexport { ExGuardPermissionGuard, RequirePermissions, RequireRoles, clearExGuardCache, EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY } from './guard.js';\n","import { Module, Global, DynamicModule } from '@nestjs/common';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n}\n\n@Global()\n@Module({})\nexport class ExGuardModule {\n static forRoot(options: ExGuardModuleOptions): DynamicModule;\n static forRoot(baseUrl: string): DynamicModule;\n static forRoot(optionsOrUrl: ExGuardModuleOptions | string): DynamicModule {\n const baseUrl = typeof optionsOrUrl === 'string' ? optionsOrUrl : optionsOrUrl.baseUrl;\n \n return {\n module: ExGuardModule,\n providers: [\n {\n provide: 'EXGUARD_BASE_URL',\n useValue: baseUrl,\n },\n ],\n exports: ['EXGUARD_BASE_URL'],\n };\n }\n}\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\n\nexport const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';\nexport const EXGUARD_ROLES_KEY = 'exguard_roles';\n\nconst cache = new Map<string, { data: any; timestamp: number }>();\nconst CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\nfunction getCached(key: string): any {\n const entry = cache.get(key);\n if (!entry) return null;\n if (Date.now() - entry.timestamp > CACHE_TTL) {\n cache.delete(key);\n return null;\n }\n return entry.data;\n}\n\nfunction setCache(key: string, data: any): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n@Injectable()\nexport class ExGuardPermissionGuard implements CanActivate {\n constructor(\n @Optional() @Inject('EXGUARD_BASE_URL') private baseUrl: string,\n private reflector: Reflector,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest();\n const token = this.extractToken(request);\n\n if (!token) {\n throw new UnauthorizedException('No token provided');\n }\n\n if (!this.baseUrl) {\n console.warn('[ExGuard] Base URL not configured. Access denied.');\n return false;\n }\n\n // Check cache first\n const cacheKey = `auth:${token}`;\n let authResult = getCached(cacheKey);\n\n if (!authResult) {\n try {\n // Verify token\n const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${token}`,\n },\n body: JSON.stringify({ id_token: token }),\n });\n\n if (!verifyResponse.ok) {\n throw new ForbiddenException('Invalid token');\n }\n\n // Get user access\n const meResponse = await fetch(`${this.baseUrl}/guard/me`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n },\n });\n\n if (!meResponse.ok) {\n throw new ForbiddenException('Failed to get user access');\n }\n\n const meData = await meResponse.json();\n authResult = { allowed: true, user: meData.data || meData };\n \n // Cache the result\n setCache(cacheKey, authResult);\n } catch (error) {\n console.error('[ExGuard] Auth error:', error);\n throw new ForbiddenException('Authentication failed');\n }\n }\n\n if (!authResult.allowed) {\n throw new ForbiddenException(authResult.error || 'Access denied');\n }\n\n if (!authResult.user) {\n throw new ForbiddenException('User not found');\n }\n\n const handler = context.getHandler();\n const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);\n\n if (permMeta) {\n const { permissions, requireAll } = permMeta;\n const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m: any) => m.permissions) : [];\n\n if (requireAll) {\n if (!permissions.every((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n } else {\n if (!permissions.some((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n }\n }\n\n const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);\n\n if (roleMeta) {\n const { roles, requireAll } = roleMeta;\n const userRoles = authResult.user.roles || [];\n\n if (requireAll) {\n if (!roles.every((r: string) => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n } else {\n if (!roles.some((r: string) => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n }\n }\n\n request.user = authResult.user;\n return true;\n }\n\n private extractToken(request: any): string | null {\n const auth = request.headers?.authorization;\n if (auth?.startsWith('Bearer ')) {\n return auth.substring(7);\n }\n return request.headers?.['x-access-token'] || null;\n }\n}\n\nexport function RequirePermissions(permissions: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function RequireRoles(roles: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function clearExGuardCache(): void {\n cache.clear();\n}\n"],"mappings":"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","names":["import_common"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -3,13 +3,10 @@ import { Reflector } from '@nestjs/core';
|
|
|
3
3
|
|
|
4
4
|
interface ExGuardModuleOptions {
|
|
5
5
|
baseUrl: string;
|
|
6
|
-
cache?: {
|
|
7
|
-
enabled?: boolean;
|
|
8
|
-
ttl?: number;
|
|
9
|
-
};
|
|
10
6
|
}
|
|
11
7
|
declare class ExGuardModule {
|
|
12
8
|
static forRoot(options: ExGuardModuleOptions): DynamicModule;
|
|
9
|
+
static forRoot(baseUrl: string): DynamicModule;
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
declare const EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
@@ -25,4 +22,4 @@ declare function RequirePermissions(permissions: string[], requireAll?: boolean)
|
|
|
25
22
|
declare function RequireRoles(roles: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
26
23
|
declare function clearExGuardCache(): void;
|
|
27
24
|
|
|
28
|
-
export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardModule,
|
|
25
|
+
export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardModule, ExGuardPermissionGuard, RequirePermissions, RequireRoles, clearExGuardCache };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,13 +3,10 @@ import { Reflector } from '@nestjs/core';
|
|
|
3
3
|
|
|
4
4
|
interface ExGuardModuleOptions {
|
|
5
5
|
baseUrl: string;
|
|
6
|
-
cache?: {
|
|
7
|
-
enabled?: boolean;
|
|
8
|
-
ttl?: number;
|
|
9
|
-
};
|
|
10
6
|
}
|
|
11
7
|
declare class ExGuardModule {
|
|
12
8
|
static forRoot(options: ExGuardModuleOptions): DynamicModule;
|
|
9
|
+
static forRoot(baseUrl: string): DynamicModule;
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
declare const EXGUARD_PERMISSIONS_KEY = "exguard_permissions";
|
|
@@ -25,4 +22,4 @@ declare function RequirePermissions(permissions: string[], requireAll?: boolean)
|
|
|
25
22
|
declare function RequireRoles(roles: string[], requireAll?: boolean): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
26
23
|
declare function clearExGuardCache(): void;
|
|
27
24
|
|
|
28
|
-
export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardModule,
|
|
25
|
+
export { EXGUARD_PERMISSIONS_KEY, EXGUARD_ROLES_KEY, ExGuardModule, ExGuardPermissionGuard, RequirePermissions, RequireRoles, clearExGuardCache };
|
package/dist/index.js
CHANGED
|
@@ -13,13 +13,14 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
|
|
|
13
13
|
// src/module.ts
|
|
14
14
|
import { Module, Global } from "@nestjs/common";
|
|
15
15
|
var ExGuardModule = class {
|
|
16
|
-
static forRoot(
|
|
16
|
+
static forRoot(optionsOrUrl) {
|
|
17
|
+
const baseUrl = typeof optionsOrUrl === "string" ? optionsOrUrl : optionsOrUrl.baseUrl;
|
|
17
18
|
return {
|
|
18
19
|
module: ExGuardModule,
|
|
19
20
|
providers: [
|
|
20
21
|
{
|
|
21
22
|
provide: "EXGUARD_BASE_URL",
|
|
22
|
-
useValue:
|
|
23
|
+
useValue: baseUrl
|
|
23
24
|
}
|
|
24
25
|
],
|
|
25
26
|
exports: ["EXGUARD_BASE_URL"]
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/module.ts","../src/guard.ts"],"sourcesContent":["import { Module, Global, DynamicModule } from '@nestjs/common';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n
|
|
1
|
+
{"version":3,"sources":["../src/module.ts","../src/guard.ts"],"sourcesContent":["import { Module, Global, DynamicModule } from '@nestjs/common';\n\nexport interface ExGuardModuleOptions {\n baseUrl: string;\n}\n\n@Global()\n@Module({})\nexport class ExGuardModule {\n static forRoot(options: ExGuardModuleOptions): DynamicModule;\n static forRoot(baseUrl: string): DynamicModule;\n static forRoot(optionsOrUrl: ExGuardModuleOptions | string): DynamicModule {\n const baseUrl = typeof optionsOrUrl === 'string' ? optionsOrUrl : optionsOrUrl.baseUrl;\n \n return {\n module: ExGuardModule,\n providers: [\n {\n provide: 'EXGUARD_BASE_URL',\n useValue: baseUrl,\n },\n ],\n exports: ['EXGUARD_BASE_URL'],\n };\n }\n}\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\n\nexport const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';\nexport const EXGUARD_ROLES_KEY = 'exguard_roles';\n\nconst cache = new Map<string, { data: any; timestamp: number }>();\nconst CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\nfunction getCached(key: string): any {\n const entry = cache.get(key);\n if (!entry) return null;\n if (Date.now() - entry.timestamp > CACHE_TTL) {\n cache.delete(key);\n return null;\n }\n return entry.data;\n}\n\nfunction setCache(key: string, data: any): void {\n cache.set(key, { data, timestamp: Date.now() });\n}\n\n@Injectable()\nexport class ExGuardPermissionGuard implements CanActivate {\n constructor(\n @Optional() @Inject('EXGUARD_BASE_URL') private baseUrl: string,\n private reflector: Reflector,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest();\n const token = this.extractToken(request);\n\n if (!token) {\n throw new UnauthorizedException('No token provided');\n }\n\n if (!this.baseUrl) {\n console.warn('[ExGuard] Base URL not configured. Access denied.');\n return false;\n }\n\n // Check cache first\n const cacheKey = `auth:${token}`;\n let authResult = getCached(cacheKey);\n\n if (!authResult) {\n try {\n // Verify token\n const verifyResponse = await fetch(`${this.baseUrl}/guard/verify-token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${token}`,\n },\n body: JSON.stringify({ id_token: token }),\n });\n\n if (!verifyResponse.ok) {\n throw new ForbiddenException('Invalid token');\n }\n\n // Get user access\n const meResponse = await fetch(`${this.baseUrl}/guard/me`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n },\n });\n\n if (!meResponse.ok) {\n throw new ForbiddenException('Failed to get user access');\n }\n\n const meData = await meResponse.json();\n authResult = { allowed: true, user: meData.data || meData };\n \n // Cache the result\n setCache(cacheKey, authResult);\n } catch (error) {\n console.error('[ExGuard] Auth error:', error);\n throw new ForbiddenException('Authentication failed');\n }\n }\n\n if (!authResult.allowed) {\n throw new ForbiddenException(authResult.error || 'Access denied');\n }\n\n if (!authResult.user) {\n throw new ForbiddenException('User not found');\n }\n\n const handler = context.getHandler();\n const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);\n\n if (permMeta) {\n const { permissions, requireAll } = permMeta;\n const userPermissions = authResult.user.modules ? authResult.user.modules.flatMap((m: any) => m.permissions) : [];\n\n if (requireAll) {\n if (!permissions.every((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n } else {\n if (!permissions.some((p: string) => userPermissions.includes(p))) {\n throw new ForbiddenException('Insufficient permissions');\n }\n }\n }\n\n const roleMeta = this.reflector.get(EXGUARD_ROLES_KEY, handler);\n\n if (roleMeta) {\n const { roles, requireAll } = roleMeta;\n const userRoles = authResult.user.roles || [];\n\n if (requireAll) {\n if (!roles.every((r: string) => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n } else {\n if (!roles.some((r: string) => userRoles.includes(r))) {\n throw new ForbiddenException('Insufficient roles');\n }\n }\n }\n\n request.user = authResult.user;\n return true;\n }\n\n private extractToken(request: any): string | null {\n const auth = request.headers?.authorization;\n if (auth?.startsWith('Bearer ')) {\n return auth.substring(7);\n }\n return request.headers?.['x-access-token'] || null;\n }\n}\n\nexport function RequirePermissions(permissions: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function RequireRoles(roles: string[], requireAll = false) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n Reflect.defineMetadata(EXGUARD_ROLES_KEY, { roles, requireAll }, descriptor.value);\n return descriptor;\n };\n}\n\nexport function clearExGuardCache(): void {\n cache.clear();\n}\n"],"mappings":"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","names":[]}
|
package/dist/setup.cjs
CHANGED
|
@@ -8,7 +8,7 @@ export const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';
|
|
|
8
8
|
export const EXGUARD_ROLES_KEY = 'exguard_roles';
|
|
9
9
|
|
|
10
10
|
const cache = new Map();
|
|
11
|
-
const CACHE_TTL = 5 * 60 * 1000;
|
|
11
|
+
const CACHE_TTL = 5 * 60 * 1000;
|
|
12
12
|
|
|
13
13
|
function getCached(key) {
|
|
14
14
|
const entry = cache.get(key);
|
|
@@ -162,13 +162,13 @@ const MODULE_CONTENT = `import { Module, Global, DynamicModule } from '@nestjs/c
|
|
|
162
162
|
@Global()
|
|
163
163
|
@Module({})
|
|
164
164
|
export class ExGuardModule {
|
|
165
|
-
static forRoot(
|
|
165
|
+
static forRoot(baseUrl: string): DynamicModule {
|
|
166
166
|
return {
|
|
167
167
|
module: ExGuardModule,
|
|
168
168
|
providers: [
|
|
169
169
|
{
|
|
170
170
|
provide: 'EXGUARD_BASE_URL',
|
|
171
|
-
useValue:
|
|
171
|
+
useValue: baseUrl,
|
|
172
172
|
},
|
|
173
173
|
],
|
|
174
174
|
exports: ['EXGUARD_BASE_URL'],
|
|
@@ -190,58 +190,101 @@ function writeFile(filePath, content) {
|
|
|
190
190
|
console.log('Created: ' + filePath);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
function updateAppModule(baseUrl) {
|
|
194
|
+
const appModulePath = path.join(process.cwd(), 'src/app.module.ts');
|
|
195
|
+
|
|
196
|
+
if (!fs.existsSync(appModulePath)) {
|
|
197
|
+
console.log('app.module.ts not found. Skipping auto-configure.');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let content = fs.readFileSync(appModulePath, 'utf8');
|
|
202
|
+
|
|
203
|
+
// Check if already configured
|
|
204
|
+
if (content.includes('ExGuardModule')) {
|
|
205
|
+
console.log('ExGuardModule already configured in app.module.ts');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Add import
|
|
210
|
+
const importStatement = "import { ExGuardModule } from './exguard/exguard.module';";
|
|
211
|
+
|
|
212
|
+
if (!content.includes(importStatement)) {
|
|
213
|
+
// Find the last import
|
|
214
|
+
const importMatch = content.match(/^import .+$/gm);
|
|
215
|
+
if (importMatch) {
|
|
216
|
+
const lastImport = importMatch[importMatch.length - 1];
|
|
217
|
+
content = content.replace(lastImport, lastImport + '\n' + importStatement);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add to imports array
|
|
222
|
+
if (content.includes('imports: [')) {
|
|
223
|
+
content = content.replace(
|
|
224
|
+
'imports: [',
|
|
225
|
+
"imports: [\n ExGuardModule.forRoot(process.env.EXGUARD_BASE_URL || '" + baseUrl + "'),"
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fs.writeFileSync(appModulePath, content);
|
|
230
|
+
console.log('Updated app.module.ts with ExGuardModule');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function updateEnvFile() {
|
|
234
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
235
|
+
const envExamplePath = path.join(process.cwd(), '.env.example');
|
|
236
|
+
|
|
237
|
+
const envContent = `# ExGuard Configuration
|
|
238
|
+
EXGUARD_BASE_URL=http://localhost:3000
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
if (!fs.existsSync(envPath) && !fs.existsSync(envExamplePath)) {
|
|
242
|
+
fs.writeFileSync(envExamplePath, envContent);
|
|
243
|
+
console.log('Created .env.example with EXGUARD_BASE_URL');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
193
247
|
function setup() {
|
|
194
248
|
const srcDir = path.join(process.cwd(), 'src');
|
|
195
|
-
const
|
|
249
|
+
const baseUrl = process.env.EXGUARD_BASE_URL || 'http://localhost:3000';
|
|
196
250
|
|
|
197
251
|
if (!fs.existsSync(srcDir)) {
|
|
198
252
|
console.error('Error: Run this command in your NestJS project root');
|
|
199
253
|
process.exit(1);
|
|
200
254
|
}
|
|
201
255
|
|
|
256
|
+
const exguardDir = path.join(srcDir, 'exguard');
|
|
202
257
|
ensureDir(exguardDir);
|
|
203
258
|
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log('===========================================');
|
|
261
|
+
console.log('🔧 Setting up ExGuard...');
|
|
262
|
+
console.log('===========================================');
|
|
263
|
+
|
|
204
264
|
writeFile(path.join(exguardDir, 'exguard.guard.ts'), GUARD_CONTENT);
|
|
205
265
|
writeFile(path.join(exguardDir, 'exguard.module.ts'), MODULE_CONTENT);
|
|
206
266
|
|
|
267
|
+
updateEnvFile();
|
|
268
|
+
updateAppModule(baseUrl);
|
|
269
|
+
|
|
207
270
|
console.log('');
|
|
208
|
-
console.log('===========================================');
|
|
209
271
|
console.log('✅ ExGuard Setup Complete!');
|
|
210
272
|
console.log('===========================================');
|
|
211
273
|
console.log('');
|
|
212
|
-
console.log('
|
|
274
|
+
console.log('Environment:');
|
|
275
|
+
console.log(' EXGUARD_BASE_URL=' + baseUrl);
|
|
213
276
|
console.log('');
|
|
214
|
-
console.log('
|
|
277
|
+
console.log('Usage in controllers:');
|
|
215
278
|
console.log('');
|
|
216
|
-
console.log('
|
|
279
|
+
console.log(' import { ExGuardPermissionGuard, RequirePermissions } from "./exguard/exguard.guard";');
|
|
217
280
|
console.log('');
|
|
218
|
-
console.log('
|
|
219
|
-
console.log('
|
|
220
|
-
console.log('
|
|
221
|
-
console.log('
|
|
222
|
-
console.log('
|
|
223
|
-
console.log(' ],');
|
|
224
|
-
console.log(' })');
|
|
225
|
-
console.log(' export class AppModule {}');
|
|
226
|
-
console.log('');
|
|
227
|
-
console.log('2. Add environment variable:');
|
|
228
|
-
console.log('');
|
|
229
|
-
console.log(' EXGUARD_BASE_URL=https://your-guard-api.com');
|
|
230
|
-
console.log('');
|
|
231
|
-
console.log('3. Use in controllers:');
|
|
232
|
-
console.log('');
|
|
233
|
-
console.log(' import { ExGuardPermissionGuard, RequirePermissions } from "./exguard/exguard.guard";');
|
|
234
|
-
console.log('');
|
|
235
|
-
console.log(' @Controller("items")');
|
|
236
|
-
console.log(' @UseGuards(ExGuardPermissionGuard)');
|
|
237
|
-
console.log(' export class ItemsController {');
|
|
238
|
-
console.log(' @Get() @RequirePermissions(["item:read"]) findAll() {}');
|
|
239
|
-
console.log(' @Post() @RequirePermissions(["item:create"]) create() {}');
|
|
240
|
-
console.log(' }');
|
|
241
|
-
console.log('');
|
|
242
|
-
console.log('===========================================');
|
|
281
|
+
console.log(' @Controller("items")');
|
|
282
|
+
console.log(' @UseGuards(ExGuardPermissionGuard)');
|
|
283
|
+
console.log(' export class ItemsController {');
|
|
284
|
+
console.log(' @Get() @RequirePermissions(["item:read"]) findAll() {}');
|
|
285
|
+
console.log(' }');
|
|
243
286
|
console.log('');
|
|
244
|
-
console.log('💡 Caching
|
|
287
|
+
console.log('💡 Caching enabled (5 min TTL)');
|
|
245
288
|
console.log('===========================================');
|
|
246
289
|
}
|
|
247
290
|
|