@nu-art/permissions-backend 0.401.9 → 0.500.6
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/RequirePermission.d.ts +33 -0
- package/RequirePermission.js +56 -0
- package/_entity/access-group/ModuleBE_AccessGroupDB.d.ts +13 -0
- package/_entity/access-group/ModuleBE_AccessGroupDB.js +36 -0
- package/_entity/access-group/module-pack.d.ts +1 -0
- package/_entity/access-group/module-pack.js +3 -0
- package/_entity/permission-scope/ModuleBE_PermissionScopeDB.d.ts +6 -0
- package/_entity/permission-scope/ModuleBE_PermissionScopeDB.js +8 -0
- package/_entity/permission-scope/module-pack.d.ts +1 -0
- package/_entity/permission-scope/module-pack.js +3 -0
- package/_entity/user-permissions/ModuleBE_UserPermissionsAPI.d.ts +9 -0
- package/_entity/user-permissions/ModuleBE_UserPermissionsAPI.js +67 -0
- package/_entity/user-permissions/ModuleBE_UserPermissionsDB.d.ts +6 -0
- package/_entity/user-permissions/ModuleBE_UserPermissionsDB.js +8 -0
- package/_entity/user-permissions/module-pack.d.ts +2 -0
- package/_entity/user-permissions/module-pack.js +3 -0
- package/assertion-types.d.ts +9 -0
- package/consts.d.ts +7 -4
- package/consts.js +4 -2
- package/core/function-permission-registry.d.ts +24 -0
- package/core/function-permission-registry.js +60 -0
- package/core/module-pack.js +6 -7
- package/document-access-api.d.ts +6 -0
- package/document-access-api.js +49 -0
- package/document-access-enforcement.d.ts +9 -0
- package/document-access-enforcement.js +137 -0
- package/index.d.ts +14 -3
- package/index.js +14 -3
- package/modules/ModuleBE_Permissions.d.ts +63 -72
- package/modules/ModuleBE_Permissions.js +493 -339
- package/modules/ModuleBE_PermissionsAssert.d.ts +7 -38
- package/modules/ModuleBE_PermissionsAssert.js +53 -212
- package/package.json +16 -12
- package/PermissionKey_BE.d.ts +0 -13
- package/PermissionKey_BE.js +0 -48
- package/_entity/permission-access-level/ModuleBE_PermissionAccessLevelDB.d.ts +0 -17
- package/_entity/permission-access-level/ModuleBE_PermissionAccessLevelDB.js +0 -55
- package/_entity/permission-access-level/index.d.ts +0 -2
- package/_entity/permission-access-level/index.js +0 -2
- package/_entity/permission-access-level/module-pack.d.ts +0 -1
- package/_entity/permission-access-level/module-pack.js +0 -3
- package/_entity/permission-api/ModuleBE_PermissionAPIDB.d.ts +0 -12
- package/_entity/permission-api/ModuleBE_PermissionAPIDB.js +0 -62
- package/_entity/permission-api/index.d.ts +0 -2
- package/_entity/permission-api/index.js +0 -2
- package/_entity/permission-api/module-pack.d.ts +0 -1
- package/_entity/permission-api/module-pack.js +0 -3
- package/_entity/permission-domain/ModuleBE_PermissionDomainDB.d.ts +0 -15
- package/_entity/permission-domain/ModuleBE_PermissionDomainDB.js +0 -25
- package/_entity/permission-domain/index.d.ts +0 -2
- package/_entity/permission-domain/index.js +0 -2
- package/_entity/permission-domain/module-pack.d.ts +0 -1
- package/_entity/permission-domain/module-pack.js +0 -3
- package/_entity/permission-group/ModuleBE_PermissionGroupDB.d.ts +0 -14
- package/_entity/permission-group/ModuleBE_PermissionGroupDB.js +0 -62
- package/_entity/permission-group/index.d.ts +0 -2
- package/_entity/permission-group/index.js +0 -2
- package/_entity/permission-group/module-pack.d.ts +0 -1
- package/_entity/permission-group/module-pack.js +0 -3
- package/_entity/permission-project/ModuleBE_PermissionProjectDB.d.ts +0 -10
- package/_entity/permission-project/ModuleBE_PermissionProjectDB.js +0 -12
- package/_entity/permission-project/index.d.ts +0 -2
- package/_entity/permission-project/index.js +0 -2
- package/_entity/permission-project/module-pack.d.ts +0 -1
- package/_entity/permission-project/module-pack.js +0 -3
- package/_entity/permission-user/ModuleBE_PermissionUserAPI.d.ts +0 -8
- package/_entity/permission-user/ModuleBE_PermissionUserAPI.js +0 -13
- package/_entity/permission-user/ModuleBE_PermissionUserDB.d.ts +0 -37
- package/_entity/permission-user/ModuleBE_PermissionUserDB.js +0 -228
- package/_entity/permission-user/index.d.ts +0 -3
- package/_entity/permission-user/index.js +0 -3
- package/_entity/permission-user/module-pack.d.ts +0 -2
- package/_entity/permission-user/module-pack.js +0 -3
- package/_entity.d.ts +0 -12
- package/_entity.js +0 -18
- package/core/utils.d.ts +0 -25
- package/core/utils.js +0 -85
- package/modules/consts.d.ts +0 -11
- package/modules/consts.js +0 -29
- package/modules/index.d.ts +0 -1
- package/modules/index.js +0 -19
- package/permissions.d.ts +0 -23
- package/permissions.js +0 -159
- package/types.d.ts +0 -28
- /package/{types.js → assertion-types.js} +0 -0
|
@@ -1,357 +1,511 @@
|
|
|
1
|
-
import { _keys,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
};
|
|
1
|
+
import { _keys, ApiException, batchActionParallel, Dispatcher, filterDuplicates, filterInstances, flatArray, Module } from '@nu-art/ts-common';
|
|
2
|
+
import { MemStorage } from '@nu-art/ts-common/mem-storage/MemStorage';
|
|
3
|
+
import { hashToUniqueId, stringToUniqueId } from '@nu-art/db-api-shared';
|
|
4
|
+
import { RuntimeBE_ModulesDB } from '@nu-art/db-api-backend';
|
|
5
|
+
import { AccessScope_Self, AllDocumentAccessKeys, getAllRegisteredScopes, getRegisteredGroupDefinitions, permissionScopeId } from '@nu-art/permissions-shared';
|
|
6
|
+
import { asSetupTaskKey } from '@nu-art/action-processor-backend';
|
|
7
|
+
import { getPermissionScopeValues } from '@nu-art/permissions-shared';
|
|
8
|
+
import { ModuleBE_PermissionScopeDB } from '../_entity/permission-scope/ModuleBE_PermissionScopeDB.js';
|
|
9
|
+
import { ModuleBE_UserPermissionsDB } from '../_entity/user-permissions/ModuleBE_UserPermissionsDB.js';
|
|
10
|
+
import { ModuleBE_AccessGroupDB } from '../_entity/access-group/ModuleBE_AccessGroupDB.js';
|
|
11
|
+
import { ModuleBE_Firebase } from '@nu-art/firebase-backend';
|
|
12
|
+
import { MemKey_ServiceAccountId, MemKey_UserAccessIds, MemKey_UserScopePermissions } from '../consts.js';
|
|
13
|
+
import { wireDocumentAccess } from '../document-access-enforcement.js';
|
|
14
|
+
import { ModuleBE_AccountDB } from '@nu-art/user-account-backend';
|
|
15
|
+
export const ServiceAccountId_Bootstrap = 'bootstrap-admin';
|
|
16
|
+
// --- Well-known group IDs ---
|
|
17
|
+
export const GroupId_AppDefault = hashToUniqueId('group/default');
|
|
18
|
+
export const GroupId_PermissionsAdmin = hashToUniqueId('group/permissions-admin');
|
|
19
|
+
const BootstrapSAGroupId = hashToUniqueId(ServiceAccountId_Bootstrap);
|
|
20
|
+
export const PermissionsInfraGroupIds = AllDocumentAccessKeys.reduce((ids, key) => {
|
|
21
|
+
ids[key] = hashToUniqueId(`permissions-infra:${key}`);
|
|
22
|
+
return ids;
|
|
23
|
+
}, {});
|
|
24
|
+
// --- Module ---
|
|
25
|
+
const dispatcher_resolveAdditionalGroupMemberships = new Dispatcher('__resolveAdditionalGroupMemberships');
|
|
26
|
+
export const SetupTaskKey_PermissionsGroups = asSetupTaskKey('permissions-groups');
|
|
88
27
|
class ModuleBE_Permissions_Class extends Module {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
28
|
+
adminGrantFlagRef;
|
|
29
|
+
accessResolvers = new Map();
|
|
30
|
+
moduleScopeKeys = new Map();
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this.setDefaultConfig({
|
|
34
|
+
serviceAccounts: {
|
|
35
|
+
[ServiceAccountId_Bootstrap]: {
|
|
36
|
+
scopes: ['permissions-ui:view', 'access-group:create'],
|
|
37
|
+
enabled: true,
|
|
38
|
+
systemOnly: true,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
permissionsAccessResolver = (item) => {
|
|
44
|
+
if (item._id === BootstrapSAGroupId)
|
|
45
|
+
return {
|
|
46
|
+
__access: {
|
|
47
|
+
readers: [BootstrapSAGroupId],
|
|
48
|
+
writers: [BootstrapSAGroupId],
|
|
49
|
+
deleters: [],
|
|
50
|
+
owners: [],
|
|
51
|
+
}
|
|
52
|
+
};
|
|
104
53
|
return {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
54
|
+
__access: {
|
|
55
|
+
readers: [GroupId_PermissionsAdmin],
|
|
56
|
+
writers: [GroupId_PermissionsAdmin],
|
|
57
|
+
deleters: [],
|
|
58
|
+
owners: [],
|
|
108
59
|
}
|
|
109
60
|
};
|
|
61
|
+
};
|
|
62
|
+
init() {
|
|
63
|
+
super.init();
|
|
64
|
+
this.adminGrantFlagRef = ModuleBE_Firebase.createModuleStateFirebaseRef(this, 'grantAdminOnLogin');
|
|
65
|
+
this.setAccessContextResolver(ModuleBE_AccessGroupDB, this.permissionsAccessResolver);
|
|
66
|
+
this.setAccessContextResolver(ModuleBE_PermissionScopeDB, this.permissionsAccessResolver);
|
|
67
|
+
this.setAccessContextResolver(ModuleBE_UserPermissionsDB, this.permissionsAccessResolver);
|
|
68
|
+
this.wireDocumentAccessToAllModules();
|
|
110
69
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
70
|
+
setAccessContextResolver(dbModule, resolver, scopeKeys) {
|
|
71
|
+
this.accessResolvers.set(dbModule.dbDef.dbKey, resolver);
|
|
72
|
+
if (scopeKeys)
|
|
73
|
+
this.moduleScopeKeys.set(dbModule.dbDef.dbKey, scopeKeys);
|
|
74
|
+
}
|
|
75
|
+
wireDocumentAccessToAllModules() {
|
|
76
|
+
for (const dbModule of RuntimeBE_ModulesDB()) {
|
|
77
|
+
const dbKey = dbModule.dbDef.dbKey;
|
|
78
|
+
wireDocumentAccess(dbModule, () => this.accessResolvers.get(dbKey), () => this.moduleScopeKeys.get(dbKey));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
getAdminGrantFlagRef() {
|
|
82
|
+
return this.adminGrantFlagRef;
|
|
83
|
+
}
|
|
84
|
+
// --- Project setup ---
|
|
85
|
+
__performProjectSetup() {
|
|
86
|
+
return [{
|
|
87
|
+
key: SetupTaskKey_PermissionsGroups,
|
|
88
|
+
dependsOn: [],
|
|
89
|
+
processor: () => this.ensureDefinedGroups()
|
|
90
|
+
}];
|
|
91
|
+
}
|
|
92
|
+
async ensureDefinedGroups() {
|
|
93
|
+
await this.runAsServiceAccount(ServiceAccountId_Bootstrap, async () => {
|
|
94
|
+
this.logDebug('[FIRST_USER] bootstrap: starting ensureDefinedGroups');
|
|
95
|
+
await this.ensureBootstrapSAAccessGroup();
|
|
96
|
+
await this.ensureServiceAccountAccessGroups();
|
|
97
|
+
await this.ensurePermissionsInfraAccessGroups();
|
|
98
|
+
await this.ensureScopeEntities();
|
|
99
|
+
await this.ensureDefaultGroup();
|
|
100
|
+
await this.ensurePermissionsAdminGroup();
|
|
101
|
+
await this.ensureAppDefinedGroups();
|
|
102
|
+
await this.syncPersonalGroupsForExistingAccounts();
|
|
103
|
+
await this.recomputePermissionsForAllUsers();
|
|
104
|
+
this.logInfoBold('Recomputed UserPermissions for all users');
|
|
105
|
+
this.logDebug('[FIRST_USER] bootstrap: ensureDefinedGroups complete');
|
|
127
106
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
await
|
|
107
|
+
}
|
|
108
|
+
// --- Account lifecycle hooks ---
|
|
109
|
+
async __onUserLogin(account) {
|
|
110
|
+
await this.runAsServiceAccount(ServiceAccountId_Bootstrap, async () => {
|
|
111
|
+
await this.ensurePersonalAccessGroup(account);
|
|
112
|
+
await this.addToDefaultGroup(account);
|
|
113
|
+
await this.promoteIfNoAdmin(account);
|
|
114
|
+
await this.checkAdminGrantFlag(account);
|
|
115
|
+
await this.resolveAdditionalGroupMemberships(account, 'login');
|
|
116
|
+
await this.recomputePermissionsForUsers([account._id]);
|
|
136
117
|
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
await this.assignSuperAdmin();
|
|
118
|
+
}
|
|
119
|
+
async __onAccountDeleted(account) {
|
|
120
|
+
await this.runAsServiceAccount(ServiceAccountId_Bootstrap, async () => {
|
|
121
|
+
const personalGroupId = stringToUniqueId(account._id);
|
|
122
|
+
const personalGroup = await ModuleBE_AccessGroupDB.query.unique(personalGroupId);
|
|
123
|
+
if (personalGroup)
|
|
124
|
+
await ModuleBE_AccessGroupDB.delete.unique(personalGroupId);
|
|
125
|
+
const userPermId = stringToUniqueId(account._id);
|
|
126
|
+
const userPerm = await ModuleBE_UserPermissionsDB.query.unique(userPermId);
|
|
127
|
+
if (userPerm)
|
|
128
|
+
await ModuleBE_UserPermissionsDB.delete.unique(userPermId);
|
|
129
|
+
const allGroups = await ModuleBE_AccessGroupDB.query.where({});
|
|
130
|
+
const groupsContainingUser = allGroups.filter(g => g.members.includes(personalGroupId));
|
|
131
|
+
for (const group of groupsContainingUser) {
|
|
132
|
+
group.members = group.members.filter(m => m !== personalGroupId);
|
|
133
|
+
await ModuleBE_AccessGroupDB.set.item(group);
|
|
154
134
|
}
|
|
155
|
-
};
|
|
135
|
+
});
|
|
156
136
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
|
137
|
+
async ensurePersonalAccessGroup(account) {
|
|
138
|
+
const personalGroupId = stringToUniqueId(account._id);
|
|
139
|
+
const existing = await ModuleBE_AccessGroupDB.query.unique(personalGroupId);
|
|
140
|
+
this.logDebug(`[FIRST_USER] ensurePersonalAccessGroup: personalGroupId=${personalGroupId}, existing=${!!existing}`);
|
|
141
|
+
if (existing)
|
|
142
|
+
return;
|
|
143
|
+
await ModuleBE_AccessGroupDB.create.item({
|
|
144
|
+
_id: personalGroupId,
|
|
145
|
+
type: 'user',
|
|
146
|
+
key: AccessScope_Self,
|
|
147
|
+
label: `User (${account.email ?? account._id})`,
|
|
148
|
+
members: [],
|
|
231
149
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
150
|
+
const verify = await ModuleBE_AccessGroupDB.query.unique(personalGroupId);
|
|
151
|
+
this.logDebug(`[FIRST_USER] ensurePersonalAccessGroup: created, verified=${!!verify}`);
|
|
152
|
+
}
|
|
153
|
+
async addToDefaultGroup(account) {
|
|
154
|
+
const personalGroupId = stringToUniqueId(account._id);
|
|
155
|
+
const defaultGroup = await ModuleBE_AccessGroupDB.query.unique(GroupId_AppDefault);
|
|
156
|
+
this.logDebug(`[FIRST_USER] addToDefaultGroup: defaultGroup=${!!defaultGroup}, members=${defaultGroup?.members?.length}`);
|
|
157
|
+
if (!defaultGroup)
|
|
158
|
+
return;
|
|
159
|
+
if (defaultGroup.members.includes(personalGroupId))
|
|
160
|
+
return;
|
|
161
|
+
defaultGroup.members.push(personalGroupId);
|
|
162
|
+
await ModuleBE_AccessGroupDB.set.item(defaultGroup);
|
|
163
|
+
this.logDebug(`[FIRST_USER] addToDefaultGroup: user added, members now=${defaultGroup.members.length}`);
|
|
164
|
+
}
|
|
165
|
+
async promoteIfNoAdmin(account) {
|
|
166
|
+
const adminGroup = await ModuleBE_AccessGroupDB.query.unique(GroupId_PermissionsAdmin);
|
|
167
|
+
this.logDebug(`[FIRST_USER] promoteIfNoAdmin: adminGroup=${!!adminGroup}, members=${JSON.stringify(adminGroup?.members)}`);
|
|
168
|
+
if (!adminGroup) {
|
|
169
|
+
this.logDebug('[FIRST_USER] promoteIfNoAdmin: NO admin group found — returning');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const hasRealMembers = adminGroup.members.some(m => m !== BootstrapSAGroupId);
|
|
173
|
+
if (hasRealMembers) {
|
|
174
|
+
this.logDebug(`[FIRST_USER] promoteIfNoAdmin: admin already has non-SA members — returning`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const personalGroupId = stringToUniqueId(account._id);
|
|
178
|
+
if (adminGroup.members.includes(personalGroupId))
|
|
179
|
+
return;
|
|
180
|
+
adminGroup.members.push(personalGroupId);
|
|
181
|
+
await ModuleBE_AccessGroupDB.set.item(adminGroup);
|
|
182
|
+
this.logDebug(`[FIRST_USER] promoteIfNoAdmin: promoted ${personalGroupId} to admin`);
|
|
183
|
+
}
|
|
184
|
+
async checkAdminGrantFlag(account) {
|
|
185
|
+
const flagValue = await this.adminGrantFlagRef.get(false);
|
|
186
|
+
if (!flagValue)
|
|
187
|
+
return;
|
|
188
|
+
const personalGroupId = stringToUniqueId(account._id);
|
|
189
|
+
const adminGroup = await ModuleBE_AccessGroupDB.query.unique(GroupId_PermissionsAdmin);
|
|
190
|
+
if (!adminGroup)
|
|
191
|
+
return;
|
|
192
|
+
if (adminGroup.members.includes(personalGroupId)) {
|
|
193
|
+
await this.adminGrantFlagRef.set(false);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
adminGroup.members.push(personalGroupId);
|
|
197
|
+
await ModuleBE_AccessGroupDB.set.item(adminGroup);
|
|
198
|
+
await this.adminGrantFlagRef.set(false);
|
|
199
|
+
this.logInfo(`Granted Permissions Admin to user ${account._id} via RTDB flag (one-shot)`);
|
|
200
|
+
}
|
|
201
|
+
async resolveAdditionalGroupMemberships(account, context) {
|
|
202
|
+
const results = await dispatcher_resolveAdditionalGroupMemberships.dispatchModuleAsync(account._id, context);
|
|
203
|
+
const additionalGroupIds = filterDuplicates(flatArray(results));
|
|
204
|
+
if (additionalGroupIds.length === 0)
|
|
205
|
+
return;
|
|
206
|
+
const personalGroupId = stringToUniqueId(account._id);
|
|
207
|
+
const typedGroupIds = additionalGroupIds.map(id => stringToUniqueId(id));
|
|
208
|
+
const groups = filterInstances(await ModuleBE_AccessGroupDB.query.all(typedGroupIds));
|
|
209
|
+
const modifiedGroups = groups.filter(group => !group.members.includes(personalGroupId));
|
|
210
|
+
if (modifiedGroups.length === 0)
|
|
211
|
+
return;
|
|
212
|
+
modifiedGroups.forEach(group => group.members.push(personalGroupId));
|
|
213
|
+
await ModuleBE_AccessGroupDB.set.all(modifiedGroups);
|
|
214
|
+
modifiedGroups.forEach(group => this.logInfo(`Added user ${account._id} to group '${group.label}'`));
|
|
215
|
+
}
|
|
216
|
+
// --- Permission recomputation (materialized DB_UserPermissions) ---
|
|
217
|
+
async recomputePermissionsForUsers(accountIds) {
|
|
218
|
+
if (!accountIds.length)
|
|
219
|
+
return;
|
|
220
|
+
const allGroups = await ModuleBE_AccessGroupDB.query.where({});
|
|
221
|
+
this.logDebug(`[FIRST_USER] recomputePermissionsForUsers: accountIds=${JSON.stringify(accountIds)}, allGroups=${allGroups.length}`);
|
|
222
|
+
const entitiesToUpsert = await Promise.all(accountIds.map(async (accountId) => {
|
|
223
|
+
const personalGroupId = stringToUniqueId(accountId);
|
|
224
|
+
const { scopeEntries, accessIds } = await this.materializeFromGroups(personalGroupId, allGroups);
|
|
225
|
+
this.logDebug(`[FIRST_USER] materialized for ${accountId}: scopes=${scopeEntries.length}, accessIds=${JSON.stringify(accessIds)}`);
|
|
226
|
+
return {
|
|
227
|
+
_id: stringToUniqueId(accountId),
|
|
228
|
+
scopeEntries,
|
|
229
|
+
accessIds,
|
|
230
|
+
};
|
|
231
|
+
}));
|
|
232
|
+
if (entitiesToUpsert.length > 0)
|
|
233
|
+
await ModuleBE_UserPermissionsDB.set.all(entitiesToUpsert);
|
|
234
|
+
}
|
|
235
|
+
async recomputePermissionsForAllUsers() {
|
|
236
|
+
const allGroups = await ModuleBE_AccessGroupDB.query.where({});
|
|
237
|
+
const userGroups = allGroups.filter(g => g.type === 'user');
|
|
238
|
+
if (!userGroups.length)
|
|
239
|
+
return;
|
|
240
|
+
await batchActionParallel(userGroups, 50, async (batch) => {
|
|
241
|
+
const entitiesToUpsert = await Promise.all(batch.map(async (pg) => {
|
|
242
|
+
const { scopeEntries, accessIds } = await this.materializeFromGroups(pg._id, allGroups);
|
|
248
243
|
return {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
})
|
|
244
|
+
_id: stringToUniqueId(pg._id),
|
|
245
|
+
scopeEntries,
|
|
246
|
+
accessIds,
|
|
261
247
|
};
|
|
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
248
|
}));
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
249
|
+
await ModuleBE_UserPermissionsDB.set.all(entitiesToUpsert);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async materializeFromGroups(personalGroupId, allGroups) {
|
|
253
|
+
const personalGroup = allGroups.find(g => g._id === personalGroupId);
|
|
254
|
+
const accessIds = {
|
|
255
|
+
[AccessScope_Self]: [personalGroupId],
|
|
256
|
+
};
|
|
257
|
+
if (!personalGroup)
|
|
258
|
+
return { scopeEntries: [], accessIds };
|
|
259
|
+
const reachableGroups = this.walkGroupGraphUp(personalGroupId, allGroups);
|
|
260
|
+
const allScopeIds = [];
|
|
261
|
+
if (personalGroup.scopeEntries?.length)
|
|
262
|
+
allScopeIds.push(...personalGroup.scopeEntries);
|
|
263
|
+
for (const group of reachableGroups) {
|
|
264
|
+
if (!accessIds[group.key])
|
|
265
|
+
accessIds[group.key] = [];
|
|
266
|
+
accessIds[group.key] = filterDuplicates([...accessIds[group.key], group._id]);
|
|
267
|
+
if (group.scopeEntries?.length)
|
|
268
|
+
allScopeIds.push(...group.scopeEntries);
|
|
269
|
+
}
|
|
270
|
+
const dedupedScopeIds = filterDuplicates(allScopeIds);
|
|
271
|
+
const scopeEntries = dedupedScopeIds.length > 0
|
|
272
|
+
? await this.resolveScopeIdsToStrings(dedupedScopeIds)
|
|
273
|
+
: [];
|
|
274
|
+
return { scopeEntries, accessIds };
|
|
275
|
+
}
|
|
276
|
+
// --- Access group change handler ---
|
|
277
|
+
async __onAccessGroupChanged(changedGroupIds) {
|
|
278
|
+
await this.rematerializeForGroups(changedGroupIds);
|
|
279
|
+
}
|
|
280
|
+
async rematerializeForGroups(changedGroupIds) {
|
|
281
|
+
const allGroups = await ModuleBE_AccessGroupDB.query.where({});
|
|
282
|
+
const userGroups = allGroups.filter(g => g.type === 'user');
|
|
283
|
+
const affectedAccountIds = [];
|
|
284
|
+
for (const personalGroup of userGroups) {
|
|
285
|
+
const reachable = this.walkGroupGraphUp(personalGroup._id, allGroups);
|
|
286
|
+
const isAffected = reachable.some(g => changedGroupIds.includes(g._id));
|
|
287
|
+
if (isAffected)
|
|
288
|
+
affectedAccountIds.push(personalGroup._id);
|
|
330
289
|
}
|
|
331
|
-
|
|
332
|
-
this.
|
|
290
|
+
if (affectedAccountIds.length > 0)
|
|
291
|
+
await this.recomputePermissionsForUsers(affectedAccountIds);
|
|
292
|
+
}
|
|
293
|
+
walkGroupGraphUp(startGroupId, allGroups) {
|
|
294
|
+
const visited = new Set();
|
|
295
|
+
const queue = [startGroupId];
|
|
296
|
+
const result = [];
|
|
297
|
+
while (queue.length > 0) {
|
|
298
|
+
const currentId = queue.shift();
|
|
299
|
+
if (visited.has(currentId))
|
|
300
|
+
continue;
|
|
301
|
+
visited.add(currentId);
|
|
302
|
+
const parents = allGroups.filter(g => g.members.includes(currentId));
|
|
303
|
+
for (const parent of parents) {
|
|
304
|
+
result.push(parent);
|
|
305
|
+
queue.push(parent._id);
|
|
306
|
+
}
|
|
333
307
|
}
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
async resolveScopeIdsToStrings(scopeIds) {
|
|
311
|
+
const typedScopeIds = scopeIds.map(id => stringToUniqueId(id));
|
|
312
|
+
const scopeEntities = filterInstances(await ModuleBE_PermissionScopeDB.query.all(typedScopeIds));
|
|
313
|
+
return this.deduplicateScopeEntries(scopeEntities);
|
|
314
|
+
}
|
|
315
|
+
deduplicateScopeEntries(scopeEntities) {
|
|
316
|
+
const scopeMaxIdx = {};
|
|
317
|
+
for (const entity of scopeEntities) {
|
|
318
|
+
const scopeValues = getPermissionScopeValues(entity.key);
|
|
319
|
+
const valueIdx = scopeValues ? scopeValues.indexOf(entity.value) : -1;
|
|
320
|
+
const current = scopeMaxIdx[entity.key];
|
|
321
|
+
if (!current || valueIdx > current.idx)
|
|
322
|
+
scopeMaxIdx[entity.key] = { value: entity.value, idx: valueIdx };
|
|
323
|
+
}
|
|
324
|
+
return _keys(scopeMaxIdx).map(k => `${k}:${scopeMaxIdx[k].value}`);
|
|
325
|
+
}
|
|
326
|
+
// --- Service account elevation ---
|
|
327
|
+
async runAsServiceAccount(saId, action) {
|
|
328
|
+
const saConfig = this.config.serviceAccounts[saId];
|
|
329
|
+
if (!saConfig || !saConfig.enabled)
|
|
330
|
+
throw new ApiException(403, `Service account '${saId}' is not enabled`);
|
|
331
|
+
if (saConfig.systemOnly) {
|
|
332
|
+
const store = MemStorage.getStore();
|
|
333
|
+
if (store && MemKey_UserScopePermissions.peak() !== undefined)
|
|
334
|
+
throw new ApiException(403, `System-only service account '${saId}' cannot be used within a user context`);
|
|
335
|
+
}
|
|
336
|
+
const scopes = saId === ServiceAccountId_Bootstrap
|
|
337
|
+
? this.resolveBootstrapScopes()
|
|
338
|
+
: saConfig.scopes;
|
|
339
|
+
const personalGroupId = hashToUniqueId(saId);
|
|
340
|
+
const accessIds = saId === ServiceAccountId_Bootstrap
|
|
341
|
+
? this.resolveBootstrapAccessIds()
|
|
342
|
+
: await this.resolveSAAccessIds(personalGroupId);
|
|
343
|
+
const memStorage = new MemStorage();
|
|
344
|
+
return memStorage.init(async () => {
|
|
345
|
+
MemKey_ServiceAccountId.set(saId);
|
|
346
|
+
MemKey_UserScopePermissions.set(scopes);
|
|
347
|
+
MemKey_UserAccessIds.set(accessIds);
|
|
348
|
+
return action();
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
async resolveSAAccessIds(personalGroupId) {
|
|
352
|
+
return this.runAsServiceAccount(ServiceAccountId_Bootstrap, async () => {
|
|
353
|
+
const allGroups = await ModuleBE_AccessGroupDB.query.where({});
|
|
354
|
+
const { accessIds } = await this.materializeFromGroups(personalGroupId, allGroups);
|
|
355
|
+
return accessIds;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
resolveBootstrapAccessIds() {
|
|
359
|
+
return {
|
|
360
|
+
[AccessScope_Self]: [BootstrapSAGroupId],
|
|
361
|
+
'permissions-admin': [GroupId_PermissionsAdmin],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
resolveBootstrapScopes() {
|
|
365
|
+
return [
|
|
366
|
+
'permissions-ui:view',
|
|
367
|
+
'access-group:create',
|
|
368
|
+
];
|
|
369
|
+
}
|
|
370
|
+
// --- Bootstrap: ensure service account access group ---
|
|
371
|
+
async ensureBootstrapSAAccessGroup() {
|
|
372
|
+
const existing = await ModuleBE_AccessGroupDB.query.unique(BootstrapSAGroupId);
|
|
373
|
+
if (existing)
|
|
349
374
|
return;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
375
|
+
await ModuleBE_AccessGroupDB.create.item({
|
|
376
|
+
_id: BootstrapSAGroupId,
|
|
377
|
+
type: 'service-account',
|
|
378
|
+
key: ServiceAccountId_Bootstrap,
|
|
379
|
+
label: 'Bootstrap Admin (SA)',
|
|
380
|
+
members: [],
|
|
381
|
+
});
|
|
382
|
+
this.logInfoBold('Created bootstrap service account access group');
|
|
383
|
+
}
|
|
384
|
+
async ensureServiceAccountAccessGroups() {
|
|
385
|
+
const saKeys = _keys(this.config.serviceAccounts).filter(saId => saId !== ServiceAccountId_Bootstrap);
|
|
386
|
+
if (saKeys.length === 0)
|
|
387
|
+
return;
|
|
388
|
+
const entries = saKeys.map(saId => ({
|
|
389
|
+
saId,
|
|
390
|
+
groupId: hashToUniqueId(saId),
|
|
391
|
+
}));
|
|
392
|
+
const existing = filterInstances(await ModuleBE_AccessGroupDB.query.all(entries.map(e => e.groupId)));
|
|
393
|
+
const existingIds = new Set(existing.map(g => g._id));
|
|
394
|
+
const toCreate = entries
|
|
395
|
+
.filter(e => !existingIds.has(e.groupId))
|
|
396
|
+
.map(e => ({
|
|
397
|
+
_id: e.groupId,
|
|
398
|
+
type: 'service-account',
|
|
399
|
+
key: e.saId,
|
|
400
|
+
label: `SA: ${e.saId}`,
|
|
401
|
+
members: [],
|
|
402
|
+
}));
|
|
403
|
+
if (toCreate.length === 0)
|
|
404
|
+
return;
|
|
405
|
+
await ModuleBE_AccessGroupDB.create.all(toCreate);
|
|
406
|
+
this.logInfoBold(`Created ${toCreate.length} service account access groups`);
|
|
407
|
+
}
|
|
408
|
+
// --- Bootstrap: ensure permissions infrastructure access groups ---
|
|
409
|
+
async ensurePermissionsInfraAccessGroups() {
|
|
410
|
+
const entries = AllDocumentAccessKeys.map(accessKey => ({
|
|
411
|
+
accessKey,
|
|
412
|
+
groupId: PermissionsInfraGroupIds[accessKey],
|
|
413
|
+
}));
|
|
414
|
+
const existing = filterInstances(await ModuleBE_AccessGroupDB.query.all(entries.map(e => e.groupId)));
|
|
415
|
+
const existingMap = new Map(existing.map(g => [g._id, g]));
|
|
416
|
+
const items = entries.map(e => {
|
|
417
|
+
const existingGroup = existingMap.get(e.groupId);
|
|
418
|
+
const members = filterDuplicates([GroupId_PermissionsAdmin, ...(existingGroup?.members ?? [])]);
|
|
419
|
+
return {
|
|
420
|
+
_id: e.groupId,
|
|
421
|
+
type: 'entity',
|
|
422
|
+
key: 'permissions',
|
|
423
|
+
label: `Permissions Infra ${e.accessKey}`,
|
|
424
|
+
members,
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
await ModuleBE_AccessGroupDB.set.all(items);
|
|
428
|
+
this.logInfoBold('Ensured permissions infrastructure access groups');
|
|
429
|
+
}
|
|
430
|
+
// --- Bootstrap: ensure scope entities ---
|
|
431
|
+
async ensureScopeEntities() {
|
|
432
|
+
const registeredScopes = getAllRegisteredScopes();
|
|
433
|
+
this.logDebug(`Registered scopes: ${registeredScopes.length} definitions`);
|
|
434
|
+
const scopeEntities = registeredScopes.flatMap(scope => scope.values.map(value => ({
|
|
435
|
+
_id: permissionScopeId(scope.key, value),
|
|
436
|
+
key: scope.key,
|
|
437
|
+
value,
|
|
438
|
+
})));
|
|
439
|
+
if (scopeEntities.length === 0)
|
|
440
|
+
return;
|
|
441
|
+
await ModuleBE_PermissionScopeDB.set.all(scopeEntities);
|
|
442
|
+
this.logInfoBold(`Ensured ${scopeEntities.length} scope entities`);
|
|
443
|
+
}
|
|
444
|
+
// --- Bootstrap: ensure default group ---
|
|
445
|
+
async ensureDefaultGroup() {
|
|
446
|
+
const existing = await ModuleBE_AccessGroupDB.query.unique(GroupId_AppDefault);
|
|
447
|
+
await ModuleBE_AccessGroupDB.set.all([{
|
|
448
|
+
_id: GroupId_AppDefault,
|
|
449
|
+
type: 'custom',
|
|
450
|
+
key: 'default',
|
|
451
|
+
label: 'Default',
|
|
452
|
+
members: existing?.members ?? [],
|
|
453
|
+
scopeEntries: [],
|
|
454
|
+
}]);
|
|
455
|
+
this.logInfoBold('Default group ensured');
|
|
456
|
+
}
|
|
457
|
+
// --- Bootstrap: permissions admin group ---
|
|
458
|
+
async ensurePermissionsAdminGroup() {
|
|
459
|
+
const scopeEntries = [
|
|
460
|
+
permissionScopeId('permissions-ui', 'view'),
|
|
461
|
+
permissionScopeId('access-group', 'create'),
|
|
462
|
+
];
|
|
463
|
+
const existingAdmin = await ModuleBE_AccessGroupDB.query.unique(GroupId_PermissionsAdmin);
|
|
464
|
+
const adminMembers = filterDuplicates([BootstrapSAGroupId, ...(existingAdmin?.members ?? [])]);
|
|
465
|
+
this.logDebug(`[FIRST_USER] ensurePermissionsAdminGroup: id=${GroupId_PermissionsAdmin}, existing=${!!existingAdmin}, members=${JSON.stringify(adminMembers)}, scopes=${scopeEntries.length}`);
|
|
466
|
+
await ModuleBE_AccessGroupDB.set.all([{
|
|
467
|
+
_id: GroupId_PermissionsAdmin,
|
|
468
|
+
type: 'custom',
|
|
469
|
+
key: 'permissions-admin',
|
|
470
|
+
label: 'Permissions Admin',
|
|
471
|
+
members: adminMembers,
|
|
472
|
+
scopeEntries,
|
|
473
|
+
}]);
|
|
474
|
+
this.logInfoBold('Permissions Admin group upserted');
|
|
475
|
+
}
|
|
476
|
+
// --- Bootstrap: app-defined groups ---
|
|
477
|
+
async ensureAppDefinedGroups() {
|
|
478
|
+
const groupDefs = getRegisteredGroupDefinitions();
|
|
479
|
+
if (groupDefs.length === 0)
|
|
480
|
+
return;
|
|
481
|
+
const entries = groupDefs.map(def => ({
|
|
482
|
+
def,
|
|
483
|
+
groupId: hashToUniqueId(`group/${def.key}`),
|
|
484
|
+
}));
|
|
485
|
+
const existing = filterInstances(await ModuleBE_AccessGroupDB.query.all(entries.map(e => e.groupId)));
|
|
486
|
+
const existingMap = new Map(existing.map(g => [g._id, g]));
|
|
487
|
+
const items = entries.map(e => {
|
|
488
|
+
const existingGroup = existingMap.get(e.groupId);
|
|
489
|
+
const declaredMemberIds = (e.def.memberKeys ?? []).map(key => hashToUniqueId(`group/${key}`));
|
|
490
|
+
const mergedMembers = filterDuplicates([...declaredMemberIds, ...(existingGroup?.members ?? [])]);
|
|
491
|
+
return {
|
|
492
|
+
_id: e.groupId,
|
|
493
|
+
type: 'custom',
|
|
494
|
+
key: e.def.scopeKey ?? e.def.key,
|
|
495
|
+
label: e.def.label,
|
|
496
|
+
members: mergedMembers,
|
|
497
|
+
scopeEntries: filterDuplicates(e.def.scopes.map(({ scope, value }) => permissionScopeId(scope.key, value))),
|
|
498
|
+
};
|
|
499
|
+
});
|
|
500
|
+
await ModuleBE_AccessGroupDB.set.all(items);
|
|
501
|
+
this.logInfoBold(`Ensured ${groupDefs.length} application-defined groups: [${groupDefs.map(d => d.label).join(', ')}]`);
|
|
502
|
+
}
|
|
503
|
+
// --- Bootstrap: sync personal groups for existing accounts ---
|
|
504
|
+
async syncPersonalGroupsForExistingAccounts() {
|
|
505
|
+
const accounts = await ModuleBE_AccountDB.query.where({});
|
|
506
|
+
this.logDebug(`Found ${accounts.length} accounts for personal group sync`);
|
|
507
|
+
for (const account of accounts)
|
|
508
|
+
await this.ensurePersonalAccessGroup(account);
|
|
509
|
+
}
|
|
356
510
|
}
|
|
357
511
|
export const ModuleBE_Permissions = new ModuleBE_Permissions_Class();
|