@flusys/nestjs-shared 1.0.0-beta → 1.0.0-rc
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 +56 -110
- package/cjs/classes/api-controller.class.js +9 -24
- package/cjs/classes/index.js +1 -0
- package/cjs/constants/index.js +14 -0
- package/cjs/constants/permissions.js +174 -0
- package/cjs/decorators/api-response.decorator.js +1 -1
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/sanitize-html.decorator.js +36 -0
- package/cjs/dtos/filter-and-pagination.dto.js +24 -34
- package/cjs/dtos/pagination.dto.js +4 -8
- package/cjs/dtos/response-payload.dto.js +0 -41
- package/cjs/entities/identity.js +4 -4
- package/cjs/entities/user-root.js +13 -14
- package/cjs/guards/permission.guard.js +39 -94
- package/cjs/interceptors/index.js +1 -0
- package/cjs/interceptors/set-create-by-on-body.interceptor.js +2 -30
- package/cjs/interceptors/set-delete-by-on-body.interceptor.js +2 -30
- package/cjs/interceptors/set-update-by-on-body.interceptor.js +2 -30
- package/cjs/interceptors/set-user-field-on-body.interceptor.js +43 -0
- package/cjs/interceptors/slug.interceptor.js +30 -9
- package/cjs/interfaces/datasource.interface.js +4 -0
- package/cjs/interfaces/index.js +2 -1
- package/cjs/interfaces/module-config.interface.js +4 -0
- package/cjs/modules/cache/cache.module.js +3 -3
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +30 -110
- package/cjs/modules/utils/utils.service.js +63 -145
- package/cjs/utils/error-handler.util.js +91 -13
- package/cjs/utils/html-sanitizer.util.js +74 -0
- package/cjs/utils/index.js +2 -0
- package/cjs/utils/query-helpers.util.js +53 -0
- package/classes/api-controller.class.d.ts +5 -5
- package/classes/api-service.class.d.ts +5 -5
- package/classes/index.d.ts +1 -0
- package/classes/request-scoped-api.service.d.ts +3 -2
- package/constants/index.d.ts +1 -0
- package/constants/permissions.d.ts +167 -0
- package/decorators/index.d.ts +1 -0
- package/decorators/sanitize-html.decorator.d.ts +2 -0
- package/dtos/filter-and-pagination.dto.d.ts +0 -2
- package/dtos/response-payload.dto.d.ts +0 -7
- package/fesm/classes/api-controller.class.js +9 -24
- package/fesm/classes/index.js +2 -0
- package/fesm/constants/index.js +2 -0
- package/fesm/constants/permissions.js +121 -0
- package/fesm/decorators/api-response.decorator.js +1 -1
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/sanitize-html.decorator.js +45 -0
- package/fesm/dtos/filter-and-pagination.dto.js +26 -47
- package/fesm/dtos/pagination.dto.js +4 -8
- package/fesm/dtos/response-payload.dto.js +0 -38
- package/fesm/entities/identity.js +4 -4
- package/fesm/entities/user-root.js +13 -14
- package/fesm/guards/permission.guard.js +39 -94
- package/fesm/interceptors/index.js +1 -0
- package/fesm/interceptors/set-create-by-on-body.interceptor.js +4 -30
- package/fesm/interceptors/set-delete-by-on-body.interceptor.js +4 -30
- package/fesm/interceptors/set-update-by-on-body.interceptor.js +4 -30
- package/fesm/interceptors/set-user-field-on-body.interceptor.js +36 -0
- package/fesm/interceptors/slug.interceptor.js +31 -10
- package/fesm/interfaces/datasource.interface.js +20 -0
- package/fesm/interfaces/index.js +2 -1
- package/fesm/interfaces/module-config.interface.js +5 -0
- package/fesm/modules/cache/cache.module.js +2 -2
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +30 -110
- package/fesm/modules/utils/utils.service.js +50 -143
- package/fesm/utils/error-handler.util.js +93 -14
- package/fesm/utils/html-sanitizer.util.js +82 -0
- package/fesm/utils/index.js +2 -0
- package/fesm/utils/query-helpers.util.js +78 -0
- package/interceptors/index.d.ts +1 -0
- package/interceptors/set-create-by-on-body.interceptor.d.ts +1 -5
- package/interceptors/set-delete-by-on-body.interceptor.d.ts +1 -5
- package/interceptors/set-update-by-on-body.interceptor.d.ts +1 -5
- package/interceptors/set-user-field-on-body.interceptor.d.ts +2 -0
- package/interceptors/slug.interceptor.d.ts +2 -1
- package/interfaces/api.interface.d.ts +2 -2
- package/interfaces/datasource.interface.d.ts +5 -0
- package/interfaces/identity.interface.d.ts +4 -4
- package/interfaces/index.d.ts +2 -1
- package/interfaces/module-config.interface.d.ts +6 -0
- package/interfaces/permission.interface.d.ts +0 -1
- package/modules/utils/utils.service.d.ts +10 -4
- package/package.json +4 -4
- package/utils/error-handler.util.d.ts +23 -13
- package/utils/html-sanitizer.util.d.ts +3 -0
- package/utils/index.d.ts +2 -0
- package/utils/query-helpers.util.d.ts +16 -0
- package/cjs/interfaces/base-query.interface.js +0 -6
- package/fesm/interfaces/base-query.interface.js +0 -3
- package/interfaces/base-query.interface.d.ts +0 -7
|
@@ -49,7 +49,6 @@ let FilterAndPaginationDto = class FilterAndPaginationDto {
|
|
|
49
49
|
_define_property(this, "sort", void 0);
|
|
50
50
|
_define_property(this, "select", void 0);
|
|
51
51
|
_define_property(this, "withDeleted", void 0);
|
|
52
|
-
_define_property(this, "extraKey", void 0);
|
|
53
52
|
}
|
|
54
53
|
};
|
|
55
54
|
_ts_decorate([
|
|
@@ -97,7 +96,7 @@ _ts_decorate([
|
|
|
97
96
|
type: [
|
|
98
97
|
String
|
|
99
98
|
],
|
|
100
|
-
description: 'Fields to return. If empty, returns all fields.',
|
|
99
|
+
description: 'Fields to return. Must be valid field names (alphanumeric, underscores only). If empty, returns all fields.',
|
|
101
100
|
example: [
|
|
102
101
|
'id',
|
|
103
102
|
'name',
|
|
@@ -107,6 +106,17 @@ _ts_decorate([
|
|
|
107
106
|
}),
|
|
108
107
|
(0, _classvalidator.IsOptional)(),
|
|
109
108
|
(0, _classvalidator.IsArray)(),
|
|
109
|
+
(0, _classvalidator.IsString)({
|
|
110
|
+
each: true
|
|
111
|
+
}),
|
|
112
|
+
(0, _classvalidator.Matches)(/^[a-zA-Z_][a-zA-Z0-9_]*$/, {
|
|
113
|
+
each: true,
|
|
114
|
+
message: 'Select fields must be valid identifiers (alphanumeric and underscores only)'
|
|
115
|
+
}),
|
|
116
|
+
(0, _classvalidator.MaxLength)(64, {
|
|
117
|
+
each: true,
|
|
118
|
+
message: 'Field names must be 64 characters or less'
|
|
119
|
+
}),
|
|
110
120
|
_ts_metadata("design:type", Array)
|
|
111
121
|
], FilterAndPaginationDto.prototype, "select", void 0);
|
|
112
122
|
_ts_decorate([
|
|
@@ -119,25 +129,9 @@ _ts_decorate([
|
|
|
119
129
|
(0, _classvalidator.IsBoolean)(),
|
|
120
130
|
_ts_metadata("design:type", Boolean)
|
|
121
131
|
], FilterAndPaginationDto.prototype, "withDeleted", void 0);
|
|
122
|
-
_ts_decorate([
|
|
123
|
-
(0, _swagger.ApiPropertyOptional)({
|
|
124
|
-
type: [
|
|
125
|
-
String
|
|
126
|
-
],
|
|
127
|
-
description: 'Additional relation keys to include',
|
|
128
|
-
example: [
|
|
129
|
-
'category',
|
|
130
|
-
'createdBy'
|
|
131
|
-
]
|
|
132
|
-
}),
|
|
133
|
-
(0, _classvalidator.IsOptional)(),
|
|
134
|
-
(0, _classvalidator.IsArray)(),
|
|
135
|
-
_ts_metadata("design:type", Array)
|
|
136
|
-
], FilterAndPaginationDto.prototype, "extraKey", void 0);
|
|
137
132
|
let GetByIdBodyDto = class GetByIdBodyDto {
|
|
138
133
|
constructor(){
|
|
139
134
|
_define_property(this, "select", void 0);
|
|
140
|
-
_define_property(this, "extraKey", void 0);
|
|
141
135
|
}
|
|
142
136
|
};
|
|
143
137
|
_ts_decorate([
|
|
@@ -145,7 +139,7 @@ _ts_decorate([
|
|
|
145
139
|
type: [
|
|
146
140
|
String
|
|
147
141
|
],
|
|
148
|
-
description: 'Fields to return. If empty, returns all fields.',
|
|
142
|
+
description: 'Fields to return. Must be valid field names (alphanumeric, underscores only). If empty, returns all fields.',
|
|
149
143
|
example: [
|
|
150
144
|
'id',
|
|
151
145
|
'name',
|
|
@@ -155,20 +149,16 @@ _ts_decorate([
|
|
|
155
149
|
}),
|
|
156
150
|
(0, _classvalidator.IsOptional)(),
|
|
157
151
|
(0, _classvalidator.IsArray)(),
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
(0,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
'createdBy'
|
|
169
|
-
]
|
|
152
|
+
(0, _classvalidator.IsString)({
|
|
153
|
+
each: true
|
|
154
|
+
}),
|
|
155
|
+
(0, _classvalidator.Matches)(/^[a-zA-Z_][a-zA-Z0-9_]*$/, {
|
|
156
|
+
each: true,
|
|
157
|
+
message: 'Select fields must be valid identifiers (alphanumeric and underscores only)'
|
|
158
|
+
}),
|
|
159
|
+
(0, _classvalidator.MaxLength)(64, {
|
|
160
|
+
each: true,
|
|
161
|
+
message: 'Field names must be 64 characters or less'
|
|
170
162
|
}),
|
|
171
|
-
(0, _classvalidator.IsOptional)(),
|
|
172
|
-
(0, _classvalidator.IsArray)(),
|
|
173
163
|
_ts_metadata("design:type", Array)
|
|
174
|
-
], GetByIdBodyDto.prototype, "
|
|
164
|
+
], GetByIdBodyDto.prototype, "select", void 0);
|
|
@@ -35,17 +35,13 @@ function _ts_metadata(k, v) {
|
|
|
35
35
|
}
|
|
36
36
|
let PaginationDto = class PaginationDto {
|
|
37
37
|
constructor(){
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*/ _define_property(this, "pageSize", 10);
|
|
41
|
-
/**
|
|
42
|
-
* Zero-based page index. Defaults to 0 when not provided or invalid.
|
|
43
|
-
*/ _define_property(this, "currentPage", 0);
|
|
38
|
+
_define_property(this, "pageSize", 10);
|
|
39
|
+
_define_property(this, "currentPage", 0);
|
|
44
40
|
}
|
|
45
41
|
};
|
|
46
42
|
_ts_decorate([
|
|
47
43
|
(0, _swagger.ApiPropertyOptional)({
|
|
48
|
-
description: 'Number of items per page
|
|
44
|
+
description: 'Number of items per page (default: 10)',
|
|
49
45
|
example: 10
|
|
50
46
|
}),
|
|
51
47
|
(0, _classvalidator.IsOptional)(),
|
|
@@ -58,7 +54,7 @@ _ts_decorate([
|
|
|
58
54
|
], PaginationDto.prototype, "pageSize", void 0);
|
|
59
55
|
_ts_decorate([
|
|
60
56
|
(0, _swagger.ApiPropertyOptional)({
|
|
61
|
-
description: 'Zero-based page index
|
|
57
|
+
description: 'Zero-based page index (default: 0)',
|
|
62
58
|
example: 0
|
|
63
59
|
}),
|
|
64
60
|
(0, _classvalidator.IsOptional)(),
|
|
@@ -30,9 +30,6 @@ _export(exports, {
|
|
|
30
30
|
get RequestMetaDto () {
|
|
31
31
|
return RequestMetaDto;
|
|
32
32
|
},
|
|
33
|
-
get ResponsePayloadDto () {
|
|
34
|
-
return ResponsePayloadDto;
|
|
35
|
-
},
|
|
36
33
|
get SingleResponseDto () {
|
|
37
34
|
return SingleResponseDto;
|
|
38
35
|
},
|
|
@@ -373,41 +370,3 @@ _ts_decorate([
|
|
|
373
370
|
ErrorResponseDto = _ts_decorate([
|
|
374
371
|
(0, _swagger.ApiExtraModels)()
|
|
375
372
|
], ErrorResponseDto);
|
|
376
|
-
let ResponsePayloadDto = class ResponsePayloadDto {
|
|
377
|
-
constructor(){
|
|
378
|
-
_define_property(this, "success", void 0);
|
|
379
|
-
_define_property(this, "message", void 0);
|
|
380
|
-
_define_property(this, "data", void 0);
|
|
381
|
-
_define_property(this, "meta", void 0);
|
|
382
|
-
_define_property(this, "_meta", void 0);
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
_ts_decorate([
|
|
386
|
-
(0, _swagger.ApiProperty)({
|
|
387
|
-
example: true
|
|
388
|
-
}),
|
|
389
|
-
_ts_metadata("design:type", Boolean)
|
|
390
|
-
], ResponsePayloadDto.prototype, "success", void 0);
|
|
391
|
-
_ts_decorate([
|
|
392
|
-
(0, _swagger.ApiProperty)({
|
|
393
|
-
example: 'Operation successful'
|
|
394
|
-
}),
|
|
395
|
-
_ts_metadata("design:type", String)
|
|
396
|
-
], ResponsePayloadDto.prototype, "message", void 0);
|
|
397
|
-
_ts_decorate([
|
|
398
|
-
(0, _swagger.ApiPropertyOptional)(),
|
|
399
|
-
_ts_metadata("design:type", typeof T === "undefined" ? Object : T)
|
|
400
|
-
], ResponsePayloadDto.prototype, "data", void 0);
|
|
401
|
-
_ts_decorate([
|
|
402
|
-
(0, _swagger.ApiPropertyOptional)(),
|
|
403
|
-
_ts_metadata("design:type", Object)
|
|
404
|
-
], ResponsePayloadDto.prototype, "meta", void 0);
|
|
405
|
-
_ts_decorate([
|
|
406
|
-
(0, _swagger.ApiPropertyOptional)({
|
|
407
|
-
type: RequestMetaDto
|
|
408
|
-
}),
|
|
409
|
-
_ts_metadata("design:type", typeof RequestMetaDto === "undefined" ? Object : RequestMetaDto)
|
|
410
|
-
], ResponsePayloadDto.prototype, "_meta", void 0);
|
|
411
|
-
ResponsePayloadDto = _ts_decorate([
|
|
412
|
-
(0, _swagger.ApiExtraModels)()
|
|
413
|
-
], ResponsePayloadDto);
|
package/cjs/entities/identity.js
CHANGED
|
@@ -36,10 +36,10 @@ let Identity = class Identity {
|
|
|
36
36
|
_define_property(this, "id", void 0);
|
|
37
37
|
_define_property(this, "createdAt", void 0);
|
|
38
38
|
_define_property(this, "updatedAt", void 0);
|
|
39
|
-
_define_property(this, "deletedAt",
|
|
40
|
-
_define_property(this, "createdById",
|
|
41
|
-
_define_property(this, "updatedById",
|
|
42
|
-
_define_property(this, "deletedById",
|
|
39
|
+
_define_property(this, "deletedAt", null);
|
|
40
|
+
_define_property(this, "createdById", null);
|
|
41
|
+
_define_property(this, "updatedById", null);
|
|
42
|
+
_define_property(this, "deletedById", null);
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
_ts_decorate([
|
|
@@ -34,23 +34,22 @@ function _ts_metadata(k, v) {
|
|
|
34
34
|
let UserRoot = class UserRoot {
|
|
35
35
|
constructor(){
|
|
36
36
|
_define_property(this, "id", void 0);
|
|
37
|
-
_define_property(this, "name",
|
|
38
|
-
_define_property(this, "password",
|
|
37
|
+
_define_property(this, "name", null);
|
|
38
|
+
_define_property(this, "password", null);
|
|
39
39
|
_define_property(this, "email", void 0);
|
|
40
|
-
_define_property(this, "phone",
|
|
41
|
-
_define_property(this, "isActive",
|
|
42
|
-
_define_property(this, "emailVerified",
|
|
43
|
-
_define_property(this, "phoneVerified",
|
|
44
|
-
_define_property(this, "profilePictureId",
|
|
45
|
-
_define_property(this, "lastLoginAt",
|
|
46
|
-
_define_property(this, "additionalFields",
|
|
40
|
+
_define_property(this, "phone", null);
|
|
41
|
+
_define_property(this, "isActive", true);
|
|
42
|
+
_define_property(this, "emailVerified", false);
|
|
43
|
+
_define_property(this, "phoneVerified", false);
|
|
44
|
+
_define_property(this, "profilePictureId", null);
|
|
45
|
+
_define_property(this, "lastLoginAt", null);
|
|
46
|
+
_define_property(this, "additionalFields", null);
|
|
47
47
|
_define_property(this, "createdAt", void 0);
|
|
48
48
|
_define_property(this, "updatedAt", void 0);
|
|
49
|
-
_define_property(this, "deletedAt",
|
|
50
|
-
|
|
51
|
-
_define_property(this, "
|
|
52
|
-
_define_property(this, "
|
|
53
|
-
_define_property(this, "deletedById", void 0);
|
|
49
|
+
_define_property(this, "deletedAt", null);
|
|
50
|
+
_define_property(this, "createdById", null);
|
|
51
|
+
_define_property(this, "updatedById", null);
|
|
52
|
+
_define_property(this, "deletedById", null);
|
|
54
53
|
}
|
|
55
54
|
};
|
|
56
55
|
_ts_decorate([
|
|
@@ -45,106 +45,79 @@ function _ts_param(paramIndex, decorator) {
|
|
|
45
45
|
}
|
|
46
46
|
let PermissionGuard = class PermissionGuard {
|
|
47
47
|
async canActivate(context) {
|
|
48
|
-
// Check if route is marked as public
|
|
49
48
|
const isPublic = this.reflector.getAllAndOverride(_constants.IS_PUBLIC_KEY, [
|
|
50
49
|
context.getHandler(),
|
|
51
50
|
context.getClass()
|
|
52
51
|
]);
|
|
53
52
|
if (isPublic) return true;
|
|
54
|
-
// Get required permissions from decorator
|
|
55
53
|
const permissionConfig = this.reflector.getAllAndOverride(_constants.PERMISSIONS_KEY, [
|
|
56
54
|
context.getHandler(),
|
|
57
55
|
context.getClass()
|
|
58
56
|
]);
|
|
59
|
-
|
|
60
|
-
if (!permissionConfig) {
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
// Normalize permission config (support old format: string[])
|
|
57
|
+
if (!permissionConfig) return true;
|
|
64
58
|
const { permissions: requiredPermissions, operator } = this.normalizePermissionConfig(permissionConfig);
|
|
65
|
-
|
|
66
|
-
if (!requiredPermissions || requiredPermissions.length === 0) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
59
|
+
if (!requiredPermissions || requiredPermissions.length === 0) return true;
|
|
69
60
|
const request = context.switchToHttp().getRequest();
|
|
70
61
|
const user = request.user;
|
|
71
|
-
|
|
72
|
-
if (!user) {
|
|
73
|
-
throw new _common.UnauthorizedException('Authentication required');
|
|
74
|
-
}
|
|
75
|
-
// Cache is required for permission checks - fail securely if unavailable
|
|
62
|
+
if (!user) throw new _common.UnauthorizedException('Authentication required');
|
|
76
63
|
if (!this.cache) {
|
|
77
|
-
|
|
78
|
-
this.logger.error(`Cache not available - permission system unavailable (userId: ${user.id})`, undefined, 'PermissionGuard');
|
|
79
|
-
// Fail securely - deny access rather than allowing without permission check
|
|
64
|
+
this.logger.error(`Cache not available (userId: ${user.id})`, undefined, 'PermissionGuard');
|
|
80
65
|
throw new _permissionexception.PermissionSystemUnavailableException();
|
|
81
66
|
}
|
|
82
|
-
// Get user's permissions from cache
|
|
83
67
|
const userPermissions = await this.getUserPermissions(user);
|
|
84
|
-
// If no permissions found in cache, deny access
|
|
85
68
|
if (!userPermissions || userPermissions.length === 0) {
|
|
86
|
-
this.logger.warn(`No permissions found
|
|
69
|
+
this.logger.warn(`No permissions found (userId: ${user.id})`, 'PermissionGuard');
|
|
87
70
|
throw new _permissionexception.NoPermissionsFoundException();
|
|
88
71
|
}
|
|
89
|
-
// Check if this is a nested condition or simple permission list
|
|
90
72
|
if (this.isNestedCondition(permissionConfig)) {
|
|
91
|
-
// Complex nested permission check
|
|
92
73
|
const result = this.evaluateCondition(permissionConfig, userPermissions);
|
|
93
74
|
if (!result.passed) {
|
|
94
|
-
this.logger.warn(`Permission
|
|
75
|
+
this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
|
|
95
76
|
throw new _permissionexception.InsufficientPermissionsException(result.missingPermissions, result.operator);
|
|
96
77
|
}
|
|
97
78
|
} else {
|
|
98
|
-
|
|
99
|
-
let hasRequiredPermissions;
|
|
79
|
+
let hasRequired;
|
|
100
80
|
if (operator === 'or') {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!hasRequiredPermissions) {
|
|
104
|
-
throw new _permissionexception.InsufficientPermissionsException(requiredPermissions, 'or');
|
|
105
|
-
}
|
|
81
|
+
hasRequired = requiredPermissions.some((p)=>this.hasPermission(userPermissions, p));
|
|
82
|
+
if (!hasRequired) throw new _permissionexception.InsufficientPermissionsException(requiredPermissions, 'or');
|
|
106
83
|
} else {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const missing = requiredPermissions.filter((permission)=>!this.hasPermission(userPermissions, permission));
|
|
84
|
+
hasRequired = requiredPermissions.every((p)=>this.hasPermission(userPermissions, p));
|
|
85
|
+
if (!hasRequired) {
|
|
86
|
+
const missing = requiredPermissions.filter((p)=>!this.hasPermission(userPermissions, p));
|
|
111
87
|
throw new _permissionexception.InsufficientPermissionsException(missing, 'and');
|
|
112
88
|
}
|
|
113
89
|
}
|
|
114
90
|
}
|
|
115
91
|
return true;
|
|
116
92
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
permissions: config,
|
|
124
|
-
operator: 'and'
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
// New format: PermissionConfig
|
|
93
|
+
normalizePermissionConfig(config) {
|
|
94
|
+
if (Array.isArray(config)) return {
|
|
95
|
+
permissions: config,
|
|
96
|
+
operator: 'and'
|
|
97
|
+
};
|
|
128
98
|
return {
|
|
129
99
|
permissions: config.permissions || [],
|
|
130
100
|
operator: config.operator || 'and'
|
|
131
101
|
};
|
|
132
102
|
}
|
|
133
|
-
|
|
134
|
-
* Check if config is a nested condition (has children)
|
|
135
|
-
*/ isNestedCondition(config) {
|
|
103
|
+
isNestedCondition(config) {
|
|
136
104
|
if (Array.isArray(config)) return false;
|
|
137
105
|
return 'children' in config && Array.isArray(config.children) && config.children.length > 0;
|
|
138
106
|
}
|
|
139
|
-
|
|
140
|
-
* Evaluate a nested permission condition recursively
|
|
141
|
-
*/ evaluateCondition(condition, userPermissions) {
|
|
107
|
+
evaluateCondition(condition, userPermissions) {
|
|
142
108
|
const { permissions = [], operator, children = [] } = condition;
|
|
143
|
-
//
|
|
109
|
+
// SECURITY: Fail-closed - deny access when no permissions configured (empty condition)
|
|
110
|
+
if (permissions.length === 0 && children.length === 0) {
|
|
111
|
+
return {
|
|
112
|
+
passed: false,
|
|
113
|
+
message: 'No permissions configured - access denied by default',
|
|
114
|
+
missingPermissions: [],
|
|
115
|
+
operator
|
|
116
|
+
};
|
|
117
|
+
}
|
|
144
118
|
const results = [];
|
|
145
119
|
const failureDetails = [];
|
|
146
120
|
const missingPermissions = [];
|
|
147
|
-
// Check permissions at this level
|
|
148
121
|
if (permissions.length > 0) {
|
|
149
122
|
if (operator === 'or') {
|
|
150
123
|
const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
|
|
@@ -163,7 +136,6 @@ let PermissionGuard = class PermissionGuard {
|
|
|
163
136
|
}
|
|
164
137
|
}
|
|
165
138
|
}
|
|
166
|
-
// Evaluate children recursively
|
|
167
139
|
for (const child of children){
|
|
168
140
|
const childResult = this.evaluateCondition(child, userPermissions);
|
|
169
141
|
results.push(childResult.passed);
|
|
@@ -172,14 +144,9 @@ let PermissionGuard = class PermissionGuard {
|
|
|
172
144
|
missingPermissions.push(...childResult.missingPermissions);
|
|
173
145
|
}
|
|
174
146
|
}
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
passed = results.length === 0 || results.some((r)=>r);
|
|
179
|
-
} else {
|
|
180
|
-
passed = results.length === 0 || results.every((r)=>r);
|
|
181
|
-
}
|
|
182
|
-
const message = passed ? 'Permission granted' : `Permission denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
|
|
147
|
+
// Evaluate based on operator - empty results already handled above
|
|
148
|
+
const passed = operator === 'or' ? results.some((r)=>r) : results.every((r)=>r);
|
|
149
|
+
const message = passed ? 'OK' : `Denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
|
|
183
150
|
return {
|
|
184
151
|
passed,
|
|
185
152
|
message,
|
|
@@ -187,48 +154,28 @@ let PermissionGuard = class PermissionGuard {
|
|
|
187
154
|
operator
|
|
188
155
|
};
|
|
189
156
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*/ async getUserPermissions(user) {
|
|
193
|
-
if (!this.cache) {
|
|
194
|
-
throw new _permissionexception.PermissionSystemUnavailableException();
|
|
195
|
-
}
|
|
157
|
+
async getUserPermissions(user) {
|
|
158
|
+
if (!this.cache) throw new _permissionexception.PermissionSystemUnavailableException();
|
|
196
159
|
let cacheKey;
|
|
197
160
|
if (this.config.enableCompanyFeature && user.companyId) {
|
|
198
|
-
// Company-based permissions (includes branchId for branch-scoped DIRECT permissions)
|
|
199
161
|
const format = this.config.companyPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`;
|
|
200
162
|
cacheKey = format.replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
|
|
201
163
|
} else {
|
|
202
|
-
// User-based permissions
|
|
203
164
|
const format = this.config.userPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`;
|
|
204
165
|
cacheKey = format.replace('{userId}', user.id);
|
|
205
166
|
}
|
|
206
|
-
|
|
207
|
-
return permissions || [];
|
|
167
|
+
return await this.cache.get(cacheKey) || [];
|
|
208
168
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
* Supports wildcard matching (e.g., 'admin.*' matches 'admin.users.read')
|
|
212
|
-
*/ hasPermission(userPermissions, requiredPermission) {
|
|
213
|
-
// Direct match
|
|
214
|
-
if (userPermissions.includes(requiredPermission)) {
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
// Wildcard match (e.g., '*' or 'admin.*')
|
|
169
|
+
hasPermission(userPermissions, requiredPermission) {
|
|
170
|
+
if (userPermissions.includes(requiredPermission)) return true;
|
|
218
171
|
for (const permission of userPermissions){
|
|
219
|
-
if (permission === '*')
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (permission.endsWith('.*')) {
|
|
223
|
-
const prefix = permission.slice(0, -1); // Remove '*'
|
|
224
|
-
if (requiredPermission.startsWith(prefix)) {
|
|
225
|
-
return true;
|
|
226
|
-
}
|
|
172
|
+
if (permission === '*') return true;
|
|
173
|
+
if (permission.endsWith('.*') && requiredPermission.startsWith(permission.slice(0, -1))) {
|
|
174
|
+
return true;
|
|
227
175
|
}
|
|
228
176
|
}
|
|
229
177
|
return false;
|
|
230
178
|
}
|
|
231
|
-
// NOTE: @Inject(Reflector) required for bundled code - external classes need explicit injection
|
|
232
179
|
constructor(reflector, cache, config, logger){
|
|
233
180
|
_define_property(this, "reflector", void 0);
|
|
234
181
|
_define_property(this, "cache", void 0);
|
|
@@ -238,12 +185,10 @@ let PermissionGuard = class PermissionGuard {
|
|
|
238
185
|
this.cache = cache;
|
|
239
186
|
this.config = {
|
|
240
187
|
enableCompanyFeature: false,
|
|
241
|
-
cacheKeyPrefix: _constants.PERMISSIONS_CACHE_PREFIX,
|
|
242
188
|
userPermissionKeyFormat: `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`,
|
|
243
189
|
companyPermissionKeyFormat: `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
|
|
244
190
|
...config
|
|
245
191
|
};
|
|
246
|
-
// Use provided logger or fallback to NestJS Logger wrapped in adapter
|
|
247
192
|
this.logger = logger || new _winstonloggeradapterclass.NestLoggerAdapter(new _common.Logger(PermissionGuard.name));
|
|
248
193
|
}
|
|
249
194
|
};
|
|
@@ -6,6 +6,7 @@ _export_star(require("./delete-empty-id-from-body.interceptor"), exports);
|
|
|
6
6
|
_export_star(require("./idempotency.interceptor"), exports);
|
|
7
7
|
_export_star(require("./query-performance.interceptor"), exports);
|
|
8
8
|
_export_star(require("./response-meta.interceptor"), exports);
|
|
9
|
+
_export_star(require("./set-user-field-on-body.interceptor"), exports);
|
|
9
10
|
_export_star(require("./set-create-by-on-body.interceptor"), exports);
|
|
10
11
|
_export_star(require("./set-delete-by-on-body.interceptor"), exports);
|
|
11
12
|
_export_star(require("./set-update-by-on-body.interceptor"), exports);
|
|
@@ -8,33 +8,5 @@ Object.defineProperty(exports, "SetCreatedByOnBody", {
|
|
|
8
8
|
return SetCreatedByOnBody;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
15
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
16
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
17
|
-
}
|
|
18
|
-
let SetCreatedByOnBody = class SetCreatedByOnBody {
|
|
19
|
-
intercept(context, next) {
|
|
20
|
-
const request = context.switchToHttp().getRequest();
|
|
21
|
-
const user = request?.user;
|
|
22
|
-
if (user) {
|
|
23
|
-
if (Array.isArray(request.body)) {
|
|
24
|
-
request.body = request.body.map((item)=>({
|
|
25
|
-
...item,
|
|
26
|
-
createdById: user.id
|
|
27
|
-
}));
|
|
28
|
-
} else if (typeof request.body === 'object' && request.body !== null) {
|
|
29
|
-
request.body = {
|
|
30
|
-
...request.body,
|
|
31
|
-
createdById: user.id
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return next.handle();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
SetCreatedByOnBody = _ts_decorate([
|
|
39
|
-
(0, _common.Injectable)()
|
|
40
|
-
], SetCreatedByOnBody);
|
|
11
|
+
const _setuserfieldonbodyinterceptor = require("./set-user-field-on-body.interceptor");
|
|
12
|
+
const SetCreatedByOnBody = (0, _setuserfieldonbodyinterceptor.createSetUserFieldInterceptor)('createdById');
|
|
@@ -8,33 +8,5 @@ Object.defineProperty(exports, "SetDeletedByOnBody", {
|
|
|
8
8
|
return SetDeletedByOnBody;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
15
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
16
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
17
|
-
}
|
|
18
|
-
let SetDeletedByOnBody = class SetDeletedByOnBody {
|
|
19
|
-
intercept(context, next) {
|
|
20
|
-
const request = context.switchToHttp().getRequest();
|
|
21
|
-
const user = request?.user;
|
|
22
|
-
if (user) {
|
|
23
|
-
if (Array.isArray(request.body)) {
|
|
24
|
-
request.body = request.body.map((item)=>({
|
|
25
|
-
...item,
|
|
26
|
-
deletedById: user.id
|
|
27
|
-
}));
|
|
28
|
-
} else if (typeof request.body === 'object' && request.body !== null) {
|
|
29
|
-
request.body = {
|
|
30
|
-
...request.body,
|
|
31
|
-
deletedById: user.id
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return next.handle();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
SetDeletedByOnBody = _ts_decorate([
|
|
39
|
-
(0, _common.Injectable)()
|
|
40
|
-
], SetDeletedByOnBody);
|
|
11
|
+
const _setuserfieldonbodyinterceptor = require("./set-user-field-on-body.interceptor");
|
|
12
|
+
const SetDeletedByOnBody = (0, _setuserfieldonbodyinterceptor.createSetUserFieldInterceptor)('deletedById');
|
|
@@ -8,33 +8,5 @@ Object.defineProperty(exports, "SetUpdateByOnBody", {
|
|
|
8
8
|
return SetUpdateByOnBody;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
15
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
16
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
17
|
-
}
|
|
18
|
-
let SetUpdateByOnBody = class SetUpdateByOnBody {
|
|
19
|
-
intercept(context, next) {
|
|
20
|
-
const request = context.switchToHttp().getRequest();
|
|
21
|
-
const user = request?.user;
|
|
22
|
-
if (user) {
|
|
23
|
-
if (Array.isArray(request.body)) {
|
|
24
|
-
request.body = request.body.map((item)=>({
|
|
25
|
-
...item,
|
|
26
|
-
updatedById: user.id
|
|
27
|
-
}));
|
|
28
|
-
} else if (typeof request.body === 'object' && request.body !== null) {
|
|
29
|
-
request.body = {
|
|
30
|
-
...request.body,
|
|
31
|
-
updatedById: user.id
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return next.handle();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
SetUpdateByOnBody = _ts_decorate([
|
|
39
|
-
(0, _common.Injectable)()
|
|
40
|
-
], SetUpdateByOnBody);
|
|
11
|
+
const _setuserfieldonbodyinterceptor = require("./set-user-field-on-body.interceptor");
|
|
12
|
+
const SetUpdateByOnBody = (0, _setuserfieldonbodyinterceptor.createSetUserFieldInterceptor)('updatedById');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "createSetUserFieldInterceptor", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return createSetUserFieldInterceptor;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _common = require("@nestjs/common");
|
|
12
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
13
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
15
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
16
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
17
|
+
}
|
|
18
|
+
function createSetUserFieldInterceptor(fieldName) {
|
|
19
|
+
let SetUserFieldOnBody = class SetUserFieldOnBody {
|
|
20
|
+
intercept(context, next) {
|
|
21
|
+
const request = context.switchToHttp().getRequest();
|
|
22
|
+
const user = request?.user;
|
|
23
|
+
if (user) {
|
|
24
|
+
if (Array.isArray(request.body)) {
|
|
25
|
+
request.body = request.body.map((item)=>({
|
|
26
|
+
...item,
|
|
27
|
+
[fieldName]: user.id
|
|
28
|
+
}));
|
|
29
|
+
} else if (typeof request.body === 'object' && request.body !== null) {
|
|
30
|
+
request.body = {
|
|
31
|
+
...request.body,
|
|
32
|
+
[fieldName]: user.id
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return next.handle();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
SetUserFieldOnBody = _ts_decorate([
|
|
40
|
+
(0, _common.Injectable)()
|
|
41
|
+
], SetUserFieldOnBody);
|
|
42
|
+
return SetUserFieldOnBody;
|
|
43
|
+
}
|