@nu-art/permissions-backend 0.400.5
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/PermissionKey_BE.d.ts +13 -0
- package/PermissionKey_BE.js +48 -0
- package/_entity/permission-access-level/ModuleBE_PermissionAccessLevelDB.d.ts +18 -0
- package/_entity/permission-access-level/ModuleBE_PermissionAccessLevelDB.js +55 -0
- package/_entity/permission-access-level/index.d.ts +2 -0
- package/_entity/permission-access-level/index.js +2 -0
- package/_entity/permission-access-level/module-pack.d.ts +1 -0
- package/_entity/permission-access-level/module-pack.js +3 -0
- package/_entity/permission-api/ModuleBE_PermissionAPIDB.d.ts +12 -0
- package/_entity/permission-api/ModuleBE_PermissionAPIDB.js +62 -0
- package/_entity/permission-api/index.d.ts +2 -0
- package/_entity/permission-api/index.js +2 -0
- package/_entity/permission-api/module-pack.d.ts +1 -0
- package/_entity/permission-api/module-pack.js +3 -0
- package/_entity/permission-domain/ModuleBE_PermissionDomainDB.d.ts +15 -0
- package/_entity/permission-domain/ModuleBE_PermissionDomainDB.js +25 -0
- package/_entity/permission-domain/index.d.ts +2 -0
- package/_entity/permission-domain/index.js +2 -0
- package/_entity/permission-domain/module-pack.d.ts +1 -0
- package/_entity/permission-domain/module-pack.js +3 -0
- package/_entity/permission-group/ModuleBE_PermissionGroupDB.d.ts +14 -0
- package/_entity/permission-group/ModuleBE_PermissionGroupDB.js +62 -0
- package/_entity/permission-group/index.d.ts +2 -0
- package/_entity/permission-group/index.js +2 -0
- package/_entity/permission-group/module-pack.d.ts +1 -0
- package/_entity/permission-group/module-pack.js +3 -0
- package/_entity/permission-project/ModuleBE_PermissionProjectDB.d.ts +10 -0
- package/_entity/permission-project/ModuleBE_PermissionProjectDB.js +12 -0
- package/_entity/permission-project/index.d.ts +2 -0
- package/_entity/permission-project/index.js +2 -0
- package/_entity/permission-project/module-pack.d.ts +1 -0
- package/_entity/permission-project/module-pack.js +3 -0
- package/_entity/permission-user/ModuleBE_PermissionUserAPI.d.ts +8 -0
- package/_entity/permission-user/ModuleBE_PermissionUserAPI.js +13 -0
- package/_entity/permission-user/ModuleBE_PermissionUserDB.d.ts +36 -0
- package/_entity/permission-user/ModuleBE_PermissionUserDB.js +222 -0
- package/_entity/permission-user/index.d.ts +3 -0
- package/_entity/permission-user/index.js +3 -0
- package/_entity/permission-user/module-pack.d.ts +2 -0
- package/_entity/permission-user/module-pack.js +3 -0
- package/_entity.d.ts +12 -0
- package/_entity.js +18 -0
- package/consts.d.ts +7 -0
- package/consts.js +5 -0
- package/core/module-pack.d.ts +3 -0
- package/core/module-pack.js +32 -0
- package/core/utils.d.ts +25 -0
- package/core/utils.js +85 -0
- package/index.d.ts +4 -0
- package/index.js +22 -0
- package/modules/ModuleBE_Permissions.d.ts +77 -0
- package/modules/ModuleBE_Permissions.js +357 -0
- package/modules/ModuleBE_PermissionsAssert.d.ts +48 -0
- package/modules/ModuleBE_PermissionsAssert.js +242 -0
- package/modules/consts.d.ts +11 -0
- package/modules/consts.js +29 -0
- package/modules/index.d.ts +1 -0
- package/modules/index.js +19 -0
- package/package.json +85 -0
- package/permissions.d.ts +22 -0
- package/permissions.js +154 -0
- package/shared.d.ts +1 -0
- package/shared.js +19 -0
- package/types.d.ts +28 -0
- package/types.js +1 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { _keys, arrayToMap, Dispatcher, filterInstances, flatArray, Module, MUSTNeverHappenException, reduceToMap, RuntimeModules, } from '@nu-art/ts-common';
|
|
2
|
+
import { addRoutes, createQueryServerApi, MemKey_ServerApi, ModuleBE_AppConfigDB, Storm } from '@nu-art/thunderstorm-backend';
|
|
3
|
+
import { ApiDef_Permissions, DefaultAccessLevel_Admin, DefaultAccessLevel_NoAccess, DefaultAccessLevel_Read, DefaultAccessLevel_Write, defaultLevelsRouteLookupWords, DuplicateDefaultAccessLevels, } from '@nu-art/permissions-shared';
|
|
4
|
+
import { MemKey_AccountId, ModuleBE_SessionDB } from '@nu-art/user-account-backend';
|
|
5
|
+
import { Domain_AccountManagement, Domain_Developer, Domain_PermissionsAssign, Domain_PermissionsDefine, PermissionsPackage_Developer, PermissionsPackage_Permissions } from '../permissions.js';
|
|
6
|
+
import { ModuleBE_PermissionsAssert } from './ModuleBE_PermissionsAssert.js';
|
|
7
|
+
import { ModuleBE_PermissionAccessLevelDB, ModuleBE_PermissionAPIDB, ModuleBE_PermissionDomainDB, ModuleBE_PermissionGroupDB, ModuleBE_PermissionProjectDB, ModuleBE_PermissionUserDB } from '../_entity.js';
|
|
8
|
+
import { trimStartingForwardSlash } from '@nu-art/thunderstorm-shared/route-tools';
|
|
9
|
+
const dispatcher_collectPermissionsProjects = new Dispatcher('__collectPermissionsProjects');
|
|
10
|
+
const GroupId_SuperAdmin = '8b54efda69b385a566735cca7be031d5';
|
|
11
|
+
export const PermissionGroup_Permissions_SuperAdmin = {
|
|
12
|
+
_id: GroupId_SuperAdmin,
|
|
13
|
+
name: 'Super Admin',
|
|
14
|
+
uiLabel: 'Super Admin',
|
|
15
|
+
accessLevels: {
|
|
16
|
+
[Domain_PermissionsDefine.namespace]: DefaultAccessLevel_Admin.name,
|
|
17
|
+
[Domain_PermissionsAssign.namespace]: DefaultAccessLevel_Admin.name,
|
|
18
|
+
[Domain_AccountManagement.namespace]: DefaultAccessLevel_Admin.name,
|
|
19
|
+
[Domain_Developer.namespace]: DefaultAccessLevel_Admin.name,
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const PermissionGroup_Permissions_Viewer = {
|
|
23
|
+
_id: '8c38d3bd2d76bbc37b5281f481c0bc1b',
|
|
24
|
+
name: 'Permissions Viewer',
|
|
25
|
+
uiLabel: 'Permissions Viewer',
|
|
26
|
+
accessLevels: {
|
|
27
|
+
[Domain_AccountManagement.namespace]: DefaultAccessLevel_Read.name,
|
|
28
|
+
[Domain_PermissionsDefine.namespace]: DefaultAccessLevel_Read.name,
|
|
29
|
+
[Domain_PermissionsAssign.namespace]: DefaultAccessLevel_Read.name,
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export const PermissionGroup_Permissions_Editor = {
|
|
33
|
+
_id: '1524909cae174d0052b76a469b339218',
|
|
34
|
+
name: 'Permissions Editor',
|
|
35
|
+
uiLabel: 'Permissions Editor',
|
|
36
|
+
accessLevels: {
|
|
37
|
+
[Domain_AccountManagement.namespace]: DefaultAccessLevel_Read.name,
|
|
38
|
+
[Domain_PermissionsDefine.namespace]: DefaultAccessLevel_Read.name,
|
|
39
|
+
[Domain_PermissionsAssign.namespace]: DefaultAccessLevel_Write.name,
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
export const PermissionGroup_Account_Manager = {
|
|
43
|
+
_id: '6bb5feb12d0712ecee77f7f44188ec79',
|
|
44
|
+
name: 'Accounts Manager',
|
|
45
|
+
uiLabel: 'Accounts Manager',
|
|
46
|
+
accessLevels: {
|
|
47
|
+
[Domain_AccountManagement.namespace]: DefaultAccessLevel_Write.name,
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export const PermissionGroup_Account_Admin = {
|
|
51
|
+
_id: '761a84bdde3f9be3fde9c50402a60401',
|
|
52
|
+
name: 'Accounts Admin',
|
|
53
|
+
uiLabel: 'Accounts Admin',
|
|
54
|
+
accessLevels: {
|
|
55
|
+
[Domain_AccountManagement.namespace]: DefaultAccessLevel_Admin.name,
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
export const PermissionGroup_Account_Viewer = {
|
|
59
|
+
_id: '7343853a980149ec94f967ac7ff4ccc3',
|
|
60
|
+
name: 'Accounts Viewer',
|
|
61
|
+
uiLabel: 'Accounts Viewer',
|
|
62
|
+
accessLevels: {
|
|
63
|
+
[Domain_AccountManagement.namespace]: DefaultAccessLevel_Read.name,
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export const PermissionGroups_Permissions = [
|
|
67
|
+
PermissionGroup_Permissions_SuperAdmin,
|
|
68
|
+
PermissionGroup_Permissions_Viewer,
|
|
69
|
+
PermissionGroup_Permissions_Editor,
|
|
70
|
+
PermissionGroup_Account_Manager,
|
|
71
|
+
PermissionGroup_Account_Admin,
|
|
72
|
+
PermissionGroup_Account_Viewer,
|
|
73
|
+
// {
|
|
74
|
+
// _id: '60a417683e4016f4d933fee88953f0d5',
|
|
75
|
+
// name: 'Permissions Read Self',
|
|
76
|
+
// accessLevels: {
|
|
77
|
+
// [Domain_PermissionsDefine.namespace]: PermissionsAccessLevel_ReadSelf.name,
|
|
78
|
+
// [Domain_PermissionsAssign.namespace]: PermissionsAccessLevel_ReadSelf.name,
|
|
79
|
+
// }
|
|
80
|
+
// },
|
|
81
|
+
];
|
|
82
|
+
export const PermissionProject_Permissions = {
|
|
83
|
+
_id: 'f60db83936835e0be33e89caa365f0c3',
|
|
84
|
+
name: 'Permissions',
|
|
85
|
+
packages: [PermissionsPackage_Permissions, PermissionsPackage_Developer],
|
|
86
|
+
groups: PermissionGroups_Permissions
|
|
87
|
+
};
|
|
88
|
+
class ModuleBE_Permissions_Class extends Module {
|
|
89
|
+
init() {
|
|
90
|
+
super.init();
|
|
91
|
+
addRoutes([
|
|
92
|
+
createQueryServerApi(ApiDef_Permissions.v1.toggleStrictMode, this.toggleStrictMode),
|
|
93
|
+
createQueryServerApi(ApiDef_Permissions.v1.createProject, this.__performProjectSetup().processor),
|
|
94
|
+
// createBodyServerApi(ApiDef_Permissions.v1.connectDomainToRoutes, this.connectDomainToRoutes)
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
// __collectPermissionsProjects() {
|
|
98
|
+
// return PermissionProject_Permissions;
|
|
99
|
+
// }
|
|
100
|
+
async __collectSessionData(data) {
|
|
101
|
+
const permissionUser = await ModuleBE_PermissionUserDB.query.uniqueAssert(data.accountId);
|
|
102
|
+
const userGroups = filterInstances(await ModuleBE_PermissionGroupDB.query.all(permissionUser.groups.map(g => g.groupId)));
|
|
103
|
+
const permissionMap = await this.getUserPermissionMap(userGroups);
|
|
104
|
+
return {
|
|
105
|
+
key: 'permissions', value: {
|
|
106
|
+
domainToValueMap: permissionMap,
|
|
107
|
+
roles: userGroups.map(group => ({ key: group.label, uiLabel: group.uiLabel })),
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
getUserPermissionMap = async (userGroups) => {
|
|
112
|
+
const permissionMap = {};
|
|
113
|
+
const levelMaps = filterInstances(userGroups.map(i => i._levelsMap));
|
|
114
|
+
levelMaps.forEach(levelMap => {
|
|
115
|
+
_keys(levelMap).forEach(domainId => {
|
|
116
|
+
if (!permissionMap[domainId])
|
|
117
|
+
permissionMap[domainId] = 0;
|
|
118
|
+
if (levelMap[domainId] > permissionMap[domainId])
|
|
119
|
+
permissionMap[domainId] = levelMap[domainId];
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//All domains that are not defined for the user, are NoAccess by default.
|
|
123
|
+
const allDomains = await ModuleBE_PermissionDomainDB.query.where({});
|
|
124
|
+
allDomains.forEach(domain => {
|
|
125
|
+
if (!permissionMap[domain._id])
|
|
126
|
+
permissionMap[domain._id] = DefaultAccessLevel_NoAccess.value; //"fill in the gaps" - All domains that are not defined for the user, are NoAccess by default.
|
|
127
|
+
});
|
|
128
|
+
return permissionMap;
|
|
129
|
+
};
|
|
130
|
+
toggleStrictMode = async () => {
|
|
131
|
+
MemKey_ServerApi.get().addPostCallAction(async () => {
|
|
132
|
+
const envConfigRef = Storm.getInstance().getEnvConfigRef(ModuleBE_PermissionsAssert);
|
|
133
|
+
const currentConfig = await envConfigRef.get({});
|
|
134
|
+
currentConfig.strictMode = !currentConfig.strictMode;
|
|
135
|
+
await envConfigRef.set(currentConfig);
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
__performProjectSetup() {
|
|
139
|
+
return {
|
|
140
|
+
priority: 0,
|
|
141
|
+
processor: async () => {
|
|
142
|
+
const projects = dispatcher_collectPermissionsProjects.dispatchModule();
|
|
143
|
+
projects.reduce((issues, project) => {
|
|
144
|
+
return project.packages.reduce((issues, _package) => {
|
|
145
|
+
return issues;
|
|
146
|
+
}, issues);
|
|
147
|
+
}, []);
|
|
148
|
+
// Create All Projects
|
|
149
|
+
await this.createPermissionProjects(projects);
|
|
150
|
+
// Create all AppConfigs
|
|
151
|
+
await this.createPermissionsKeys(projects);
|
|
152
|
+
//Assign Super Admin if necessary
|
|
153
|
+
await this.assignSuperAdmin();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
;
|
|
158
|
+
async createPermissionProjects(projects) {
|
|
159
|
+
const map_nameToDBProject = await this.createProjects(projects);
|
|
160
|
+
const map_nameToDbDomain = await this.createDomains(projects, map_nameToDBProject);
|
|
161
|
+
const domainNameToLevelNameToDBAccessLevel = await this.createAccessLevels(projects, map_nameToDbDomain);
|
|
162
|
+
await this.createGroups(projects, map_nameToDbDomain, domainNameToLevelNameToDBAccessLevel);
|
|
163
|
+
await this.createApis(projects, domainNameToLevelNameToDBAccessLevel);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Creates All the DB_PermissionProject
|
|
167
|
+
*
|
|
168
|
+
* @param projects - predefined permissions projects
|
|
169
|
+
*/
|
|
170
|
+
async createProjects(projects) {
|
|
171
|
+
this.logInfoBold('Creating Projects');
|
|
172
|
+
const _auditorId = MemKey_AccountId.get();
|
|
173
|
+
const preDBProjects = await ModuleBE_PermissionProjectDB.set.all(projects.map(project => ({
|
|
174
|
+
_id: project._id,
|
|
175
|
+
name: project.name,
|
|
176
|
+
_auditorId
|
|
177
|
+
})));
|
|
178
|
+
const projectsMap_nameToDBProject = reduceToMap(preDBProjects, project => project.name, project => project);
|
|
179
|
+
this.logInfoBold(`Created ${preDBProjects.length} Projects`);
|
|
180
|
+
return projectsMap_nameToDBProject;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Creates All the DB_PermissionDomains
|
|
184
|
+
*
|
|
185
|
+
* @param projects - predefined permissions projects
|
|
186
|
+
* @param map_nameToDBProject
|
|
187
|
+
*/
|
|
188
|
+
async createDomains(projects, map_nameToDBProject) {
|
|
189
|
+
this.logInfoBold('Creating Domains');
|
|
190
|
+
const _auditorId = MemKey_AccountId.get();
|
|
191
|
+
const domainsToUpsert = flatArray(projects.map(project => project.packages.map(_package => _package.domains.map(domain => ({
|
|
192
|
+
_id: domain._id,
|
|
193
|
+
namespace: domain.namespace,
|
|
194
|
+
projectId: map_nameToDBProject[project.name]._id,
|
|
195
|
+
_auditorId
|
|
196
|
+
})))));
|
|
197
|
+
const dbDomain = await ModuleBE_PermissionDomainDB.set.all(domainsToUpsert);
|
|
198
|
+
const domainsMap_nameToDbDomain = reduceToMap(dbDomain, domain => domain.namespace, domain => domain);
|
|
199
|
+
this.logInfoBold(`Created ${dbDomain.length} Domains`);
|
|
200
|
+
return domainsMap_nameToDbDomain;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Creates All the DB_PermissionAccessLevel
|
|
204
|
+
*
|
|
205
|
+
* @param projects - predefined permissions projects
|
|
206
|
+
* @param map_nameToDbDomain
|
|
207
|
+
*/
|
|
208
|
+
async createAccessLevels(projects, map_nameToDbDomain) {
|
|
209
|
+
this.logInfoBold('Creating Access Levels');
|
|
210
|
+
const _auditorId = MemKey_AccountId.get();
|
|
211
|
+
const levelsToUpsert = flatArray(projects.map(project => project.packages.map(_package => _package.domains.map(domain => {
|
|
212
|
+
let levels = domain.levels;
|
|
213
|
+
if (!levels)
|
|
214
|
+
levels = DuplicateDefaultAccessLevels(domain._id);
|
|
215
|
+
return levels.map(level => {
|
|
216
|
+
return {
|
|
217
|
+
_id: level._id,
|
|
218
|
+
domainId: map_nameToDbDomain[domain.namespace]._id,
|
|
219
|
+
value: level.value,
|
|
220
|
+
name: level.name,
|
|
221
|
+
uiLabel: level.name,
|
|
222
|
+
_auditorId
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
}))));
|
|
226
|
+
const dbLevels = await ModuleBE_PermissionAccessLevelDB.set.all(levelsToUpsert);
|
|
227
|
+
const domainNameToLevelNameToDBAccessLevel = reduceToMap(dbLevels, level => level.domainId, (level, index, map) => {
|
|
228
|
+
const domainLevels = map[level.domainId] || (map[level.domainId] = {});
|
|
229
|
+
domainLevels[level.name] = level;
|
|
230
|
+
return domainLevels;
|
|
231
|
+
});
|
|
232
|
+
this.logInfoBold(`Created ${dbLevels.length} Access Levels`);
|
|
233
|
+
return domainNameToLevelNameToDBAccessLevel;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Creates All the DB_PermissionGroup
|
|
237
|
+
*
|
|
238
|
+
* @param projects - predefined permissions projects
|
|
239
|
+
* @param map_nameToDbDomain
|
|
240
|
+
* @param domainNameToLevelNameToDBAccessLevel
|
|
241
|
+
*/
|
|
242
|
+
async createGroups(projects, map_nameToDbDomain, domainNameToLevelNameToDBAccessLevel) {
|
|
243
|
+
this.logInfoBold('Creating Groups');
|
|
244
|
+
const _auditorId = MemKey_AccountId.get();
|
|
245
|
+
const groupsToUpsert = flatArray(projects.map(project => {
|
|
246
|
+
const groupsDef = flatArray([...project.packages.map(p => p.groups || []), ...project.groups || []]);
|
|
247
|
+
return (groupsDef).map(group => {
|
|
248
|
+
return {
|
|
249
|
+
projectId: project._id,
|
|
250
|
+
_id: group._id,
|
|
251
|
+
_auditorId,
|
|
252
|
+
label: group.name,
|
|
253
|
+
uiLabel: group.name,
|
|
254
|
+
accessLevelIds: _keys(group.accessLevels)
|
|
255
|
+
.map(key => {
|
|
256
|
+
const domainsMapNameToDbDomainElement = map_nameToDbDomain[key];
|
|
257
|
+
if (!domainsMapNameToDbDomainElement)
|
|
258
|
+
throw new MUSTNeverHappenException(`bah for key ${key}`);
|
|
259
|
+
return domainNameToLevelNameToDBAccessLevel[domainsMapNameToDbDomainElement._id][group.accessLevels[key]]._id;
|
|
260
|
+
})
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}));
|
|
264
|
+
const dbGroups = await ModuleBE_PermissionGroupDB.set.all(groupsToUpsert);
|
|
265
|
+
this.logInfoBold(`Created ${dbGroups.length} Groups`);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Creates All the DB_PermissionApi
|
|
269
|
+
*
|
|
270
|
+
* @param projects - predefined permissions projects
|
|
271
|
+
* @param domainNameToLevelNameToDBAccessLevel
|
|
272
|
+
*/
|
|
273
|
+
async createApis(projects, domainNameToLevelNameToDBAccessLevel) {
|
|
274
|
+
this.logInfoBold('Creating APIs');
|
|
275
|
+
const _auditorId = MemKey_AccountId.get();
|
|
276
|
+
const apisToUpsert = flatArray(projects.map(project => {
|
|
277
|
+
return project.packages.map(_package => _package.domains.map(domain => {
|
|
278
|
+
const apis = [];
|
|
279
|
+
apis.push(...(domain.customApis || []).map(api => ({
|
|
280
|
+
projectId: project._id,
|
|
281
|
+
path: trimStartingForwardSlash(api.path),
|
|
282
|
+
_auditorId,
|
|
283
|
+
accessLevelIds: [domainNameToLevelNameToDBAccessLevel[api.domainId ?? domain._id][api.accessLevel]._id]
|
|
284
|
+
})));
|
|
285
|
+
const apiModules = arrayToMap(RuntimeModules()
|
|
286
|
+
.filter((module) => !!module.apiDef && !!module.dbModule?.dbDef?.dbKey), item => item.dbModule.dbDef.dbKey);
|
|
287
|
+
this.logDebug(_keys(apiModules));
|
|
288
|
+
// / I think there is a bug here... comment it and see what happens
|
|
289
|
+
const _apis = (domain.dbNames || []).map(dbName => {
|
|
290
|
+
const apiModule = apiModules[dbName];
|
|
291
|
+
if (!apiModule)
|
|
292
|
+
throw new MUSTNeverHappenException(`Could not find api module with dbName: ${dbName}`);
|
|
293
|
+
const _apiDefs = apiModule.apiDef;
|
|
294
|
+
return _keys(_apiDefs).map(_apiDefKey => {
|
|
295
|
+
const apiDefs = _apiDefs[_apiDefKey];
|
|
296
|
+
return filterInstances(_keys(apiDefs).map(apiDefKey => {
|
|
297
|
+
const apiDef = apiDefs[apiDefKey];
|
|
298
|
+
const accessLevelNameToAssign = defaultLevelsRouteLookupWords[apiDef.path.substring(apiDef.path.lastIndexOf('/') + 1)];
|
|
299
|
+
const accessLevel = domainNameToLevelNameToDBAccessLevel[domain._id][accessLevelNameToAssign];
|
|
300
|
+
if (!accessLevel)
|
|
301
|
+
return;
|
|
302
|
+
const accessId = accessLevel._id;
|
|
303
|
+
return {
|
|
304
|
+
projectId: project._id,
|
|
305
|
+
path: apiDef.path,
|
|
306
|
+
_auditorId,
|
|
307
|
+
accessLevelIds: [accessId]
|
|
308
|
+
};
|
|
309
|
+
}));
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
apis.push(...flatArray(_apis));
|
|
313
|
+
return apis;
|
|
314
|
+
}));
|
|
315
|
+
}));
|
|
316
|
+
const dbApis = await ModuleBE_PermissionAPIDB.set.all(apisToUpsert);
|
|
317
|
+
this.logInfoBold(`Created ${dbApis.length} APIs`);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Creates permission keys associated with the given projects.
|
|
321
|
+
*
|
|
322
|
+
* @param projects - An array of projects.
|
|
323
|
+
*/
|
|
324
|
+
async createPermissionsKeys(projects) {
|
|
325
|
+
this.logInfoBold('Creating App Config');
|
|
326
|
+
// const permissionKeysToCreate: PermissionKey_BE<any>[] = filterInstances(flatArray(projects.map(project => project.packages.map(_package => _package.domains.map(domain => domain.permissionKeys)))));
|
|
327
|
+
try {
|
|
328
|
+
await ModuleBE_AppConfigDB.createDefaults(this);
|
|
329
|
+
this.logInfoBold('Created Permission Key defaults.');
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
this.logErrorBold('Failed creating Permission Key defaults.', e);
|
|
333
|
+
}
|
|
334
|
+
this.logInfoBold('Created App Config');
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* If no "Super Admin" user is defined in the system!
|
|
338
|
+
* The first user to press the create project button will become the "Super Admin" of the system
|
|
339
|
+
*
|
|
340
|
+
* If a "Super Admin" already exists in the system, a 403 will be thrown
|
|
341
|
+
*/
|
|
342
|
+
assignSuperAdmin = async () => {
|
|
343
|
+
this.logInfoBold('Assigning SuperAdmin permissions');
|
|
344
|
+
const existingSuperAdmin = (await ModuleBE_PermissionUserDB.query.custom({
|
|
345
|
+
where: { __groupIds: { $ac: GroupId_SuperAdmin } },
|
|
346
|
+
limit: 1
|
|
347
|
+
}))[0];
|
|
348
|
+
if (existingSuperAdmin)
|
|
349
|
+
return;
|
|
350
|
+
const currentUser = await ModuleBE_PermissionUserDB.query.uniqueAssert(MemKey_AccountId.get());
|
|
351
|
+
(currentUser.groups || (currentUser.groups = [])).push({ groupId: GroupId_SuperAdmin });
|
|
352
|
+
await ModuleBE_PermissionUserDB.set.item(currentUser);
|
|
353
|
+
await ModuleBE_SessionDB._session.rotate.reissue.bySession();
|
|
354
|
+
this.logInfoBold('Assigned SuperAdmin permissions');
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
export const ModuleBE_Permissions = new ModuleBE_Permissions_Class();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Module, StringMap, TypedMap } from '@nu-art/ts-common';
|
|
2
|
+
import { ServerApi_Middleware } from '@nu-art/thunderstorm-backend';
|
|
3
|
+
import { CollectSessionData } from '@nu-art/user-account-backend';
|
|
4
|
+
import { Base_AccessLevel, DB_PermissionAccessLevel, DB_PermissionAPI, DomainToLevelValueMap, SessionData_StrictMode } from '@nu-art/permissions-shared';
|
|
5
|
+
import { PermissionKey_BE } from '../PermissionKey_BE.js';
|
|
6
|
+
export type UserCalculatedAccessLevel = {
|
|
7
|
+
[domainId: string]: number;
|
|
8
|
+
};
|
|
9
|
+
export type GroupPairWithBaseLevelsObj = {
|
|
10
|
+
accessLevels: Base_AccessLevel[];
|
|
11
|
+
};
|
|
12
|
+
export type RequestPairWithLevelsObj = {
|
|
13
|
+
accessLevels: DB_PermissionAccessLevel[];
|
|
14
|
+
};
|
|
15
|
+
type Config = {
|
|
16
|
+
strictMode?: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* [DomainId uniqueString]: accessLevel's numerical value
|
|
20
|
+
*/
|
|
21
|
+
export declare class ModuleBE_PermissionsAssert_Class extends Module<Config> implements CollectSessionData<SessionData_StrictMode> {
|
|
22
|
+
private projectId;
|
|
23
|
+
_keys: TypedMap<boolean>;
|
|
24
|
+
permissionKeys: TypedMap<PermissionKey_BE<any>>;
|
|
25
|
+
readonly Middleware: (keys?: string[]) => ServerApi_Middleware;
|
|
26
|
+
readonly LoadPermissionsMiddleware: ServerApi_Middleware;
|
|
27
|
+
readonly CustomMiddleware: (keys: string[], action: (projectId: string, customFields: StringMap) => Promise<void>) => ServerApi_Middleware;
|
|
28
|
+
__collectSessionData(): Promise<SessionData_StrictMode>;
|
|
29
|
+
init(): void;
|
|
30
|
+
private assertPermission;
|
|
31
|
+
assertUserPermissions(projectId: string, path: string): Promise<void>;
|
|
32
|
+
assertUserPassesAccessLevels(domainToLevelValueMap: DomainToLevelValueMap, userPermissions: TypedMap<number>): void;
|
|
33
|
+
getApiDetails(_path: string, projectId: string): Promise<{
|
|
34
|
+
dbApi: DB_PermissionAPI;
|
|
35
|
+
requestPermissions: DB_PermissionAccessLevel[];
|
|
36
|
+
} | undefined>;
|
|
37
|
+
getApisDetails(urls: string[], projectId: string): Promise<({
|
|
38
|
+
apiDb: DB_PermissionAPI;
|
|
39
|
+
requestPermissions: DB_PermissionAccessLevel[];
|
|
40
|
+
} | undefined)[]>;
|
|
41
|
+
private getAccessLevels;
|
|
42
|
+
setProjectId: (projectId: string) => void;
|
|
43
|
+
getRegEx(value: string): RegExp;
|
|
44
|
+
registerPermissionKeys(keys: string[]): void;
|
|
45
|
+
private isStrictMode;
|
|
46
|
+
}
|
|
47
|
+
export declare const ModuleBE_PermissionsAssert: ModuleBE_PermissionsAssert_Class;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Permissions management system, define access level for each of
|
|
3
|
+
* your server apis, and restrict users by giving them access levels
|
|
4
|
+
*
|
|
5
|
+
* Copyright (C) 2020 Adam van der Kruk aka TacB0sS
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { _keys, ApiException, arrayToMap, BadImplementationException, batchActionParallel, exists, filterDuplicates, filterInstances, ImplementationMissingException, Module, RuntimeModules } from '@nu-art/ts-common';
|
|
20
|
+
import { addRoutes, createBodyServerApi, ModuleBE_SyncManager } from '@nu-art/thunderstorm-backend';
|
|
21
|
+
import { HttpMethod } from '@nu-art/thunderstorm-shared';
|
|
22
|
+
import { MemKey_AccountEmail } from '@nu-art/user-account-backend';
|
|
23
|
+
import { ApiDef_PermissionsAssert } from '@nu-art/permissions-shared';
|
|
24
|
+
import { MemKey_HttpRequestBody, MemKey_HttpRequestMethod, MemKey_HttpRequestQuery, MemKey_HttpRequestUrl } from '@nu-art/thunderstorm-backend/modules/server/consts';
|
|
25
|
+
import { MemKey_UserPermissions, SessionKey_Permissions_BE } from '../consts.js';
|
|
26
|
+
import { PermissionKey_BE } from '../PermissionKey_BE.js';
|
|
27
|
+
import { ModuleBE_PermissionAccessLevelDB, ModuleBE_PermissionAPIDB } from '../_entity.js';
|
|
28
|
+
/**
|
|
29
|
+
* [DomainId uniqueString]: accessLevel's numerical value
|
|
30
|
+
*/
|
|
31
|
+
export class ModuleBE_PermissionsAssert_Class extends Module {
|
|
32
|
+
projectId;
|
|
33
|
+
_keys = {};
|
|
34
|
+
permissionKeys = {};
|
|
35
|
+
// constructor() {
|
|
36
|
+
// super();
|
|
37
|
+
// this.setMinLevel(LogLevel.Debug);
|
|
38
|
+
// }
|
|
39
|
+
Middleware = (keys = []) => async () => {
|
|
40
|
+
await this.CustomMiddleware(keys, async (projectId) => {
|
|
41
|
+
return this.assertUserPermissions(projectId, MemKey_HttpRequestUrl.get());
|
|
42
|
+
})();
|
|
43
|
+
};
|
|
44
|
+
LoadPermissionsMiddleware = async () => {
|
|
45
|
+
try {
|
|
46
|
+
MemKey_UserPermissions.get();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
MemKey_UserPermissions.set(SessionKey_Permissions_BE.get().domainToValueMap);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
CustomMiddleware = (keys, action) => async () => {
|
|
53
|
+
const customFields = {};
|
|
54
|
+
let object;
|
|
55
|
+
const reqMethod = MemKey_HttpRequestMethod.get();
|
|
56
|
+
switch (reqMethod) {
|
|
57
|
+
case HttpMethod.POST:
|
|
58
|
+
case HttpMethod.PATCH:
|
|
59
|
+
case HttpMethod.PUT:
|
|
60
|
+
object = MemKey_HttpRequestBody.get();
|
|
61
|
+
break;
|
|
62
|
+
case HttpMethod.GET:
|
|
63
|
+
case HttpMethod.DELETE:
|
|
64
|
+
object = MemKey_HttpRequestQuery.get();
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
throw new BadImplementationException(`Generic custom fields cannot be extracted on api with method: ${reqMethod}`);
|
|
68
|
+
}
|
|
69
|
+
_keys(object).filter(key => keys.includes(key)).forEach(key => {
|
|
70
|
+
const oElement = object[key];
|
|
71
|
+
if (oElement === undefined || oElement === null)
|
|
72
|
+
return;
|
|
73
|
+
if (typeof oElement !== 'string')
|
|
74
|
+
return;
|
|
75
|
+
customFields[key] = oElement;
|
|
76
|
+
});
|
|
77
|
+
const projectId = this.projectId;
|
|
78
|
+
await action(projectId, customFields);
|
|
79
|
+
};
|
|
80
|
+
async __collectSessionData() {
|
|
81
|
+
return { key: 'strictMode', value: { isStrictMode: this.isStrictMode() } };
|
|
82
|
+
}
|
|
83
|
+
init() {
|
|
84
|
+
super.init();
|
|
85
|
+
addRoutes([createBodyServerApi(ApiDef_PermissionsAssert.vv1.assertUserPermissions, this.assertPermission)]);
|
|
86
|
+
_keys(this._keys).forEach(key => this.permissionKeys[key] = new PermissionKey_BE(key));
|
|
87
|
+
ModuleBE_SyncManager.setModuleFilter(async (dbModules) => {
|
|
88
|
+
// return dbModules;
|
|
89
|
+
//Filter out any module we don't have permission to sync
|
|
90
|
+
const userPermissions = MemKey_UserPermissions.get();
|
|
91
|
+
const mapDbNameToApiModules = arrayToMap(RuntimeModules()
|
|
92
|
+
.filter((module) => !!module.apiDef && !!module.dbModule?.dbDef?.dbKey), item => item.dbModule.dbDef.dbKey);
|
|
93
|
+
const paths = dbModules.map(module => {
|
|
94
|
+
const mapDbNameToApiModule = mapDbNameToApiModules[module.dbDef.dbKey];
|
|
95
|
+
if (!mapDbNameToApiModule) {
|
|
96
|
+
// this.logWarning(`no module found for ${module.dbDef.dbKey}`);
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return mapDbNameToApiModule.apiDef?.['v1']?.['query'].path;
|
|
100
|
+
});
|
|
101
|
+
// this.logWarning(`Paths(${paths.length}):`, paths);
|
|
102
|
+
const _allApis = await ModuleBE_PermissionAPIDB.query.where({});
|
|
103
|
+
const apis = _allApis.filter(_api => paths.includes(_api.path));
|
|
104
|
+
const mapPathToDBApi = arrayToMap(apis, api => api.path);
|
|
105
|
+
return dbModules.filter((dbModule, index) => {
|
|
106
|
+
const path = paths[index];
|
|
107
|
+
if (!path) {
|
|
108
|
+
// this.logWarningBold('no path');
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const dbApi = mapPathToDBApi[path];
|
|
112
|
+
if (!dbApi) {
|
|
113
|
+
// this.logWarningBold(`no dbApi ${path}`);
|
|
114
|
+
return !ModuleBE_PermissionsAssert.isStrictMode();
|
|
115
|
+
}
|
|
116
|
+
const accessLevels = dbApi._accessLevels;
|
|
117
|
+
return _keys(accessLevels).reduce((hasAccess, domainId) => {
|
|
118
|
+
if (!hasAccess)
|
|
119
|
+
return false;
|
|
120
|
+
const userDomainAccessValue = userPermissions[domainId];
|
|
121
|
+
const apiRequiredAccessValue = accessLevels[domainId];
|
|
122
|
+
if (!userDomainAccessValue)
|
|
123
|
+
return false;
|
|
124
|
+
if (!exists(userDomainAccessValue) || userDomainAccessValue < apiRequiredAccessValue) {
|
|
125
|
+
this.logErrorBold(`${(userDomainAccessValue ?? 0)} < ${apiRequiredAccessValue} === ${(userDomainAccessValue ?? 0) < apiRequiredAccessValue}`);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return hasAccess;
|
|
129
|
+
}, true);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
assertPermission = async (body) => {
|
|
134
|
+
await ModuleBE_PermissionsAssert.assertUserPermissions(body.projectId, body.path);
|
|
135
|
+
return { userId: MemKey_AccountEmail.get() };
|
|
136
|
+
};
|
|
137
|
+
async assertUserPermissions(projectId, path) {
|
|
138
|
+
// [DomainId]: accessLevel's numerical value
|
|
139
|
+
const userPermissions = MemKey_UserPermissions.get();
|
|
140
|
+
const apiDetails = await this.getApiDetails(path, projectId);
|
|
141
|
+
this.logDebug('______________________________');
|
|
142
|
+
this.logDebug(userPermissions);
|
|
143
|
+
this.logDebug('______________________________');
|
|
144
|
+
this.logDebug(apiDetails?.dbApi);
|
|
145
|
+
this.logDebug('______________________________');
|
|
146
|
+
if (!apiDetails || !apiDetails.dbApi.accessLevelIds) {
|
|
147
|
+
if (!this.config.strictMode)
|
|
148
|
+
return;
|
|
149
|
+
throw new ApiException(403, `No permissions configuration specified for api: ${projectId ? `${projectId}--` : ''}${path}`);
|
|
150
|
+
}
|
|
151
|
+
//_accessLevels is a map[domain id <> access level numeric value]
|
|
152
|
+
this.assertUserPassesAccessLevels(apiDetails.dbApi._accessLevels, userPermissions);
|
|
153
|
+
}
|
|
154
|
+
assertUserPassesAccessLevels(domainToLevelValueMap, userPermissions) {
|
|
155
|
+
_keys(domainToLevelValueMap).forEach(domainId => {
|
|
156
|
+
const userDomainPermission = userPermissions[domainId];
|
|
157
|
+
if (!exists(userDomainPermission))
|
|
158
|
+
throw new ApiException(403, 'Missing Access For This Domain');
|
|
159
|
+
if (userDomainPermission < domainToLevelValueMap[domainId]) {
|
|
160
|
+
this.logErrorBold(`for domain - userAccessLevel <> expectedAccessLevel: "${domainId}" ${(userDomainPermission ?? 0)} <> ${domainToLevelValueMap[domainId]}`);
|
|
161
|
+
throw new ApiException(403, 'Action Forbidden');
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async getApiDetails(_path, projectId) {
|
|
166
|
+
let path = _path.substring(0, (_path + '?').indexOf('?')); //Get raw path without query
|
|
167
|
+
if (path.at(0) === '/')
|
|
168
|
+
path = path.substring(1);
|
|
169
|
+
this.logDebug(`Fetching Permission API for path: ${path} and project id: ${projectId}`);
|
|
170
|
+
const dbApi = (await ModuleBE_PermissionAPIDB.query.custom({
|
|
171
|
+
where: {
|
|
172
|
+
path,
|
|
173
|
+
projectId
|
|
174
|
+
}
|
|
175
|
+
}))[0];
|
|
176
|
+
if (!dbApi)
|
|
177
|
+
return undefined;
|
|
178
|
+
const requestPermissions = await this.getAccessLevels(dbApi.accessLevelIds || []);
|
|
179
|
+
return {
|
|
180
|
+
dbApi,
|
|
181
|
+
requestPermissions
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async getApisDetails(urls, projectId) {
|
|
185
|
+
const paths = urls.map(_path => _path.substring(0, (_path + '?').indexOf('?')));
|
|
186
|
+
const apiDbs = await batchActionParallel(paths, 10, elements => ModuleBE_PermissionAPIDB.query.custom({
|
|
187
|
+
where: {
|
|
188
|
+
projectId,
|
|
189
|
+
path: { $in: elements }
|
|
190
|
+
}
|
|
191
|
+
}));
|
|
192
|
+
return Promise.all(paths.map(async (path) => {
|
|
193
|
+
const apiDb = apiDbs.find(_apiDb => _apiDb.path === path);
|
|
194
|
+
if (!apiDb)
|
|
195
|
+
return;
|
|
196
|
+
try {
|
|
197
|
+
const requestPermissions = await this.getAccessLevels(apiDb.accessLevelIds);
|
|
198
|
+
return ({
|
|
199
|
+
apiDb,
|
|
200
|
+
requestPermissions
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
async getAccessLevels(_accessLevelIds) {
|
|
209
|
+
const accessLevelIds = filterDuplicates(_accessLevelIds || []);
|
|
210
|
+
const requestPermissions = filterInstances(await ModuleBE_PermissionAccessLevelDB.query.all(accessLevelIds));
|
|
211
|
+
const idNotFound = accessLevelIds.find(lId => !requestPermissions.find(r => r._id === lId));
|
|
212
|
+
if (idNotFound)
|
|
213
|
+
throw new ApiException(404, `Could not find api level with _id: ${idNotFound}`);
|
|
214
|
+
return requestPermissions;
|
|
215
|
+
}
|
|
216
|
+
setProjectId = (projectId) => {
|
|
217
|
+
this.projectId = projectId;
|
|
218
|
+
};
|
|
219
|
+
getRegEx(value) {
|
|
220
|
+
if (!value)
|
|
221
|
+
return new RegExp(`^${value}$`, 'g');
|
|
222
|
+
let regExValue = value;
|
|
223
|
+
const startRegEx = '^';
|
|
224
|
+
const endRegEx = '$';
|
|
225
|
+
if (value[0] !== startRegEx)
|
|
226
|
+
regExValue = startRegEx + regExValue;
|
|
227
|
+
if (value[value.length - 1] !== endRegEx)
|
|
228
|
+
regExValue = regExValue + endRegEx;
|
|
229
|
+
return new RegExp(regExValue, 'g');
|
|
230
|
+
}
|
|
231
|
+
registerPermissionKeys(keys) {
|
|
232
|
+
keys.forEach(key => {
|
|
233
|
+
if (this._keys[key])
|
|
234
|
+
throw new ImplementationMissingException(`Registered PermissionKey '${key}' more than once!`);
|
|
235
|
+
this._keys[key] = true;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
isStrictMode() {
|
|
239
|
+
return !!this.config.strictMode;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export const ModuleBE_PermissionsAssert = new ModuleBE_PermissionsAssert_Class();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DefaultDef_Package } from '../types.js';
|
|
2
|
+
export declare const Domain_PermissionAssignment: Readonly<{
|
|
3
|
+
_id: "1f41541c4514b50140ae62c1f7097029";
|
|
4
|
+
namespace: "Permissions Assignment";
|
|
5
|
+
}>;
|
|
6
|
+
export declare const Permissions_PermissionAssignment: DefaultDef_Package;
|
|
7
|
+
export declare const Domain_PermissionManagement: Readonly<{
|
|
8
|
+
_id: "1f41541c4514b50140ae62c1f7097029";
|
|
9
|
+
namespace: "Permissions Management";
|
|
10
|
+
}>;
|
|
11
|
+
export declare const Permissions_PermissionManagement: DefaultDef_Package;
|