@unchainedshop/roles 4.8.29 → 5.0.0-alpha.10

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/README.md CHANGED
@@ -20,6 +20,8 @@ import { Roles, Role } from '@unchainedshop/roles';
20
20
 
21
21
  // Create a new role
22
22
  const editorRole = new Role('editor');
23
+ Roles.addRole(editorRole);
24
+ Roles.registerAction('updateProduct');
23
25
 
24
26
  // Allow actions for the role
25
27
  editorRole.allow('updateProduct', async (product, params, context) => {
@@ -42,6 +44,7 @@ const allowed = await Roles.userHasPermission(
42
44
  | Method | Description |
43
45
  |--------|-------------|
44
46
  | `Roles.registerAction` | Register a new action name |
47
+ | `Roles.addRole` | Register a role instance |
45
48
  | `Roles.registerHelper` | Register a new helper name |
46
49
  | `Roles.getUserRoles` | Get user roles including special roles |
47
50
  | `Roles.allow` | Check if any role allows an action |
@@ -51,12 +54,14 @@ const allowed = await Roles.userHasPermission(
51
54
 
52
55
  | Method | Description |
53
56
  |--------|-------------|
54
- | `new Role(name)` | Create and register a new role |
57
+ | `new Role(name)` | Create a role; register it with `roles.addRole()` |
55
58
  | `role.allow(action, fn)` | Add allow rule for an action |
56
59
  | `role.helper(name, fn)` | Add helper function to role |
57
60
 
58
61
  ### Built-in Roles
59
62
 
63
+ `createRoles()` returns an empty role registry. Call `initDefaultRoles(registry)` to add the following roles, or use the API layer's `roles.configureRoles()` to configure the platform's permission rules.
64
+
60
65
  | Role | Description |
61
66
  |------|-------------|
62
67
  | `Roles.adminRole` | Admin role with elevated privileges |
@@ -70,7 +75,7 @@ const allowed = await Roles.userHasPermission(
70
75
  | `__all__` | Automatically assigned to everyone |
71
76
  | `__loggedIn__` | Automatically assigned to authenticated users |
72
77
  | `__notLoggedIn__` | Automatically assigned to anonymous users |
73
- | `__notAdmin__` | Automatically assigned to non-admin users |
78
+ | `__notAdmin__` | Automatically assigned to authenticated non-admin users |
74
79
 
75
80
  ### Utility Functions
76
81
 
@@ -93,13 +98,13 @@ const allowed = await Roles.userHasPermission(
93
98
  ## Configuration
94
99
 
95
100
  ```typescript
96
- import type { IRoleOptionConfig } from '@unchainedshop/roles';
101
+ import { Role, type IRoleOptionConfig } from '@unchainedshop/roles';
97
102
 
98
103
  const config: IRoleOptionConfig = {
99
104
  additionalRoles: {
100
105
  customRole: (roles, actions) => {
101
- const role = new Role('customRole');
102
- role.allow(actions.READ, () => true);
106
+ const role = roles.addRole(new Role('customRole'));
107
+ role.allow(actions.CUSTOM_ACTION, async () => true);
103
108
  },
104
109
  },
105
110
  additionalActions: ['CUSTOM_ACTION'],
package/lib/roles.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface RolesInterface {
14
14
  roles: Record<string, RoleInterface>;
15
15
  actions: string[];
16
16
  helpers: string[];
17
+ addRole(role: RoleInterface): RoleInterface;
17
18
  registerAction(name: string): void;
18
19
  registerHelper(name: string): void;
19
20
  getUserRoles(userId: string, roles: string[], includeSpecial: boolean): string[];
@@ -23,16 +24,11 @@ export interface RolesInterface {
23
24
  loggedInRole?: RoleInterface;
24
25
  allRole?: RoleInterface;
25
26
  }
26
- export interface RoleInterface {
27
- name: string;
28
- allowRules: Record<string, any>;
29
- allow(action: string, fn: (root: any, props: any, context: RoleUserContext) => Promise<boolean>): void;
30
- helpers: Record<string, unknown>;
31
- }
32
27
  export interface IRoleOptionConfig {
33
28
  additionalRoles?: Record<string, (role: RolesInterface, actions: Record<string, string>) => void>;
34
29
  additionalActions?: string[];
35
30
  }
31
+ export declare function createRoles(): RolesInterface;
36
32
  export declare const Roles: RolesInterface;
37
33
  export declare class Role implements RoleInterface {
38
34
  name: string;
@@ -42,4 +38,5 @@ export declare class Role implements RoleInterface {
42
38
  helper(helper: string, func: any): void;
43
39
  allow(action: any, allow: any): void;
44
40
  }
41
+ export declare function initDefaultRoles(roles?: RolesInterface): void;
45
42
  export {};
package/lib/roles.js CHANGED
@@ -1,74 +1,81 @@
1
1
  import { has } from "./utils/has.js";
2
2
  import { isFunction } from "./utils/isFunction.js";
3
- export const Roles = {
4
- roles: {},
5
- actions: [],
6
- helpers: [],
7
- registerAction(name) {
8
- if (!this.actions.includes(name)) {
9
- this.actions.push(name);
10
- }
11
- },
12
- registerHelper(name) {
13
- if (!this.helpers.includes(name)) {
14
- this.helpers.push(name);
15
- }
16
- },
17
- getUserRoles(userId, roles, includeSpecial) {
18
- const newRoles = [...(roles || [])];
19
- if (includeSpecial) {
20
- newRoles.push('__all__');
21
- if (!userId) {
22
- newRoles.push('__notLoggedIn__');
3
+ export function createRoles() {
4
+ const instance = {
5
+ roles: {},
6
+ actions: [],
7
+ helpers: [],
8
+ addRole(role) {
9
+ if (has(this.roles, role.name)) {
10
+ throw new Error(`"${role.name}" role is already defined`);
23
11
  }
24
- else {
25
- newRoles.push('__loggedIn__');
26
- if (!newRoles.includes('admin')) {
27
- newRoles.push('__notAdmin__');
28
- }
12
+ this.roles[role.name] = role;
13
+ return role;
14
+ },
15
+ registerAction(name) {
16
+ if (!this.actions.includes(name)) {
17
+ this.actions.push(name);
29
18
  }
30
- }
31
- return newRoles;
32
- },
33
- async allow(context, roles, action, [obj, params]) {
34
- const userRoles = Roles.getUserRoles(context.userId, roles, true);
35
- return userRoles.reduce(async (roleIsAllowedPromise, role) => {
36
- const roleIsAllowed = await roleIsAllowedPromise;
37
- if (roleIsAllowed)
38
- return true;
39
- if (Roles.roles[role] && Roles.roles[role].allowRules && Roles.roles[role].allowRules[action]) {
40
- return Roles.roles[role].allowRules[action].reduce(async (rulesIsAllowedPromise, allowFn) => {
41
- const ruleIsAllowed = await rulesIsAllowedPromise;
42
- if (ruleIsAllowed)
43
- return true;
44
- return allowFn(obj, params, context);
45
- }, Promise.resolve(false));
19
+ },
20
+ registerHelper(name) {
21
+ if (!this.helpers.includes(name)) {
22
+ this.helpers.push(name);
23
+ }
24
+ },
25
+ getUserRoles(userId, roles, includeSpecial) {
26
+ const newRoles = [...(roles || [])];
27
+ if (includeSpecial) {
28
+ newRoles.push('__all__');
29
+ if (!userId) {
30
+ newRoles.push('__notLoggedIn__');
31
+ }
32
+ else {
33
+ newRoles.push('__loggedIn__');
34
+ if (!newRoles.includes('admin')) {
35
+ newRoles.push('__notAdmin__');
36
+ }
37
+ }
46
38
  }
47
- return roleIsAllowed;
48
- }, Promise.resolve(false));
49
- },
50
- userHasPermission: async (context, action, args) => {
51
- const roles = Array.isArray(context.user?.roles) ? context.user.roles : [];
52
- const allows = await Roles.allow(context, roles, action, args);
53
- return allows === true;
54
- },
55
- };
39
+ return newRoles;
40
+ },
41
+ async allow(context, roles, action, [obj, params]) {
42
+ const userRoles = instance.getUserRoles(context.userId, roles, true);
43
+ return userRoles.reduce(async (roleIsAllowedPromise, role) => {
44
+ const roleIsAllowed = await roleIsAllowedPromise;
45
+ if (roleIsAllowed)
46
+ return true;
47
+ if (instance.roles[role] &&
48
+ instance.roles[role].allowRules &&
49
+ instance.roles[role].allowRules[action]) {
50
+ return instance.roles[role].allowRules[action].reduce(async (rulesIsAllowedPromise, allowFn) => {
51
+ const ruleIsAllowed = await rulesIsAllowedPromise;
52
+ if (ruleIsAllowed)
53
+ return true;
54
+ return allowFn(obj, params, context);
55
+ }, Promise.resolve(false));
56
+ }
57
+ return roleIsAllowed;
58
+ }, Promise.resolve(false));
59
+ },
60
+ async userHasPermission(context, action, args) {
61
+ const roles = Array.isArray(context.user?.roles) ? context.user.roles : [];
62
+ const allows = await instance.allow(context, roles, action, args);
63
+ return allows === true;
64
+ },
65
+ };
66
+ return instance;
67
+ }
68
+ export const Roles = createRoles();
56
69
  export class Role {
57
70
  name;
58
71
  allowRules;
59
72
  helpers;
60
73
  constructor(name) {
61
- if (has(Roles.roles, name))
62
- throw new Error(`"${name}" role is already defined`);
63
74
  this.name = name;
64
75
  this.allowRules = {};
65
76
  this.helpers = {};
66
- Roles.roles[name] = this;
67
77
  }
68
78
  helper(helper, func) {
69
- if (!Roles.helpers.includes(helper)) {
70
- Roles.registerHelper(helper);
71
- }
72
79
  let helperFn = func;
73
80
  if (!isFunction(helperFn)) {
74
81
  const clonedValue = structuredClone(helperFn);
@@ -85,9 +92,6 @@ export class Role {
85
92
  if (!action) {
86
93
  throw new Error(`Action doesn't exist`);
87
94
  }
88
- if (!Roles.actions.includes(action)) {
89
- Roles.registerAction(action);
90
- }
91
95
  let allowFn = allow;
92
96
  if (!isFunction(allowFn)) {
93
97
  const clonedValue = structuredClone(allowFn);
@@ -99,6 +103,10 @@ export class Role {
99
103
  this.allowRules[action].push(allowFn);
100
104
  }
101
105
  }
102
- Roles.adminRole = new Role('admin');
103
- Roles.loggedInRole = new Role('__loggedIn__');
104
- Roles.allRole = new Role('__all__');
106
+ export function initDefaultRoles(roles = Roles) {
107
+ if (roles.adminRole)
108
+ return;
109
+ roles.adminRole = roles.addRole(new Role('admin'));
110
+ roles.loggedInRole = roles.addRole(new Role('__loggedIn__'));
111
+ roles.allRole = roles.addRole(new Role('__all__'));
112
+ }
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@unchainedshop/roles",
3
- "version": "4.8.29",
3
+ "version": "5.0.0-alpha.10",
4
+ "files": [
5
+ "lib"
6
+ ],
4
7
  "description": "Role-based access control system for the Unchained Engine",
5
8
  "main": "lib/roles-index.js",
6
9
  "types": "lib/roles-index.d.ts",
@@ -35,6 +38,6 @@
35
38
  "homepage": "https://github.com/unchainedshop/unchained#readme",
36
39
  "devDependencies": {
37
40
  "@types/node": "^26.2.0",
38
- "typescript": "^5.8.3"
41
+ "typescript": "^6.0.3"
39
42
  }
40
43
  }