@nu-art/permissions-backend 0.500.0 → 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.
Files changed (88) hide show
  1. package/RequirePermission.d.ts +22 -10
  2. package/RequirePermission.js +24 -15
  3. package/_entity/access-group/ModuleBE_AccessGroupDB.d.ts +13 -0
  4. package/_entity/access-group/ModuleBE_AccessGroupDB.js +36 -0
  5. package/_entity/access-group/module-pack.d.ts +1 -0
  6. package/_entity/access-group/module-pack.js +3 -0
  7. package/_entity/permission-scope/ModuleBE_PermissionScopeDB.d.ts +6 -0
  8. package/_entity/permission-scope/ModuleBE_PermissionScopeDB.js +8 -0
  9. package/_entity/permission-scope/module-pack.d.ts +1 -0
  10. package/_entity/permission-scope/module-pack.js +3 -0
  11. package/_entity/user-permissions/ModuleBE_UserPermissionsAPI.d.ts +9 -0
  12. package/_entity/{permission-user/ModuleBE_PermissionUserAPI.js → user-permissions/ModuleBE_UserPermissionsAPI.js} +17 -16
  13. package/_entity/user-permissions/ModuleBE_UserPermissionsDB.d.ts +6 -0
  14. package/_entity/user-permissions/ModuleBE_UserPermissionsDB.js +8 -0
  15. package/_entity/user-permissions/module-pack.d.ts +2 -0
  16. package/_entity/user-permissions/module-pack.js +3 -0
  17. package/assertion-types.d.ts +9 -0
  18. package/consts.d.ts +7 -4
  19. package/consts.js +4 -2
  20. package/core/function-permission-registry.d.ts +5 -6
  21. package/core/function-permission-registry.js +10 -0
  22. package/core/module-pack.js +6 -7
  23. package/document-access-api.d.ts +6 -0
  24. package/document-access-api.js +49 -0
  25. package/document-access-enforcement.d.ts +9 -0
  26. package/document-access-enforcement.js +137 -0
  27. package/index.d.ts +12 -6
  28. package/index.js +12 -6
  29. package/modules/ModuleBE_Permissions.d.ts +63 -78
  30. package/modules/ModuleBE_Permissions.js +494 -441
  31. package/modules/ModuleBE_PermissionsAssert.d.ts +6 -54
  32. package/modules/ModuleBE_PermissionsAssert.js +60 -285
  33. package/package.json +14 -12
  34. package/PermissionKey_BE.d.ts +0 -16
  35. package/PermissionKey_BE.js +0 -59
  36. package/_entity/permission-access-level/ModuleBE_PermissionAccessLevelDB.d.ts +0 -13
  37. package/_entity/permission-access-level/ModuleBE_PermissionAccessLevelDB.js +0 -49
  38. package/_entity/permission-access-level/index.d.ts +0 -2
  39. package/_entity/permission-access-level/index.js +0 -2
  40. package/_entity/permission-access-level/module-pack.d.ts +0 -1
  41. package/_entity/permission-access-level/module-pack.js +0 -3
  42. package/_entity/permission-api/ModuleBE_PermissionAPIDB.d.ts +0 -10
  43. package/_entity/permission-api/ModuleBE_PermissionAPIDB.js +0 -62
  44. package/_entity/permission-api/index.d.ts +0 -2
  45. package/_entity/permission-api/index.js +0 -2
  46. package/_entity/permission-api/module-pack.d.ts +0 -1
  47. package/_entity/permission-api/module-pack.js +0 -3
  48. package/_entity/permission-domain/ModuleBE_PermissionDomainDB.d.ts +0 -9
  49. package/_entity/permission-domain/ModuleBE_PermissionDomainDB.js +0 -22
  50. package/_entity/permission-domain/index.d.ts +0 -2
  51. package/_entity/permission-domain/index.js +0 -2
  52. package/_entity/permission-domain/module-pack.d.ts +0 -1
  53. package/_entity/permission-domain/module-pack.js +0 -3
  54. package/_entity/permission-group/ModuleBE_PermissionGroupDB.d.ts +0 -12
  55. package/_entity/permission-group/ModuleBE_PermissionGroupDB.js +0 -65
  56. package/_entity/permission-group/index.d.ts +0 -2
  57. package/_entity/permission-group/index.js +0 -2
  58. package/_entity/permission-group/module-pack.d.ts +0 -1
  59. package/_entity/permission-group/module-pack.js +0 -3
  60. package/_entity/permission-project/ModuleBE_PermissionProjectDB.d.ts +0 -8
  61. package/_entity/permission-project/ModuleBE_PermissionProjectDB.js +0 -12
  62. package/_entity/permission-project/index.d.ts +0 -2
  63. package/_entity/permission-project/index.js +0 -2
  64. package/_entity/permission-project/module-pack.d.ts +0 -1
  65. package/_entity/permission-project/module-pack.js +0 -3
  66. package/_entity/permission-user/ModuleBE_PermissionUserAPI.d.ts +0 -9
  67. package/_entity/permission-user/ModuleBE_PermissionUserDB.d.ts +0 -34
  68. package/_entity/permission-user/ModuleBE_PermissionUserDB.js +0 -241
  69. package/_entity/permission-user/index.d.ts +0 -3
  70. package/_entity/permission-user/index.js +0 -3
  71. package/_entity/permission-user/module-pack.d.ts +0 -2
  72. package/_entity/permission-user/module-pack.js +0 -3
  73. package/_entity.d.ts +0 -12
  74. package/_entity.js +0 -18
  75. package/core/external-api-paths.d.ts +0 -13
  76. package/core/external-api-paths.js +0 -13
  77. package/core/utils.d.ts +0 -25
  78. package/core/utils.js +0 -85
  79. package/modules/consts.d.ts +0 -11
  80. package/modules/consts.js +0 -29
  81. package/modules/index.d.ts +0 -2
  82. package/modules/index.js +0 -20
  83. package/permissions-wire.d.ts +0 -46
  84. package/permissions-wire.js +0 -47
  85. package/permissions.d.ts +0 -22
  86. package/permissions.js +0 -152
  87. package/types.d.ts +0 -28
  88. /package/{types.js → assertion-types.js} +0 -0
