@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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PermissionScope } from '@nu-art/permissions-shared';
|
|
2
|
+
import type { FunctionPermissionDef } from './core/function-permission-registry.js';
|
|
3
|
+
import type { PermissionAsserter } from './assertion-types.js';
|
|
4
|
+
export { type FunctionPermissionDef } from './core/function-permission-registry.js';
|
|
5
|
+
export { type PermissionAssertionContext, type PermissionAsserter } from './assertion-types.js';
|
|
6
|
+
export declare const RequirePermissionDefKey: unique symbol;
|
|
7
|
+
type AsyncMethodDecorator = <This, Args extends any[], Return>(originalMethod: (this: This, ...args: Args) => Promise<Return>, _context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Promise<Return>>) => (this: This, ...args: Args) => Promise<Return>;
|
|
8
|
+
/**
|
|
9
|
+
* Simple overload: checks that the caller has at least the required value
|
|
10
|
+
* for the given scope (position-based in the scope's ordered values array).
|
|
11
|
+
*/
|
|
12
|
+
export declare function RequirePermission<P extends PermissionScope>(scope: P, value: P['values'][number]): AsyncMethodDecorator;
|
|
13
|
+
/**
|
|
14
|
+
* Complex overload: receives a PermissionAssertionContext and the decorated
|
|
15
|
+
* method's arguments. Return true to allow, false to deny (throws 403).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* @RequirePermission((assert, body: EditArticleRequest) => {
|
|
19
|
+
* return assert.or(
|
|
20
|
+
* assert.and(
|
|
21
|
+
* assert.hasScope(PermissionScope_Articles, 'write'),
|
|
22
|
+
* assert.ownsEntity({dbKey: 'articles', id: body.articleId})
|
|
23
|
+
* ),
|
|
24
|
+
* assert.hasScope(PermissionScope_Articles, 'admin')
|
|
25
|
+
* );
|
|
26
|
+
* })
|
|
27
|
+
*/
|
|
28
|
+
export declare function RequirePermission(asserter: PermissionAsserter): AsyncMethodDecorator;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the function-permission def attached to a handler, or undefined.
|
|
31
|
+
* Only set for the simple overload (scope + value).
|
|
32
|
+
*/
|
|
33
|
+
export declare function getRequirePermissionDef(handler: ((...args: any[]) => any) | null | undefined): FunctionPermissionDef | undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { ApiException } from '@nu-art/ts-common';
|
|
20
|
+
import { registerFunctionPermission } from './core/function-permission-registry.js';
|
|
21
|
+
import { ModuleBE_PermissionsAssert } from './modules/ModuleBE_PermissionsAssert.js';
|
|
22
|
+
export const RequirePermissionDefKey = Symbol.for('RequirePermissionDef');
|
|
23
|
+
export function RequirePermission(scopeOrAsserter, value) {
|
|
24
|
+
if (typeof scopeOrAsserter === 'function') {
|
|
25
|
+
const asserter = scopeOrAsserter;
|
|
26
|
+
return function (originalMethod, _context) {
|
|
27
|
+
const wrapper = async function (...args) {
|
|
28
|
+
const ctx = ModuleBE_PermissionsAssert.createAssertionContext();
|
|
29
|
+
const result = await asserter(ctx, ...args);
|
|
30
|
+
if (!result)
|
|
31
|
+
throw new ApiException(403, 'Permission assertion failed');
|
|
32
|
+
return originalMethod.call(this, ...args);
|
|
33
|
+
};
|
|
34
|
+
return wrapper;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const scope = scopeOrAsserter;
|
|
38
|
+
return function (originalMethod, _context) {
|
|
39
|
+
const def = registerFunctionPermission(scope, value);
|
|
40
|
+
const wrapper = async function (...args) {
|
|
41
|
+
ModuleBE_PermissionsAssert.assertScopePermission(scope, value);
|
|
42
|
+
return originalMethod.call(this, ...args);
|
|
43
|
+
};
|
|
44
|
+
wrapper[RequirePermissionDefKey] = def;
|
|
45
|
+
return wrapper;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns the function-permission def attached to a handler, or undefined.
|
|
50
|
+
* Only set for the simple overload (scope + value).
|
|
51
|
+
*/
|
|
52
|
+
export function getRequirePermissionDef(handler) {
|
|
53
|
+
if (!handler || typeof handler !== 'function')
|
|
54
|
+
return undefined;
|
|
55
|
+
return handler[RequirePermissionDefKey];
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UniqueId } from '@nu-art/ts-common';
|
|
2
|
+
import { ModuleBE_BaseDB, PostWriteProcessingDataShape } from '@nu-art/db-api-backend';
|
|
3
|
+
import type { DatabaseDef_AccessGroup, DB_AccessGroup } from '@nu-art/permissions-shared';
|
|
4
|
+
import { CollectionActionType } from '@nu-art/firebase-backend/firestore/FirestoreCollection';
|
|
5
|
+
export interface OnAccessGroupChanged {
|
|
6
|
+
__onAccessGroupChanged(changedGroupIds: UniqueId[]): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare class ModuleBE_AccessGroupDB_Class extends ModuleBE_BaseDB<DatabaseDef_AccessGroup> {
|
|
9
|
+
constructor();
|
|
10
|
+
protected preWriteProcessing(instance: DB_AccessGroup, _original: DatabaseDef_AccessGroup['dbType']): Promise<void>;
|
|
11
|
+
protected postWriteProcessing(data: PostWriteProcessingDataShape<DatabaseDef_AccessGroup['dbType']>, _actionType: CollectionActionType): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare const ModuleBE_AccessGroupDB: ModuleBE_AccessGroupDB_Class;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ApiException, Dispatcher, filterDuplicates } from '@nu-art/ts-common';
|
|
2
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
3
|
+
import { DBDef_AccessGroup } from '@nu-art/permissions-shared';
|
|
4
|
+
const dispatcher_accessGroupChanged = new Dispatcher('__onAccessGroupChanged');
|
|
5
|
+
export class ModuleBE_AccessGroupDB_Class extends ModuleBE_BaseDB {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(DBDef_AccessGroup);
|
|
8
|
+
}
|
|
9
|
+
async preWriteProcessing(instance, _original) {
|
|
10
|
+
if ((instance.type === 'user' || instance.type === 'service-account') && instance.members.length > 0)
|
|
11
|
+
throw new ApiException(400, `${instance.type} access groups cannot have members`);
|
|
12
|
+
}
|
|
13
|
+
async postWriteProcessing(data, _actionType) {
|
|
14
|
+
const updated = Array.isArray(data.updated) ? data.updated : data.updated ? [data.updated] : [];
|
|
15
|
+
const before = Array.isArray(data.before) ? data.before : data.before ? [data.before] : [];
|
|
16
|
+
const deleted = Array.isArray(data.deleted) ? data.deleted : data.deleted ? [data.deleted] : [];
|
|
17
|
+
const changedGroupIds = [];
|
|
18
|
+
for (let i = 0; i < updated.length; i++) {
|
|
19
|
+
const prev = before[i];
|
|
20
|
+
if (!prev) {
|
|
21
|
+
changedGroupIds.push(updated[i]._id);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const membersChanged = JSON.stringify([...updated[i].members].sort()) !== JSON.stringify([...prev.members].sort());
|
|
25
|
+
const scopesChanged = JSON.stringify([...(updated[i].scopeEntries ?? [])].sort()) !== JSON.stringify([...(prev.scopeEntries ?? [])].sort());
|
|
26
|
+
if (membersChanged || scopesChanged)
|
|
27
|
+
changedGroupIds.push(updated[i]._id);
|
|
28
|
+
}
|
|
29
|
+
for (const del of deleted)
|
|
30
|
+
changedGroupIds.push(del._id);
|
|
31
|
+
if (changedGroupIds.length === 0)
|
|
32
|
+
return;
|
|
33
|
+
await dispatcher_accessGroupChanged.dispatchModuleAsync(filterDuplicates(changedGroupIds));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export const ModuleBE_AccessGroupDB = new ModuleBE_AccessGroupDB_Class();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ModulePackBE_AccessGroup: (import("./ModuleBE_AccessGroupDB.js").ModuleBE_AccessGroupDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/permissions-shared").DatabaseDef_AccessGroup, any>)[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
2
|
+
import { DatabaseDef_PermissionScope } from '@nu-art/permissions-shared';
|
|
3
|
+
export declare class ModuleBE_PermissionScopeDB_Class extends ModuleBE_BaseDB<DatabaseDef_PermissionScope> {
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
export declare const ModuleBE_PermissionScopeDB: ModuleBE_PermissionScopeDB_Class;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
2
|
+
import { DBDef_PermissionScope } from '@nu-art/permissions-shared';
|
|
3
|
+
export class ModuleBE_PermissionScopeDB_Class extends ModuleBE_BaseDB {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(DBDef_PermissionScope);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export const ModuleBE_PermissionScopeDB = new ModuleBE_PermissionScopeDB_Class();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ModulePackBE_PermissionScope: (import("./ModuleBE_PermissionScopeDB.js").ModuleBE_PermissionScopeDB_Class | import("@nu-art/db-api-backend").ModuleBE_BaseApi_Class<import("@nu-art/permissions-shared").DatabaseDef_PermissionScope, any>)[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ModuleBE_BaseApi_Class } from '@nu-art/db-api-backend';
|
|
2
|
+
import type { QueryParams } from '@nu-art/api-types';
|
|
3
|
+
import { DatabaseDef_UserPermissions, Response_MyPermissions } from '@nu-art/permissions-shared';
|
|
4
|
+
declare class ModuleBE_UserPermissionsAPI_Class extends ModuleBE_BaseApi_Class<DatabaseDef_UserPermissions> {
|
|
5
|
+
constructor();
|
|
6
|
+
getMyPermissions(_params: QueryParams): Promise<Response_MyPermissions>;
|
|
7
|
+
}
|
|
8
|
+
export declare const ModuleBE_UserPermissionsAPI: ModuleBE_UserPermissionsAPI_Class;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { ModuleBE_BaseApi_Class } from '@nu-art/db-api-backend';
|
|
36
|
+
import { CrudApiDef, stringToUniqueId } from '@nu-art/db-api-shared';
|
|
37
|
+
import { ApiHandler } from '@nu-art/http-server';
|
|
38
|
+
import { ApiDef_UserPermissions, DBDef_UserPermissions, } from '@nu-art/permissions-shared';
|
|
39
|
+
import { ModuleBE_UserPermissionsDB } from './ModuleBE_UserPermissionsDB.js';
|
|
40
|
+
import { SessionKey_Account_BE } from '@nu-art/user-account-backend';
|
|
41
|
+
let ModuleBE_UserPermissionsAPI_Class = (() => {
|
|
42
|
+
let _classSuper = ModuleBE_BaseApi_Class;
|
|
43
|
+
let _instanceExtraInitializers = [];
|
|
44
|
+
let _getMyPermissions_decorators;
|
|
45
|
+
return class ModuleBE_UserPermissionsAPI_Class extends _classSuper {
|
|
46
|
+
static {
|
|
47
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
48
|
+
_getMyPermissions_decorators = [ApiHandler(ApiDef_UserPermissions.getMyPermissions)];
|
|
49
|
+
__esDecorate(this, null, _getMyPermissions_decorators, { kind: "method", name: "getMyPermissions", static: false, private: false, access: { has: obj => "getMyPermissions" in obj, get: obj => obj.getMyPermissions }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
50
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
51
|
+
}
|
|
52
|
+
constructor() {
|
|
53
|
+
super({
|
|
54
|
+
dbModule: ModuleBE_UserPermissionsDB,
|
|
55
|
+
crudApiDef: CrudApiDef(DBDef_UserPermissions.dbKey),
|
|
56
|
+
});
|
|
57
|
+
__runInitializers(this, _instanceExtraInitializers);
|
|
58
|
+
}
|
|
59
|
+
async getMyPermissions(_params) {
|
|
60
|
+
const account = SessionKey_Account_BE.get();
|
|
61
|
+
const permissionsId = stringToUniqueId(account._id);
|
|
62
|
+
const entity = await ModuleBE_UserPermissionsDB.query.unique(permissionsId);
|
|
63
|
+
return { scopeEntries: entity?.scopeEntries ?? [] };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
})();
|
|
67
|
+
export const ModuleBE_UserPermissionsAPI = new ModuleBE_UserPermissionsAPI_Class();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
2
|
+
import { DatabaseDef_UserPermissions } from '@nu-art/permissions-shared';
|
|
3
|
+
export declare class ModuleBE_UserPermissionsDB_Class extends ModuleBE_BaseDB<DatabaseDef_UserPermissions> {
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
export declare const ModuleBE_UserPermissionsDB: ModuleBE_UserPermissionsDB_Class;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
2
|
+
import { DBDef_UserPermissions } from '@nu-art/permissions-shared';
|
|
3
|
+
export class ModuleBE_UserPermissionsDB_Class extends ModuleBE_BaseDB {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(DBDef_UserPermissions);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export const ModuleBE_UserPermissionsDB = new ModuleBE_UserPermissionsDB_Class();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PermissionScope } from '@nu-art/permissions-shared';
|
|
2
|
+
import type { DBPointer } from '@nu-art/ts-common';
|
|
3
|
+
export type PermissionAssertionContext = {
|
|
4
|
+
readonly hasScope: (scope: PermissionScope, value: string) => boolean;
|
|
5
|
+
readonly ownsEntity: (pointer: DBPointer) => Promise<boolean>;
|
|
6
|
+
readonly and: (...predicates: (boolean | Promise<boolean>)[]) => Promise<boolean>;
|
|
7
|
+
readonly or: (...predicates: (boolean | Promise<boolean>)[]) => Promise<boolean>;
|
|
8
|
+
};
|
|
9
|
+
export type PermissionAsserter = (assert: PermissionAssertionContext, ...args: any[]) => boolean | Promise<boolean>;
|
package/consts.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { SessionKey_BE } from '@nu-art/user-account-backend';
|
|
2
2
|
import { MemKey } from '@nu-art/ts-common/mem-storage/MemStorage';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import type { DBPointer } from '@nu-art/ts-common';
|
|
4
|
+
import type { ScopedAccessIds } from '@nu-art/permissions-shared';
|
|
5
|
+
import { SessionData_StrictMode } from '@nu-art/permissions-shared';
|
|
6
6
|
export declare const SessionKey_StrictMode_BE: SessionKey_BE<SessionData_StrictMode>;
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const MemKey_UserScopePermissions: MemKey<string[]>;
|
|
8
|
+
export declare const MemKey_UserEntityContexts: MemKey<DBPointer[]>;
|
|
9
|
+
export declare const MemKey_ServiceAccountId: MemKey<string>;
|
|
10
|
+
export declare const MemKey_UserAccessIds: MemKey<ScopedAccessIds>;
|
package/consts.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SessionKey_BE } from '@nu-art/user-account-backend';
|
|
2
2
|
import { MemKey } from '@nu-art/ts-common/mem-storage/MemStorage';
|
|
3
|
-
export const SessionKey_Permissions_BE = new SessionKey_BE('permissions');
|
|
4
3
|
export const SessionKey_StrictMode_BE = new SessionKey_BE('strictMode');
|
|
5
|
-
export const
|
|
4
|
+
export const MemKey_UserScopePermissions = new MemKey('user-scope-permissions');
|
|
5
|
+
export const MemKey_UserEntityContexts = new MemKey('user-entity-contexts');
|
|
6
|
+
export const MemKey_ServiceAccountId = new MemKey('service-account-id');
|
|
7
|
+
export const MemKey_UserAccessIds = new MemKey('user-access-ids');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PermissionScope } from '@nu-art/permissions-shared';
|
|
2
|
+
export type FunctionPermissionDef = {
|
|
3
|
+
id: string;
|
|
4
|
+
scopeKey: string;
|
|
5
|
+
value: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Registers a function permission (scope + value). Called from @RequirePermission decorator init.
|
|
9
|
+
* Returns the same def if (scopeKey, value) was already registered (stable id).
|
|
10
|
+
* Also stores the scope's ordered values array for position-based assertion.
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerFunctionPermission(scope: PermissionScope, value: string): FunctionPermissionDef;
|
|
13
|
+
/**
|
|
14
|
+
* Returns all registered function permissions for server load (create domains/levels in DB).
|
|
15
|
+
*/
|
|
16
|
+
export declare function getRegisteredFunctionPermissions(): FunctionPermissionDef[];
|
|
17
|
+
/**
|
|
18
|
+
* Returns the def for a given (scopeKey, value), or undefined if not registered.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getFunctionPermissionDef(scopeKey: string, value: string): FunctionPermissionDef | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the ordered values array for a scope key, or undefined if the scope was never registered.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getScopeValues(scopeKey: string): readonly string[] | undefined;
|
|
@@ -0,0 +1,60 @@
|
|
|
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 { md5 } from '@nu-art/ts-common';
|
|
20
|
+
const registry = new Map();
|
|
21
|
+
const scopeValuesRegistry = new Map();
|
|
22
|
+
function compositeKey(scopeKey, value) {
|
|
23
|
+
return `${scopeKey}\0${value}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Registers a function permission (scope + value). Called from @RequirePermission decorator init.
|
|
27
|
+
* Returns the same def if (scopeKey, value) was already registered (stable id).
|
|
28
|
+
* Also stores the scope's ordered values array for position-based assertion.
|
|
29
|
+
*/
|
|
30
|
+
export function registerFunctionPermission(scope, value) {
|
|
31
|
+
const scopeKey = scope.key;
|
|
32
|
+
if (!scopeValuesRegistry.has(scopeKey))
|
|
33
|
+
scopeValuesRegistry.set(scopeKey, scope.values);
|
|
34
|
+
const key = compositeKey(scopeKey, value);
|
|
35
|
+
const existing = registry.get(key);
|
|
36
|
+
if (existing)
|
|
37
|
+
return existing;
|
|
38
|
+
const id = md5(`function-permission/${scopeKey}/${value}`);
|
|
39
|
+
const def = { id, scopeKey, value };
|
|
40
|
+
registry.set(key, def);
|
|
41
|
+
return def;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returns all registered function permissions for server load (create domains/levels in DB).
|
|
45
|
+
*/
|
|
46
|
+
export function getRegisteredFunctionPermissions() {
|
|
47
|
+
return [...registry.values()];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the def for a given (scopeKey, value), or undefined if not registered.
|
|
51
|
+
*/
|
|
52
|
+
export function getFunctionPermissionDef(scopeKey, value) {
|
|
53
|
+
return registry.get(compositeKey(scopeKey, value));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Returns the ordered values array for a scope key, or undefined if the scope was never registered.
|
|
57
|
+
*/
|
|
58
|
+
export function getScopeValues(scopeKey) {
|
|
59
|
+
return scopeValuesRegistry.get(scopeKey);
|
|
60
|
+
}
|
package/core/module-pack.js
CHANGED
|
@@ -18,14 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { ModuleBE_PermissionsAssert } from '../modules/ModuleBE_PermissionsAssert.js';
|
|
20
20
|
import { ModuleBE_Permissions } from '../modules/ModuleBE_Permissions.js';
|
|
21
|
-
import {
|
|
21
|
+
import { ModulePackBE_PermissionScope } from '../_entity/permission-scope/module-pack.js';
|
|
22
|
+
import { ModulePackBE_UserPermissions } from '../_entity/user-permissions/module-pack.js';
|
|
23
|
+
import { ModulePackBE_AccessGroup } from '../_entity/access-group/module-pack.js';
|
|
22
24
|
export const ModulePackBE_Permissions = [
|
|
23
|
-
...
|
|
24
|
-
...
|
|
25
|
-
...
|
|
26
|
-
...ModulePackBE_PermissionDomain,
|
|
27
|
-
...ModulePackBE_PermissionGroup,
|
|
28
|
-
...ModulePackBE_PermissionUser,
|
|
25
|
+
...ModulePackBE_PermissionScope,
|
|
26
|
+
...ModulePackBE_UserPermissions,
|
|
27
|
+
...ModulePackBE_AccessGroup,
|
|
29
28
|
ModuleBE_PermissionsAssert,
|
|
30
29
|
ModuleBE_Permissions,
|
|
31
30
|
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UniqueId } from '@nu-art/ts-common';
|
|
2
|
+
import type { DB_Prototype } from '@nu-art/db-api-shared';
|
|
3
|
+
import type { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
4
|
+
import type { DocumentAccessCapabilities } from '@nu-art/permissions-shared';
|
|
5
|
+
export declare function shareDocument<Database extends DB_Prototype>(dbModule: ModuleBE_BaseDB<Database>, documentId: Database['uniqueParam'], principalId: UniqueId, capabilities: DocumentAccessCapabilities): Promise<Database['dbType']>;
|
|
6
|
+
export declare function unshareDocument<Database extends DB_Prototype>(dbModule: ModuleBE_BaseDB<Database>, documentId: Database['uniqueParam'], principalId: UniqueId, capabilities: DocumentAccessCapabilities): Promise<Database['dbType']>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ApiException, filterDuplicates, removeItemFromArray } from '@nu-art/ts-common';
|
|
2
|
+
import { CapabilityToAccessKey } from '@nu-art/permissions-shared';
|
|
3
|
+
import { MemKey_UserAccessIds } from './consts.js';
|
|
4
|
+
function getAllAccessIds() {
|
|
5
|
+
const dict = MemKey_UserAccessIds.get();
|
|
6
|
+
return filterDuplicates(Object.values(dict).flat());
|
|
7
|
+
}
|
|
8
|
+
function assertOwnership(access) {
|
|
9
|
+
const accessIds = getAllAccessIds();
|
|
10
|
+
const isOwner = access?.owners?.some(id => accessIds.includes(id));
|
|
11
|
+
if (!isOwner)
|
|
12
|
+
throw new ApiException(403, 'Only document owners can manage access');
|
|
13
|
+
}
|
|
14
|
+
export async function shareDocument(dbModule, documentId, principalId, capabilities) {
|
|
15
|
+
const doc = await dbModule.query.unique(documentId);
|
|
16
|
+
if (!doc)
|
|
17
|
+
throw new ApiException(404, 'Document not found');
|
|
18
|
+
const mutable = doc;
|
|
19
|
+
assertOwnership(mutable.__access);
|
|
20
|
+
if (!mutable.__access)
|
|
21
|
+
mutable.__access = {};
|
|
22
|
+
for (const [cap, enabled] of Object.entries(capabilities)) {
|
|
23
|
+
if (!enabled)
|
|
24
|
+
continue;
|
|
25
|
+
const accessKey = CapabilityToAccessKey[cap];
|
|
26
|
+
if (!mutable.__access[accessKey])
|
|
27
|
+
mutable.__access[accessKey] = [];
|
|
28
|
+
mutable.__access[accessKey] = filterDuplicates([...mutable.__access[accessKey], principalId]);
|
|
29
|
+
}
|
|
30
|
+
return dbModule.set.item(mutable);
|
|
31
|
+
}
|
|
32
|
+
export async function unshareDocument(dbModule, documentId, principalId, capabilities) {
|
|
33
|
+
const doc = await dbModule.query.unique(documentId);
|
|
34
|
+
if (!doc)
|
|
35
|
+
throw new ApiException(404, 'Document not found');
|
|
36
|
+
const mutable = doc;
|
|
37
|
+
assertOwnership(mutable.__access);
|
|
38
|
+
if (!mutable.__access)
|
|
39
|
+
return dbModule.set.item(mutable);
|
|
40
|
+
for (const [cap, enabled] of Object.entries(capabilities)) {
|
|
41
|
+
if (!enabled)
|
|
42
|
+
continue;
|
|
43
|
+
const accessKey = CapabilityToAccessKey[cap];
|
|
44
|
+
if (!mutable.__access[accessKey])
|
|
45
|
+
continue;
|
|
46
|
+
removeItemFromArray(mutable.__access[accessKey], principalId);
|
|
47
|
+
}
|
|
48
|
+
return dbModule.set.item(mutable);
|
|
49
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DB_Prototype } from '@nu-art/db-api-shared';
|
|
2
|
+
import type { ModuleBE_BaseDB } from '@nu-art/db-api-backend';
|
|
3
|
+
import type { DatabaseDef_AccessGroup, DocumentAccessFields, DocumentAccessInner } from '@nu-art/permissions-shared';
|
|
4
|
+
import { UniqueId } from '@nu-art/ts-common';
|
|
5
|
+
export type AccessContextResolver<Database extends DB_Prototype = DB_Prototype> = (item: Database['uiType']) => Promise<DocumentAccessFields> | DocumentAccessFields;
|
|
6
|
+
export declare function copyAccessFields(source: Record<string, unknown>): DocumentAccessFields;
|
|
7
|
+
export declare function deriveEntityGroupId(entityId: UniqueId, accessKey: keyof DocumentAccessInner): DatabaseDef_AccessGroup['id'];
|
|
8
|
+
export declare function deriveEntityAccessFields(entityId: UniqueId): DocumentAccessFields;
|
|
9
|
+
export declare function wireDocumentAccess<Database extends DB_Prototype>(dbModule: ModuleBE_BaseDB<Database>, resolverProvider: () => AccessContextResolver<Database> | undefined, scopeKeysProvider: () => string[] | undefined): void;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { hashToUniqueId } from '@nu-art/db-api-shared';
|
|
2
|
+
import { AccessScope_Self, AllDocumentAccessKeys } from '@nu-art/permissions-shared';
|
|
3
|
+
import { ApiException, filterDuplicates, tsValidateResult, tsValidator_arrayOfUniqueIds } from '@nu-art/ts-common';
|
|
4
|
+
import { MemKey_UserAccessIds } from './consts.js';
|
|
5
|
+
const documentAccessInnerValidator = {
|
|
6
|
+
readers: tsValidator_arrayOfUniqueIds,
|
|
7
|
+
writers: tsValidator_arrayOfUniqueIds,
|
|
8
|
+
deleters: tsValidator_arrayOfUniqueIds,
|
|
9
|
+
owners: tsValidator_arrayOfUniqueIds,
|
|
10
|
+
};
|
|
11
|
+
function getCallerScopedAccessIds() {
|
|
12
|
+
return MemKey_UserAccessIds.peak();
|
|
13
|
+
}
|
|
14
|
+
function resolveAccessIds(scopedDict, scopeKeys) {
|
|
15
|
+
const selfIds = scopedDict[AccessScope_Self] ?? [];
|
|
16
|
+
if (!scopeKeys)
|
|
17
|
+
return filterDuplicates([...selfIds, ...Object.values(scopedDict).flat()]);
|
|
18
|
+
const scopedIds = scopeKeys.flatMap(key => scopedDict[key] ?? []);
|
|
19
|
+
return filterDuplicates([...selfIds, ...scopedIds]);
|
|
20
|
+
}
|
|
21
|
+
function defaultAccessFields(callerAccessIds) {
|
|
22
|
+
const callerId = callerAccessIds[0];
|
|
23
|
+
return {
|
|
24
|
+
__access: {
|
|
25
|
+
readers: [callerId],
|
|
26
|
+
writers: [callerId],
|
|
27
|
+
deleters: [callerId],
|
|
28
|
+
owners: [callerId],
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function assertCallerAccess(access, callerIds, ...keys) {
|
|
33
|
+
if (!keys.some(key => access[key]?.length))
|
|
34
|
+
return;
|
|
35
|
+
if (keys.some(key => access[key]?.some(id => callerIds.includes(id))))
|
|
36
|
+
return;
|
|
37
|
+
throw new ApiException(403, 'Insufficient document access');
|
|
38
|
+
}
|
|
39
|
+
function createQueryInterceptor(scopeKeysProvider) {
|
|
40
|
+
return (query) => {
|
|
41
|
+
const scopedDict = getCallerScopedAccessIds();
|
|
42
|
+
if (!scopedDict)
|
|
43
|
+
return query;
|
|
44
|
+
const accessIds = resolveAccessIds(scopedDict, scopeKeysProvider());
|
|
45
|
+
const where = (query.where ?? {});
|
|
46
|
+
where.__access = { readers: { $aca: accessIds } };
|
|
47
|
+
query.where = where;
|
|
48
|
+
return query;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createPreWriteInterceptor(resolverProvider, scopeKeysProvider) {
|
|
52
|
+
return async (dbItem, original) => {
|
|
53
|
+
const scopedDict = getCallerScopedAccessIds();
|
|
54
|
+
if (!scopedDict)
|
|
55
|
+
return;
|
|
56
|
+
const item = dbItem;
|
|
57
|
+
delete item.__access;
|
|
58
|
+
if (!original) {
|
|
59
|
+
const selfIds = scopedDict[AccessScope_Self] ?? [];
|
|
60
|
+
const resolver = resolverProvider();
|
|
61
|
+
const resolved = resolver ? await resolver(dbItem) : defaultAccessFields(selfIds);
|
|
62
|
+
item.__access = {
|
|
63
|
+
...resolved.__access,
|
|
64
|
+
owners: filterDuplicates([...(resolved.__access.owners ?? []), ...selfIds]),
|
|
65
|
+
};
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const existingAccess = original.__access;
|
|
69
|
+
if (!existingAccess)
|
|
70
|
+
return;
|
|
71
|
+
item.__access = { ...existingAccess };
|
|
72
|
+
assertCallerAccess(existingAccess, resolveAccessIds(scopedDict, scopeKeysProvider()), 'writers', 'owners');
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function createPreDeleteInterceptor(scopeKeysProvider) {
|
|
76
|
+
return async (dbItems) => {
|
|
77
|
+
const scopedDict = getCallerScopedAccessIds();
|
|
78
|
+
if (!scopedDict)
|
|
79
|
+
return;
|
|
80
|
+
const accessIds = resolveAccessIds(scopedDict, scopeKeysProvider());
|
|
81
|
+
for (const dbItem of dbItems) {
|
|
82
|
+
const access = dbItem.__access;
|
|
83
|
+
if (!access)
|
|
84
|
+
throw new ApiException(403, 'No delete access to this document');
|
|
85
|
+
assertCallerAccess(access, accessIds, 'deleters', 'owners');
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function copyAccessFields(source) {
|
|
90
|
+
const access = source.__access;
|
|
91
|
+
return {
|
|
92
|
+
__access: {
|
|
93
|
+
readers: [...(access?.readers ?? [])],
|
|
94
|
+
writers: [...(access?.writers ?? [])],
|
|
95
|
+
deleters: [...(access?.deleters ?? [])],
|
|
96
|
+
owners: [...(access?.owners ?? [])],
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function deriveEntityGroupId(entityId, accessKey) {
|
|
101
|
+
return hashToUniqueId(`${entityId}:${accessKey}`);
|
|
102
|
+
}
|
|
103
|
+
export function deriveEntityAccessFields(entityId) {
|
|
104
|
+
const inner = AllDocumentAccessKeys.reduce((fields, key) => {
|
|
105
|
+
fields[key] = [deriveEntityGroupId(entityId, key)];
|
|
106
|
+
return fields;
|
|
107
|
+
}, {});
|
|
108
|
+
return { __access: inner };
|
|
109
|
+
}
|
|
110
|
+
function patchCollectionValidator(dbModule) {
|
|
111
|
+
let patched = false;
|
|
112
|
+
dbModule.registerPreWriteInterceptor(async () => {
|
|
113
|
+
if (patched)
|
|
114
|
+
return;
|
|
115
|
+
patched = true;
|
|
116
|
+
const collection = dbModule.collection;
|
|
117
|
+
const originalValidate = collection.validateItem.bind(collection);
|
|
118
|
+
collection.validateItem = (dbItem) => {
|
|
119
|
+
const extractedAccess = dbItem.__access;
|
|
120
|
+
delete dbItem.__access;
|
|
121
|
+
originalValidate(dbItem);
|
|
122
|
+
if (extractedAccess) {
|
|
123
|
+
const results = tsValidateResult(extractedAccess, documentAccessInnerValidator);
|
|
124
|
+
if (results)
|
|
125
|
+
throw new ApiException(400, `Invalid document access fields: ${JSON.stringify(results)}`);
|
|
126
|
+
}
|
|
127
|
+
if (extractedAccess)
|
|
128
|
+
dbItem.__access = extractedAccess;
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
export function wireDocumentAccess(dbModule, resolverProvider, scopeKeysProvider) {
|
|
133
|
+
patchCollectionValidator(dbModule);
|
|
134
|
+
dbModule.registerQueryInterceptor(createQueryInterceptor(scopeKeysProvider));
|
|
135
|
+
dbModule.registerPreWriteInterceptor(createPreWriteInterceptor(resolverProvider, scopeKeysProvider));
|
|
136
|
+
dbModule.registerPreDeleteInterceptor(createPreDeleteInterceptor(scopeKeysProvider));
|
|
137
|
+
}
|