@smartsoft001/nestjs 1.1.91 → 2.61.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/.eslintrc.json +30 -0
- package/README.md +345 -4
- package/jest.config.ts +10 -0
- package/package.json +12 -46
- package/project.json +37 -0
- package/src/{index.d.ts → index.ts} +3 -3
- package/src/lib/auth/{index.d.ts → index.ts} +1 -0
- package/src/lib/auth/jwt.strategy.ts +24 -0
- package/src/lib/auth/permission.service.spec.ts +61 -0
- package/src/lib/auth/permission.service.ts +34 -0
- package/src/lib/decorators/user/user.decorator.ts +26 -0
- package/src/lib/filters/execution/exception.filter.ts +89 -0
- package/src/lib/filters/execution/execution.filter.spec.ts +107 -0
- package/src/lib/filters/index.ts +1 -0
- package/src/lib/shared.config.ts +21 -0
- package/src/lib/shared.module.ts +55 -0
- package/tsconfig.json +22 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +15 -0
- package/src/index.js +0 -9
- package/src/index.js.map +0 -1
- package/src/lib/auth/index.js +0 -5
- package/src/lib/auth/index.js.map +0 -1
- package/src/lib/auth/jwt.strategy.d.ts +0 -8
- package/src/lib/auth/jwt.strategy.js +0 -30
- package/src/lib/auth/jwt.strategy.js.map +0 -1
- package/src/lib/auth/permission.service.d.ts +0 -7
- package/src/lib/auth/permission.service.js +0 -28
- package/src/lib/auth/permission.service.js.map +0 -1
- package/src/lib/controllers/health/health.controller.d.ts +0 -10
- package/src/lib/controllers/health/health.controller.js +0 -37
- package/src/lib/controllers/health/health.controller.js.map +0 -1
- package/src/lib/controllers/index.d.ts +0 -2
- package/src/lib/controllers/index.js +0 -8
- package/src/lib/controllers/index.js.map +0 -1
- package/src/lib/decorators/index.js +0 -5
- package/src/lib/decorators/index.js.map +0 -1
- package/src/lib/decorators/user/user.decorator.d.ts +0 -1
- package/src/lib/decorators/user/user.decorator.js +0 -9
- package/src/lib/decorators/user/user.decorator.js.map +0 -1
- package/src/lib/filters/exception/exception.filter.d.ts +0 -4
- package/src/lib/filters/exception/exception.filter.js +0 -49
- package/src/lib/filters/exception/exception.filter.js.map +0 -1
- package/src/lib/filters/index.d.ts +0 -1
- package/src/lib/filters/index.js +0 -5
- package/src/lib/filters/index.js.map +0 -1
- package/src/lib/services/health-db/health-db.service.d.ts +0 -7
- package/src/lib/services/health-db/health-db.service.js +0 -39
- package/src/lib/services/health-db/health-db.service.js.map +0 -1
- package/src/lib/services/index.d.ts +0 -3
- package/src/lib/services/index.js +0 -10
- package/src/lib/services/index.js.map +0 -1
- package/src/lib/shared.config.d.ts +0 -15
- package/src/lib/shared.config.js +0 -11
- package/src/lib/shared.config.js.map +0 -1
- package/src/lib/shared.module.d.ts +0 -14
- package/src/lib/shared.module.js +0 -49
- package/src/lib/shared.module.js.map +0 -1
- package/test-setup.d.ts +0 -0
- package/test-setup.js +0 -1
- package/test-setup.js.map +0 -1
- /package/src/lib/decorators/{index.d.ts → index.ts} +0 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../../.eslintrc.json"],
|
|
3
|
+
"ignorePatterns": ["!**/*"],
|
|
4
|
+
"overrides": [
|
|
5
|
+
{
|
|
6
|
+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
7
|
+
"rules": {}
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"files": ["*.ts", "*.tsx"],
|
|
11
|
+
"rules": {}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"files": ["*.js", "*.jsx"],
|
|
15
|
+
"rules": {}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"files": ["*.json"],
|
|
19
|
+
"parser": "jsonc-eslint-parser",
|
|
20
|
+
"rules": {
|
|
21
|
+
"@nx/dependency-checks": [
|
|
22
|
+
"error",
|
|
23
|
+
{
|
|
24
|
+
"ignoredFiles": ["{projectRoot}/eslint.config.{js,cjs,mjs}"]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
package/README.md
CHANGED
|
@@ -1,7 +1,348 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 📦 @smartsoft001/nestjs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
 
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 🚀 Usage
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @smartsoft001/nestjs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## ⚙️ Configuration
|
|
12
|
+
|
|
13
|
+
The library uses the `SharedConfig` class for authentication and permissions configuration:
|
|
14
|
+
|
|
15
|
+
### Token Configuration
|
|
16
|
+
|
|
17
|
+
| Field | Type | Description |
|
|
18
|
+
| -------------------------------- | -------- | ---------------------------------------- |
|
|
19
|
+
| `tokenConfig.secretOrPrivateKey` | `string` | Secret or private key for signing tokens |
|
|
20
|
+
| `tokenConfig.expiredIn` | `number` | Token expiration time in seconds |
|
|
21
|
+
|
|
22
|
+
### Permissions Configuration
|
|
23
|
+
|
|
24
|
+
| Field | Type | Description |
|
|
25
|
+
| -------------------- | --------------- | ----------------------------------------- |
|
|
26
|
+
| `permissions.create` | `Array<string>` | List of permissions for create operations |
|
|
27
|
+
| `permissions.read` | `Array<string>` | List of permissions for read operations |
|
|
28
|
+
| `permissions.update` | `Array<string>` | List of permissions for update operations |
|
|
29
|
+
| `permissions.delete` | `Array<string>` | List of permissions for delete operations |
|
|
30
|
+
| `permissions.[key]` | `Array<string>` | Any additional custom permissions |
|
|
31
|
+
|
|
32
|
+
## Types
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
export type PermissionType = 'create' | 'read' | 'update' | 'delete' | string;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
# Modules
|
|
39
|
+
|
|
40
|
+
## SharedModule
|
|
41
|
+
|
|
42
|
+
The `SharedModule` is a dynamic NestJS module that provides shared configuration and services across the application. It offers two static methods for configuration: `forFeature()` and `forRoot()`.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### forFeature() Method
|
|
47
|
+
|
|
48
|
+
The `forFeature()` method configures the module's core functionalities:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
static forFeature(config: SharedConfig): DynamicModule
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Parameters:
|
|
55
|
+
|
|
56
|
+
- `config`: An object of type `SharedConfig` containing the module's configuration.
|
|
57
|
+
|
|
58
|
+
#### Returns:
|
|
59
|
+
|
|
60
|
+
A `DynamicModule` object with the following elements:
|
|
61
|
+
|
|
62
|
+
- Providers: `SharedConfig`, `JwtStrategy`, `PermissionService`
|
|
63
|
+
- Exports: `SharedConfig`, `PermissionService`, `JwtStrategy`
|
|
64
|
+
|
|
65
|
+
#### Example Usage:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { SharedModule, SharedConfig } from '@smartsoft001/nestjs';
|
|
69
|
+
|
|
70
|
+
@Module({
|
|
71
|
+
imports: [
|
|
72
|
+
SharedModule.forFeature({
|
|
73
|
+
tokenConfig: {
|
|
74
|
+
secretOrPrivateKey: 'secret_key',
|
|
75
|
+
expiredIn: 3600,
|
|
76
|
+
},
|
|
77
|
+
permissions: {
|
|
78
|
+
create: ['admin'],
|
|
79
|
+
read: ['user', 'admin'],
|
|
80
|
+
update: ['admin'],
|
|
81
|
+
delete: ['admin'],
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
],
|
|
85
|
+
})
|
|
86
|
+
export class AppModule {}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### forRoot() Method
|
|
92
|
+
|
|
93
|
+
The `forRoot()` method extends the `forFeature()` configuration by adding database settings:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
static forRoot(config: SharedConfig & {
|
|
97
|
+
db: {
|
|
98
|
+
host: string;
|
|
99
|
+
port: number;
|
|
100
|
+
database: string;
|
|
101
|
+
username?: string;
|
|
102
|
+
password?: string;
|
|
103
|
+
};
|
|
104
|
+
}): DynamicModule
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Parameters:
|
|
108
|
+
|
|
109
|
+
- `config`: An extended `SharedConfig` object that includes additional database settings.
|
|
110
|
+
|
|
111
|
+
#### Returns:
|
|
112
|
+
|
|
113
|
+
A `DynamicModule` object that imports and exports the `forFeature()` configuration.
|
|
114
|
+
|
|
115
|
+
#### Example Usage:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { SharedModule, SharedConfig } from '@smartsoft001/nestjs';
|
|
119
|
+
|
|
120
|
+
@Module({
|
|
121
|
+
imports: [
|
|
122
|
+
SharedModule.forRoot({
|
|
123
|
+
tokenConfig: {
|
|
124
|
+
secretOrPrivateKey: 'secret_key',
|
|
125
|
+
expiredIn: 3600,
|
|
126
|
+
},
|
|
127
|
+
permissions: {
|
|
128
|
+
create: ['admin'],
|
|
129
|
+
read: ['user', 'admin'],
|
|
130
|
+
update: ['admin'],
|
|
131
|
+
delete: ['admin'],
|
|
132
|
+
},
|
|
133
|
+
db: {
|
|
134
|
+
host: 'localhost',
|
|
135
|
+
port: 5432,
|
|
136
|
+
database: 'my_database',
|
|
137
|
+
username: 'user',
|
|
138
|
+
password: 'password',
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
],
|
|
142
|
+
})
|
|
143
|
+
export class AppModule {}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### Summary
|
|
149
|
+
|
|
150
|
+
The `SharedModule` provides flexible configuration for various use cases, enabling easy sharing of authorization, permissions, and database settings across the entire NestJS application.
|
|
151
|
+
|
|
152
|
+
# Decorators
|
|
153
|
+
|
|
154
|
+
## @User Decorator
|
|
155
|
+
|
|
156
|
+
The `@User` decorator is a custom parameter decorator in NestJS that simplifies retrieving the user object from an HTTP request.
|
|
157
|
+
|
|
158
|
+
### Functionality
|
|
159
|
+
|
|
160
|
+
The `@User` decorator performs the following actions:
|
|
161
|
+
|
|
162
|
+
- Extracts the request object (`req`) from the execution context.
|
|
163
|
+
- Returns the `req.user` value, which typically contains information about the authenticated user.
|
|
164
|
+
|
|
165
|
+
### Usage
|
|
166
|
+
|
|
167
|
+
The `@User` decorator can be used in NestJS controllers to easily access user data:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { Controller, Get } from '@nestjs/common';
|
|
171
|
+
|
|
172
|
+
import { IUser } from '@smartsoft001/users';
|
|
173
|
+
|
|
174
|
+
import { User } from './user.decorator';
|
|
175
|
+
|
|
176
|
+
@Controller('profile')
|
|
177
|
+
export class ProfileController {
|
|
178
|
+
@Get()
|
|
179
|
+
getProfile(@User() user: IUser) {
|
|
180
|
+
return user;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
# Auth
|
|
186
|
+
|
|
187
|
+
After registering the authentication module, you can use `JwtStrategy` and `PermissionService` as follows:
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Using JwtStrategy
|
|
192
|
+
|
|
193
|
+
`JwtStrategy` is automatically used by `AuthGuard('jwt')`. You don't need to call it directly. Instead, use it indirectly via the `@UseGuards()` decorator:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { Controller, Get, UseGuards } from '@nestjs/common';
|
|
197
|
+
import { AuthGuard } from '@nestjs/passport';
|
|
198
|
+
|
|
199
|
+
@Controller('secure')
|
|
200
|
+
@UseGuards(AuthGuard('jwt'))
|
|
201
|
+
export class SecureController {
|
|
202
|
+
@Get()
|
|
203
|
+
getSecureData() {
|
|
204
|
+
return 'This is protected content';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Using PermissionService
|
|
212
|
+
|
|
213
|
+
`PermissionService` can be injected into controllers or services and used directly. Here are the main usage patterns:
|
|
214
|
+
|
|
215
|
+
### 1. **Injecting the Service**
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { Injectable } from '@nestjs/common';
|
|
219
|
+
import { PermissionService } from './permission.service';
|
|
220
|
+
|
|
221
|
+
@Injectable()
|
|
222
|
+
export class SomeService {
|
|
223
|
+
constructor(private permissionService: PermissionService) {}
|
|
224
|
+
|
|
225
|
+
// ...
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### 2. **Validating Permissions**
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { User } from './user.decorator';
|
|
235
|
+
|
|
236
|
+
@Get('admin-panel')
|
|
237
|
+
adminPanel(@User() user) {
|
|
238
|
+
this.permissionService.valid('admin-panel', user);
|
|
239
|
+
return 'Admin Panel';
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### 3. **Handling Errors**
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
try {
|
|
249
|
+
this.permissionService.valid('some-action', user);
|
|
250
|
+
// Perform the protected action
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error instanceof DomainForbiddenError) {
|
|
253
|
+
// Handle lack of permissions
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### 4. **Using in a Custom Guard**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
265
|
+
import { PermissionService } from './permission.service';
|
|
266
|
+
|
|
267
|
+
@Injectable()
|
|
268
|
+
export class CustomPermissionGuard implements CanActivate {
|
|
269
|
+
constructor(private permissionService: PermissionService) {}
|
|
270
|
+
|
|
271
|
+
canActivate(context: ExecutionContext): boolean {
|
|
272
|
+
const request = context.switchToHttp().getRequest();
|
|
273
|
+
const user = request.user;
|
|
274
|
+
const requiredPermission = 'some-permission';
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
this.permissionService.valid(requiredPermission, user);
|
|
278
|
+
return true;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
### 5. **Checking Multiple Permissions**
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
multipleChecks(@User() user) {
|
|
292
|
+
this.permissionService.valid('permission1', user);
|
|
293
|
+
this.permissionService.valid('permission2', user);
|
|
294
|
+
// If both checks pass, perform the action
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### Notes
|
|
301
|
+
|
|
302
|
+
- `PermissionService` throws a `DomainForbiddenError` if the required permissions are missing. Always be prepared to handle this exception or let it be caught by a global exception filter.
|
|
303
|
+
|
|
304
|
+
# Filters
|
|
305
|
+
|
|
306
|
+
## AppExceptionFilter Overview
|
|
307
|
+
|
|
308
|
+
The `AppExceptionFilter` class implements the `ExceptionFilter` interface and is used for handling exceptions in a NestJS application.
|
|
309
|
+
|
|
310
|
+
### Functionality
|
|
311
|
+
|
|
312
|
+
The filter intercepts all exceptions and processes them as follows:
|
|
313
|
+
|
|
314
|
+
- For `HttpException`, it uses the original HTTP status and message.
|
|
315
|
+
- For `DomainValidationError`, it sets a 400 (Bad Request) status.
|
|
316
|
+
- For `DomainForbiddenError`, it sets a 403 (Forbidden) status.
|
|
317
|
+
- For all other exceptions, it sets a 500 (Internal Server Error) status.
|
|
318
|
+
|
|
319
|
+
### Logging
|
|
320
|
+
|
|
321
|
+
The filter logs exception details (stack trace or message) using `Logger.error()`.
|
|
322
|
+
|
|
323
|
+
### Response
|
|
324
|
+
|
|
325
|
+
The filter returns an HTTP response with the appropriate status code and optionally a JSON object containing error details.
|
|
326
|
+
|
|
327
|
+
### Usage
|
|
328
|
+
|
|
329
|
+
To use this filter globally in a NestJS application, register it in the main module:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { Module } from '@nestjs/common';
|
|
333
|
+
import { APP_FILTER } from '@nestjs/core';
|
|
334
|
+
|
|
335
|
+
import { AppExceptionFilter } from '@smartsoft001/nestjs';
|
|
336
|
+
|
|
337
|
+
@Module({
|
|
338
|
+
providers: [
|
|
339
|
+
{
|
|
340
|
+
provide: APP_FILTER,
|
|
341
|
+
useClass: AppExceptionFilter,
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
})
|
|
345
|
+
export class AppModule {}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
This filter ensures consistent error handling throughout the application, mapping different exception types to appropriate HTTP status codes and formatting responses accordingly.
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
displayName: 'nestjs',
|
|
3
|
+
preset: '../../../jest.preset.js',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
transform: {
|
|
6
|
+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
|
7
|
+
},
|
|
8
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
9
|
+
coverageDirectory: '../../../coverage/packages/shared/nestjs',
|
|
10
|
+
};
|
package/package.json
CHANGED
|
@@ -1,50 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartsoft001/nestjs",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/emiljuchnikowski/smartsoft.git"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [
|
|
10
|
-
"smartsoft",
|
|
11
|
-
"oauth",
|
|
12
|
-
"nestjs"
|
|
13
|
-
],
|
|
14
|
-
"author": "Emil Juchnikowski",
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/emiljuchnikowski/smartsoft/issues"
|
|
18
|
-
},
|
|
19
|
-
"homepage": "https://github.com/emiljuchnikowski/smartsoft#readme",
|
|
20
|
-
"dependencies": {
|
|
21
|
-
"@angular/common": "16.1.3",
|
|
22
|
-
"@angular/core": "16.1.3",
|
|
23
|
-
"@angular/platform-browser-dynamic": "16.1.3",
|
|
24
|
-
"@nestjs/axios": "3.0.0",
|
|
25
|
-
"@nestjs/common": "^10.0.5",
|
|
26
|
-
"@nestjs/passport": "^10.0.0",
|
|
27
|
-
"@nestjs/terminus": "^10.0.1",
|
|
28
|
-
"@nestjs/typeorm": "^10.0.0",
|
|
29
|
-
"flatted": "^3.2.4",
|
|
30
|
-
"guid-typescript": "1.0.9",
|
|
31
|
-
"lodash": "^4.17.20",
|
|
32
|
-
"md5": "2.3.0",
|
|
33
|
-
"mongodb": "5.6.0",
|
|
34
|
-
"passport-jwt": "4.0.0",
|
|
35
|
-
"reflect-metadata": "0.1.13",
|
|
36
|
-
"rxjs": "7.8.1"
|
|
37
|
-
},
|
|
38
|
-
"peerDependencies": {
|
|
39
|
-
"@smartsoft001/crud-shell-dtos": "1.1.91",
|
|
40
|
-
"@smartsoft001/domain-core": "1.1.91",
|
|
41
|
-
"@smartsoft001/models": "1.1.91",
|
|
42
|
-
"@smartsoft001/mongo": "1.1.91",
|
|
43
|
-
"@smartsoft001/users": "1.1.91",
|
|
44
|
-
"@smartsoft001/utils": "1.1.91",
|
|
45
|
-
"util": "0.12.5",
|
|
46
|
-
"tslib": "2.5.3"
|
|
47
|
-
},
|
|
3
|
+
"version": "2.61.0",
|
|
4
|
+
"type": "commonjs",
|
|
48
5
|
"main": "./src/index.js",
|
|
49
|
-
"types": "./src/index.d.ts"
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"tslib": "^2.3.0",
|
|
9
|
+
"passport-jwt": "^4.0.1",
|
|
10
|
+
"@nestjs/passport": "^11.0.5",
|
|
11
|
+
"@nestjs/common": "^11.0.7",
|
|
12
|
+
"@nestjs/testing": "^11.0.7",
|
|
13
|
+
"@smartsoft001/domain-core": "2.61.0",
|
|
14
|
+
"@smartsoft001/users": "2.61.0"
|
|
15
|
+
}
|
|
50
16
|
}
|
package/project.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nestjs",
|
|
3
|
+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/shared/nestjs/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@nx/js:tsc",
|
|
10
|
+
"outputs": ["{options.outputPath}"],
|
|
11
|
+
"options": {
|
|
12
|
+
"outputPath": "dist/packages/shared/nestjs",
|
|
13
|
+
"main": "packages/shared/nestjs/src/index.ts",
|
|
14
|
+
"tsConfig": "packages/shared/nestjs/tsconfig.lib.json",
|
|
15
|
+
"assets": ["packages/shared/nestjs/*.md"]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"lint": {
|
|
19
|
+
"executor": "@nx/eslint:lint"
|
|
20
|
+
},
|
|
21
|
+
"test": {
|
|
22
|
+
"executor": "@nx/jest:jest",
|
|
23
|
+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
24
|
+
"options": {
|
|
25
|
+
"jestConfig": "packages/shared/nestjs/jest.config.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"deploy": {
|
|
29
|
+
"executor": "ngx-deploy-npm:deploy",
|
|
30
|
+
"options": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"distFolderPath": "dist/packages/shared/nestjs"
|
|
33
|
+
},
|
|
34
|
+
"dependsOn": ["build"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from "./lib/filters";
|
|
2
|
-
export * from './lib/decorators';
|
|
3
|
-
export * from './lib/shared.module';
|
|
4
1
|
export * from './lib/shared.config';
|
|
2
|
+
export * from './lib/shared.module';
|
|
3
|
+
export * from './lib/filters';
|
|
4
|
+
export * from './lib/decorators';
|
|
5
5
|
export * from './lib/auth';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { PassportStrategy } from '@nestjs/passport';
|
|
3
|
+
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
4
|
+
|
|
5
|
+
import { SharedConfig } from '../shared.config';
|
|
6
|
+
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
9
|
+
constructor(private config: SharedConfig) {
|
|
10
|
+
super({
|
|
11
|
+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
12
|
+
ignoreExpiration: false,
|
|
13
|
+
secretOrKey: config.tokenConfig?.secretOrPrivateKey as string,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async validate(payload: any) {
|
|
18
|
+
return {
|
|
19
|
+
...payload,
|
|
20
|
+
permissions: payload.permissions,
|
|
21
|
+
username: payload.sub,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { DomainForbiddenError } from '@smartsoft001/domain-core';
|
|
3
|
+
import { IUser } from '@smartsoft001/users';
|
|
4
|
+
|
|
5
|
+
import { PermissionService } from './permission.service';
|
|
6
|
+
import { SharedConfig } from '../shared.config';
|
|
7
|
+
|
|
8
|
+
describe('shared-nestjs: PermissionService', () => {
|
|
9
|
+
let service: PermissionService;
|
|
10
|
+
let config: SharedConfig;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
config = {
|
|
14
|
+
permissions: {
|
|
15
|
+
create: ['admin', 'editor'],
|
|
16
|
+
read: ['admin', 'editor', 'viewer'],
|
|
17
|
+
update: ['admin', 'editor'],
|
|
18
|
+
delete: ['admin'],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
23
|
+
providers: [
|
|
24
|
+
PermissionService,
|
|
25
|
+
{ provide: SharedConfig, useValue: config },
|
|
26
|
+
],
|
|
27
|
+
}).compile();
|
|
28
|
+
|
|
29
|
+
service = module.get<PermissionService>(PermissionService);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should be defined', () => {
|
|
33
|
+
expect(service).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should allow access if user has the required permission', () => {
|
|
37
|
+
const user: IUser = { username: 'testuser', permissions: ['admin'] };
|
|
38
|
+
expect(() => service.valid('create', user)).not.toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should throw DomainForbiddenError if user does not have the required permission', () => {
|
|
42
|
+
const user: IUser = { username: 'testuser', permissions: ['viewer'] };
|
|
43
|
+
expect(() => service.valid('create', user)).toThrow(DomainForbiddenError);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should not throw if permissions config is not defined', () => {
|
|
47
|
+
config.permissions = undefined;
|
|
48
|
+
const user: IUser = { username: 'testuser', permissions: ['admin'] };
|
|
49
|
+
expect(() => service.valid('create', user)).not.toThrow();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should not throw if permission type config is not defined', () => {
|
|
53
|
+
const user: IUser = { username: 'testuser', permissions: ['admin'] };
|
|
54
|
+
expect(() => service.valid('nonexistent', user)).not.toThrow();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should throw DomainForbiddenError if user permissions are not defined', () => {
|
|
58
|
+
const user: IUser = { username: 'testuser', permissions: [] };
|
|
59
|
+
expect(() => service.valid('create', user)).toThrow(DomainForbiddenError);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { DomainForbiddenError } from '@smartsoft001/domain-core';
|
|
3
|
+
import { IUser } from '@smartsoft001/users';
|
|
4
|
+
|
|
5
|
+
import { PermissionType, SharedConfig } from '../shared.config';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Service to handle permission validation for users.
|
|
9
|
+
*
|
|
10
|
+
* @class PermissionService
|
|
11
|
+
*/
|
|
12
|
+
@Injectable()
|
|
13
|
+
export class PermissionService {
|
|
14
|
+
constructor(private config: SharedConfig) {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validates if the user has the required permissions for a given type.
|
|
18
|
+
* @param {PermissionType} type - The type of permission to validate.
|
|
19
|
+
* @param {IUser} user - The user object containing permissions.
|
|
20
|
+
* @throws {DomainForbiddenError} If the user does not have the required permissions.
|
|
21
|
+
*/
|
|
22
|
+
valid(type: PermissionType, user: IUser): void {
|
|
23
|
+
if (!this.config.permissions) return;
|
|
24
|
+
|
|
25
|
+
const typeConfig = this.config.permissions[type];
|
|
26
|
+
|
|
27
|
+
if (!typeConfig) return;
|
|
28
|
+
|
|
29
|
+
if (!user.permissions) throw new DomainForbiddenError(`Context forbidden`);
|
|
30
|
+
|
|
31
|
+
if (!typeConfig.some((tc) => user.permissions.some((up) => tc === up)))
|
|
32
|
+
throw new DomainForbiddenError(`Context forbidden`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom decorator to extract the user object from the request.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { Controller, Get } from '@nestjs/common';
|
|
9
|
+
*
|
|
10
|
+
* import { IUser } from '@smartsoft001/users';
|
|
11
|
+
*
|
|
12
|
+
* import { User } from './user.decorator';
|
|
13
|
+
*
|
|
14
|
+
* @Controller('profile')
|
|
15
|
+
* export class ProfileController {
|
|
16
|
+
* @Get()
|
|
17
|
+
* getProfile(@User() user: IUser) {
|
|
18
|
+
* return user;
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const User = createParamDecorator((_, context: ExecutionContext) => {
|
|
24
|
+
const [req] = context.getArgs();
|
|
25
|
+
return req.user;
|
|
26
|
+
});
|