@@ -1,21 +1,33 @@
1
1
  import type { PermissionScope } from '@nu-art/permissions-shared';
2
2
  import type { FunctionPermissionDef } from './core/function-permission-registry.js';
3
+ import type { PermissionAsserter } from './assertion-types.js';
3
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>;
4
8
  /**
5
- * Symbol key used to attach the function-permission def to a method.
6
- * PermissionsAssert (or middleware) can read this to assert before invoking the handler.
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).
7
11
  */
8
- export declare const RequirePermissionDefKey: unique symbol;
12
+ export declare function RequirePermission<P extends PermissionScope>(scope: P, value: P['values'][number]): AsyncMethodDecorator;
9
13
  /**
10
- * Method decorator that registers a function permission (scope + value) and attaches
11
- * the def to the method. No assert in the decorator; assert runs at request time
12
- * when the handler is invoked (via PermissionsAssert or module wrapper).
14
+ * Complex overload: receives a PermissionAssertionContext and the decorated
15
+ * method's arguments. Return true to allow, false to deny (throws 403).
13
16
  *
14
- * @param scope - Branded permission scope (e.g. definePermissionScope('pathway', ['read','write','delete','admin']))
15
- * @param value - One of scope.values (e.g. 'write')
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
+ * })
16
27
  */
17
- export declare function RequirePermission<P extends PermissionScope>(scope: P, value: P['values'][number]): <T extends (this: unknown, ...args: unknown[]) => Promise<unknown>>(originalMethod: T, _context: ClassMethodDecoratorContext<unknown, T>) => T;
28
+ export declare function RequirePermission(asserter: PermissionAsserter): AsyncMethodDecorator;
18
29
  /**
19
30
  * Returns the function-permission def attached to a handler, or undefined.
31
+ * Only set for the simple overload (scope + value).
20
32
  */
21
- export declare function getRequirePermissionDef(handler: ((...args: unknown[]) => unknown) | null | undefined): FunctionPermissionDef | undefined;
33
+ export declare function getRequirePermissionDef(handler: ((...args: any[]) => any) | null | undefined): FunctionPermissionDef | undefined;
@@ -16,29 +16,38 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
+ import { ApiException } from '@nu-art/ts-common';
19
20
  import { registerFunctionPermission } from './core/function-permission-registry.js';
20
- /**
21
- * Symbol key used to attach the function-permission def to a method.
22
- * PermissionsAssert (or middleware) can read this to assert before invoking the handler.
23
- */
21
+ import { ModuleBE_PermissionsAssert } from './modules/ModuleBE_PermissionsAssert.js';
24
22
  export const RequirePermissionDefKey = Symbol.for('RequirePermissionDef');
