@flusys/nestjs-shared 1.0.0-beta → 1.0.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.
Files changed (113) hide show
  1. package/README.md +501 -720
  2. package/cjs/classes/api-controller.class.js +9 -24
  3. package/cjs/classes/api-service.class.js +59 -92
  4. package/cjs/classes/index.js +1 -0
  5. package/cjs/classes/winston-logger-adapter.class.js +23 -40
  6. package/cjs/constants/index.js +14 -0
  7. package/cjs/constants/permissions.js +184 -0
  8. package/cjs/decorators/api-response.decorator.js +1 -1
  9. package/cjs/decorators/index.js +1 -0
  10. package/cjs/decorators/sanitize-html.decorator.js +36 -0
  11. package/cjs/dtos/delete.dto.js +10 -0
  12. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  13. package/cjs/dtos/pagination.dto.js +4 -8
  14. package/cjs/dtos/response-payload.dto.js +0 -116
  15. package/cjs/entities/identity.js +4 -4
  16. package/cjs/entities/user-root.js +13 -14
  17. package/cjs/guards/permission.guard.js +51 -105
  18. package/cjs/interceptors/index.js +1 -3
  19. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  20. package/cjs/interceptors/slug.interceptor.js +30 -9
  21. package/cjs/interfaces/datasource.interface.js +4 -0
  22. package/cjs/interfaces/index.js +2 -1
  23. package/cjs/interfaces/module-config.interface.js +4 -0
  24. package/cjs/middlewares/logger.middleware.js +50 -89
  25. package/cjs/modules/cache/cache.module.js +3 -3
  26. package/cjs/modules/datasource/datasource.module.js +11 -14
  27. package/cjs/modules/datasource/multi-tenant-datasource.service.js +29 -113
  28. package/cjs/modules/utils/utils.service.js +40 -203
  29. package/cjs/utils/error-handler.util.js +35 -12
  30. package/cjs/utils/html-sanitizer.util.js +64 -0
  31. package/cjs/utils/index.js +4 -0
  32. package/cjs/utils/query-helpers.util.js +53 -0
  33. package/cjs/utils/request.util.js +70 -0
  34. package/cjs/utils/string.util.js +63 -0
  35. package/classes/api-controller.class.d.ts +5 -5
  36. package/classes/api-service.class.d.ts +7 -5
  37. package/classes/index.d.ts +1 -0
  38. package/classes/request-scoped-api.service.d.ts +3 -2
  39. package/classes/winston-logger-adapter.class.d.ts +2 -0
  40. package/constants/index.d.ts +1 -0
  41. package/constants/permissions.d.ts +179 -0
  42. package/decorators/index.d.ts +1 -0
  43. package/decorators/sanitize-html.decorator.d.ts +2 -0
  44. package/dtos/delete.dto.d.ts +1 -0
  45. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  46. package/dtos/response-payload.dto.d.ts +0 -20
  47. package/fesm/classes/api-controller.class.js +9 -24
  48. package/fesm/classes/api-service.class.js +59 -92
  49. package/fesm/classes/index.js +2 -0
  50. package/fesm/classes/winston-logger-adapter.class.js +23 -40
  51. package/fesm/constants/index.js +2 -0
  52. package/fesm/constants/permissions.js +128 -0
  53. package/fesm/decorators/api-response.decorator.js +1 -1
  54. package/fesm/decorators/index.js +1 -0
  55. package/fesm/decorators/sanitize-html.decorator.js +45 -0
  56. package/fesm/dtos/delete.dto.js +12 -2
  57. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  58. package/fesm/dtos/pagination.dto.js +4 -8
  59. package/fesm/dtos/response-payload.dto.js +0 -107
  60. package/fesm/entities/identity.js +4 -4
  61. package/fesm/entities/user-root.js +13 -14
  62. package/fesm/guards/permission.guard.js +51 -105
  63. package/fesm/interceptors/index.js +1 -3
  64. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  65. package/fesm/interceptors/slug.interceptor.js +31 -10
  66. package/fesm/interfaces/datasource.interface.js +20 -0
  67. package/fesm/interfaces/index.js +2 -1
  68. package/fesm/interfaces/module-config.interface.js +5 -0
  69. package/fesm/middlewares/logger.middleware.js +50 -83
  70. package/fesm/modules/cache/cache.module.js +2 -2
  71. package/fesm/modules/datasource/datasource.module.js +11 -14
  72. package/fesm/modules/datasource/multi-tenant-datasource.service.js +29 -113
  73. package/fesm/modules/utils/utils.service.js +41 -204
  74. package/fesm/utils/error-handler.util.js +36 -13
  75. package/fesm/utils/html-sanitizer.util.js +69 -0
  76. package/fesm/utils/index.js +4 -0
  77. package/fesm/utils/query-helpers.util.js +78 -0
  78. package/fesm/utils/request.util.js +58 -0
  79. package/fesm/utils/string.util.js +71 -0
  80. package/guards/permission.guard.d.ts +2 -0
  81. package/interceptors/index.d.ts +1 -3
  82. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  83. package/interceptors/slug.interceptor.d.ts +2 -1
  84. package/interfaces/api.interface.d.ts +2 -2
  85. package/interfaces/datasource.interface.d.ts +5 -0
  86. package/interfaces/identity.interface.d.ts +4 -4
  87. package/interfaces/index.d.ts +2 -1
  88. package/interfaces/logged-user-info.interface.d.ts +0 -2
  89. package/interfaces/module-config.interface.d.ts +6 -0
  90. package/interfaces/permission.interface.d.ts +0 -1
  91. package/middlewares/logger.middleware.d.ts +2 -2
  92. package/modules/datasource/datasource.module.d.ts +1 -0
  93. package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
  94. package/modules/utils/utils.service.d.ts +4 -14
  95. package/package.json +4 -4
  96. package/utils/error-handler.util.d.ts +14 -19
  97. package/utils/html-sanitizer.util.d.ts +2 -0
  98. package/utils/index.d.ts +4 -0
  99. package/utils/query-helpers.util.d.ts +16 -0
  100. package/utils/request.util.d.ts +4 -0
  101. package/utils/string.util.d.ts +2 -0
  102. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  103. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  104. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  105. package/cjs/interfaces/base-query.interface.js +0 -6
  106. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  107. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  108. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  109. package/fesm/interfaces/base-query.interface.js +0 -3
  110. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  111. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  112. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  113. 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
