@klerick/acl-json-api-nestjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +3556 -0
- package/package.json +41 -0
- package/src/index.d.ts +8 -0
- package/src/index.js +15 -0
- package/src/index.js.map +1 -0
- package/src/lib/constants/index.d.ts +14 -0
- package/src/lib/constants/index.js +18 -0
- package/src/lib/constants/index.js.map +1 -0
- package/src/lib/decorators/acl-controller.decorator.d.ts +4 -0
- package/src/lib/decorators/acl-controller.decorator.js +19 -0
- package/src/lib/decorators/acl-controller.decorator.js.map +1 -0
- package/src/lib/decorators/index.d.ts +1 -0
- package/src/lib/decorators/index.js +6 -0
- package/src/lib/decorators/index.js.map +1 -0
- package/src/lib/factories/ability-proxy.factory.d.ts +17 -0
- package/src/lib/factories/ability-proxy.factory.js +100 -0
- package/src/lib/factories/ability-proxy.factory.js.map +1 -0
- package/src/lib/factories/ability.factory.d.ts +49 -0
- package/src/lib/factories/ability.factory.js +235 -0
- package/src/lib/factories/ability.factory.js.map +1 -0
- package/src/lib/factories/index.d.ts +2 -0
- package/src/lib/factories/index.js +6 -0
- package/src/lib/factories/index.js.map +1 -0
- package/src/lib/guards/acl.guard.d.ts +21 -0
- package/src/lib/guards/acl.guard.js +68 -0
- package/src/lib/guards/acl.guard.js.map +1 -0
- package/src/lib/guards/index.d.ts +1 -0
- package/src/lib/guards/index.js +5 -0
- package/src/lib/guards/index.js.map +1 -0
- package/src/lib/nestjs-acl-permissions.module.d.ts +9 -0
- package/src/lib/nestjs-acl-permissions.module.js +56 -0
- package/src/lib/nestjs-acl-permissions.module.js.map +1 -0
- package/src/lib/services/acl-authorization.service.d.ts +10 -0
- package/src/lib/services/acl-authorization.service.js +100 -0
- package/src/lib/services/acl-authorization.service.js.map +1 -0
- package/src/lib/services/index.d.ts +2 -0
- package/src/lib/services/index.js +6 -0
- package/src/lib/services/index.js.map +1 -0
- package/src/lib/services/rule-materializer.service.d.ts +73 -0
- package/src/lib/services/rule-materializer.service.js +251 -0
- package/src/lib/services/rule-materializer.service.js.map +1 -0
- package/src/lib/types/acl-context.types.d.ts +14 -0
- package/src/lib/types/acl-context.types.js +3 -0
- package/src/lib/types/acl-context.types.js.map +1 -0
- package/src/lib/types/acl-options.types.d.ts +97 -0
- package/src/lib/types/acl-options.types.js +3 -0
- package/src/lib/types/acl-options.types.js.map +1 -0
- package/src/lib/types/acl-rules.types.d.ts +201 -0
- package/src/lib/types/acl-rules.types.js +27 -0
- package/src/lib/types/acl-rules.types.js.map +1 -0
- package/src/lib/types/decorator-options.types.d.ts +64 -0
- package/src/lib/types/decorator-options.types.js +3 -0
- package/src/lib/types/decorator-options.types.js.map +1 -0
- package/src/lib/types/index.d.ts +4 -0
- package/src/lib/types/index.js +8 -0
- package/src/lib/types/index.js.map +1 -0
- package/src/lib/utils/index.d.ts +10 -0
- package/src/lib/utils/index.js +53 -0
- package/src/lib/utils/index.js.map +1 -0
- package/src/lib/utils/orm-proxy/extract-field-paths.d.ts +73 -0
- package/src/lib/utils/orm-proxy/extract-field-paths.js +155 -0
- package/src/lib/utils/orm-proxy/extract-field-paths.js.map +1 -0
- package/src/lib/utils/orm-proxy/handle-acl-query-error.d.ts +19 -0
- package/src/lib/utils/orm-proxy/handle-acl-query-error.js +53 -0
- package/src/lib/utils/orm-proxy/handle-acl-query-error.js.map +1 -0
- package/src/lib/utils/orm-proxy/index.d.ts +9 -0
- package/src/lib/utils/orm-proxy/index.js +24 -0
- package/src/lib/utils/orm-proxy/index.js.map +1 -0
- package/src/lib/utils/orm-proxy/merge-query-with-acl-data.d.ts +27 -0
- package/src/lib/utils/orm-proxy/merge-query-with-acl-data.js +78 -0
- package/src/lib/utils/orm-proxy/merge-query-with-acl-data.js.map +1 -0
- package/src/lib/utils/orm-proxy/prepare-acl-query.d.ts +11 -0
- package/src/lib/utils/orm-proxy/prepare-acl-query.js +35 -0
- package/src/lib/utils/orm-proxy/prepare-acl-query.js.map +1 -0
- package/src/lib/utils/orm-proxy/process-item-field-restrictions.d.ts +24 -0
- package/src/lib/utils/orm-proxy/process-item-field-restrictions.js +42 -0
- package/src/lib/utils/orm-proxy/process-item-field-restrictions.js.map +1 -0
- package/src/lib/utils/orm-proxy/remove-acl-added-fields.d.ts +31 -0
- package/src/lib/utils/orm-proxy/remove-acl-added-fields.js +104 -0
- package/src/lib/utils/orm-proxy/remove-acl-added-fields.js.map +1 -0
- package/src/lib/utils/orm-proxy/unset-deep.d.ts +13 -0
- package/src/lib/utils/orm-proxy/unset-deep.js +41 -0
- package/src/lib/utils/orm-proxy/unset-deep.js.map +1 -0
- package/src/lib/utils/orm-proxy/validate-no-current-in-rules.d.ts +19 -0
- package/src/lib/utils/orm-proxy/validate-no-current-in-rules.js +33 -0
- package/src/lib/utils/orm-proxy/validate-no-current-in-rules.js.map +1 -0
- package/src/lib/utils/orm-proxy/validate-rules-for-orm.d.ts +16 -0
- package/src/lib/utils/orm-proxy/validate-rules-for-orm.js +35 -0
- package/src/lib/utils/orm-proxy/validate-rules-for-orm.js.map +1 -0
- package/src/lib/wrappers/index.d.ts +9 -0
- package/src/lib/wrappers/index.js +32 -0
- package/src/lib/wrappers/index.js.map +1 -0
- package/src/lib/wrappers/logger-init.d.ts +2 -0
- package/src/lib/wrappers/logger-init.js +9 -0
- package/src/lib/wrappers/logger-init.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/get-proxy-orm.d.ts +4 -0
- package/src/lib/wrappers/wrapper-json-method-controller/get-proxy-orm.js +47 -0
- package/src/lib/wrappers/wrapper-json-method-controller/get-proxy-orm.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/index.d.ts +3 -0
- package/src/lib/wrappers/wrapper-json-method-controller/index.js +21 -0
- package/src/lib/wrappers/wrapper-json-method-controller/index.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/delete-one-proxy.d.ts +3 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/delete-one-proxy.js +51 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/delete-one-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/delete-relationship-proxy.d.ts +4 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/delete-relationship-proxy.js +59 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/delete-relationship-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-all-proxy.d.ts +13 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-all-proxy.js +67 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-all-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-one-proxy.d.ts +12 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-one-proxy.js +50 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-one-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-relationship-proxy.d.ts +4 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-relationship-proxy.js +50 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/get-relationship-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/index.d.ts +9 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/index.js +13 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/index.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/patch-one-proxy.d.ts +3 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/patch-one-proxy.js +132 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/patch-one-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/patch-relationship-proxy.d.ts +4 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/patch-relationship-proxy.js +68 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/patch-relationship-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/post-one-proxy.d.ts +3 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/post-one-proxy.js +73 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/post-one-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/post-relationship-proxy.d.ts +4 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/post-relationship-proxy.js +66 -0
- package/src/lib/wrappers/wrapper-json-method-controller/method-proxy/post-relationship-proxy.js.map +1 -0
- package/src/lib/wrappers/wrapper-json-method-controller/on-module-init.d.ts +2 -0
- package/src/lib/wrappers/wrapper-json-method-controller/on-module-init.js +16 -0
- package/src/lib/wrappers/wrapper-json-method-controller/on-module-init.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acl-context.types.js","sourceRoot":"","sources":["../../../../../../../libs/acl-permissions/nestjs-acl-permissions/src/lib/types/acl-context.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Type } from '@nestjs/common';
|
|
2
|
+
import { AclRule, AclRulesLoader } from './acl-rules.types';
|
|
3
|
+
import { AclContextStore } from './acl-context.types';
|
|
4
|
+
/**
|
|
5
|
+
* Policy for handling resources with no ACL rules defined
|
|
6
|
+
*/
|
|
7
|
+
export type OnNoRulesPolicy = 'deny' | 'allow';
|
|
8
|
+
/**
|
|
9
|
+
* Options for configuring ACL module
|
|
10
|
+
*/
|
|
11
|
+
export interface AclModuleOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Service class that loads ACL rules from external source
|
|
14
|
+
* Must implement AclRulesLoader interface
|
|
15
|
+
* Will be retrieved via moduleRef to support services from other modules
|
|
16
|
+
*/
|
|
17
|
+
rulesLoader: Type<AclRulesLoader>;
|
|
18
|
+
/**
|
|
19
|
+
* Context store for passing ACL data through request pipeline
|
|
20
|
+
* Required to access ExtendableAbility in pipes/guards/services via CLS
|
|
21
|
+
* Can be ClsService from nestjs-cls or any service with set/get methods
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { ClsService } from 'nestjs-cls';
|
|
26
|
+
*
|
|
27
|
+
* AclPermissionsModule.forRoot({
|
|
28
|
+
* rulesLoader: MyRulesLoader,
|
|
29
|
+
* contextStore: ClsService
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
contextStore: Type<AclContextStore>;
|
|
34
|
+
/**
|
|
35
|
+
* Strict mode for template interpolation in rules
|
|
36
|
+
*
|
|
37
|
+
* - `true`: Throws error if variable/function not found in context
|
|
38
|
+
* - `false` (default): Logs warning and omits field with missing variable
|
|
39
|
+
*
|
|
40
|
+
* @default false
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // Development mode - fail fast on configuration errors
|
|
45
|
+
* AclPermissionsModule.forRoot({
|
|
46
|
+
* rulesLoader: MyRulesLoader,
|
|
47
|
+
* strictInterpolation: true
|
|
48
|
+
* })
|
|
49
|
+
*
|
|
50
|
+
* // Production mode - graceful degradation
|
|
51
|
+
* AclPermissionsModule.forRoot({
|
|
52
|
+
* rulesLoader: MyRulesLoader,
|
|
53
|
+
* strictInterpolation: false
|
|
54
|
+
* })
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
strictInterpolation?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Policy for handling resources with no ACL rules defined
|
|
60
|
+
*
|
|
61
|
+
* - 'deny': Throw 403 Forbidden - DEFAULT (production mode)
|
|
62
|
+
* - 'allow': Allow access + warning in logs (development mode)
|
|
63
|
+
*
|
|
64
|
+
* @default 'deny'
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Production - deny by default (strict)
|
|
69
|
+
* AclPermissionsModule.forRoot({
|
|
70
|
+
* rulesLoader: MyRulesLoader,
|
|
71
|
+
* onNoRules: 'deny'
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // Development - allow access with warning
|
|
75
|
+
* AclPermissionsModule.forRoot({
|
|
76
|
+
* rulesLoader: MyRulesLoader,
|
|
77
|
+
* onNoRules: 'allow'
|
|
78
|
+
* })
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
onNoRules?: OnNoRulesPolicy;
|
|
82
|
+
/**
|
|
83
|
+
* Дефолтные правила fallback (опционально)
|
|
84
|
+
* Используются когда rulesLoader возвращает пустой массив
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* AclPermissionsModule.forRoot({
|
|
89
|
+
* rulesLoader: MyRulesLoader,
|
|
90
|
+
* defaultRules: [
|
|
91
|
+
* { action: 'getAll', subject: 'all', inverted: false }
|
|
92
|
+
* ]
|
|
93
|
+
* })
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
defaultRules?: AclRule[];
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acl-options.types.js","sourceRoot":"","sources":["../../../../../../../libs/acl-permissions/nestjs-acl-permissions/src/lib/types/acl-options.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { MongoQuery } from '@casl/ability';
|
|
2
|
+
import type { MethodName } from '@klerick/json-api-nestjs';
|
|
3
|
+
import type { AnyEntity, EntityClass } from '@klerick/json-api-nestjs-shared';
|
|
4
|
+
/**
|
|
5
|
+
* Reserved variable name for external input data in rule templates
|
|
6
|
+
* This data comes from outside (request body, query params, database entity, etc.)
|
|
7
|
+
* and is NOT part of the context returned by getContext()
|
|
8
|
+
*
|
|
9
|
+
* In templates, use @input (with @) for readability: '${@input.userId}'
|
|
10
|
+
* Internally, the @ symbol is removed before interpolation since @ is not valid in JS variable names
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // In rule template (user-facing):
|
|
15
|
+
* { conditions: { ownerId: '${@input.userId}' } }
|
|
16
|
+
*
|
|
17
|
+
* // Internally reserved variable name (without @):
|
|
18
|
+
* const scope = { input: externalData };
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare const ACL_INPUT_VAR: "input";
|
|
22
|
+
/**
|
|
23
|
+
* Template placeholder with @ symbol for user-facing templates
|
|
24
|
+
* This is replaced with ACL_INPUT_VAR before interpolation
|
|
25
|
+
*/
|
|
26
|
+
export declare const ACL_INPUT_TEMPLATE: "@input";
|
|
27
|
+
/**
|
|
28
|
+
* Type for external input data
|
|
29
|
+
*
|
|
30
|
+
* TODO: Extend to support both new input AND old values from database
|
|
31
|
+
* Use case: In patchOne, we need to compare old value vs new value in rules
|
|
32
|
+
* Example: Allow removing only self from coAuthorIds array
|
|
33
|
+
* - Current: { coAuthorIds: [1, 2, 3] } from DB
|
|
34
|
+
* - New: { coAuthorIds: [1, 3] } from @input
|
|
35
|
+
* - Helper: isOnlyRemovedUser(@input.__current.coAuthorIds, @input.coAuthorIds, userId)
|
|
36
|
+
*
|
|
37
|
+
* Proposed structure:
|
|
38
|
+
* - @input.* - new values from request
|
|
39
|
+
* - @input.__current.* - old values from database (for patchOne only)
|
|
40
|
+
*/
|
|
41
|
+
export type AclInputData = Record<string, any>;
|
|
42
|
+
/**
|
|
43
|
+
* Utility type to exclude reserved @input variable from context/helpers
|
|
44
|
+
*/
|
|
45
|
+
type WithoutReservedVars<T extends Record<string, any>> = {
|
|
46
|
+
[K in keyof T as K extends typeof ACL_INPUT_VAR ? never : K]: T[K];
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Type for CASL action
|
|
50
|
+
* Can be any method from JsonBaseController or custom string
|
|
51
|
+
*/
|
|
52
|
+
export type AclAction<E extends MethodName | string = MethodName | string> = E;
|
|
53
|
+
/**
|
|
54
|
+
* Type for CASL subject - entity class or instance
|
|
55
|
+
* Can be:
|
|
56
|
+
* - Entity class (e.g., User, Post)
|
|
57
|
+
* - Entity instance (e.g., new User())
|
|
58
|
+
* - String with entity name (e.g., 'User', 'Post')
|
|
59
|
+
*/
|
|
60
|
+
export type AclSubject<E extends AnyEntity = AnyEntity> = EntityClass<E> | E | string;
|
|
61
|
+
/**
|
|
62
|
+
* ACL rule definition
|
|
63
|
+
*/
|
|
64
|
+
export interface AclRule<E extends AnyEntity = AnyEntity, Action extends string = string> {
|
|
65
|
+
/**
|
|
66
|
+
* Action to check (e.g., 'getAll', 'postOne', 'patchOne', etc.)
|
|
67
|
+
*/
|
|
68
|
+
action: Action;
|
|
69
|
+
/**
|
|
70
|
+
* Subject to check against (entity class or name)
|
|
71
|
+
*/
|
|
72
|
+
subject: AclSubject<E>;
|
|
73
|
+
/**
|
|
74
|
+
* Optional conditions (MongoDB query format)
|
|
75
|
+
*/
|
|
76
|
+
conditions?: MongoQuery<E>;
|
|
77
|
+
/**
|
|
78
|
+
* Optional fields restriction
|
|
79
|
+
*/
|
|
80
|
+
fields?: Array<keyof E | string>;
|
|
81
|
+
/**
|
|
82
|
+
* Whether this is an inverted rule (cannot)
|
|
83
|
+
*/
|
|
84
|
+
inverted?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Optional reason for the rule
|
|
87
|
+
*/
|
|
88
|
+
reason?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Interface for loading ACL rules from external source
|
|
92
|
+
* Implementation is provided by the user
|
|
93
|
+
*/
|
|
94
|
+
export interface AclRulesLoader {
|
|
95
|
+
/**
|
|
96
|
+
* Loads ACL rules for the current request
|
|
97
|
+
*
|
|
98
|
+
* @param subject - Entity class or name of subject for which to load rules
|
|
99
|
+
* @param action - Action being performed (method name from JsonBaseController)
|
|
100
|
+
* @returns Array of CASL rules in JSON format (may contain template strings like '${userId}')
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* @Injectable()
|
|
105
|
+
* class MyRulesLoader implements AclRulesLoader {
|
|
106
|
+
* async loadRules<E extends AnyEntity>(
|
|
107
|
+
* entity: EntityClass<E>,
|
|
108
|
+
* action: AclAction
|
|
109
|
+
* ): Promise<AclRule<E>[]> {
|
|
110
|
+
* const user = this.request.user;
|
|
111
|
+
* const rules = await this.db.query(
|
|
112
|
+
* 'SELECT * FROM permissions WHERE userId = ? AND entity = ? AND action = ?',
|
|
113
|
+
* [user.id, entity.name, action]
|
|
114
|
+
* );
|
|
115
|
+
* return rules.map(r => ({
|
|
116
|
+
* action: r.action,
|
|
117
|
+
* subject: entity,
|
|
118
|
+
* conditions: r.conditions, // May contain: { userId: '${userId}' }
|
|
119
|
+
* fields: r.fields
|
|
120
|
+
* }));
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
loadRules<E extends AnyEntity>(subject: AclSubject<E>, action: AclAction): Promise<AclRule<E>[]>;
|
|
126
|
+
/**
|
|
127
|
+
* Provides context variables for template interpolation in rules
|
|
128
|
+
*
|
|
129
|
+
* IMPORTANT: Cannot use reserved variable name 'input' - it's reserved for external input data
|
|
130
|
+
* In templates, write @input which gets converted to input internally
|
|
131
|
+
*
|
|
132
|
+
* @returns Promise with object containing variables (without 'input' key)
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* @Injectable()
|
|
137
|
+
* class MyRulesLoader implements AclRulesLoader {
|
|
138
|
+
* constructor(
|
|
139
|
+
* @Inject(REQUEST) private request: Request,
|
|
140
|
+
* private usersService: UsersService
|
|
141
|
+
* ) {}
|
|
142
|
+
*
|
|
143
|
+
* async getContext() {
|
|
144
|
+
* const user = this.request.user;
|
|
145
|
+
* const userGroups = await this.usersService.getUserGroups(user.id);
|
|
146
|
+
*
|
|
147
|
+
* return {
|
|
148
|
+
* userId: user.id,
|
|
149
|
+
* userEmail: user.email,
|
|
150
|
+
* userData: {
|
|
151
|
+
* groups: userGroups,
|
|
152
|
+
* roles: user.roles,
|
|
153
|
+
* },
|
|
154
|
+
* // 'input': {} // ← TypeScript error: reserved variable
|
|
155
|
+
* };
|
|
156
|
+
* }
|
|
157
|
+
* }
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
getContext(): Promise<WithoutReservedVars<Record<string, unknown>>>;
|
|
161
|
+
/**
|
|
162
|
+
* Provides custom helper functions for template interpolation in rules
|
|
163
|
+
*
|
|
164
|
+
* These functions can be used in rule templates like: '${getValProps(@input.userGroups, "id")}'
|
|
165
|
+
*
|
|
166
|
+
* IMPORTANT: Cannot use reserved function name 'input' - it's reserved for external input data
|
|
167
|
+
* In templates, write @input which gets converted to input internally
|
|
168
|
+
*
|
|
169
|
+
* @returns Promise with object containing helper functions (without 'input' key)
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* @Injectable()
|
|
174
|
+
* class MyRulesLoader implements AclRulesLoader {
|
|
175
|
+
* async getHelpers() {
|
|
176
|
+
* return {
|
|
177
|
+
* // Extract property values from array of objects
|
|
178
|
+
* getValProps: (arr: any[], prop: string) => arr.map(item => item[prop]),
|
|
179
|
+
*
|
|
180
|
+
* // Check if array contains value
|
|
181
|
+
* includes: (arr: any[], value: any) => arr.includes(value),
|
|
182
|
+
*
|
|
183
|
+
* // Get current timestamp
|
|
184
|
+
* now: () => Date.now(),
|
|
185
|
+
*
|
|
186
|
+
* // Custom business logic
|
|
187
|
+
* isOwner: (resource: any, userId: number) => resource.ownerId === userId,
|
|
188
|
+
*
|
|
189
|
+
* // 'input': () => {} // ← TypeScript error: reserved variable
|
|
190
|
+
* };
|
|
191
|
+
* }
|
|
192
|
+
* }
|
|
193
|
+
*
|
|
194
|
+
* // Usage in rule template:
|
|
195
|
+
* // { groupIds: { $in: '${getValProps(@input.userGroups, "id")}' } }
|
|
196
|
+
* // { ownerId: '${@input.userId}' }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
getHelpers(): Promise<WithoutReservedVars<Record<string, (...args: any[]) => any>>>;
|
|
200
|
+
}
|
|
201
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ACL_INPUT_TEMPLATE = exports.ACL_INPUT_VAR = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Reserved variable name for external input data in rule templates
|
|
6
|
+
* This data comes from outside (request body, query params, database entity, etc.)
|
|
7
|
+
* and is NOT part of the context returned by getContext()
|
|
8
|
+
*
|
|
9
|
+
* In templates, use @input (with @) for readability: '${@input.userId}'
|
|
10
|
+
* Internally, the @ symbol is removed before interpolation since @ is not valid in JS variable names
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // In rule template (user-facing):
|
|
15
|
+
* { conditions: { ownerId: '${@input.userId}' } }
|
|
16
|
+
*
|
|
17
|
+
* // Internally reserved variable name (without @):
|
|
18
|
+
* const scope = { input: externalData };
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
exports.ACL_INPUT_VAR = 'input';
|
|
22
|
+
/**
|
|
23
|
+
* Template placeholder with @ symbol for user-facing templates
|
|
24
|
+
* This is replaced with ACL_INPUT_VAR before interpolation
|
|
25
|
+
*/
|
|
26
|
+
exports.ACL_INPUT_TEMPLATE = '@input';
|
|
27
|
+
//# sourceMappingURL=acl-rules.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acl-rules.types.js","sourceRoot":"","sources":["../../../../../../../libs/acl-permissions/nestjs-acl-permissions/src/lib/types/acl-rules.types.ts"],"names":[],"mappings":";;;AAIA;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,aAAa,GAAG,OAAgB,CAAC;AAE9C;;;GAGG;AACU,QAAA,kBAAkB,GAAG,QAAiB,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { JsonBaseController } from '@klerick/json-api-nestjs';
|
|
2
|
+
import type { AnyEntity } from '@klerick/json-api-nestjs-shared';
|
|
3
|
+
import type { AclSubject } from './acl-rules.types';
|
|
4
|
+
import { AclModuleOptions } from './acl-options.types';
|
|
5
|
+
/**
|
|
6
|
+
* Extract function property names from a type
|
|
7
|
+
*/
|
|
8
|
+
type FunctionPropertyNames<T> = {
|
|
9
|
+
[K in keyof T]: T[K] extends Function ? K : never;
|
|
10
|
+
}[keyof T];
|
|
11
|
+
/**
|
|
12
|
+
* Extract method names from controller class
|
|
13
|
+
*/
|
|
14
|
+
export type ControllerMethods<T> = FunctionPropertyNames<T>;
|
|
15
|
+
export type AclControllerMethodsOptions = boolean | Omit<AclModuleOptions, 'rulesLoader' | 'contextStore' | 'strictInterpolation'>;
|
|
16
|
+
/**
|
|
17
|
+
* Partial record of controller methods with boolean values
|
|
18
|
+
* Typed by specific controller class
|
|
19
|
+
*/
|
|
20
|
+
export type ControllerMethodsConfig<T> = Partial<Record<ControllerMethods<T>, AclControllerMethodsOptions>>;
|
|
21
|
+
/**
|
|
22
|
+
* Options for @AclController decorator
|
|
23
|
+
* Generic Controller type allows type-safe method configuration
|
|
24
|
+
*/
|
|
25
|
+
export interface AclControllerOptions<E extends AnyEntity = AnyEntity, Controller = JsonBaseController<E, 'id'>> {
|
|
26
|
+
/**
|
|
27
|
+
* Subject for ACL checks
|
|
28
|
+
* Can be Entity class, instance, or string name
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* subject: User
|
|
32
|
+
* subject: 'User'
|
|
33
|
+
*/
|
|
34
|
+
subject: AclSubject<E>;
|
|
35
|
+
/**
|
|
36
|
+
* Configuration for which controller methods should have ACL enabled
|
|
37
|
+
* If not specified, all methods are enabled by default
|
|
38
|
+
* Type-safe based on Controller generic parameter
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* methods: {
|
|
42
|
+
* getAll: true,
|
|
43
|
+
* getOne: true,
|
|
44
|
+
* postOne: true,
|
|
45
|
+
* patchOne: false, // disabled
|
|
46
|
+
* deleteOne: false, // disabled
|
|
47
|
+
* }
|
|
48
|
+
*/
|
|
49
|
+
methods?: ControllerMethodsConfig<Controller>;
|
|
50
|
+
/**
|
|
51
|
+
* Whether ACL is enabled for this controller
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Metadata stored by @AclController decorator
|
|
58
|
+
*/
|
|
59
|
+
export interface AclControllerMetadata<E extends AnyEntity = AnyEntity> {
|
|
60
|
+
subject: AclSubject<E>;
|
|
61
|
+
methods: Record<string, AclControllerMethodsOptions>;
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
}
|
|
64
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator-options.types.js","sourceRoot":"","sources":["../../../../../../../libs/acl-permissions/nestjs-acl-permissions/src/lib/types/decorator-options.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./acl-options.types"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./acl-rules.types"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./decorator-options.types"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./acl-context.types"), exports);
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../libs/acl-permissions/nestjs-acl-permissions/src/lib/types/index.ts"],"names":[],"mappings":";;;AAAA,8DAAmC;AACnC,4DAAiC;AACjC,oEAAyC;AACzC,8DAAmC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PipeTransform, Type } from '@nestjs/common';
|
|
2
|
+
import { AnyEntity, EntityClass } from '@klerick/json-api-nestjs-shared';
|
|
3
|
+
import { AclControllerMethodsOptions, AclModuleOptions } from '../types';
|
|
4
|
+
export * from './orm-proxy';
|
|
5
|
+
export declare function factoryPipeMixin(entity: EntityClass<AnyEntity>, pipe: Type<PipeTransform>): Type<PipeTransform<any, any>>;
|
|
6
|
+
export declare const nameIt: (name: string, cls: new (...rest: unknown[]) => Record<never, unknown>) => {
|
|
7
|
+
new (...arg: unknown[]): {};
|
|
8
|
+
};
|
|
9
|
+
export declare function copyMethodMetadata(source: Function, target: Function): void;
|
|
10
|
+
export declare function getActionOptions(moduleOptions: AclModuleOptions, actionOptions: AclControllerMethodsOptions): Exclude<AclControllerMethodsOptions, boolean>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nameIt = void 0;
|
|
4
|
+
exports.factoryPipeMixin = factoryPipeMixin;
|
|
5
|
+
exports.copyMethodMetadata = copyMethodMetadata;
|
|
6
|
+
exports.getActionOptions = getActionOptions;
|
|
7
|
+
const tslib_1 = require("tslib");
|
|
8
|
+
const change_case_commonjs_1 = require("change-case-commonjs");
|
|
9
|
+
const common_1 = require("@nestjs/common");
|
|
10
|
+
tslib_1.__exportStar(require("./orm-proxy"), exports);
|
|
11
|
+
function factoryPipeMixin(entity, pipe) {
|
|
12
|
+
const entityName = entity.name;
|
|
13
|
+
const pipeClass = (0, exports.nameIt)(`${(0, change_case_commonjs_1.pascalCase)(entityName)}${pipe.name}`, pipe);
|
|
14
|
+
(0, common_1.Injectable)()(pipeClass);
|
|
15
|
+
return pipeClass;
|
|
16
|
+
}
|
|
17
|
+
const nameIt = (name, cls) => ({
|
|
18
|
+
[name]: class extends cls {
|
|
19
|
+
constructor(...arg) {
|
|
20
|
+
super(...arg);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
}[name]);
|
|
24
|
+
exports.nameIt = nameIt;
|
|
25
|
+
function copyMethodMetadata(source, target) {
|
|
26
|
+
const metadataKeys = Reflect.getMetadataKeys(source);
|
|
27
|
+
for (const key of metadataKeys) {
|
|
28
|
+
const value = Reflect.getMetadata(key, source);
|
|
29
|
+
Reflect.defineMetadata(key, value, target);
|
|
30
|
+
}
|
|
31
|
+
Object.defineProperty(target, 'name', {
|
|
32
|
+
value: source.name,
|
|
33
|
+
writable: false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function getActionOptions(moduleOptions, actionOptions) {
|
|
37
|
+
const defaultOptions = {
|
|
38
|
+
onNoRules: moduleOptions.onNoRules,
|
|
39
|
+
defaultRules: moduleOptions.defaultRules,
|
|
40
|
+
};
|
|
41
|
+
if (actionOptions === undefined ||
|
|
42
|
+
actionOptions === true ||
|
|
43
|
+
actionOptions === false)
|
|
44
|
+
return defaultOptions;
|
|
45
|
+
return {
|
|
46
|
+
...defaultOptions,
|
|
47
|
+
...{
|
|
48
|
+
onNoRules: actionOptions.onNoRules,
|
|
49
|
+
defaultRules: actionOptions.defaultRules,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../libs/acl-permissions/nestjs-acl-permissions/src/lib/utils/index.ts"],"names":[],"mappings":";;;AAQA,4CAcC;AAcD,gDAYC;AAED,4CAuBC;;AAzED,+DAAkD;AAClD,2CAAiE;AAKjE,sDAA4B;AAE5B,SAAgB,gBAAgB,CAC9B,MAA8B,EAC9B,IAAyB;IAEzB,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;IAE/B,MAAM,SAAS,GAAG,IAAA,cAAM,EACtB,GAAG,IAAA,iCAAU,EAAC,UAAU,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,EACvC,IAAI,CACkB,CAAC;IAEzB,IAAA,mBAAU,GAAE,CAAC,SAAS,CAAC,CAAC;IAExB,OAAO,SAAS,CAAC;AACnB,CAAC;AAEM,MAAM,MAAM,GAAG,CACpB,IAAY,EACZ,GAAuD,EACvD,EAAE,CACF,CAAC;IACC,CAAC,IAAI,CAAC,EAAE,KAAM,SAAQ,GAAG;QACvB,YAAY,GAAG,GAAc;YAC3B,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;QAChB,CAAC;KACF;CACF,CAAC,IAAI,CAAC,CAAC,CAAC;AAVE,QAAA,MAAM,UAUR;AAEX,SAAgB,kBAAkB,CAAC,MAAgB,EAAE,MAAgB;IACnE,MAAM,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAErD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC/C,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE;QACpC,KAAK,EAAE,MAAM,CAAC,IAAI;QAClB,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;AACL,CAAC;AAED,SAAgB,gBAAgB,CAC9B,aAA+B,EAC/B,aAA0C;IAE1C,MAAM,cAAc,GAAG;QACrB,SAAS,EAAE,aAAa,CAAC,SAAS;QAClC,YAAY,EAAE,aAAa,CAAC,YAAY;KACzC,CAAC;IAEF,IACE,aAAa,KAAK,SAAS;QAC3B,aAAa,KAAK,IAAI;QACtB,aAAa,KAAK,KAAK;QAEvB,OAAO,cAAc,CAAC;IAExB,OAAO;QACL,GAAG,cAAc;QACjB,GAAG;YACD,SAAS,EAAE,aAAa,CAAC,SAAS;YAClC,YAAY,EAAE,aAAa,CAAC,YAAY;SACzC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AnyEntity, EntityClass, QueryField } from '@klerick/json-api-nestjs-shared';
|
|
2
|
+
import { EntityParam, EntityParamMap, QueryOne, Query } from '@klerick/json-api-nestjs';
|
|
3
|
+
import { ModuleRef } from '@nestjs/core';
|
|
4
|
+
/**
|
|
5
|
+
* Singleton class for extracting field paths from entity objects
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Skips primary keys (they are always accessible, no ACL check needed)
|
|
9
|
+
* - Recursively processes relationships
|
|
10
|
+
* - Returns flat array of dot-notation paths
|
|
11
|
+
* - Handles one-to-many (arrays) and one-to-one (objects) relationships
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const extractor = ExtractFieldPaths.getInstance(entityParamMap);
|
|
15
|
+
* const obj = {
|
|
16
|
+
* id: 1, // primary key - will be skipped
|
|
17
|
+
* login: 'user',
|
|
18
|
+
* profile: { id: 10, phone: '123' } // profile.id also skipped
|
|
19
|
+
* };
|
|
20
|
+
*
|
|
21
|
+
* const paths = extractor.fields(obj, User);
|
|
22
|
+
* // Returns: ['login', 'profile.phone']
|
|
23
|
+
*/
|
|
24
|
+
export declare class ExtractFieldPaths {
|
|
25
|
+
private entityParamMap;
|
|
26
|
+
private constructor();
|
|
27
|
+
private extractField;
|
|
28
|
+
fields<E extends object>(obj: E, entityClass: EntityClass<E>): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Extracts only props (entity fields) from object, excluding relationships and primary key
|
|
31
|
+
* Skips primary key (same as fields() method)
|
|
32
|
+
*
|
|
33
|
+
* Use case: Create merged entity with only base fields for ACL checks
|
|
34
|
+
*
|
|
35
|
+
* @param obj - Source entity object (may contain loaded relationships)
|
|
36
|
+
* @param entityClass - Entity class
|
|
37
|
+
* @returns New object with only entity props (no relationships, no primary key)
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const entity = { id: 1, login: 'user', role: 'admin', profile: { phone: '123' } };
|
|
41
|
+
* const propsOnly = extractor.props(entity, User);
|
|
42
|
+
* // Returns: { login: 'user', role: 'admin' }
|
|
43
|
+
* // Note: id (primary key) and profile (relationship) excluded
|
|
44
|
+
*/
|
|
45
|
+
props<E extends object>(obj: E, entityClass: EntityClass<E>): Partial<E>;
|
|
46
|
+
private static instance;
|
|
47
|
+
static getInstance(entityParamMap: EntityParamMap<EntityClass<AnyEntity>>): ExtractFieldPaths;
|
|
48
|
+
}
|
|
49
|
+
export declare function getCurrentEntityAndParamMap<E extends object>(moduleRef: ModuleRef): {
|
|
50
|
+
readonly currentEntity: EntityClass<E>;
|
|
51
|
+
readonly entityParamMap: EntityParam<EntityClass<object>>;
|
|
52
|
+
readonly entityParamMapService: EntityParamMap<EntityClass<object>>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Extracts field paths for ACL checking
|
|
56
|
+
*
|
|
57
|
+
* This function:
|
|
58
|
+
* 1. Gets entity metadata from moduleRef
|
|
59
|
+
* 2. Clones the sample item
|
|
60
|
+
* 3. Removes ACL-added fields that weren't requested by user
|
|
61
|
+
* 4. Extracts field paths for checking
|
|
62
|
+
*
|
|
63
|
+
* @param moduleRef - NestJS module reference
|
|
64
|
+
* @param sampleItem - Sample entity item (first item for getAll, result for getOne)
|
|
65
|
+
* @param userQuery - Original user query
|
|
66
|
+
* @param aclQueryData - ACL query data (fields and include that were added by ACL)
|
|
67
|
+
* @returns Array of field paths to check
|
|
68
|
+
*/
|
|
69
|
+
export declare function extractFieldsForCheck<E extends object, IdKey extends string, Q extends QueryOne<E, IdKey> | Query<E, IdKey>>(moduleRef: ModuleRef, sampleItem: E, userQuery: Q, aclQueryData?: {
|
|
70
|
+
fields?: Q[QueryField.fields];
|
|
71
|
+
include?: Q[QueryField.include];
|
|
72
|
+
rulesForQuery?: Record<string, unknown>;
|
|
73
|
+
}): string[];
|