25
- /**
26
- * Method decorator that registers a function permission (scope + value) and attaches
27
- * the def to the method. No assert in the decorator; assert runs at request time
28
- * when the handler is invoked (via PermissionsAssert or module wrapper).
29
- *
30
- * @param scope - Branded permission scope (e.g. definePermissionScope('pathway', ['read','write','delete','admin']))
31
- * @param value - One of scope.values (e.g. 'write')
32
- */
33
- export function RequirePermission(scope, value) {
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;
34
38
  return function (originalMethod, _context) {
35
39
  const def = registerFunctionPermission(scope, value);
36
- originalMethod[RequirePermissionDefKey] = def;
37
- return originalMethod;
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;
38
46
  };
39
47
  }
40
48
  /**
41
49
  * Returns the function-permission def attached to a handler, or undefined.
50
+ * Only set for the simple overload (scope + value).
42
51
  */
43
52
  export function getRequirePermissionDef(handler) {
44
53
  if (!handler || typeof handler !== 'function')
@@ -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,3 @@
1
+ import { createApisForDBModule } from '@nu-art/db-api-backend';
2
+ import { ModuleBE_AccessGroupDB } from './ModuleBE_AccessGroupDB.js';
3
+ export const ModulePackBE_AccessGroup = [ModuleBE_AccessGroupDB, createApisForDBModule(ModuleBE_AccessGroupDB)];
@@ -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,3 @@
1
+ import { createApisForDBModule } from '@nu-art/db-api-backend';
2
+ import { ModuleBE_PermissionScopeDB } from './ModuleBE_PermissionScopeDB.js';
3
+ export const ModulePackBE_PermissionScope = [ModuleBE_PermissionScopeDB, createApisForDBModule(ModuleBE_PermissionScopeDB)];
@@ -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 {};
@@ -32,35 +32,36 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
32
32
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
33
  done = true;
34
34
  };
35
- import { CrudApiDef } from '@nu-art/db-api-shared';
36
35
  import { ModuleBE_BaseApi_Class } from '@nu-art/db-api-backend';
36
+ import { CrudApiDef, stringToUniqueId } from '@nu-art/db-api-shared';
37
37
  import { ApiHandler } from '@nu-art/http-server';
