@flusys/nestjs-shared 1.1.0-beta → 1.2.0-rc.1
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 +146 -1330
- package/cjs/classes/api-controller.class.js +325 -49
- package/cjs/classes/api-service.class.js +271 -149
- package/cjs/classes/handle-custome-db.js +37 -0
- package/cjs/classes/index.js +2 -2
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +15 -4
- package/cjs/constants/message-keys.js +68 -0
- package/cjs/constants/permissions.js +287 -0
- package/cjs/decorators/api-response.decorator.js +2 -3
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/decorators/require-permission.decorator.js +5 -5
- package/cjs/dtos/delete.dto.js +10 -0
- package/cjs/dtos/filter-and-pagination.dto.js +24 -34
- package/cjs/dtos/get-by-ids.dto.js +65 -0
- package/cjs/dtos/identity-response.dto.js +4 -8
- package/cjs/dtos/index.js +1 -0
- package/cjs/dtos/pagination.dto.js +4 -8
- package/cjs/dtos/response-payload.dto.js +68 -112
- package/cjs/entities/identity.js +12 -6
- package/cjs/entities/index.js +1 -0
- package/cjs/entities/raw-type.js +4 -0
- package/cjs/entities/user-root.js +15 -15
- package/cjs/enums/index.js +20 -0
- package/cjs/enums/notification-type.enum.js +17 -0
- package/cjs/enums/participant-status.enum.js +17 -0
- package/cjs/enums/recurrence-type.enum.js +18 -0
- package/cjs/exceptions/base-app.exception.js +151 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +13 -9
- package/cjs/filters/global-exception.filter.js +279 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +88 -148
- package/cjs/index.js +2 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +1 -4
- package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
- package/cjs/interceptors/slug.interceptor.js +30 -9
- package/cjs/interfaces/datasource.interface.js +4 -0
- package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
- package/cjs/interfaces/index.js +4 -2
- package/cjs/interfaces/module-config.interface.js +4 -0
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/interfaces/permission.interface.js +1 -1
- package/cjs/middlewares/logger.middleware.js +133 -115
- package/cjs/modules/cache/cache.module.js +3 -3
- package/cjs/modules/datasource/index.js +0 -1
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +60 -183
- package/cjs/modules/utils/utils.service.js +27 -206
- package/cjs/utils/date-time.util.js +94 -0
- package/cjs/utils/html-sanitizer.util.js +64 -0
- package/cjs/utils/index.js +5 -1
- package/cjs/utils/query-helpers.util.js +60 -0
- package/cjs/utils/request.util.js +96 -0
- package/cjs/utils/string.util.js +63 -0
- package/classes/api-controller.class.d.ts +16 -8
- package/classes/api-service.class.d.ts +30 -25
- package/classes/handle-custome-db.d.ts +3 -0
- package/classes/index.d.ts +2 -2
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +2 -1
- package/constants/message-keys.d.ts +45 -0
- package/constants/permissions.d.ts +306 -0
- package/decorators/api-response.decorator.d.ts +1 -1
- package/decorators/current-user.decorator.d.ts +1 -1
- package/decorators/index.d.ts +1 -0
- package/decorators/log-action.decorator.d.ts +8 -0
- package/decorators/require-permission.decorator.d.ts +2 -2
- package/dtos/delete.dto.d.ts +1 -0
- package/dtos/filter-and-pagination.dto.d.ts +0 -2
- package/dtos/get-by-ids.dto.d.ts +4 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/response-payload.dto.d.ts +8 -20
- package/entities/index.d.ts +1 -0
- package/entities/raw-type.d.ts +1 -0
- package/enums/index.d.ts +3 -0
- package/enums/notification-type.enum.d.ts +6 -0
- package/enums/participant-status.enum.d.ts +6 -0
- package/enums/recurrence-type.enum.d.ts +7 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +2 -2
- package/fesm/classes/api-controller.class.js +326 -50
- package/fesm/classes/api-service.class.js +274 -152
- package/fesm/classes/handle-custome-db.js +19 -0
- package/fesm/classes/index.js +3 -2
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +4 -1
- package/fesm/constants/message-keys.js +49 -0
- package/fesm/constants/permissions.js +200 -0
- package/fesm/decorators/api-response.decorator.js +3 -21
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/decorators/require-permission.decorator.js +17 -65
- package/fesm/dtos/delete.dto.js +12 -2
- package/fesm/dtos/filter-and-pagination.dto.js +26 -47
- package/fesm/dtos/get-by-ids.dto.js +55 -0
- package/fesm/dtos/identity-response.dto.js +4 -8
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/pagination.dto.js +4 -8
- package/fesm/dtos/response-payload.dto.js +68 -103
- package/fesm/entities/identity.js +12 -6
- package/fesm/entities/index.js +1 -0
- package/fesm/entities/raw-type.js +1 -0
- package/fesm/entities/user-root.js +15 -15
- package/fesm/enums/index.js +3 -0
- package/fesm/enums/notification-type.enum.js +7 -0
- package/fesm/enums/participant-status.enum.js +7 -0
- package/fesm/enums/recurrence-type.enum.js +8 -0
- package/fesm/exceptions/base-app.exception.js +115 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +16 -18
- package/fesm/filters/global-exception.filter.js +269 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +90 -150
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +1 -4
- package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
- package/fesm/interceptors/slug.interceptor.js +31 -10
- package/fesm/interfaces/datasource.interface.js +1 -0
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +4 -2
- package/fesm/interfaces/module-config.interface.js +5 -0
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/interfaces/permission.interface.js +1 -3
- package/fesm/middlewares/logger.middleware.js +133 -109
- package/fesm/modules/cache/cache.module.js +2 -2
- package/fesm/modules/datasource/index.js +0 -1
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +61 -184
- package/fesm/modules/utils/utils.service.js +27 -206
- package/fesm/utils/date-time.util.js +70 -0
- package/fesm/utils/html-sanitizer.util.js +69 -0
- package/fesm/utils/index.js +5 -1
- package/fesm/utils/query-helpers.util.js +85 -0
- package/fesm/utils/request.util.js +81 -0
- package/fesm/utils/string.util.js +71 -0
- package/filters/global-exception.filter.d.ts +9 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +4 -6
- package/index.d.ts +2 -0
- package/interceptors/idempotency.interceptor.d.ts +1 -1
- package/interceptors/index.d.ts +1 -4
- package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
- package/interceptors/slug.interceptor.d.ts +2 -1
- package/interfaces/api.interface.d.ts +4 -2
- package/interfaces/datasource.interface.d.ts +5 -0
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/identity.interface.d.ts +4 -4
- package/interfaces/index.d.ts +4 -2
- package/interfaces/logged-user-info.interface.d.ts +0 -2
- package/interfaces/module-config.interface.d.ts +6 -0
- package/interfaces/notification-adapter.interface.d.ts +21 -0
- package/interfaces/permission.interface.d.ts +13 -9
- package/middlewares/logger.middleware.d.ts +2 -2
- package/modules/datasource/index.d.ts +0 -1
- package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -16
- package/modules/utils/utils.service.d.ts +3 -14
- package/package.json +21 -14
- package/utils/date-time.util.d.ts +8 -0
- package/utils/html-sanitizer.util.d.ts +2 -0
- package/utils/index.d.ts +5 -1
- package/utils/query-helpers.util.d.ts +16 -0
- package/utils/request.util.d.ts +6 -0
- package/utils/string.util.d.ts +2 -0
- package/cjs/classes/request-scoped-api.service.js +0 -61
- package/cjs/classes/winston-logger-adapter.class.js +0 -121
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
- package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
- package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
- package/cjs/interfaces/base-query.interface.js +0 -6
- package/cjs/interfaces/logger.interface.js +0 -7
- package/cjs/modules/datasource/datasource.module.js +0 -120
- package/cjs/utils/error-handler.util.js +0 -67
- package/classes/request-scoped-api.service.d.ts +0 -13
- package/classes/winston-logger-adapter.class.d.ts +0 -20
- package/fesm/classes/request-scoped-api.service.js +0 -51
- package/fesm/classes/winston-logger-adapter.class.js +0 -124
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
- package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
- package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
- package/fesm/interfaces/base-query.interface.js +0 -3
- package/fesm/interfaces/logger.interface.js +0 -4
- package/fesm/modules/datasource/datasource.module.js +0 -110
- package/fesm/utils/error-handler.util.js +0 -59
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
- package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
- package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
- package/interfaces/base-query.interface.d.ts +0 -7
- package/interfaces/logger.interface.d.ts +0 -7
- package/modules/datasource/datasource.module.d.ts +0 -18
- package/utils/error-handler.util.d.ts +0 -24
|
@@ -25,216 +25,159 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { Inject, Injectable,
|
|
28
|
+
import { Inject, Injectable, Optional, UnauthorizedException } from '@nestjs/common';
|
|
29
29
|
import { Reflector } from '@nestjs/core';
|
|
30
30
|
import { HybridCache } from '../classes/hybrid-cache.class';
|
|
31
|
-
import {
|
|
32
|
-
import { CACHE_INSTANCE, IS_PUBLIC_KEY, LOGGER_INSTANCE, PERMISSION_GUARD_CONFIG, PERMISSIONS_CACHE_PREFIX, PERMISSIONS_KEY } from '../constants';
|
|
31
|
+
import { AUTH_MESSAGES, CACHE_INSTANCE, IS_PUBLIC_KEY, PERMISSION_GUARD_CONFIG, PERMISSIONS_CACHE_PREFIX, PERMISSIONS_KEY } from '../constants';
|
|
33
32
|
import { InsufficientPermissionsException, NoPermissionsFoundException, PermissionSystemUnavailableException } from '../exceptions/permission.exception';
|
|
34
|
-
import { ILogger } from '../interfaces/logger.interface';
|
|
35
33
|
import { PermissionGuardConfig } from '../interfaces/permission.interface';
|
|
36
34
|
export class PermissionGuard {
|
|
37
35
|
async canActivate(context) {
|
|
38
|
-
// Check if route is marked as public
|
|
39
36
|
const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [
|
|
40
37
|
context.getHandler(),
|
|
41
38
|
context.getClass()
|
|
42
39
|
]);
|
|
43
40
|
if (isPublic) return true;
|
|
44
|
-
// Get required permissions from decorator
|
|
45
41
|
const permissionConfig = this.reflector.getAllAndOverride(PERMISSIONS_KEY, [
|
|
46
42
|
context.getHandler(),
|
|
47
43
|
context.getClass()
|
|
48
44
|
]);
|
|
49
|
-
|
|
50
|
-
if (!permissionConfig) {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
// Normalize permission config (support old format: string[])
|
|
54
|
-
const { permissions: requiredPermissions, operator } = this.normalizePermissionConfig(permissionConfig);
|
|
55
|
-
// If no permissions required, allow access
|
|
56
|
-
if (!requiredPermissions || requiredPermissions.length === 0) {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
45
|
+
if (!permissionConfig) return true;
|
|
59
46
|
const request = context.switchToHttp().getRequest();
|
|
60
47
|
const user = request.user;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
// Cache is required for permission checks - fail securely if unavailable
|
|
48
|
+
if (!user) throw new UnauthorizedException({
|
|
49
|
+
message: 'Authentication required',
|
|
50
|
+
messageKey: AUTH_MESSAGES.TOKEN_REQUIRED
|
|
51
|
+
});
|
|
66
52
|
if (!this.cache) {
|
|
67
|
-
// Log error (in production, this should be monitored)
|
|
68
|
-
this.logger.error(`Cache not available - permission system unavailable (userId: ${user.id})`, undefined, 'PermissionGuard');
|
|
69
|
-
// Fail securely - deny access rather than allowing without permission check
|
|
70
53
|
throw new PermissionSystemUnavailableException();
|
|
71
54
|
}
|
|
72
|
-
// Get user's permissions from cache
|
|
73
55
|
const userPermissions = await this.getUserPermissions(user);
|
|
74
|
-
// If no permissions found in cache, deny access
|
|
75
56
|
if (!userPermissions || userPermissions.length === 0) {
|
|
76
|
-
this.logger.warn(`No permissions found for user (userId: ${user.id})`, 'PermissionGuard');
|
|
77
57
|
throw new NoPermissionsFoundException();
|
|
78
58
|
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.logger.warn(`Permission check failed (userId: ${user.id}, missing: ${result.missingPermissions.join(', ')})`, 'PermissionGuard');
|
|
85
|
-
throw new InsufficientPermissionsException(result.missingPermissions, result.operator);
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
// Simple permission check (backward compatible)
|
|
89
|
-
let hasRequiredPermissions;
|
|
90
|
-
if (operator === 'or') {
|
|
91
|
-
// OR: User must have at least ONE permission
|
|
92
|
-
hasRequiredPermissions = requiredPermissions.some((permission)=>this.hasPermission(userPermissions, permission));
|
|
93
|
-
if (!hasRequiredPermissions) {
|
|
94
|
-
throw new InsufficientPermissionsException(requiredPermissions, 'or');
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
// AND (default): User must have ALL permissions
|
|
98
|
-
hasRequiredPermissions = requiredPermissions.every((permission)=>this.hasPermission(userPermissions, permission));
|
|
99
|
-
if (!hasRequiredPermissions) {
|
|
100
|
-
const missing = requiredPermissions.filter((permission)=>!this.hasPermission(userPermissions, permission));
|
|
101
|
-
throw new InsufficientPermissionsException(missing, 'and');
|
|
102
|
-
}
|
|
103
|
-
}
|
|
59
|
+
const logicNode = this.normalizeToLogicNode(permissionConfig);
|
|
60
|
+
if (!logicNode) return true;
|
|
61
|
+
const result = this.evaluateLogicNode(logicNode, userPermissions);
|
|
62
|
+
if (!result.passed) {
|
|
63
|
+
throw new InsufficientPermissionsException(result.missingPermissions, result.operator);
|
|
104
64
|
}
|
|
105
65
|
return true;
|
|
106
66
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
67
|
+
normalizeToLogicNode(config) {
|
|
68
|
+
// string - single permission
|
|
69
|
+
if (typeof config === 'string') {
|
|
70
|
+
return config ? {
|
|
71
|
+
type: 'action',
|
|
72
|
+
actionId: config
|
|
73
|
+
} : null;
|
|
74
|
+
}
|
|
75
|
+
// string[] - treat as AND of multiple permissions
|
|
111
76
|
if (Array.isArray(config)) {
|
|
77
|
+
if (config.length === 0) return null;
|
|
78
|
+
if (config.length === 1) return {
|
|
79
|
+
type: 'action',
|
|
80
|
+
actionId: config[0]
|
|
81
|
+
};
|
|
112
82
|
return {
|
|
113
|
-
|
|
114
|
-
operator: '
|
|
83
|
+
type: 'group',
|
|
84
|
+
operator: 'AND',
|
|
85
|
+
children: config.map((p)=>({
|
|
86
|
+
type: 'action',
|
|
87
|
+
actionId: p
|
|
88
|
+
}))
|
|
115
89
|
};
|
|
116
90
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
91
|
+
// SimplePermissionConfig - { permissions: string[], operator: 'AND'|'OR' }
|
|
92
|
+
if ('permissions' in config && Array.isArray(config.permissions)) {
|
|
93
|
+
const simple = config;
|
|
94
|
+
if (simple.permissions.length === 0) return null;
|
|
95
|
+
if (simple.permissions.length === 1) return {
|
|
96
|
+
type: 'action',
|
|
97
|
+
actionId: simple.permissions[0]
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
type: 'group',
|
|
101
|
+
operator: simple.operator || 'AND',
|
|
102
|
+
children: simple.permissions.map((p)=>({
|
|
103
|
+
type: 'action',
|
|
104
|
+
actionId: p
|
|
105
|
+
}))
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// ILogicNode
|
|
109
|
+
return config;
|
|
128
110
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
111
|
+
evaluateLogicNode(node, userPermissions) {
|
|
112
|
+
if (node.type === 'action') {
|
|
113
|
+
const passed = this.hasPermission(userPermissions, node.actionId);
|
|
114
|
+
return {
|
|
115
|
+
passed,
|
|
116
|
+
missingPermissions: passed ? [] : [
|
|
117
|
+
node.actionId
|
|
118
|
+
],
|
|
119
|
+
operator: 'AND'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Group node
|
|
123
|
+
const { operator, children } = node;
|
|
124
|
+
// SECURITY: Fail-closed - deny access when no children configured
|
|
125
|
+
if (!children || children.length === 0) {
|
|
126
|
+
return {
|
|
127
|
+
passed: false,
|
|
128
|
+
missingPermissions: [],
|
|
129
|
+
operator
|
|
130
|
+
};
|
|
131
|
+
}
|
|
134
132
|
const results = [];
|
|
135
|
-
const failureDetails = [];
|
|
136
133
|
const missingPermissions = [];
|
|
137
|
-
// Check permissions at this level
|
|
138
|
-
if (permissions.length > 0) {
|
|
139
|
-
if (operator === 'or') {
|
|
140
|
-
const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
|
|
141
|
-
results.push(hasAny);
|
|
142
|
-
if (!hasAny) {
|
|
143
|
-
failureDetails.push(`needs one of: [${permissions.join(', ')}]`);
|
|
144
|
-
missingPermissions.push(...permissions);
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
const hasAll = permissions.every((p)=>this.hasPermission(userPermissions, p));
|
|
148
|
-
results.push(hasAll);
|
|
149
|
-
if (!hasAll) {
|
|
150
|
-
const missing = permissions.filter((p)=>!this.hasPermission(userPermissions, p));
|
|
151
|
-
failureDetails.push(`missing: [${missing.join(', ')}]`);
|
|
152
|
-
missingPermissions.push(...missing);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// Evaluate children recursively
|
|
157
134
|
for (const child of children){
|
|
158
|
-
const childResult = this.
|
|
135
|
+
const childResult = this.evaluateLogicNode(child, userPermissions);
|
|
159
136
|
results.push(childResult.passed);
|
|
160
137
|
if (!childResult.passed) {
|
|
161
|
-
failureDetails.push(`(${childResult.message})`);
|
|
162
138
|
missingPermissions.push(...childResult.missingPermissions);
|
|
163
139
|
}
|
|
164
140
|
}
|
|
165
|
-
|
|
166
|
-
let passed;
|
|
167
|
-
if (operator === 'or') {
|
|
168
|
-
passed = results.length === 0 || results.some((r)=>r);
|
|
169
|
-
} else {
|
|
170
|
-
passed = results.length === 0 || results.every((r)=>r);
|
|
171
|
-
}
|
|
172
|
-
const message = passed ? 'Permission granted' : `Permission denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
|
|
141
|
+
const passed = operator === 'OR' ? results.some((r)=>r) : results.every((r)=>r);
|
|
173
142
|
return {
|
|
174
143
|
passed,
|
|
175
|
-
|
|
176
|
-
missingPermissions,
|
|
144
|
+
missingPermissions: passed ? [] : missingPermissions,
|
|
177
145
|
operator
|
|
178
146
|
};
|
|
179
147
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
let cacheKey;
|
|
148
|
+
async getUserPermissions(user) {
|
|
149
|
+
if (!this.cache) throw new PermissionSystemUnavailableException();
|
|
150
|
+
const cacheKey = this.buildPermissionCacheKey(user);
|
|
151
|
+
return await this.cache.get(cacheKey) || [];
|
|
152
|
+
}
|
|
153
|
+
buildPermissionCacheKey(user) {
|
|
187
154
|
if (this.config.enableCompanyFeature && user.companyId) {
|
|
188
|
-
|
|
189
|
-
const format = this.config.companyPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`;
|
|
190
|
-
cacheKey = format.replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
|
|
191
|
-
} else {
|
|
192
|
-
// User-based permissions
|
|
193
|
-
const format = this.config.userPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:user:{userId}`;
|
|
194
|
-
cacheKey = format.replace('{userId}', user.id);
|
|
155
|
+
return (this.config.companyPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`).replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
|
|
195
156
|
}
|
|
196
|
-
|
|
197
|
-
return permissions || [];
|
|
157
|
+
return (this.config.userPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:user:{userId}`).replace('{userId}', user.id);
|
|
198
158
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* Supports wildcard matching (e.g., 'admin.*' matches 'admin.users.read')
|
|
202
|
-
*/ hasPermission(userPermissions, requiredPermission) {
|
|
203
|
-
// Direct match
|
|
204
|
-
if (userPermissions.includes(requiredPermission)) {
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
// Wildcard match (e.g., '*' or 'admin.*')
|
|
159
|
+
hasPermission(userPermissions, requiredPermission) {
|
|
160
|
+
if (userPermissions.includes(requiredPermission)) return true;
|
|
208
161
|
for (const permission of userPermissions){
|
|
209
|
-
if (permission === '*')
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (permission.endsWith('.*')) {
|
|
213
|
-
const prefix = permission.slice(0, -1); // Remove '*'
|
|
214
|
-
if (requiredPermission.startsWith(prefix)) {
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
162
|
+
if (permission === '*') return true;
|
|
163
|
+
if (permission.endsWith('.*') && requiredPermission.startsWith(permission.slice(0, -1))) {
|
|
164
|
+
return true;
|
|
217
165
|
}
|
|
218
166
|
}
|
|
219
167
|
return false;
|
|
220
168
|
}
|
|
221
|
-
|
|
222
|
-
constructor(reflector, cache, config, logger){
|
|
169
|
+
constructor(reflector, cache, config){
|
|
223
170
|
_define_property(this, "reflector", void 0);
|
|
224
171
|
_define_property(this, "cache", void 0);
|
|
225
172
|
_define_property(this, "config", void 0);
|
|
226
|
-
_define_property(this, "logger", void 0);
|
|
227
173
|
this.reflector = reflector;
|
|
228
174
|
this.cache = cache;
|
|
229
175
|
this.config = {
|
|
230
176
|
enableCompanyFeature: false,
|
|
231
|
-
cacheKeyPrefix: PERMISSIONS_CACHE_PREFIX,
|
|
232
177
|
userPermissionKeyFormat: `${PERMISSIONS_CACHE_PREFIX}:user:{userId}`,
|
|
233
178
|
companyPermissionKeyFormat: `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
|
|
234
179
|
...config
|
|
235
180
|
};
|
|
236
|
-
// Use provided logger or fallback to NestJS Logger wrapped in adapter
|
|
237
|
-
this.logger = logger || new NestLoggerAdapter(new Logger(PermissionGuard.name));
|
|
238
181
|
}
|
|
239
182
|
}
|
|
240
183
|
PermissionGuard = _ts_decorate([
|
|
@@ -244,13 +187,10 @@ PermissionGuard = _ts_decorate([
|
|
|
244
187
|
_ts_param(1, Inject(CACHE_INSTANCE)),
|
|
245
188
|
_ts_param(2, Optional()),
|
|
246
189
|
_ts_param(2, Inject(PERMISSION_GUARD_CONFIG)),
|
|
247
|
-
_ts_param(3, Optional()),
|
|
248
|
-
_ts_param(3, Inject(LOGGER_INSTANCE)),
|
|
249
190
|
_ts_metadata("design:type", Function),
|
|
250
191
|
_ts_metadata("design:paramtypes", [
|
|
251
192
|
typeof Reflector === "undefined" ? Object : Reflector,
|
|
252
193
|
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
253
|
-
typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig
|
|
254
|
-
typeof ILogger === "undefined" ? Object : ILogger
|
|
194
|
+
typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig
|
|
255
195
|
])
|
|
256
196
|
], PermissionGuard);
|
package/fesm/index.js
CHANGED
|
@@ -3,7 +3,9 @@ export * from './constants';
|
|
|
3
3
|
export * from './decorators';
|
|
4
4
|
export * from './dtos';
|
|
5
5
|
export * from './entities';
|
|
6
|
+
export * from './enums';
|
|
6
7
|
export * from './exceptions';
|
|
8
|
+
export * from './filters';
|
|
7
9
|
export * from './guards';
|
|
8
10
|
export * from './interceptors';
|
|
9
11
|
export * from './interfaces';
|
|
@@ -29,7 +29,7 @@ import { ConflictException, Inject, Injectable, Optional } from '@nestjs/common'
|
|
|
29
29
|
import { of } from 'rxjs';
|
|
30
30
|
import { tap } from 'rxjs/operators';
|
|
31
31
|
import { HybridCache } from '../classes/hybrid-cache.class';
|
|
32
|
-
import { CACHE_INSTANCE, IDEMPOTENCY_CACHE_PREFIX, IDEMPOTENCY_KEY_HEADER } from '../constants';
|
|
32
|
+
import { CACHE_INSTANCE, IDEMPOTENCY_CACHE_PREFIX, IDEMPOTENCY_KEY_HEADER, SYSTEM_MESSAGES } from '../constants';
|
|
33
33
|
export class IdempotencyInterceptor {
|
|
34
34
|
async intercept(context, next) {
|
|
35
35
|
// Skip if no cache available
|
|
@@ -51,7 +51,7 @@ export class IdempotencyInterceptor {
|
|
|
51
51
|
throw new ConflictException({
|
|
52
52
|
success: false,
|
|
53
53
|
message: 'Request is already being processed',
|
|
54
|
-
|
|
54
|
+
messageKey: SYSTEM_MESSAGES.DUPLICATE_REQUEST
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
// Return cached response
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
export * from './delete-empty-id-from-body.interceptor';
|
|
2
2
|
export * from './idempotency.interceptor';
|
|
3
|
-
export * from './query-performance.interceptor';
|
|
4
3
|
export * from './response-meta.interceptor';
|
|
5
|
-
export * from './set-
|
|
6
|
-
export * from './set-delete-by-on-body.interceptor';
|
|
7
|
-
export * from './set-update-by-on-body.interceptor';
|
|
4
|
+
export * from './set-user-field-on-body.interceptor';
|
|
8
5
|
export * from './slug.interceptor';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Injectable } from '@nestjs/common';
|
|
8
|
+
/**
|
|
9
|
+
* Factory function to create interceptors that set user ID fields on request body
|
|
10
|
+
* @param fieldName - The field name to set (e.g., 'createdById', 'updatedById', 'deletedById')
|
|
11
|
+
*/ export function createSetUserFieldInterceptor(fieldName) {
|
|
12
|
+
let SetUserFieldOnBody = class SetUserFieldOnBody {
|
|
13
|
+
intercept(context, next) {
|
|
14
|
+
const request = context.switchToHttp().getRequest();
|
|
15
|
+
const user = request?.user;
|
|
16
|
+
if (user) {
|
|
17
|
+
if (Array.isArray(request.body)) {
|
|
18
|
+
request.body = request.body.map((item)=>({
|
|
19
|
+
...item,
|
|
20
|
+
[fieldName]: user.id
|
|
21
|
+
}));
|
|
22
|
+
} else if (typeof request.body === 'object' && request.body !== null) {
|
|
23
|
+
request.body = {
|
|
24
|
+
...request.body,
|
|
25
|
+
[fieldName]: user.id
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return next.handle();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
SetUserFieldOnBody = _ts_decorate([
|
|
33
|
+
Injectable()
|
|
34
|
+
], SetUserFieldOnBody);
|
|
35
|
+
return SetUserFieldOnBody;
|
|
36
|
+
}
|
|
37
|
+
/** Sets createdById field on request body from authenticated user */ export const SetCreatedByOnBody = createSetUserFieldInterceptor('createdById');
|
|
38
|
+
/** Sets updatedById field on request body from authenticated user */ export const SetUpdateByOnBody = createSetUserFieldInterceptor('updatedById');
|
|
39
|
+
/** Sets deletedById field on request body from authenticated user */ export const SetDeletedByOnBody = createSetUserFieldInterceptor('deletedById');
|
|
@@ -17,28 +17,49 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
17
17
|
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;
|
|
18
18
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
function _ts_metadata(k, v) {
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
|
+
}
|
|
23
|
+
function _ts_param(paramIndex, decorator) {
|
|
24
|
+
return function(target, key) {
|
|
25
|
+
decorator(target, key, paramIndex);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
21
29
|
import { UtilsService } from '../modules/utils/utils.service';
|
|
22
30
|
export class Slug {
|
|
23
31
|
intercept(context, next) {
|
|
24
32
|
const request = context.switchToHttp().getRequest();
|
|
25
33
|
if (Array.isArray(request.body)) {
|
|
26
|
-
request.body = request.body.map((item)=>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
request.body = request.body.map((item)=>{
|
|
35
|
+
// Only generate slug if not provided and name exists
|
|
36
|
+
if (!item.slug && item?.name) {
|
|
37
|
+
return {
|
|
38
|
+
...item,
|
|
39
|
+
slug: this.utilsService.transformToSlug(item.name)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return item;
|
|
43
|
+
});
|
|
44
|
+
} else if (!request.body?.slug && request.body?.name) {
|
|
45
|
+
// Only generate slug if not provided and name exists
|
|
31
46
|
request.body = {
|
|
32
47
|
...request.body,
|
|
33
|
-
slug:
|
|
48
|
+
slug: this.utilsService.transformToSlug(request.body.name)
|
|
34
49
|
};
|
|
35
50
|
}
|
|
36
51
|
return next.handle();
|
|
37
52
|
}
|
|
38
|
-
constructor(){
|
|
39
|
-
_define_property(this, "utilsService",
|
|
53
|
+
constructor(utilsService){
|
|
54
|
+
_define_property(this, "utilsService", void 0);
|
|
55
|
+
this.utilsService = utilsService;
|
|
40
56
|
}
|
|
41
57
|
}
|
|
42
58
|
Slug = _ts_decorate([
|
|
43
|
-
Injectable()
|
|
59
|
+
Injectable(),
|
|
60
|
+
_ts_param(0, Inject(UtilsService)),
|
|
61
|
+
_ts_metadata("design:type", Function),
|
|
62
|
+
_ts_metadata("design:paramtypes", [
|
|
63
|
+
typeof UtilsService === "undefined" ? Object : UtilsService
|
|
64
|
+
])
|
|
44
65
|
], Slug);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const EVENT_MANAGER_ADAPTER = 'EVENT_MANAGER_ADAPTER';
|
package/fesm/interfaces/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './api.interface';
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './datasource.interface';
|
|
3
|
+
export * from './event-manager-adapter.interface';
|
|
3
4
|
export * from './identity.interface';
|
|
4
5
|
export * from './logged-user-info.interface';
|
|
5
|
-
export * from './
|
|
6
|
+
export * from './module-config.interface';
|
|
7
|
+
export * from './notification-adapter.interface';
|
|
6
8
|
export * from './permission.interface';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const NOTIFICATION_ADAPTER = 'NOTIFICATION_ADAPTER';
|