- _ts_metadata("design:type", Array)
159
- ], GetByIdBodyDto.prototype, "select", void 0);
160
- _ts_decorate([
161
- (0, _swagger.ApiPropertyOptional)({
162
- type: [
163
- String
164
- ],
165
- description: 'Additional relation keys to include',
166
- example: [
167
- 'category',
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, "extraKey", void 0);
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
- * Number of items per page. Defaults to 10 when not provided or invalid.
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. Defaults to 10 when not provided or invalid.',
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. Defaults to 0 when not provided or invalid.',
57
+ description: 'Zero-based page index (default: 0)',
62
58
  example: 0
63
59
  }),
64
60
  (0, _classvalidator.IsOptional)(),
@@ -15,9 +15,6 @@ _export(exports, {
15
15
  get BulkResponseDto () {
16
16
  return BulkResponseDto;
17
17
  },
18
- get ErrorResponseDto () {
19
- return ErrorResponseDto;
20
- },
21
18
  get ListResponseDto () {
22
19
  return ListResponseDto;
23
20
  },
@@ -30,14 +27,8 @@ _export(exports, {
30
27
  get RequestMetaDto () {
31
28
  return RequestMetaDto;
32
29
  },
33
- get ResponsePayloadDto () {
34
- return ResponsePayloadDto;
35
- },
36
30
  get SingleResponseDto () {
37
31
  return SingleResponseDto;
38
- },
39
- get ValidationErrorDto () {
40
- return ValidationErrorDto;
41
32
  }
42
33
  });
43
34
  const _swagger = require("@nestjs/swagger");
@@ -304,110 +295,3 @@ _ts_decorate([
304
295
  MessageResponseDto = _ts_decorate([
305
296
  (0, _swagger.ApiExtraModels)()
306
297
  ], MessageResponseDto);
307
- let ValidationErrorDto = class ValidationErrorDto {
308
- constructor(){
309
- _define_property(this, "field", void 0);
310
- _define_property(this, "message", void 0);
311
- _define_property(this, "constraint", void 0);
312
- }
313
- };
314
- _ts_decorate([
315
- (0, _swagger.ApiProperty)({
316
- example: 'email'
317
- }),
318
- _ts_metadata("design:type", String)
319
- ], ValidationErrorDto.prototype, "field", void 0);
320
- _ts_decorate([
321
- (0, _swagger.ApiProperty)({
322
- example: 'Invalid email format'
323
- }),
324
- _ts_metadata("design:type", String)
325
- ], ValidationErrorDto.prototype, "message", void 0);
326
- _ts_decorate([
327
- (0, _swagger.ApiPropertyOptional)({
328
- example: 'isEmail'
329
- }),
330
- _ts_metadata("design:type", String)
331
- ], ValidationErrorDto.prototype, "constraint", void 0);
332
- let ErrorResponseDto = class ErrorResponseDto {
333
- constructor(){
334
- _define_property(this, "success", void 0);
335
- _define_property(this, "message", void 0);
336
- _define_property(this, "code", void 0);
337
- _define_property(this, "errors", void 0);
338
- _define_property(this, "_meta", void 0);
339
- }
340
- };
341
- _ts_decorate([
342
- (0, _swagger.ApiProperty)({
343
- example: false
344
- }),
345
- _ts_metadata("design:type", Boolean)
346
- ], ErrorResponseDto.prototype, "success", void 0);
347
- _ts_decorate([
348
- (0, _swagger.ApiProperty)({
349
- example: 'Validation failed'
350
- }),
351
- _ts_metadata("design:type", String)
352
- ], ErrorResponseDto.prototype, "message", void 0);
353
- _ts_decorate([
354
- (0, _swagger.ApiPropertyOptional)({
355
- example: 'VALIDATION_ERROR'
356
- }),
357
- _ts_metadata("design:type", String)
358
- ], ErrorResponseDto.prototype, "code", void 0);
359
- _ts_decorate([
360
- (0, _swagger.ApiPropertyOptional)({
361
- type: [
362
- ValidationErrorDto
363
- ]
364
- }),
365
- _ts_metadata("design:type", Array)
366
- ], ErrorResponseDto.prototype, "errors", void 0);
367
- _ts_decorate([
368
- (0, _swagger.ApiPropertyOptional)({
369
- type: RequestMetaDto
370
- }),
371
- _ts_metadata("design:type", typeof RequestMetaDto === "undefined" ? Object : RequestMetaDto)
372
- ], ErrorResponseDto.prototype, "_meta", void 0);
373
- ErrorResponseDto = _ts_decorate([
374
- (0, _swagger.ApiExtraModels)()
375
- ], 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);
@@ -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", void 0);
40
- _define_property(this, "createdById", void 0);
41
- _define_property(this, "updatedById", void 0);
42
- _define_property(this, "deletedById", void 0);
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", void 0);
38
- _define_property(this, "password", void 0);
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", void 0);
41
- _define_property(this, "isActive", void 0);
42
- _define_property(this, "emailVerified", void 0);
43
- _define_property(this, "phoneVerified", void 0);
44
- _define_property(this, "profilePictureId", void 0);
45
- _define_property(this, "lastLoginAt", void 0);
46
- _define_property(this, "additionalFields", void 0);
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", void 0);
50
- // Audit columns (for ApiService compatibility)
51
- _define_property(this, "createdById", void 0);
52
- _define_property(this, "updatedById", void 0);
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,81 @@ 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
- // If no permissions required, allow access
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
- // If no permissions required, allow access
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
- // User must be authenticated
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
- // Log error (in production, this should be monitored)
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 for user (userId: ${user.id})`, 'PermissionGuard');
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 check failed (userId: ${user.id}, missing: ${result.missingPermissions.join(', ')})`, 'PermissionGuard');
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
- // Simple permission check (backward compatible)
99
- let hasRequiredPermissions;
100
- if (operator === 'or') {
101
- // OR: User must have at least ONE permission
102
- hasRequiredPermissions = requiredPermissions.some((permission)=>this.hasPermission(userPermissions, permission));
103
- if (!hasRequiredPermissions) {
104
- throw new _permissionexception.InsufficientPermissionsException(requiredPermissions, 'or');
105
- }
106
- } else {
107
- // AND (default): User must have ALL permissions
108
- hasRequiredPermissions = requiredPermissions.every((permission)=>this.hasPermission(userPermissions, permission));
109
- if (!hasRequiredPermissions) {
110
- const missing = requiredPermissions.filter((permission)=>!this.hasPermission(userPermissions, permission));
111
- throw new _permissionexception.InsufficientPermissionsException(missing, 'and');
112
- }
113
- }
79
+ this.validateSimplePermissions(requiredPermissions, userPermissions, operator);
114
80
  }
115
81
  return true;
116
82
  }
117
- /**
118
- * Normalize permission config to handle both old and new formats
119
- */ normalizePermissionConfig(config) {
120
- // Old format: string[]
121
- if (Array.isArray(config)) {
122
- return {
123
- permissions: config,
124
- operator: 'and'
125
- };
126
- }
127
- // New format: PermissionConfig
83
+ normalizePermissionConfig(config) {
84
+ if (Array.isArray(config)) return {
85
+ permissions: config,
86
+ operator: 'and'
87
+ };
128
88
  return {
129
89
  permissions: config.permissions || [],
130
90
  operator: config.operator || 'and'
131
91
  };
132
92
  }
133
- /**
134
- * Check if config is a nested condition (has children)
135
- */ isNestedCondition(config) {
93
+ validateSimplePermissions(requiredPermissions, userPermissions, operator) {
94
+ if (operator === 'or') {
95
+ const hasAny = requiredPermissions.some((p)=>this.hasPermission(userPermissions, p));
96
+ if (!hasAny) throw new _permissionexception.InsufficientPermissionsException(requiredPermissions, 'or');
97
+ } else {
98
+ const hasAll = requiredPermissions.every((p)=>this.hasPermission(userPermissions, p));
99
+ if (!hasAll) {
100
+ const missing = requiredPermissions.filter((p)=>!this.hasPermission(userPermissions, p));
101
+ throw new _permissionexception.InsufficientPermissionsException(missing, 'and');
102
+ }
103
+ }
104
+ }
105
+ isNestedCondition(config) {
136
106
  if (Array.isArray(config)) return false;
137
107
  return 'children' in config && Array.isArray(config.children) && config.children.length > 0;
138
108
  }
139
- /**
140
- * Evaluate a nested permission condition recursively
141
- */ evaluateCondition(condition, userPermissions) {
109
+ evaluateCondition(condition, userPermissions) {
142
110
  const { permissions = [], operator, children = [] } = condition;
143
- // Results for this level
111
+ // SECURITY: Fail-closed - deny access when no permissions configured (empty condition)
112
+ if (permissions.length === 0 && children.length === 0) {
113
+ return {
114
+ passed: false,
115
+ message: 'No permissions configured - access denied by default',
116
+ missingPermissions: [],
117
+ operator
118
+ };
119
+ }
144
120
  const results = [];
145
121
  const failureDetails = [];
146
122
  const missingPermissions = [];
147
- // Check permissions at this level
148
123
  if (permissions.length > 0) {
149
124
  if (operator === 'or') {
150
125
  const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
@@ -163,7 +138,6 @@ let PermissionGuard = class PermissionGuard {
163
138
  }
164
139
  }
165
140
  }
166
- // Evaluate children recursively
167
141
  for (const child of children){
168
142
  const childResult = this.evaluateCondition(child, userPermissions);
169
143
  results.push(childResult.passed);
@@ -172,14 +146,9 @@ let PermissionGuard = class PermissionGuard {
172
146
  missingPermissions.push(...childResult.missingPermissions);
173
147
  }
174
148
  }
175
- // Combine results based on operator
176
- let passed;
177
- if (operator === 'or') {
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()} `)}`;
149
+ // Evaluate based on operator - empty results already handled above
150
+ const passed = operator === 'or' ? results.some((r)=>r) : results.every((r)=>r);
151
+ const message = passed ? 'OK' : `Denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
183
152
  return {
184
153
  passed,
185
154
  message,
@@ -187,48 +156,27 @@ let PermissionGuard = class PermissionGuard {
187
156
  operator
188
157
  };
189
158
  }
190
- /**
191
- * Get user's permissions from cache
192
- */ async getUserPermissions(user) {
193
- if (!this.cache) {
194
- throw new _permissionexception.PermissionSystemUnavailableException();
195
- }
196
- let cacheKey;
159
+ async getUserPermissions(user) {
160
+ if (!this.cache) throw new _permissionexception.PermissionSystemUnavailableException();
161
+ const cacheKey = this.buildPermissionCacheKey(user);
162
+ return await this.cache.get(cacheKey) || [];
163
+ }
164
+ buildPermissionCacheKey(user) {
197
165
  if (this.config.enableCompanyFeature && user.companyId) {
198
- // Company-based permissions (includes branchId for branch-scoped DIRECT permissions)
199
- const format = this.config.companyPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`;
200
- cacheKey = format.replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
201
- } else {
202
- // User-based permissions
203
- const format = this.config.userPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`;
204
- cacheKey = format.replace('{userId}', user.id);
166
+ return (this.config.companyPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`).replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
205
167
  }
206
- const permissions = await this.cache.get(cacheKey);
207
- return permissions || [];
168
+ return (this.config.userPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`).replace('{userId}', user.id);
208
169
  }
209
- /**
210
- * Check if user has a specific permission
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.*')
170
+ hasPermission(userPermissions, requiredPermission) {
171
+ if (userPermissions.includes(requiredPermission)) return true;
218
172
  for (const permission of userPermissions){
219
- if (permission === '*') {
220
- return true; // Super admin
221
- }
222
- if (permission.endsWith('.*')) {
223
- const prefix = permission.slice(0, -1); // Remove '*'
224
- if (requiredPermission.startsWith(prefix)) {
225
- return true;
226
- }
173
+ if (permission === '*') return true;
174
+ if (permission.endsWith('.*') && requiredPermission.startsWith(permission.slice(0, -1))) {
175
+ return true;
227
176
  }
228
177
  }
229
178
  return false;
230
179
  }
231
- // NOTE: @Inject(Reflector) required for bundled code - external classes need explicit injection
232
180
  constructor(reflector, cache, config, logger){
233
181
  _define_property(this, "reflector", void 0);
234
182
  _define_property(this, "cache", void 0);
@@ -238,12 +186,10 @@ let PermissionGuard = class PermissionGuard {
238
186
  this.cache = cache;
239
187
  this.config = {
240
188
  enableCompanyFeature: false,
241
- cacheKeyPrefix: _constants.PERMISSIONS_CACHE_PREFIX,
242
189
  userPermissionKeyFormat: `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`,
243
190
  companyPermissionKeyFormat: `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
244
191
  ...config
245
192
  };
246
- // Use provided logger or fallback to NestJS Logger wrapped in adapter
247
193
  this.logger = logger || new _winstonloggeradapterclass.NestLoggerAdapter(new _common.Logger(PermissionGuard.name));
248
194
  }
249
195
  };
@@ -6,9 +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-create-by-on-body.interceptor"), exports);
10
- _export_star(require("./set-delete-by-on-body.interceptor"), exports);
11
- _export_star(require("./set-update-by-on-body.interceptor"), exports);
9
+ _export_star(require("./set-user-field-on-body.interceptor"), exports);
12
10
  _export_star(require("./slug.interceptor"), exports);
13
11
  function _export_star(from, to) {
14
12
  Object.keys(from).forEach(function(k) {
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get SetCreatedByOnBody () {
13
+ return SetCreatedByOnBody;
14
+ },
15
+ get SetDeletedByOnBody () {
16
+ return SetDeletedByOnBody;
17
+ },
18
+ get SetUpdateByOnBody () {
19
+ return SetUpdateByOnBody;
20
+ },
21
+ get createSetUserFieldInterceptor () {
22
+ return createSetUserFieldInterceptor;
23
+ }
24
+ });
25
+ const _common = require("@nestjs/common");
26
+ function _ts_decorate(decorators, target, key, desc) {
27
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
28
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
29
+ 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;
30
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
31
+ }
32
+ function createSetUserFieldInterceptor(fieldName) {
33
+ let SetUserFieldOnBody = class SetUserFieldOnBody {
34
+ intercept(context, next) {
35
+ const request = context.switchToHttp().getRequest();
36
+ const user = request?.user;
37
+ if (user) {
38
+ if (Array.isArray(request.body)) {
39
+ request.body = request.body.map((item)=>({
40
+ ...item,
41
+ [fieldName]: user.id
42
+ }));
43
+ } else if (typeof request.body === 'object' && request.body !== null) {
44
+ request.body = {
45
+ ...request.body,
46
+ [fieldName]: user.id
47
+ };
48
+ }
49
+ }
50
+ return next.handle();
51
+ }
52
+ };
53
+ SetUserFieldOnBody = _ts_decorate([
54
+ (0, _common.Injectable)()
55
+ ], SetUserFieldOnBody);
56
+ return SetUserFieldOnBody;
57
+ }
58
+ const SetCreatedByOnBody = createSetUserFieldInterceptor('createdById');
59
+ const SetUpdateByOnBody = createSetUserFieldInterceptor('updatedById');
60
+ const SetDeletedByOnBody = createSetUserFieldInterceptor('deletedById');