38
- import { ApiDef_PermissionUser, DBDef_PermissionUser } from '@nu-art/permissions-shared';
39
- import { ModuleBE_PermissionUserDB } from './ModuleBE_PermissionUserDB.js';
40
- let ModuleBE_PermissionUserAPI_Class = (() => {
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 = (() => {
41
42
  let _classSuper = ModuleBE_BaseApi_Class;
42
43
  let _instanceExtraInitializers = [];
43
- let _assignPermissions_decorators;
44
- return class ModuleBE_PermissionUserAPI_Class extends _classSuper {
44
+ let _getMyPermissions_decorators;
45
+ return class ModuleBE_UserPermissionsAPI_Class extends _classSuper {
45
46
  static {
46
47
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
47
- _assignPermissions_decorators = [ApiHandler(ApiDef_PermissionUser.assignPermissions)];
48
- __esDecorate(this, null, _assignPermissions_decorators, { kind: "method", name: "assignPermissions", static: false, private: false, access: { has: obj => "assignPermissions" in obj, get: obj => obj.assignPermissions }, metadata: _metadata }, null, _instanceExtraInitializers);
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);
49
50
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
50
51
  }
51
52
  constructor() {
52
53
  super({
53
- dbModule: ModuleBE_PermissionUserDB,
54
- crudApiDef: CrudApiDef(DBDef_PermissionUser.dbKey),
54
+ dbModule: ModuleBE_UserPermissionsDB,
55
+ crudApiDef: CrudApiDef(DBDef_UserPermissions.dbKey),
55
56
  });
56
57
  __runInitializers(this, _instanceExtraInitializers);
57
58
  }
58
- init() {
59
- super.init();
60
- }
61
- async assignPermissions(body) {
62
- await ModuleBE_PermissionUserDB.assignPermissions(body);
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 ?? [] };
63
64
  }
64
65
  };
65
66
  })();
66
- export const ModuleBE_PermissionUserAPI = new ModuleBE_PermissionUserAPI_Class();
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,2 @@
1
+ import { Module } from '@nu-art/ts-common';
2
+ export declare const ModulePackBE_UserPermissions: Module[];
@@ -0,0 +1,3 @@
1
+ import { ModuleBE_UserPermissionsDB } from './ModuleBE_UserPermissionsDB.js';
2
+ import { ModuleBE_UserPermissionsAPI } from './ModuleBE_UserPermissionsAPI.js';
3
+ export const ModulePackBE_UserPermissions = [ModuleBE_UserPermissionsDB, ModuleBE_UserPermissionsAPI];
@@ -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 { TypedMap } from '@nu-art/ts-common';
4
- import { SessionData_Permissions, SessionData_StrictMode } from '@nu-art/permissions-shared';
5
- export declare const SessionKey_Permissions_BE: SessionKey_BE<SessionData_Permissions>;
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 MemKey_UserPermissions: MemKey<TypedMap<number>>;
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 MemKey_UserPermissions = new MemKey('user-permissions'); //[domainId]: access level numerical value
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');
@@ -3,16 +3,11 @@ export type FunctionPermissionDef = {
3
3
  id: string;
4
4
  scopeKey: string;
5
5
  value: string;
6
- /** Set on server load when domains/levels are created from registry. */
7
- domainId?: string;
8
- /** Set on server load when domains/levels are created from registry. */
9
- levelId?: string;
10
- /** Numeric level value for assert (user level >= required). Set on server load. */
11
- levelValue?: number;
12
6
  };
13
7
  /**
14
8
  * Registers a function permission (scope + value). Called from @RequirePermission decorator init.
15
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.
16
11
  */
17
12
  export declare function registerFunctionPermission(scope: PermissionScope, value: string): FunctionPermissionDef;
18
13
  /**
@@ -23,3 +18,7 @@ export declare function getRegisteredFunctionPermissions(): FunctionPermissionDe
23
18
  * Returns the def for a given (scopeKey, value), or undefined if not registered.
24
19
  */
25
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;
@@ -18,15 +18,19 @@
18
18
  */
19
19
  import { md5 } from '@nu-art/ts-common';
20
20
  const registry = new Map();
21
+ const scopeValuesRegistry = new Map();
21
22
  function compositeKey(scopeKey, value) {
22
23
  return `${scopeKey}\0${value}`;
23
24
  }
24
25
  /**
25
26
  * Registers a function permission (scope + value). Called from @RequirePermission decorator init.
26
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.
27
29
  */
28
30
  export function registerFunctionPermission(scope, value) {
29
31
  const scopeKey = scope.key;
32
+ if (!scopeValuesRegistry.has(scopeKey))
33
+ scopeValuesRegistry.set(scopeKey, scope.values);
30
34
  const key = compositeKey(scopeKey, value);
31
35
  const existing = registry.get(key);
32
36
  if (existing)
@@ -48,3 +52,9 @@ export function getRegisteredFunctionPermissions() {
48
52
  export function getFunctionPermissionDef(scopeKey, value) {
49
53
  return registry.get(compositeKey(scopeKey, value));
50
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
+ }
@@ -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 { ModulePackBE_PermissionAccessLevel, ModulePackBE_PermissionAPI, ModulePackBE_PermissionDomain, ModulePackBE_PermissionGroup, ModulePackBE_PermissionProject, ModulePackBE_PermissionUser } from '../_entity.js';
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
- ...ModulePackBE_PermissionAccessLevel,
24
- ...ModulePackBE_PermissionAPI,
25
- ...ModulePackBE_PermissionProject,
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
+ }
package/index.d.ts CHANGED
@@ -1,9 +1,15 @@
1
- export * from './consts.js';
2
1
  export * from './core/module-pack.js';
3
- export * from './permissions-wire.js';
4
2
  export * from './core/function-permission-registry.js';
3
+ export * from './assertion-types.js';
5
4
  export * from './RequirePermission.js';
6
- export * from './modules/index.js';
7
- export * from './permissions.js';
8
- export * from './_entity.js';
9
- export * from './types.js';
5
+ export * from './modules/ModuleBE_Permissions.js';
6
+ export * from './modules/ModuleBE_PermissionsAssert.js';
7
+ export * from './_entity/permission-scope/ModuleBE_PermissionScopeDB.js';
8
+ export * from './_entity/permission-scope/module-pack.js';
9
+ export * from './_entity/user-permissions/ModuleBE_UserPermissionsDB.js';
10
+ export * from './_entity/user-permissions/ModuleBE_UserPermissionsAPI.js';
11
+ export * from './_entity/user-permissions/module-pack.js';
12
+ export * from './_entity/access-group/ModuleBE_AccessGroupDB.js';
13
+ export * from './_entity/access-group/module-pack.js';
14
+ export * from './document-access-enforcement.js';
15
+ export * from './document-access-api.js';