Skip to content

Commit 20f39ab

Browse files
committed
feat(acl-permissions): remove legacy ACL service, types, rules handling, and permissions factories, replace with updated utilities and proxy functions for enhanced control
1 parent bb808cd commit 20f39ab

File tree

73 files changed

+10747
-465
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+10747
-465
lines changed

libs/acl-permissions/nestjs-acl-permissions/README.md

Lines changed: 3551 additions & 6 deletions
Large diffs are not rendered by default.

libs/acl-permissions/nestjs-acl-permissions/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
"name": "@klerick/acl-json-api-nestjs",
33
"version": "0.0.1",
44
"dependencies": {
5-
"tslib": "^2.3.0"
5+
"@casl/ability": "^6.0.0"
66
},
77
"type": "commonjs",
88
"main": "./src/index.js",
9-
"typings": "./src/index.d.ts"
9+
"typings": "./src/index.d.ts",
10+
"peerDependencies": {
11+
"@klerick/json-api-nestjs": "0.0.0"
12+
}
1013
}

libs/acl-permissions/nestjs-acl-permissions/project.json

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,57 @@
11
{
22
"name": "nestjs-acl-permissions",
33
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4-
"sourceRoot": "libs/nestjs-acl-permissions/src",
4+
"sourceRoot": "libs/acl-permissions/nestjs-acl-permissions",
55
"projectType": "library",
6+
"implicitDependencies": ["json-api-nestjs"],
67
"targets": {
8+
"build-common": {
9+
"dependsOn": ["^build", "^build-cjs", "^build-mjs"],
10+
"executor": "@nx/js:tsc",
11+
"outputs": ["{options.outputPath}"],
12+
"options": {
13+
"outputPath": "dist/{projectRoot}",
14+
"tsConfig": "{projectRoot}/tsconfig.lib.json",
15+
"packageJson": "{projectRoot}/package.json",
16+
"main": "{projectRoot}/src/index.ts",
17+
"assets": ["{projectRoot}/*.md"],
18+
"generateExportsField": true,
19+
"updateBuildableProjectDepsInPackageJson": false
20+
}
21+
},
722
"build": {
23+
"executor": "nx:run-commands",
24+
"dependsOn": [
25+
"build-common"
26+
],
27+
"options": {
28+
"outputPath": "dist/{projectRoot}",
29+
"commands": [
30+
{
31+
"command": "node tools/scripts/prepare-package-json.mjs nestjs-acl-permissions"
32+
},
33+
{
34+
"command": "mkdir -p node_modules/@klerick && rm -rf node_modules/@klerick/acl-json-api-nestjs",
35+
"forwardAllArgs": false
36+
},
37+
{
38+
"command": "ln -s $(pwd)/dist/{projectRoot} node_modules/@klerick/acl-json-api-nestjs",
39+
"forwardAllArgs": false
40+
}
41+
],
42+
"cwd": "./",
43+
"parallel": false
44+
}
45+
},
46+
"build1": {
847
"executor": "@nx/js:tsc",
948
"outputs": ["{options.outputPath}"],
1049
"options": {
11-
"outputPath": "dist/libs/nestjs-acl-permissions",
12-
"tsConfig": "libs/nestjs-acl-permissions/tsconfig.lib.json",
13-
"packageJson": "libs/nestjs-acl-permissions/package.json",
14-
"main": "libs/nestjs-acl-permissions/src/index.ts",
15-
"assets": ["libs/nestjs-acl-permissions/*.md"]
50+
"outputPath": "dist/libs/acl-permissions/nestjs-acl-permissions",
51+
"tsConfig": "libs/acl-permissions/nestjs-acl-permissions/tsconfig.lib.json",
52+
"packageJson": "libs/acl-permissions/nestjs-acl-permissions/package.json",
53+
"main": "libs/acl-permissions/nestjs-acl-permissions/src/index.ts",
54+
"assets": ["libs/acl-permissions/nestjs-acl-permissions/*.md"]
1655
}
1756
},
1857
"publish": {
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
1-
export * from './lib/nestjs-acl-permissions.module';
1+
/**
2+
* NestJS ACL Permissions Module
3+
* CASL-based access control for JSON API controllers
4+
*/
5+
6+
// Main module
7+
export { AclPermissionsModule } from './lib/nestjs-acl-permissions.module';
8+
9+
export { AclAction, AclRule, AclRulesLoader, AclSubject } from './lib/types';
10+
export { ExtendAbility } from './lib/factories';
11+
export { wrapperJsonApiController } from './lib/wrappers';
Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import {
2-
Actions,
3-
Method,
4-
MethodActionMap as MethodActionMapType,
5-
} from '../types';
1+
/**
2+
* Token for injecting ACL module options
3+
*/
4+
export const ACL_MODULE_OPTIONS = Symbol('ACL_MODULE_OPTIONS');
65

7-
export const IS_PUBLIC_META_KEY = Symbol('IS_PUBLIC_META_KEY');
8-
export const GET_PERMISSION_RULES = Symbol('GET_PERMISSION_RULES');
6+
/**
7+
* Metadata key for @AclController decorator
8+
*/
9+
export const ACL_CONTROLLER_METADATA = Symbol('ACL_CONTROLLER_METADATA');
10+
/**
11+
* Key for storing ACL data in context
12+
*/
13+
export const ACL_CONTEXT_KEY = Symbol('ACL_CONTEXT_KEY');
914

10-
export const MethodActionMap: MethodActionMapType = {
11-
[Method.DELETE]: Actions.delete,
12-
[Method.GET]: Actions.read,
13-
[Method.PATCH]: Actions.update,
14-
[Method.POST]: Actions.create,
15-
};
15+
16+
export const MODULE_REF_PROPS = Symbol('MODULE_REF_PROPS');
17+
export const ORIGINAL_ORM_SERVICE = Symbol('ORIGINAL_ORM_SERVICE');
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { AnyEntity } from '@klerick/json-api-nestjs-shared';
2+
import type { JsonBaseController } from '@klerick/json-api-nestjs';
3+
import { UseGuards } from '@nestjs/common';
4+
import { ACL_CONTROLLER_METADATA } from '../constants';
5+
import type { AclControllerOptions, AclControllerMetadata, AclControllerMethodsOptions } from '../types';
6+
7+
import { AclGuard } from '../guards';
8+
9+
export function AclController<
10+
E extends AnyEntity = AnyEntity,
11+
Controller = JsonBaseController<E, 'id'>
12+
>(options: AclControllerOptions<E, Controller>) {
13+
return function <T extends abstract new (...args: any[]) => any>(
14+
target: T
15+
): T {
16+
const metadata: AclControllerMetadata<E> = {
17+
subject: options.subject,
18+
methods: (options.methods || {}) as Record<string, AclControllerMethodsOptions>,
19+
enabled: options.enabled ?? true,
20+
};
21+
22+
Reflect.defineMetadata(ACL_CONTROLLER_METADATA, metadata, target);
23+
UseGuards(AclGuard)(target);
24+
return target;
25+
};
26+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AclController } from './acl-controller.decorator';
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { ModuleRef } from '@nestjs/core';
2+
import type { AclContextStore, AclModuleOptions } from '../types';
3+
import { ExtendAbility } from './ability.factory';
4+
import { ACL_CONTEXT_KEY, ACL_MODULE_OPTIONS } from '../constants';
5+
import { FactoryProvider } from '@nestjs/common';
6+
7+
8+
/**
9+
* Creates a Proxy for ExtendableAbility that lazily retrieves the actual ability
10+
* from CLS (Continuation Local Storage) on each method/property access.
11+
*
12+
* This allows the provider to be a SINGLETON while still accessing request-specific
13+
* ability instances without using Scope.REQUEST.
14+
*
15+
* @param moduleRef - NestJS ModuleRef for lazy dependency resolution
16+
* @param options - ACL module options containing contextStore configuration
17+
* @returns Proxy that acts as ExtendableAbility
18+
*/
19+
export function createAbilityProxy(
20+
moduleRef: ModuleRef,
21+
options: AclModuleOptions
22+
): ExtendAbility {
23+
let contextStore: AclContextStore | undefined;
24+
25+
/**
26+
* Lazily initializes and retrieves the context store
27+
*/
28+
const getContextStore = (): AclContextStore | undefined => {
29+
if (!contextStore && options.contextStore) {
30+
contextStore = moduleRef.get(options.contextStore, {
31+
strict: false,
32+
});
33+
}
34+
return contextStore;
35+
};
36+
37+
/**
38+
* Retrieves ExtendableAbility from CLS for the current request
39+
* Throws descriptive error if ability is not found
40+
*/
41+
const getAbility = (): ExtendAbility => {
42+
const store = getContextStore();
43+
if (!store) {
44+
throw new Error(
45+
'[ACL] contextStore is not configured. ' +
46+
'Make sure you configured AclPermissionsModule.forRoot() with contextStore option. ' +
47+
'Example: { contextStore: ClsService }'
48+
);
49+
}
50+
51+
const ability = store.get<ExtendAbility>(ACL_CONTEXT_KEY);
52+
53+
if (!ability) {
54+
throw new Error(
55+
'[ACL] ExtendAbility not found in context store. ' +
56+
'Possible causes:\n' +
57+
' 1. Service is called BEFORE AclGuard executed (ability not yet created)\n' +
58+
' 2. No rules loaded for this action (check your RulesLoader.loadRules())\n' +
59+
' 3. AclGuard was not applied to this controller (check @AclController or wrapperJsonApiController hook)\n' +
60+
' 4. contextStore middleware is not mounted (check ClsModule configuration)'
61+
);
62+
}
63+
64+
return ability;
65+
};
66+
67+
// Create Proxy that forwards all method/property access to the real ExtendableAbility
68+
return new Proxy({} as ExtendAbility, {
69+
get(target, prop: string | symbol) {
70+
if (!Reflect.has(ExtendAbility.prototype, prop)) {
71+
return undefined;
72+
}
73+
74+
// Special handling for Symbol.toStringTag (used by Object.prototype.toString)
75+
if (prop === Symbol.toStringTag) {
76+
return 'ExtendableAbility';
77+
}
78+
79+
// Special handling for typeof checks
80+
if (prop === 'constructor') {
81+
return ExtendAbility;
82+
}
83+
84+
// Get the actual ability from CLS
85+
const ability = getAbility();
86+
// Get the property/method from the real ability
87+
const value = Reflect.get(ability, prop, ability);
88+
89+
// If it's a function, bind it to the ability instance
90+
if (typeof value === 'function') {
91+
return value.bind(ability);
92+
}
93+
94+
return value;
95+
},
96+
97+
// Support for 'prop in ability' checks
98+
has(target, prop) {
99+
const ability = getAbility();
100+
return Reflect.has(ability, prop);
101+
},
102+
103+
// Support for Object.keys(), Object.getOwnPropertyNames(), etc.
104+
ownKeys(target) {
105+
const ability = getAbility();
106+
return Reflect.ownKeys(ability);
107+
},
108+
109+
// Support for Object.getOwnPropertyDescriptor()
110+
getOwnPropertyDescriptor(target, prop) {
111+
const ability = getAbility();
112+
return Reflect.getOwnPropertyDescriptor(ability, prop);
113+
},
114+
});
115+
}
116+
117+
export const AbilityProvider: FactoryProvider<ExtendAbility> = {
118+
provide: ExtendAbility,
119+
useFactory: createAbilityProxy,
120+
inject: [ModuleRef, ACL_MODULE_OPTIONS],
121+
};

0 commit comments

Comments
 (0)