@flusys/nestjs-iam 1.0.0-rc → 2.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.
- package/README.md +219 -118
- package/cjs/controllers/company-action-permission.controller.js +2 -17
- package/cjs/controllers/my-permission.controller.js +1 -2
- package/cjs/controllers/role-permission.controller.js +3 -9
- package/cjs/controllers/user-action-permission.controller.js +3 -9
- package/cjs/dtos/action.dto.js +0 -27
- package/cjs/dtos/permission.dto.js +81 -27
- package/cjs/dtos/role.dto.js +0 -27
- package/cjs/helpers/company-access.helper.js +19 -0
- package/cjs/helpers/index.js +1 -1
- package/cjs/interfaces/iam-module-options.interface.js +0 -14
- package/cjs/interfaces/index.js +0 -1
- package/cjs/modules/iam.module.js +38 -106
- package/cjs/services/action.service.js +30 -41
- package/cjs/services/iam-config.service.js +2 -5
- package/cjs/services/{iam-datasource.provider.js → iam-datasource.service.js} +33 -36
- package/cjs/services/index.js +1 -1
- package/cjs/services/permission-cache.service.js +6 -46
- package/cjs/services/permission.service.js +52 -41
- package/cjs/services/role.service.js +3 -3
- package/controllers/company-action-permission.controller.d.ts +2 -5
- package/controllers/role-permission.controller.d.ts +0 -1
- package/controllers/user-action-permission.controller.d.ts +0 -1
- package/dtos/action.dto.d.ts +0 -4
- package/dtos/role.dto.d.ts +0 -4
- package/fesm/controllers/company-action-permission.controller.js +4 -19
- package/fesm/controllers/my-permission.controller.js +1 -2
- package/fesm/controllers/role-permission.controller.js +4 -10
- package/fesm/controllers/user-action-permission.controller.js +4 -10
- package/fesm/dtos/action.dto.js +0 -24
- package/fesm/dtos/permission.dto.js +81 -27
- package/fesm/dtos/role.dto.js +0 -24
- package/fesm/helpers/company-access.helper.js +14 -0
- package/fesm/helpers/index.js +1 -1
- package/fesm/interfaces/iam-module-options.interface.js +3 -1
- package/fesm/interfaces/index.js +0 -1
- package/fesm/modules/iam.module.js +40 -108
- package/fesm/services/action.service.js +31 -42
- package/fesm/services/iam-config.service.js +2 -5
- package/fesm/services/{iam-datasource.provider.js → iam-datasource.service.js} +31 -34
- package/fesm/services/index.js +1 -1
- package/fesm/services/permission-cache.service.js +6 -46
- package/fesm/services/permission.service.js +53 -42
- package/fesm/services/role.service.js +3 -3
- package/helpers/company-access.helper.d.ts +3 -0
- package/helpers/index.d.ts +1 -1
- package/interfaces/iam-module-options.interface.d.ts +9 -1
- package/interfaces/index.d.ts +0 -1
- package/modules/iam.module.d.ts +1 -2
- package/package.json +3 -3
- package/services/action.service.d.ts +6 -4
- package/services/iam-config.service.d.ts +0 -1
- package/services/{iam-datasource.provider.d.ts → iam-datasource.service.d.ts} +4 -5
- package/services/index.d.ts +1 -1
- package/services/permission-cache.service.d.ts +1 -4
- package/services/permission.service.d.ts +4 -2
- package/services/role.service.d.ts +3 -3
- package/cjs/helpers/permission-evaluator.helper.js +0 -175
- package/cjs/interfaces/iam-module-async-options.interface.js +0 -4
- package/fesm/helpers/permission-evaluator.helper.js +0 -165
- package/fesm/interfaces/iam-module-async-options.interface.js +0 -3
- package/helpers/permission-evaluator.helper.d.ts +0 -26
- package/interfaces/iam-module-async-options.interface.d.ts +0 -11
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
Object.defineProperty(exports, "
|
|
5
|
+
Object.defineProperty(exports, "IAMDataSourceService", {
|
|
6
6
|
enumerable: true,
|
|
7
7
|
get: function() {
|
|
8
|
-
return
|
|
8
|
+
return IAMDataSourceService;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
13
|
const _core = require("@nestjs/core");
|
|
14
14
|
const _express = require("express");
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const _helpers = require("../helpers");
|
|
16
|
+
const _iamconfigservice = require("./iam-config.service");
|
|
17
17
|
function _define_property(obj, key, value) {
|
|
18
18
|
if (key in obj) {
|
|
19
19
|
Object.defineProperty(obj, key, {
|
|
@@ -82,7 +82,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
82
82
|
decorator(target, key, paramIndex);
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
-
let
|
|
85
|
+
let IAMDataSourceService = class IAMDataSourceService extends _modules.MultiTenantDataSourceService {
|
|
86
86
|
// Factory Methods
|
|
87
87
|
static buildParentOptions(options) {
|
|
88
88
|
return {
|
|
@@ -93,20 +93,17 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
|
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
// Feature Flags
|
|
96
|
-
getEnableCompanyFeature() {
|
|
97
|
-
return this.iamOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
98
|
-
}
|
|
99
96
|
getEnableCompanyFeatureForTenant(tenant) {
|
|
100
|
-
return tenant?.enableCompanyFeature ?? this.
|
|
97
|
+
return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
|
|
101
98
|
}
|
|
102
99
|
getEnableCompanyFeatureForCurrentTenant() {
|
|
103
100
|
return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
|
|
104
101
|
}
|
|
105
102
|
// Entity Management
|
|
106
103
|
async getIAMEntities() {
|
|
107
|
-
const {
|
|
104
|
+
const { getIAMEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
|
|
108
105
|
const enableCompanyFeature = this.getEnableCompanyFeatureForCurrentTenant();
|
|
109
|
-
const permissionMode = this.
|
|
106
|
+
const permissionMode = _helpers.PermissionModeHelper.toString(this.configService.getPermissionMode());
|
|
110
107
|
return getIAMEntitiesByConfig(enableCompanyFeature, permissionMode);
|
|
111
108
|
}
|
|
112
109
|
// Overrides
|
|
@@ -115,9 +112,9 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
|
|
|
115
112
|
return super.createDataSourceFromConfig(config, entities);
|
|
116
113
|
}
|
|
117
114
|
async getSingleDataSource() {
|
|
118
|
-
if (!
|
|
119
|
-
if (
|
|
120
|
-
return
|
|
115
|
+
if (!IAMDataSourceService.singleDataSource) {
|
|
116
|
+
if (IAMDataSourceService.singleConnectionLock) {
|
|
117
|
+
return IAMDataSourceService.singleConnectionLock;
|
|
121
118
|
}
|
|
122
119
|
const lockPromise = (async ()=>{
|
|
123
120
|
const config = this.getDefaultDatabaseConfig();
|
|
@@ -125,63 +122,63 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
|
|
|
125
122
|
throw new Error('Default database config is not available');
|
|
126
123
|
}
|
|
127
124
|
const ds = await this.createDataSourceFromConfig(config);
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
IAMDataSourceService.singleDataSource = ds;
|
|
126
|
+
IAMDataSourceService.initialized = true;
|
|
130
127
|
return ds;
|
|
131
128
|
})();
|
|
132
|
-
|
|
129
|
+
IAMDataSourceService.singleConnectionLock = lockPromise;
|
|
133
130
|
try {
|
|
134
131
|
return await lockPromise;
|
|
135
132
|
} finally{
|
|
136
|
-
|
|
133
|
+
IAMDataSourceService.singleConnectionLock = null;
|
|
137
134
|
}
|
|
138
135
|
}
|
|
139
|
-
return
|
|
136
|
+
return IAMDataSourceService.singleDataSource;
|
|
140
137
|
}
|
|
141
138
|
async getOrCreateTenantConnection(tenant) {
|
|
142
139
|
// Return existing initialized connection from IAM-specific cache
|
|
143
|
-
const existing =
|
|
140
|
+
const existing = IAMDataSourceService.tenantConnections.get(tenant.id);
|
|
144
141
|
if (existing?.isInitialized) {
|
|
145
142
|
return existing;
|
|
146
143
|
}
|
|
147
144
|
// If another request is creating this tenant's connection, wait for it
|
|
148
|
-
const pendingConnection =
|
|
145
|
+
const pendingConnection = IAMDataSourceService.connectionLocks.get(tenant.id);
|
|
149
146
|
if (pendingConnection) {
|
|
150
147
|
return pendingConnection;
|
|
151
148
|
}
|
|
152
149
|
// Create connection with lock to prevent race conditions
|
|
153
150
|
const config = this.buildTenantDatabaseConfig(tenant);
|
|
154
151
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
155
|
-
|
|
152
|
+
IAMDataSourceService.connectionLocks.set(tenant.id, connectionPromise);
|
|
156
153
|
try {
|
|
157
154
|
const dataSource = await connectionPromise;
|
|
158
|
-
|
|
155
|
+
IAMDataSourceService.tenantConnections.set(tenant.id, dataSource);
|
|
159
156
|
return dataSource;
|
|
160
157
|
} finally{
|
|
161
|
-
|
|
158
|
+
IAMDataSourceService.connectionLocks.delete(tenant.id);
|
|
162
159
|
}
|
|
163
160
|
}
|
|
164
|
-
constructor(
|
|
165
|
-
super(
|
|
161
|
+
constructor(configService, request){
|
|
162
|
+
super(IAMDataSourceService.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new _common.Logger(IAMDataSourceService.name);
|
|
166
163
|
}
|
|
167
164
|
};
|
|
168
165
|
// Override parent's static properties to have IAM-specific cache
|
|
169
|
-
_define_property(
|
|
170
|
-
_define_property(
|
|
171
|
-
_define_property(
|
|
172
|
-
_define_property(
|
|
173
|
-
_define_property(
|
|
174
|
-
_define_property(
|
|
175
|
-
|
|
166
|
+
_define_property(IAMDataSourceService, "tenantConnections", new Map());
|
|
167
|
+
_define_property(IAMDataSourceService, "singleDataSource", null);
|
|
168
|
+
_define_property(IAMDataSourceService, "tenantsRegistry", new Map());
|
|
169
|
+
_define_property(IAMDataSourceService, "initialized", false);
|
|
170
|
+
_define_property(IAMDataSourceService, "connectionLocks", new Map());
|
|
171
|
+
_define_property(IAMDataSourceService, "singleConnectionLock", null);
|
|
172
|
+
IAMDataSourceService = _ts_decorate([
|
|
176
173
|
(0, _common.Injectable)({
|
|
177
174
|
scope: _common.Scope.REQUEST
|
|
178
175
|
}),
|
|
179
|
-
_ts_param(0, (0, _common.Inject)(
|
|
176
|
+
_ts_param(0, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
|
|
180
177
|
_ts_param(1, (0, _common.Optional)()),
|
|
181
178
|
_ts_param(1, (0, _common.Inject)(_core.REQUEST)),
|
|
182
179
|
_ts_metadata("design:type", Function),
|
|
183
180
|
_ts_metadata("design:paramtypes", [
|
|
184
|
-
typeof
|
|
181
|
+
typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
|
|
185
182
|
typeof _express.Request === "undefined" ? Object : _express.Request
|
|
186
183
|
])
|
|
187
|
-
],
|
|
184
|
+
], IAMDataSourceService);
|
package/cjs/services/index.js
CHANGED
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./action.service"), exports);
|
|
6
6
|
_export_star(require("./iam-config.service"), exports);
|
|
7
|
-
_export_star(require("./iam-datasource.
|
|
7
|
+
_export_star(require("./iam-datasource.service"), exports);
|
|
8
8
|
_export_star(require("./permission-cache.service"), exports);
|
|
9
9
|
_export_star(require("./permission.service"), exports);
|
|
10
10
|
_export_star(require("./role.service"), exports);
|
|
@@ -41,18 +41,17 @@ function _ts_param(paramIndex, decorator) {
|
|
|
41
41
|
let PermissionCacheService = class PermissionCacheService {
|
|
42
42
|
// Cache Key Generation
|
|
43
43
|
generateCacheKey(options) {
|
|
44
|
-
|
|
45
|
-
if (enableCompanyFeature && companyId) {
|
|
46
|
-
return `${this.CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
|
|
47
|
-
}
|
|
48
|
-
return `${this.CACHE_PREFIX}:user:${userId}`;
|
|
44
|
+
return this.buildCacheKey(this.CACHE_PREFIX, options);
|
|
49
45
|
}
|
|
50
46
|
generateMyPermissionsCacheKey(options) {
|
|
47
|
+
return this.buildCacheKey(this.MY_PERMISSIONS_PREFIX, options);
|
|
48
|
+
}
|
|
49
|
+
buildCacheKey(prefix, options) {
|
|
51
50
|
const { userId, companyId, branchId, enableCompanyFeature } = options;
|
|
52
51
|
if (enableCompanyFeature && companyId) {
|
|
53
|
-
return `${
|
|
52
|
+
return `${prefix}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
|
|
54
53
|
}
|
|
55
|
-
return `${
|
|
54
|
+
return `${prefix}:user:${userId}`;
|
|
56
55
|
}
|
|
57
56
|
// Cache Operations
|
|
58
57
|
async setPermissions(options, permissions) {
|
|
@@ -66,17 +65,6 @@ let PermissionCacheService = class PermissionCacheService {
|
|
|
66
65
|
// Don't throw - cache failure shouldn't break the operation
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
|
-
async getPermissions(options) {
|
|
70
|
-
try {
|
|
71
|
-
const key = this.generateCacheKey(options);
|
|
72
|
-
const result = await this.cacheManager.get(key);
|
|
73
|
-
return result || null;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
|
|
76
|
-
this.logger.error(`Failed to get permissions from cache: ${errorMessage}`);
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
68
|
// My-Permissions Cache Operations
|
|
81
69
|
async setMyPermissions(options, data) {
|
|
82
70
|
try {
|
|
@@ -139,16 +127,6 @@ let PermissionCacheService = class PermissionCacheService {
|
|
|
139
127
|
return null;
|
|
140
128
|
}
|
|
141
129
|
}
|
|
142
|
-
async invalidateActionCodeCache(tenantId) {
|
|
143
|
-
try {
|
|
144
|
-
const key = this.generateActionCodeCacheKey(tenantId);
|
|
145
|
-
await this.cacheManager.del(key);
|
|
146
|
-
this.logger.debug(`Invalidated action code cache${tenantId ? ` for tenant ${tenantId}` : ''}`);
|
|
147
|
-
} catch (error) {
|
|
148
|
-
const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
|
|
149
|
-
this.logger.warn(`Failed to invalidate action code cache: ${errorMessage}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
130
|
// Cache Invalidation
|
|
153
131
|
async invalidateUser(userId, companyId, branchIds) {
|
|
154
132
|
try {
|
|
@@ -188,13 +166,6 @@ let PermissionCacheService = class PermissionCacheService {
|
|
|
188
166
|
}
|
|
189
167
|
return successCount;
|
|
190
168
|
}
|
|
191
|
-
/** Invalidate cache for all users in company (requires userIds via invalidateUsers) */ async invalidateCompany(companyId) {
|
|
192
|
-
// Note: HybridCache doesn't support pattern matching (keys() method)
|
|
193
|
-
// Company-wide invalidation requires passing individual user IDs
|
|
194
|
-
// This is a placeholder that logs a warning
|
|
195
|
-
this.logger.warn(`invalidateCompany called for ${companyId}, but pattern matching is not supported. ` + `Use invalidateUsers() with specific user IDs instead.`);
|
|
196
|
-
return 0;
|
|
197
|
-
}
|
|
198
169
|
async invalidateRole(roleId, userIds, companyId, branchIds) {
|
|
199
170
|
if (userIds.length === 0) {
|
|
200
171
|
this.logger.debug(`No users found for role ${roleId}`);
|
|
@@ -206,17 +177,6 @@ let PermissionCacheService = class PermissionCacheService {
|
|
|
206
177
|
}
|
|
207
178
|
return count;
|
|
208
179
|
}
|
|
209
|
-
// Administrative Operations
|
|
210
|
-
/** Clear all permission caches (memory and redis) */ async clearAll() {
|
|
211
|
-
try {
|
|
212
|
-
await this.cacheManager.reset(); // Clear memory cache
|
|
213
|
-
await this.cacheManager.resetL2(); // Clear redis cache
|
|
214
|
-
this.logger.warn('Cleared all cache entries (memory and redis)');
|
|
215
|
-
} catch (error) {
|
|
216
|
-
const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
|
|
217
|
-
this.logger.error(`Failed to clear all caches: ${errorMessage}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
180
|
constructor(cacheManager){
|
|
221
181
|
_define_property(this, "cacheManager", void 0);
|
|
222
182
|
_define_property(this, "logger", void 0);
|
|
@@ -19,7 +19,7 @@ const _useriampermissionentity = require("../entities/user-iam-permission.entity
|
|
|
19
19
|
const _actiontypeenum = require("../enums/action-type.enum");
|
|
20
20
|
const _permissiontypeenum = require("../enums/permission-type.enum");
|
|
21
21
|
const _iamconfigservice = require("./iam-config.service");
|
|
22
|
-
const
|
|
22
|
+
const _iamdatasourceservice = require("./iam-datasource.service");
|
|
23
23
|
const _permissioncacheservice = require("./permission-cache.service");
|
|
24
24
|
function _define_property(obj, key, value) {
|
|
25
25
|
if (key in obj) {
|
|
@@ -72,8 +72,7 @@ let PermissionService = class PermissionService {
|
|
|
72
72
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
73
73
|
const branchId = dto.branchId ?? null;
|
|
74
74
|
const companyId = dto.companyId ?? null;
|
|
75
|
-
const itemsToAdd = dto.items
|
|
76
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
75
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
77
76
|
let added = 0;
|
|
78
77
|
let removed = 0;
|
|
79
78
|
if (itemsToAdd.length > 0) {
|
|
@@ -107,8 +106,15 @@ let PermissionService = class PermissionService {
|
|
|
107
106
|
branchId: enableCompanyFeature ? branchId : null
|
|
108
107
|
}));
|
|
109
108
|
if (newPermissions.length > 0) {
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
try {
|
|
110
|
+
await permissionRepo.save(newPermissions);
|
|
111
|
+
added = newPermissions.length;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
114
|
+
throw new _common.ConflictException('Some permissions already exist for this user. Please refresh and try again.');
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
if (itemsToRemove.length > 0) {
|
|
@@ -128,12 +134,7 @@ let PermissionService = class PermissionService {
|
|
|
128
134
|
removed = result.affected || 0;
|
|
129
135
|
}
|
|
130
136
|
await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
|
|
131
|
-
return
|
|
132
|
-
success: true,
|
|
133
|
-
added,
|
|
134
|
-
removed,
|
|
135
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
|
|
136
|
-
};
|
|
137
|
+
return this.buildOperationResult(dto.items.length, added, removed);
|
|
137
138
|
}
|
|
138
139
|
async getUserActions(userId, branchId, companyId) {
|
|
139
140
|
const permissionRepo = await this.getPermissionRepository();
|
|
@@ -199,8 +200,7 @@ let PermissionService = class PermissionService {
|
|
|
199
200
|
});
|
|
200
201
|
roleCompanyId = role?.companyId ?? null;
|
|
201
202
|
}
|
|
202
|
-
const itemsToAdd = dto.items
|
|
203
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
203
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
204
204
|
let added = 0;
|
|
205
205
|
let removed = 0;
|
|
206
206
|
if (itemsToAdd.length > 0) {
|
|
@@ -229,8 +229,15 @@ let PermissionService = class PermissionService {
|
|
|
229
229
|
branchId: null
|
|
230
230
|
}));
|
|
231
231
|
if (newPermissions.length > 0) {
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
try {
|
|
233
|
+
await permissionRepo.save(newPermissions);
|
|
234
|
+
added = newPermissions.length;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
237
|
+
throw new _common.ConflictException('Some role-action permissions already exist. Please refresh and try again.');
|
|
238
|
+
}
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
if (itemsToRemove.length > 0) {
|
|
@@ -245,12 +252,7 @@ let PermissionService = class PermissionService {
|
|
|
245
252
|
removed = result.affected || 0;
|
|
246
253
|
}
|
|
247
254
|
const affectedUsers = await this.invalidateRoleMembersCache(dto.roleId);
|
|
248
|
-
return {
|
|
249
|
-
success: true,
|
|
250
|
-
added,
|
|
251
|
-
removed,
|
|
252
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed. Invalidated cache for ${affectedUsers} users.`
|
|
253
|
-
};
|
|
255
|
+
return this.buildOperationResult(dto.items.length, added, removed, `. Invalidated cache for ${affectedUsers} users.`);
|
|
254
256
|
}
|
|
255
257
|
async getRoleActions(roleId) {
|
|
256
258
|
const permissionRepo = await this.getPermissionRepository();
|
|
@@ -292,8 +294,7 @@ let PermissionService = class PermissionService {
|
|
|
292
294
|
/** Assign or remove actions to/from a company (whitelist) */ async assignCompanyActions(dto) {
|
|
293
295
|
const permissionRepo = await this.getPermissionRepository();
|
|
294
296
|
const dataSource = permissionRepo.manager.connection;
|
|
295
|
-
const itemsToAdd = dto.items
|
|
296
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
297
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
297
298
|
let added = 0;
|
|
298
299
|
let removed = 0;
|
|
299
300
|
let removedRoleActions = 0;
|
|
@@ -313,12 +314,7 @@ let PermissionService = class PermissionService {
|
|
|
313
314
|
});
|
|
314
315
|
const affectedCacheEntries = await this.invalidateCompanyMembersCache(dto.companyId);
|
|
315
316
|
const cascadeInfo = removedRoleActions > 0 || removedUserActions > 0 ? ` Cascaded removal: ${removedRoleActions} role permissions, ${removedUserActions} user permissions.` : '';
|
|
316
|
-
return {
|
|
317
|
-
success: true,
|
|
318
|
-
added,
|
|
319
|
-
removed,
|
|
320
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed.${cascadeInfo} Invalidated ${affectedCacheEntries} cache entries.`
|
|
321
|
-
};
|
|
317
|
+
return this.buildOperationResult(dto.items.length, added, removed, `.${cascadeInfo} Invalidated ${affectedCacheEntries} cache entries.`);
|
|
322
318
|
}
|
|
323
319
|
async addCompanyActions(permissionRepo, companyId, actionIds) {
|
|
324
320
|
const existingPermissions = await permissionRepo.find({
|
|
@@ -459,8 +455,7 @@ let PermissionService = class PermissionService {
|
|
|
459
455
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
460
456
|
const branchId = dto.branchId ?? null;
|
|
461
457
|
const companyId = dto.companyId ?? null;
|
|
462
|
-
const itemsToAdd = dto.items
|
|
463
|
-
const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
|
|
458
|
+
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
464
459
|
let added = 0;
|
|
465
460
|
let removed = 0;
|
|
466
461
|
if (itemsToAdd.length > 0) {
|
|
@@ -494,8 +489,15 @@ let PermissionService = class PermissionService {
|
|
|
494
489
|
branchId: enableCompanyFeature ? branchId : null
|
|
495
490
|
}));
|
|
496
491
|
if (newPermissions.length > 0) {
|
|
497
|
-
|
|
498
|
-
|
|
492
|
+
try {
|
|
493
|
+
await permissionRepo.save(newPermissions);
|
|
494
|
+
added = newPermissions.length;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
497
|
+
throw new _common.ConflictException('Some user-role permissions already exist. Please refresh and try again.');
|
|
498
|
+
}
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
499
501
|
}
|
|
500
502
|
}
|
|
501
503
|
if (itemsToRemove.length > 0) {
|
|
@@ -515,12 +517,7 @@ let PermissionService = class PermissionService {
|
|
|
515
517
|
removed = result.affected || 0;
|
|
516
518
|
}
|
|
517
519
|
await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
|
|
518
|
-
return
|
|
519
|
-
success: true,
|
|
520
|
-
added,
|
|
521
|
-
removed,
|
|
522
|
-
message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
|
|
523
|
-
};
|
|
520
|
+
return this.buildOperationResult(dto.items.length, added, removed);
|
|
524
521
|
}
|
|
525
522
|
/** Get user's roles (branch-scoped, filtered by companyId and branchId if provided) */ async getUserRoles(userId, branchId, companyId) {
|
|
526
523
|
const permissionRepo = await this.getPermissionRepository();
|
|
@@ -712,6 +709,20 @@ let PermissionService = class PermissionService {
|
|
|
712
709
|
return cacheData;
|
|
713
710
|
}
|
|
714
711
|
// Helper Methods
|
|
712
|
+
/** Split permission items into add and remove arrays */ splitItemsByAction(items) {
|
|
713
|
+
return {
|
|
714
|
+
toAdd: items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD),
|
|
715
|
+
toRemove: items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE)
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
/** Build standard operation result DTO */ buildOperationResult(totalItems, added, removed, additionalMessage = '') {
|
|
719
|
+
return {
|
|
720
|
+
success: true,
|
|
721
|
+
added,
|
|
722
|
+
removed,
|
|
723
|
+
message: `Successfully processed ${totalItems} items: ${added} added, ${removed} removed${additionalMessage}`
|
|
724
|
+
};
|
|
725
|
+
}
|
|
715
726
|
/** Get role IDs assigned to a user (merges company-wide + branch-specific roles) */ async getUserRoleIds(userId, branchId, companyId) {
|
|
716
727
|
const permissionRepo = await this.getPermissionRepository();
|
|
717
728
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
@@ -899,11 +910,11 @@ PermissionService = _ts_decorate([
|
|
|
899
910
|
}),
|
|
900
911
|
_ts_param(0, (0, _common.Inject)(_permissioncacheservice.PermissionCacheService)),
|
|
901
912
|
_ts_param(1, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
|
|
902
|
-
_ts_param(2, (0, _common.Inject)(
|
|
913
|
+
_ts_param(2, (0, _common.Inject)(_iamdatasourceservice.IAMDataSourceService)),
|
|
903
914
|
_ts_metadata("design:type", Function),
|
|
904
915
|
_ts_metadata("design:paramtypes", [
|
|
905
916
|
typeof _permissioncacheservice.PermissionCacheService === "undefined" ? Object : _permissioncacheservice.PermissionCacheService,
|
|
906
917
|
typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
|
|
907
|
-
typeof
|
|
918
|
+
typeof _iamdatasourceservice.IAMDataSourceService === "undefined" ? Object : _iamdatasourceservice.IAMDataSourceService
|
|
908
919
|
])
|
|
909
920
|
], PermissionService);
|
|
@@ -15,7 +15,7 @@ const _common = require("@nestjs/common");
|
|
|
15
15
|
const _rolewithcompanyentity = require("../entities/role-with-company.entity");
|
|
16
16
|
const _roleentity = require("../entities/role.entity");
|
|
17
17
|
const _iamconfigservice = require("./iam-config.service");
|
|
18
|
-
const
|
|
18
|
+
const _iamdatasourceservice = require("./iam-datasource.service");
|
|
19
19
|
function _define_property(obj, key, value) {
|
|
20
20
|
if (key in obj) {
|
|
21
21
|
Object.defineProperty(obj, key, {
|
|
@@ -132,12 +132,12 @@ RoleService = _ts_decorate([
|
|
|
132
132
|
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
133
133
|
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
134
134
|
_ts_param(2, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
|
|
135
|
-
_ts_param(3, (0, _common.Inject)(
|
|
135
|
+
_ts_param(3, (0, _common.Inject)(_iamdatasourceservice.IAMDataSourceService)),
|
|
136
136
|
_ts_metadata("design:type", Function),
|
|
137
137
|
_ts_metadata("design:paramtypes", [
|
|
138
138
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
139
139
|
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
140
140
|
typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
|
|
141
|
-
typeof
|
|
141
|
+
typeof _iamdatasourceservice.IAMDataSourceService === "undefined" ? Object : _iamdatasourceservice.IAMDataSourceService
|
|
142
142
|
])
|
|
143
143
|
], RoleService);
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ILoggedUserInfo, SingleResponseDto } from '@flusys/nestjs-shared';
|
|
2
2
|
import { AssignCompanyActionsDto, CompanyActionResponseDto, GetCompanyActionsDto, PermissionOperationResultDto } from '../dtos/permission.dto';
|
|
3
3
|
import { PermissionService } from '../services/permission.service';
|
|
4
|
-
import { IAMConfigService } from '../services/iam-config.service';
|
|
5
4
|
export declare class CompanyActionPermissionController {
|
|
6
5
|
private readonly permissionService;
|
|
7
|
-
|
|
8
|
-
constructor(permissionService: PermissionService, config: IAMConfigService);
|
|
9
|
-
private validateCompanyAccess;
|
|
6
|
+
constructor(permissionService: PermissionService);
|
|
10
7
|
assignCompanyActions(dto: AssignCompanyActionsDto, user: ILoggedUserInfo): Promise<PermissionOperationResultDto>;
|
|
11
8
|
getCompanyActions(dto: GetCompanyActionsDto, user: ILoggedUserInfo): Promise<SingleResponseDto<CompanyActionResponseDto[]>>;
|
|
12
9
|
}
|
|
@@ -6,7 +6,6 @@ export declare class RolePermissionController {
|
|
|
6
6
|
private readonly permissionService;
|
|
7
7
|
private readonly config;
|
|
8
8
|
constructor(permissionService: PermissionService, config: IAMConfigService);
|
|
9
|
-
private validateCompanyAccess;
|
|
10
9
|
assignRoleActions(dto: AssignRoleActionsDto): Promise<PermissionOperationResultDto>;
|
|
11
10
|
getRoleActions(dto: GetRoleActionsDto): Promise<SingleResponseDto<RoleActionResponseDto[]>>;
|
|
12
11
|
assignUserRoles(dto: AssignUserRolesDto, user: ILoggedUserInfo): Promise<PermissionOperationResultDto>;
|
|
@@ -6,7 +6,6 @@ export declare class UserActionPermissionController {
|
|
|
6
6
|
private readonly permissionService;
|
|
7
7
|
private readonly config;
|
|
8
8
|
constructor(permissionService: PermissionService, config: IAMConfigService);
|
|
9
|
-
private validateCompanyAccess;
|
|
10
9
|
assignUserActions(dto: AssignUserActionsDto, user: ILoggedUserInfo): Promise<PermissionOperationResultDto>;
|
|
11
10
|
getUserActions(dto: GetUserActionsDto, user: ILoggedUserInfo): Promise<SingleResponseDto<UserActionResponseDto[]>>;
|
|
12
11
|
}
|
package/dtos/action.dto.d.ts
CHANGED
|
@@ -37,10 +37,6 @@ export declare class ActionResponseDto {
|
|
|
37
37
|
export declare class ActionTreeDto extends ActionResponseDto {
|
|
38
38
|
children: ActionTreeDto[];
|
|
39
39
|
}
|
|
40
|
-
export declare class ActionQueryDto {
|
|
41
|
-
isActive?: boolean;
|
|
42
|
-
parentId?: string;
|
|
43
|
-
}
|
|
44
40
|
export declare class ActionTreeQueryDto {
|
|
45
41
|
search?: string;
|
|
46
42
|
isActive?: boolean;
|
package/dtos/role.dto.d.ts
CHANGED
|
@@ -10,10 +10,6 @@ declare const UpdateRoleDto_base: import("@nestjs/common").Type<Partial<CreateRo
|
|
|
10
10
|
export declare class UpdateRoleDto extends UpdateRoleDto_base {
|
|
11
11
|
id: string;
|
|
12
12
|
}
|
|
13
|
-
export declare class RoleQueryDto {
|
|
14
|
-
companyId?: string;
|
|
15
|
-
isActive?: boolean;
|
|
16
|
-
}
|
|
17
13
|
export declare class RoleResponseDto {
|
|
18
14
|
id: string;
|
|
19
15
|
readOnly: boolean;
|
|
@@ -25,26 +25,16 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
28
|
+
import { COMPANY_ACTION_PERMISSIONS, CurrentUser, ILoggedUserInfo, JwtAuthGuard, RequirePermission, SingleResponseDto } from '@flusys/nestjs-shared';
|
|
29
|
+
import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
|
|
30
30
|
import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
31
31
|
import { AssignCompanyActionsDto, GetCompanyActionsDto, PermissionOperationResultDto } from '../dtos/permission.dto';
|
|
32
32
|
import { PermissionService } from '../services/permission.service';
|
|
33
|
-
import { IAMConfigService } from '../services/iam-config.service';
|
|
34
33
|
export class CompanyActionPermissionController {
|
|
35
|
-
/** Validates that user can only manage permissions for their own company */ validateCompanyAccess(companyId, user) {
|
|
36
|
-
if (this.config.isCompanyFeatureEnabled() && user.companyId) {
|
|
37
|
-
if (companyId !== user.companyId) {
|
|
38
|
-
throw new BadRequestException('Cannot manage permissions for another company');
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
34
|
async assignCompanyActions(dto, user) {
|
|
43
|
-
this.validateCompanyAccess(dto.companyId, user);
|
|
44
35
|
return this.permissionService.assignCompanyActions(dto);
|
|
45
36
|
}
|
|
46
37
|
async getCompanyActions(dto, user) {
|
|
47
|
-
this.validateCompanyAccess(dto.companyId, user);
|
|
48
38
|
const actions = await this.permissionService.getCompanyActions(dto.companyId);
|
|
49
39
|
return {
|
|
50
40
|
success: true,
|
|
@@ -52,12 +42,9 @@ export class CompanyActionPermissionController {
|
|
|
52
42
|
data: actions
|
|
53
43
|
};
|
|
54
44
|
}
|
|
55
|
-
|
|
56
|
-
constructor(permissionService, config){
|
|
45
|
+
constructor(permissionService){
|
|
57
46
|
_define_property(this, "permissionService", void 0);
|
|
58
|
-
_define_property(this, "config", void 0);
|
|
59
47
|
this.permissionService = permissionService;
|
|
60
|
-
this.config = config;
|
|
61
48
|
}
|
|
62
49
|
}
|
|
63
50
|
_ts_decorate([
|
|
@@ -112,10 +99,8 @@ CompanyActionPermissionController = _ts_decorate([
|
|
|
112
99
|
UseGuards(JwtAuthGuard),
|
|
113
100
|
ApiBearerAuth(),
|
|
114
101
|
_ts_param(0, Inject(PermissionService)),
|
|
115
|
-
_ts_param(1, Inject(IAMConfigService)),
|
|
116
102
|
_ts_metadata("design:type", Function),
|
|
117
103
|
_ts_metadata("design:paramtypes", [
|
|
118
|
-
typeof PermissionService === "undefined" ? Object : PermissionService
|
|
119
|
-
typeof IAMConfigService === "undefined" ? Object : IAMConfigService
|
|
104
|
+
typeof PermissionService === "undefined" ? Object : PermissionService
|
|
120
105
|
])
|
|
121
106
|
], CompanyActionPermissionController);
|
|
@@ -25,8 +25,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { CurrentUser, ILoggedUserInfo } from '@flusys/nestjs-shared';
|
|
29
|
-
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
28
|
+
import { CurrentUser, ILoggedUserInfo, JwtAuthGuard } from '@flusys/nestjs-shared';
|
|
30
29
|
import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
|
|
31
30
|
import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
32
31
|
import { MyPermissionsQueryDto, MyPermissionsResponseDto } from '../dtos/permission.dto';
|
|
@@ -26,19 +26,13 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { JwtAuthGuard, SingleResponseDto, RequirePermission, ROLE_ACTION_PERMISSIONS, USER_ROLE_PERMISSIONS, CurrentUser, ILoggedUserInfo } from '@flusys/nestjs-shared';
|
|
29
|
-
import {
|
|
29
|
+
import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
|
|
30
30
|
import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
31
31
|
import { AssignRoleActionsDto, AssignUserRolesDto, GetRoleActionsDto, GetUserRolesDto, PermissionOperationResultDto } from '../dtos/permission.dto';
|
|
32
|
+
import { validateCompanyAccess } from '../helpers';
|
|
32
33
|
import { PermissionService } from '../services/permission.service';
|
|
33
34
|
import { IAMConfigService } from '../services/iam-config.service';
|
|
34
35
|
export class RolePermissionController {
|
|
35
|
-
/** Validates that user can only manage permissions within their company */ validateCompanyAccess(companyId, user) {
|
|
36
|
-
if (this.config.isCompanyFeatureEnabled() && user.companyId && companyId) {
|
|
37
|
-
if (companyId !== user.companyId) {
|
|
38
|
-
throw new BadRequestException('Cannot manage permissions for users in another company');
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
36
|
async assignRoleActions(dto) {
|
|
43
37
|
return this.permissionService.assignRoleActions(dto);
|
|
44
38
|
}
|
|
@@ -51,11 +45,11 @@ export class RolePermissionController {
|
|
|
51
45
|
};
|
|
52
46
|
}
|
|
53
47
|
async assignUserRoles(dto, user) {
|
|
54
|
-
this.
|
|
48
|
+
validateCompanyAccess(this.config, dto.companyId, user, 'Cannot manage permissions for users in another company');
|
|
55
49
|
return this.permissionService.assignUserRoles(dto);
|
|
56
50
|
}
|
|
57
51
|
async getUserRoles(dto, user) {
|
|
58
|
-
this.
|
|
52
|
+
validateCompanyAccess(this.config, dto.companyId, user, 'Cannot manage permissions for users in another company');
|
|
59
53
|
const roles = await this.permissionService.getUserRoles(dto.userId, dto.branchId, dto.companyId);
|
|
60
54
|
return {
|
|
61
55
|
success: true,
|