payload-rbac-plugin 1.0.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/README.md +172 -0
- package/dist/collections/Permissions.d.ts +3 -0
- package/dist/collections/Permissions.js +28 -0
- package/dist/collections/Permissions.js.map +1 -0
- package/dist/collections/Roles.d.ts +3 -0
- package/dist/collections/Roles.js +38 -0
- package/dist/collections/Roles.js.map +1 -0
- package/dist/components/BeforeDashboardClient.d.ts +1 -0
- package/dist/components/BeforeDashboardClient.js +40 -0
- package/dist/components/BeforeDashboardClient.js.map +1 -0
- package/dist/components/BeforeDashboardServer.d.ts +2 -0
- package/dist/components/BeforeDashboardServer.js +22 -0
- package/dist/components/BeforeDashboardServer.js.map +1 -0
- package/dist/components/BeforeDashboardServer.module.css +5 -0
- package/dist/endpoints/customEndpointHandler.d.ts +2 -0
- package/dist/endpoints/customEndpointHandler.js +7 -0
- package/dist/endpoints/customEndpointHandler.js.map +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +3 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/exports/rsc.js.map +1 -0
- package/dist/hooks/beforePermissionChange.d.ts +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities/checkPermission.d.ts +8 -0
- package/dist/utilities/checkPermission.js +16 -0
- package/dist/utilities/checkPermission.js.map +1 -0
- package/dist/utilities/hasPermission.d.ts +10 -0
- package/dist/utilities/hasPermission.js +37 -0
- package/dist/utilities/hasPermission.js.map +1 -0
- package/dist/utilities/hasPermission.spec.d.ts +1 -0
- package/dist/utilities/hasPermission.spec.js +120 -0
- package/dist/utilities/hasPermission.spec.js.map +1 -0
- package/dist/utilities/index.d.ts +2 -0
- package/dist/utilities/index.js +4 -0
- package/dist/utilities/index.js.map +1 -0
- package/package.json +126 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Payload CMS Dynamic RBAC Plugin
|
|
2
|
+
|
|
3
|
+
A professional, database-backed Role-Based Access Control (RBAC) system for [Payload CMS](https://payloadcms.com) (v3).
|
|
4
|
+
|
|
5
|
+
This plugin emphasizes checking **permissions instead of roles** to avoid hard-coded authorization logic in your source code, making your access control highly scalable, modular, and dynamic.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Dynamic Collections**: Automatically injects database-backed `Roles` and `Permissions` collections.
|
|
12
|
+
- **Auth Collection Extension**: Injects a `roles` relationship field into your target auth collection (e.g., `users`) with `saveToJWT: true` for zero-cost runtime checks.
|
|
13
|
+
- **Bulk Permission Generator**: In the Admin UI, easily switch between creating a single permission or using the Bulk CRUD Generator to automatically generate separate permissions (e.g., `posts:create`, `posts:read`, `posts:update`, `posts:delete`) at once.
|
|
14
|
+
- **UI-Only Helpers**: Bulk generator fields (`type`, `collectionName`, CRUD checkboxes) are purely for the UI and are never saved to your database, keeping your collections clean.
|
|
15
|
+
- **Customizable Schemas**: Allow developers to inject additional custom fields into both `Roles` and `Permissions` schemas.
|
|
16
|
+
- **Granular Control**: Set visibility permissions (`hideRoles`, `hidePermissions`) dynamically or statically for the RBAC admin interface.
|
|
17
|
+
- **Helper Utilities**: Simple and robust functions to check permissions (`hasPermission`) and access control wrappers (`checkPermission`).
|
|
18
|
+
- **Fully Tested**: Powered by a robust integration and unit test suite built with Vitest.
|
|
19
|
+
|
|
20
|
+
## Screenshots
|
|
21
|
+
|
|
22
|
+
### 1. Permissions List View
|
|
23
|
+
Shows flat generated permissions (e.g. `posts:create`, `posts:read`) and legacy system permissions.
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
### 2. Single Permission Form
|
|
27
|
+
Create individual custom permissions.
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
### 3. Bulk CRUD Generator Form
|
|
31
|
+
Select a collection and check CRUD actions to instantly generate multiple permissions.
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
### 4. Roles List View
|
|
35
|
+
Displays roles and all permissions associated with them.
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
### 5. Role Configuration Form
|
|
39
|
+
Assign permissions directly to roles in the admin UI.
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
Add the plugin to your project:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm add payload-rbac-plugin
|
|
50
|
+
# or
|
|
51
|
+
npm install payload-rbac-plugin
|
|
52
|
+
# or
|
|
53
|
+
yarn add payload-rbac-plugin
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Setup & Configuration
|
|
59
|
+
|
|
60
|
+
Import and configure the plugin in your `payload.config.ts`:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { buildConfig } from 'payload'
|
|
64
|
+
import { rbac, hasPermission } from 'payload-rbac-plugin'
|
|
65
|
+
|
|
66
|
+
export default buildConfig({
|
|
67
|
+
collections: [
|
|
68
|
+
{
|
|
69
|
+
slug: 'posts',
|
|
70
|
+
fields: [],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
plugins: [
|
|
74
|
+
rbac({
|
|
75
|
+
enabled: true,
|
|
76
|
+
// Optional configurations:
|
|
77
|
+
authCollectionSlug: 'users', // Default: 'users'
|
|
78
|
+
rolesCollectionSlug: 'roles', // Default: 'roles'
|
|
79
|
+
permissionsCollectionSlug: 'permissions', // Default: 'permissions'
|
|
80
|
+
|
|
81
|
+
// Control access/visibility of the RBAC panel:
|
|
82
|
+
hidePermissions: ({ user }) => !hasPermission(user, 'access:permissions'),
|
|
83
|
+
hideRoles: ({ user }) => !hasPermission(user, 'access:roles'),
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Configuration Options
|
|
90
|
+
|
|
91
|
+
| Option | Type | Default | Description |
|
|
92
|
+
| :--- | :--- | :--- | :--- |
|
|
93
|
+
| `enabled` | `boolean` | `true` | Enable or disable the plugin. |
|
|
94
|
+
| `authCollectionSlug` | `string` | `'users'` | The collection slug representing the users collection. |
|
|
95
|
+
| `rolesCollectionSlug` | `string` | `'roles'` | The slug for the automatically injected Roles collection. |
|
|
96
|
+
| `permissionsCollectionSlug` | `string` | `'permissions'` | The slug for the automatically injected Permissions collection. |
|
|
97
|
+
| `rolesFields` | `Field[]` | `[]` | Extra custom fields to inject into the Roles collection. |
|
|
98
|
+
| `permissionsFields` | `Field[]` | `[]` | Extra custom fields to inject into the Permissions collection. |
|
|
99
|
+
| `hideRoles` | `boolean \| ((args: { user: any }) => boolean)` | `false` | Hide the Roles collection from the sidebar navigation. |
|
|
100
|
+
| `hidePermissions` | `boolean \| ((args: { user: any }) => boolean)` | `false` | Hide the Permissions collection from the sidebar navigation. |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Usage
|
|
105
|
+
|
|
106
|
+
### 1. Protecting Collections (Access Control)
|
|
107
|
+
|
|
108
|
+
You can protect collections using the `checkPermission` Higher-Order Function. It returns a standard Payload `Access` control resolver.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { checkPermission } from 'payload-rbac-plugin'
|
|
112
|
+
|
|
113
|
+
export const PostsCollection = {
|
|
114
|
+
slug: 'posts',
|
|
115
|
+
access: {
|
|
116
|
+
create: checkPermission('posts:create'),
|
|
117
|
+
read: checkPermission('posts:read'),
|
|
118
|
+
update: checkPermission('posts:update'),
|
|
119
|
+
delete: checkPermission('posts:delete'),
|
|
120
|
+
},
|
|
121
|
+
fields: [],
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 2. Manual Permission Verification (`hasPermission`)
|
|
126
|
+
|
|
127
|
+
For custom endpoints, hooks, or conditionally rendering logic, use the `hasPermission` utility:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { hasPermission } from 'payload-rbac-plugin'
|
|
131
|
+
|
|
132
|
+
const myCustomEndpoint = (req, res) => {
|
|
133
|
+
const user = req.user
|
|
134
|
+
|
|
135
|
+
if (hasPermission(user, 'export:reports')) {
|
|
136
|
+
// allow operation
|
|
137
|
+
} else {
|
|
138
|
+
res.status(403).send('Forbidden')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Bulk Permission Generator UI
|
|
146
|
+
|
|
147
|
+
When navigating to the **Permissions** collection in your Payload Admin panel:
|
|
148
|
+
1. Select **Bulk CRUD Generator** in the `Type` select box (visible only during document creation).
|
|
149
|
+
2. Enter the target collection slug (e.g. `posts`) in the `Collection Name` field.
|
|
150
|
+
3. Check the CRUD operations you wish to generate (e.g., `create`, `read`).
|
|
151
|
+
4. Save the document.
|
|
152
|
+
5. The plugin runs a `beforeChange` hook that creates individual flat records (`posts:create`, `posts:read`) in the database, and avoids saving any temporary helper fields to the database.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Development & Testing
|
|
157
|
+
|
|
158
|
+
If you are developing this plugin locally, you can run the test suite:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Run unit and integration tests
|
|
162
|
+
pnpm test:int
|
|
163
|
+
|
|
164
|
+
# Run tests in watch mode
|
|
165
|
+
pnpm test:int --watch
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const createPermissionsCollection = (options)=>{
|
|
2
|
+
const slug = options.permissionsCollectionSlug || 'permissions';
|
|
3
|
+
const customFields = options.permissionsFields || [];
|
|
4
|
+
return {
|
|
5
|
+
slug,
|
|
6
|
+
admin: {
|
|
7
|
+
useAsTitle: 'name',
|
|
8
|
+
group: 'Access Control'
|
|
9
|
+
},
|
|
10
|
+
access: {
|
|
11
|
+
read: ()=>true
|
|
12
|
+
},
|
|
13
|
+
fields: [
|
|
14
|
+
{
|
|
15
|
+
name: 'name',
|
|
16
|
+
type: 'text',
|
|
17
|
+
required: true,
|
|
18
|
+
unique: true,
|
|
19
|
+
admin: {
|
|
20
|
+
description: 'The unique name of the permission (e.g., "create:users", "read:posts").'
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
...customFields
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=Permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Permissions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport type { PluginOptions } from '../types.js'\n\nexport const createPermissionsCollection = (options: PluginOptions): CollectionConfig => {\n const slug = options.permissionsCollectionSlug || 'permissions'\n const customFields = options.permissionsFields || []\n\n return {\n slug,\n admin: {\n useAsTitle: 'name',\n group: 'Access Control',\n },\n access: {\n read: () => true, // Access logic can be customized or injected later\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n required: true,\n unique: true,\n admin: {\n description: 'The unique name of the permission (e.g., \"create:users\", \"read:posts\").',\n },\n },\n ...customFields,\n ],\n }\n}\n"],"names":["createPermissionsCollection","options","slug","permissionsCollectionSlug","customFields","permissionsFields","admin","useAsTitle","group","access","read","fields","name","type","required","unique","description"],"mappings":"AAGA,OAAO,MAAMA,8BAA8B,CAACC;IAC1C,MAAMC,OAAOD,QAAQE,yBAAyB,IAAI;IAClD,MAAMC,eAAeH,QAAQI,iBAAiB,IAAI,EAAE;IAEpD,OAAO;QACLH;QACAI,OAAO;YACLC,YAAY;YACZC,OAAO;QACT;QACAC,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,UAAU;gBACVC,QAAQ;gBACRT,OAAO;oBACLU,aAAa;gBACf;YACF;eACGZ;SACJ;IACH;AACF,EAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const createRolesCollection = (options)=>{
|
|
2
|
+
const slug = options.rolesCollectionSlug || 'roles';
|
|
3
|
+
const permissionsSlug = options.permissionsCollectionSlug || 'permissions';
|
|
4
|
+
const customFields = options.rolesFields || [];
|
|
5
|
+
return {
|
|
6
|
+
slug,
|
|
7
|
+
admin: {
|
|
8
|
+
useAsTitle: 'name',
|
|
9
|
+
group: 'Access Control'
|
|
10
|
+
},
|
|
11
|
+
access: {
|
|
12
|
+
read: ()=>true
|
|
13
|
+
},
|
|
14
|
+
fields: [
|
|
15
|
+
{
|
|
16
|
+
name: 'name',
|
|
17
|
+
type: 'text',
|
|
18
|
+
required: true,
|
|
19
|
+
unique: true,
|
|
20
|
+
admin: {
|
|
21
|
+
description: 'The unique name of the role (e.g., "admin", "editor").'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'permissions',
|
|
26
|
+
type: 'relationship',
|
|
27
|
+
relationTo: permissionsSlug,
|
|
28
|
+
hasMany: true,
|
|
29
|
+
admin: {
|
|
30
|
+
description: 'The permissions assigned to this role.'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
...customFields
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=Roles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Roles.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport type { PluginOptions } from '../types.js'\n\nexport const createRolesCollection = (options: PluginOptions): CollectionConfig => {\n const slug = options.rolesCollectionSlug || 'roles'\n const permissionsSlug = options.permissionsCollectionSlug || 'permissions'\n const customFields = options.rolesFields || []\n\n return {\n slug,\n admin: {\n useAsTitle: 'name',\n group: 'Access Control',\n },\n access: {\n read: () => true,\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n required: true,\n unique: true,\n admin: {\n description: 'The unique name of the role (e.g., \"admin\", \"editor\").',\n },\n },\n {\n name: 'permissions',\n type: 'relationship',\n relationTo: permissionsSlug,\n hasMany: true,\n admin: {\n description: 'The permissions assigned to this role.',\n },\n },\n ...customFields,\n ],\n }\n}\n"],"names":["createRolesCollection","options","slug","rolesCollectionSlug","permissionsSlug","permissionsCollectionSlug","customFields","rolesFields","admin","useAsTitle","group","access","read","fields","name","type","required","unique","description","relationTo","hasMany"],"mappings":"AAGA,OAAO,MAAMA,wBAAwB,CAACC;IACpC,MAAMC,OAAOD,QAAQE,mBAAmB,IAAI;IAC5C,MAAMC,kBAAkBH,QAAQI,yBAAyB,IAAI;IAC7D,MAAMC,eAAeL,QAAQM,WAAW,IAAI,EAAE;IAE9C,OAAO;QACLL;QACAM,OAAO;YACLC,YAAY;YACZC,OAAO;QACT;QACAC,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,UAAU;gBACVC,QAAQ;gBACRT,OAAO;oBACLU,aAAa;gBACf;YACF;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNI,YAAYf;gBACZgB,SAAS;gBACTZ,OAAO;oBACLU,aAAa;gBACf;YACF;eACGZ;SACJ;IACH;AACF,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BeforeDashboardClient: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useConfig } from '@payloadcms/ui';
|
|
4
|
+
import { formatAdminURL } from 'payload/shared';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
export const BeforeDashboardClient = ()=>{
|
|
7
|
+
const { config } = useConfig();
|
|
8
|
+
const [message, setMessage] = useState('');
|
|
9
|
+
useEffect(()=>{
|
|
10
|
+
const fetchMessage = async ()=>{
|
|
11
|
+
const response = await fetch(formatAdminURL({
|
|
12
|
+
apiRoute: config.routes.api,
|
|
13
|
+
path: '/my-plugin-endpoint'
|
|
14
|
+
}));
|
|
15
|
+
const result = await response.json();
|
|
16
|
+
setMessage(result.message);
|
|
17
|
+
};
|
|
18
|
+
void fetchMessage();
|
|
19
|
+
}, [
|
|
20
|
+
config.serverURL,
|
|
21
|
+
config.routes.api
|
|
22
|
+
]);
|
|
23
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
26
|
+
children: "Added by the plugin: Before Dashboard Client"
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
29
|
+
children: [
|
|
30
|
+
"Message from the endpoint:",
|
|
31
|
+
/*#__PURE__*/ _jsx("div", {
|
|
32
|
+
children: message || 'Loading...'
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
})
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=BeforeDashboardClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/BeforeDashboardClient.tsx"],"sourcesContent":["'use client'\nimport { useConfig } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect, useState } from 'react'\n\nexport const BeforeDashboardClient = () => {\n const { config } = useConfig()\n\n const [message, setMessage] = useState('')\n\n useEffect(() => {\n const fetchMessage = async () => {\n const response = await fetch(\n formatAdminURL({\n apiRoute: config.routes.api,\n path: '/my-plugin-endpoint',\n }),\n )\n const result = await response.json()\n setMessage(result.message)\n }\n\n void fetchMessage()\n }, [config.serverURL, config.routes.api])\n\n return (\n <div>\n <h1>Added by the plugin: Before Dashboard Client</h1>\n <div>\n Message from the endpoint:\n <div>{message || 'Loading...'}</div>\n </div>\n </div>\n )\n}\n"],"names":["useConfig","formatAdminURL","useEffect","useState","BeforeDashboardClient","config","message","setMessage","fetchMessage","response","fetch","apiRoute","routes","api","path","result","json","serverURL","div","h1"],"mappings":"AAAA;;AACA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE3C,OAAO,MAAMC,wBAAwB;IACnC,MAAM,EAAEC,MAAM,EAAE,GAAGL;IAEnB,MAAM,CAACM,SAASC,WAAW,GAAGJ,SAAS;IAEvCD,UAAU;QACR,MAAMM,eAAe;YACnB,MAAMC,WAAW,MAAMC,MACrBT,eAAe;gBACbU,UAAUN,OAAOO,MAAM,CAACC,GAAG;gBAC3BC,MAAM;YACR;YAEF,MAAMC,SAAS,MAAMN,SAASO,IAAI;YAClCT,WAAWQ,OAAOT,OAAO;QAC3B;QAEA,KAAKE;IACP,GAAG;QAACH,OAAOY,SAAS;QAAEZ,OAAOO,MAAM,CAACC,GAAG;KAAC;IAExC,qBACE,MAACK;;0BACC,KAACC;0BAAG;;0BACJ,MAACD;;oBAAI;kCAEH,KAACA;kCAAKZ,WAAW;;;;;;AAIzB,EAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import styles from './BeforeDashboardServer.module.css';
|
|
3
|
+
export const BeforeDashboardServer = async (props)=>{
|
|
4
|
+
const { payload } = props;
|
|
5
|
+
const { docs } = await payload.find({
|
|
6
|
+
collection: 'plugin-collection'
|
|
7
|
+
});
|
|
8
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
9
|
+
className: styles.wrapper,
|
|
10
|
+
children: [
|
|
11
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
12
|
+
children: "Added by the plugin: Before Dashboard Server"
|
|
13
|
+
}),
|
|
14
|
+
"Docs from Local API:",
|
|
15
|
+
docs.map((doc)=>/*#__PURE__*/ _jsx("div", {
|
|
16
|
+
children: doc.id
|
|
17
|
+
}, doc.id))
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=BeforeDashboardServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/BeforeDashboardServer.tsx"],"sourcesContent":["import type { ServerComponentProps } from 'payload'\n\nimport styles from './BeforeDashboardServer.module.css'\n\nexport const BeforeDashboardServer = async (props: ServerComponentProps) => {\n const { payload } = props\n\n const { docs } = await payload.find({ collection: 'plugin-collection' })\n\n return (\n <div className={styles.wrapper}>\n <h1>Added by the plugin: Before Dashboard Server</h1>\n Docs from Local API:\n {docs.map((doc) => (\n <div key={doc.id}>{doc.id}</div>\n ))}\n </div>\n )\n}\n"],"names":["styles","BeforeDashboardServer","props","payload","docs","find","collection","div","className","wrapper","h1","map","doc","id"],"mappings":";AAEA,OAAOA,YAAY,qCAAoC;AAEvD,OAAO,MAAMC,wBAAwB,OAAOC;IAC1C,MAAM,EAAEC,OAAO,EAAE,GAAGD;IAEpB,MAAM,EAAEE,IAAI,EAAE,GAAG,MAAMD,QAAQE,IAAI,CAAC;QAAEC,YAAY;IAAoB;IAEtE,qBACE,MAACC;QAAIC,WAAWR,OAAOS,OAAO;;0BAC5B,KAACC;0BAAG;;YAAiD;YAEpDN,KAAKO,GAAG,CAAC,CAACC,oBACT,KAACL;8BAAkBK,IAAIC,EAAE;mBAAfD,IAAIC,EAAE;;;AAIxB,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/customEndpointHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\n\nexport const customEndpointHandler: PayloadHandler = () => {\n return Response.json({ message: 'Hello from custom endpoint' })\n}\n"],"names":["customEndpointHandler","Response","json","message"],"mappings":"AAEA,OAAO,MAAMA,wBAAwC;IACnD,OAAOC,SAASC,IAAI,CAAC;QAAEC,SAAS;IAA6B;AAC/D,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BeforeDashboardClient } from '../components/BeforeDashboardClient';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { BeforeDashboardClient } from '../components/BeforeDashboardClient.js'\n"],"names":["BeforeDashboardClient"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,yCAAwC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BeforeDashboardServer } from '../components/BeforeDashboardServer';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["export { BeforeDashboardServer } from '../components/BeforeDashboardServer.js'\n"],"names":["BeforeDashboardServer"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,yCAAwC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createPermissionsCollection } from './collections/Permissions.js';
|
|
2
|
+
import { createRolesCollection } from './collections/Roles.js';
|
|
3
|
+
export const rbac = (pluginOptions)=>(config)=>{
|
|
4
|
+
const options = pluginOptions || {};
|
|
5
|
+
if (options.enabled === false) {
|
|
6
|
+
return config;
|
|
7
|
+
}
|
|
8
|
+
if (!config.collections) {
|
|
9
|
+
config.collections = [];
|
|
10
|
+
}
|
|
11
|
+
// Add Roles and Permissions collections
|
|
12
|
+
config.collections.push(createPermissionsCollection(options));
|
|
13
|
+
config.collections.push(createRolesCollection(options));
|
|
14
|
+
// Extend the target auth collection
|
|
15
|
+
const authSlug = options.authCollectionSlug || 'users';
|
|
16
|
+
const rolesSlug = options.rolesCollectionSlug || 'roles';
|
|
17
|
+
const authCollection = config.collections.find((collection)=>collection.slug === authSlug);
|
|
18
|
+
if (authCollection) {
|
|
19
|
+
authCollection.fields.push({
|
|
20
|
+
name: 'roles',
|
|
21
|
+
type: 'relationship',
|
|
22
|
+
relationTo: rolesSlug,
|
|
23
|
+
hasMany: true,
|
|
24
|
+
saveToJWT: true,
|
|
25
|
+
admin: {
|
|
26
|
+
description: 'Roles assigned to this user.'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
console.warn(`[rbac-plugin] Auth collection '${authSlug}' not found. Cannot inject roles field.`);
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
};
|
|
34
|
+
// Export utilities and types for consumers
|
|
35
|
+
export * from './types.js';
|
|
36
|
+
export * from './utilities/index.js';
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'payload'\nimport type { PluginOptions } from './types.js'\n\nimport { createPermissionsCollection } from './collections/Permissions.js'\nimport { createRolesCollection } from './collections/Roles.js'\n\nexport const rbac =\n (pluginOptions?: PluginOptions) =>\n (config: Config): Config => {\n const options = pluginOptions || {}\n \n if (options.enabled === false) {\n return config\n }\n\n if (!config.collections) {\n config.collections = []\n }\n\n // Add Roles and Permissions collections\n config.collections.push(createPermissionsCollection(options))\n config.collections.push(createRolesCollection(options))\n\n // Extend the target auth collection\n const authSlug = options.authCollectionSlug || 'users'\n const rolesSlug = options.rolesCollectionSlug || 'roles'\n\n const authCollection = config.collections.find(\n (collection) => collection.slug === authSlug,\n )\n\n if (authCollection) {\n authCollection.fields.push({\n name: 'roles',\n type: 'relationship',\n relationTo: rolesSlug,\n hasMany: true,\n saveToJWT: true,\n admin: {\n description: 'Roles assigned to this user.',\n },\n })\n } else {\n console.warn(`[rbac-plugin] Auth collection '${authSlug}' not found. Cannot inject roles field.`)\n }\n\n return config\n }\n\n// Export utilities and types for consumers\nexport * from './types.js'\nexport * from './utilities/index.js'\n"],"names":["createPermissionsCollection","createRolesCollection","rbac","pluginOptions","config","options","enabled","collections","push","authSlug","authCollectionSlug","rolesSlug","rolesCollectionSlug","authCollection","find","collection","slug","fields","name","type","relationTo","hasMany","saveToJWT","admin","description","console","warn"],"mappings":"AAGA,SAASA,2BAA2B,QAAQ,+BAA8B;AAC1E,SAASC,qBAAqB,QAAQ,yBAAwB;AAE9D,OAAO,MAAMC,OACX,CAACC,gBACD,CAACC;QACC,MAAMC,UAAUF,iBAAiB,CAAC;QAElC,IAAIE,QAAQC,OAAO,KAAK,OAAO;YAC7B,OAAOF;QACT;QAEA,IAAI,CAACA,OAAOG,WAAW,EAAE;YACvBH,OAAOG,WAAW,GAAG,EAAE;QACzB;QAEA,wCAAwC;QACxCH,OAAOG,WAAW,CAACC,IAAI,CAACR,4BAA4BK;QACpDD,OAAOG,WAAW,CAACC,IAAI,CAACP,sBAAsBI;QAE9C,oCAAoC;QACpC,MAAMI,WAAWJ,QAAQK,kBAAkB,IAAI;QAC/C,MAAMC,YAAYN,QAAQO,mBAAmB,IAAI;QAEjD,MAAMC,iBAAiBT,OAAOG,WAAW,CAACO,IAAI,CAC5C,CAACC,aAAeA,WAAWC,IAAI,KAAKP;QAGtC,IAAII,gBAAgB;YAClBA,eAAeI,MAAM,CAACT,IAAI,CAAC;gBACzBU,MAAM;gBACNC,MAAM;gBACNC,YAAYT;gBACZU,SAAS;gBACTC,WAAW;gBACXC,OAAO;oBACLC,aAAa;gBACf;YACF;QACF,OAAO;YACLC,QAAQC,IAAI,CAAC,CAAC,+BAA+B,EAAEjB,SAAS,uCAAuC,CAAC;QAClG;QAEA,OAAOL;IACT,EAAC;AAEH,2CAA2C;AAC3C,cAAc,aAAY;AAC1B,cAAc,uBAAsB"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Field } from 'payload';
|
|
2
|
+
export interface PluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Enable or disable plugin
|
|
5
|
+
* @default false
|
|
6
|
+
*/
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Override the default collection slug for Roles
|
|
10
|
+
* @default 'roles'
|
|
11
|
+
*/
|
|
12
|
+
rolesCollectionSlug?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Override the default collection slug for Permissions
|
|
15
|
+
* @default 'permissions'
|
|
16
|
+
*/
|
|
17
|
+
permissionsCollectionSlug?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The collection slug for the authentication collection (typically 'users')
|
|
20
|
+
* @default 'users'
|
|
21
|
+
*/
|
|
22
|
+
authCollectionSlug?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Inject additional custom fields into the generated Roles collection
|
|
25
|
+
*/
|
|
26
|
+
rolesFields?: Field[];
|
|
27
|
+
/**
|
|
28
|
+
* Inject additional custom fields into the generated Permissions collection
|
|
29
|
+
*/
|
|
30
|
+
permissionsFields?: Field[];
|
|
31
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nexport interface PluginOptions {\n /**\n * Enable or disable plugin\n * @default false\n */\n enabled?: boolean\n\n /**\n * Override the default collection slug for Roles\n * @default 'roles'\n */\n rolesCollectionSlug?: string\n\n /**\n * Override the default collection slug for Permissions\n * @default 'permissions'\n */\n permissionsCollectionSlug?: string\n\n /**\n * The collection slug for the authentication collection (typically 'users')\n * @default 'users'\n */\n authCollectionSlug?: string\n\n /**\n * Inject additional custom fields into the generated Roles collection\n */\n rolesFields?: Field[]\n\n /**\n * Inject additional custom fields into the generated Permissions collection\n */\n permissionsFields?: Field[]\n}\n"],"names":[],"mappings":"AAEA,WAkCC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Access } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* A Higher-Order Function to drop into Payload Collection access control fields.
|
|
4
|
+
*
|
|
5
|
+
* @param permissionName - The required permission name
|
|
6
|
+
* @returns Access function
|
|
7
|
+
*/
|
|
8
|
+
export declare const checkPermission: (permissionName: string) => Access;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { hasPermission } from './hasPermission.js';
|
|
2
|
+
/**
|
|
3
|
+
* A Higher-Order Function to drop into Payload Collection access control fields.
|
|
4
|
+
*
|
|
5
|
+
* @param permissionName - The required permission name
|
|
6
|
+
* @returns Access function
|
|
7
|
+
*/ export const checkPermission = (permissionName)=>{
|
|
8
|
+
return ({ req: { user } })=>{
|
|
9
|
+
// We can just rely on the synchronous hasPermission check.
|
|
10
|
+
// If relations are unpopulated, it will return false. Ensure your auth
|
|
11
|
+
// collection is configured with adequate depth for saveToJWT or default depth.
|
|
12
|
+
return hasPermission(user, permissionName);
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=checkPermission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/checkPermission.ts"],"sourcesContent":["import type { Access } from 'payload'\nimport { hasPermission } from './hasPermission.js'\n\n/**\n * A Higher-Order Function to drop into Payload Collection access control fields.\n * \n * @param permissionName - The required permission name\n * @returns Access function\n */\nexport const checkPermission = (permissionName: string): Access => {\n return ({ req: { user } }) => {\n // We can just rely on the synchronous hasPermission check.\n // If relations are unpopulated, it will return false. Ensure your auth\n // collection is configured with adequate depth for saveToJWT or default depth.\n return hasPermission(user, permissionName)\n }\n}\n"],"names":["hasPermission","checkPermission","permissionName","req","user"],"mappings":"AACA,SAASA,aAAa,QAAQ,qBAAoB;AAElD;;;;;CAKC,GACD,OAAO,MAAMC,kBAAkB,CAACC;IAC9B,OAAO,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE;QACvB,2DAA2D;QAC3D,uEAAuE;QACvE,+EAA+E;QAC/E,OAAOJ,cAAcI,MAAMF;IAC7B;AACF,EAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a user has a specific permission.
|
|
4
|
+
* Gracefully handles populated and unpopulated relationship states as far as possible synchronously.
|
|
5
|
+
*
|
|
6
|
+
* @param user - The Payload user object from req.user
|
|
7
|
+
* @param requiredPermission - The name of the permission to check for
|
|
8
|
+
* @returns boolean
|
|
9
|
+
*/
|
|
10
|
+
export declare const hasPermission: (user: PayloadRequest["user"] | null | undefined, requiredPermission: string) => boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a user has a specific permission.
|
|
3
|
+
* Gracefully handles populated and unpopulated relationship states as far as possible synchronously.
|
|
4
|
+
*
|
|
5
|
+
* @param user - The Payload user object from req.user
|
|
6
|
+
* @param requiredPermission - The name of the permission to check for
|
|
7
|
+
* @returns boolean
|
|
8
|
+
*/ export const hasPermission = (user, requiredPermission)=>{
|
|
9
|
+
if (!user || !user.roles || !Array.isArray(user.roles)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
for (const role of user.roles){
|
|
13
|
+
// If role is just an ID (unpopulated), we can't check its permissions synchronously
|
|
14
|
+
if (typeof role !== 'object' || role === null) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const permissions = role.permissions;
|
|
18
|
+
if (!permissions || !Array.isArray(permissions)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const permission of permissions){
|
|
22
|
+
// If permission is an object (populated) and has a name
|
|
23
|
+
if (typeof permission === 'object' && permission !== null) {
|
|
24
|
+
if ('name' in permission && permission.name === requiredPermission) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Note: If permission is just an ID (unpopulated), we cannot synchronously
|
|
29
|
+
// determine its name here. In a strictly synchronous check, we must return false
|
|
30
|
+
// or ignore it. For a fully robust check with unpopulated data, an async version
|
|
31
|
+
// requiring the payload instance would be necessary.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=hasPermission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/hasPermission.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\n/**\n * Checks if a user has a specific permission.\n * Gracefully handles populated and unpopulated relationship states as far as possible synchronously.\n * \n * @param user - The Payload user object from req.user\n * @param requiredPermission - The name of the permission to check for\n * @returns boolean\n */\nexport const hasPermission = (\n user: PayloadRequest['user'] | null | undefined,\n requiredPermission: string,\n): boolean => {\n if (!user || !user.roles || !Array.isArray(user.roles)) {\n return false\n }\n\n for (const role of user.roles) {\n // If role is just an ID (unpopulated), we can't check its permissions synchronously\n if (typeof role !== 'object' || role === null) {\n continue\n }\n\n const permissions = role.permissions\n if (!permissions || !Array.isArray(permissions)) {\n continue\n }\n\n for (const permission of permissions) {\n // If permission is an object (populated) and has a name\n if (typeof permission === 'object' && permission !== null) {\n if ('name' in permission && permission.name === requiredPermission) {\n return true\n }\n } \n // Note: If permission is just an ID (unpopulated), we cannot synchronously\n // determine its name here. In a strictly synchronous check, we must return false \n // or ignore it. For a fully robust check with unpopulated data, an async version \n // requiring the payload instance would be necessary.\n }\n }\n\n return false\n}\n"],"names":["hasPermission","user","requiredPermission","roles","Array","isArray","role","permissions","permission","name"],"mappings":"AAEA;;;;;;;CAOC,GACD,OAAO,MAAMA,gBAAgB,CAC3BC,MACAC;IAEA,IAAI,CAACD,QAAQ,CAACA,KAAKE,KAAK,IAAI,CAACC,MAAMC,OAAO,CAACJ,KAAKE,KAAK,GAAG;QACtD,OAAO;IACT;IAEA,KAAK,MAAMG,QAAQL,KAAKE,KAAK,CAAE;QAC7B,oFAAoF;QACpF,IAAI,OAAOG,SAAS,YAAYA,SAAS,MAAM;YAC7C;QACF;QAEA,MAAMC,cAAcD,KAAKC,WAAW;QACpC,IAAI,CAACA,eAAe,CAACH,MAAMC,OAAO,CAACE,cAAc;YAC/C;QACF;QAEA,KAAK,MAAMC,cAAcD,YAAa;YACpC,wDAAwD;YACxD,IAAI,OAAOC,eAAe,YAAYA,eAAe,MAAM;gBACzD,IAAI,UAAUA,cAAcA,WAAWC,IAAI,KAAKP,oBAAoB;oBAClE,OAAO;gBACT;YACF;QACA,2EAA2E;QAC3E,kFAAkF;QAClF,kFAAkF;QAClF,qDAAqD;QACvD;IACF;IAEA,OAAO;AACT,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { hasPermission } from './hasPermission.js';
|
|
2
|
+
describe('hasPermission', ()=>{
|
|
3
|
+
it('should return false if user is null', ()=>{
|
|
4
|
+
expect(hasPermission(null, 'read:posts')).toBe(false);
|
|
5
|
+
});
|
|
6
|
+
it('should return false if user has no roles array', ()=>{
|
|
7
|
+
const user = {
|
|
8
|
+
id: '1'
|
|
9
|
+
};
|
|
10
|
+
expect(hasPermission(user, 'read:posts')).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
it('should return false if user roles is unpopulated (array of strings)', ()=>{
|
|
13
|
+
const user = {
|
|
14
|
+
id: '1',
|
|
15
|
+
roles: [
|
|
16
|
+
'role-id-1'
|
|
17
|
+
]
|
|
18
|
+
};
|
|
19
|
+
expect(hasPermission(user, 'read:posts')).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it('should return false if role has no permissions', ()=>{
|
|
22
|
+
const user = {
|
|
23
|
+
id: '1',
|
|
24
|
+
roles: [
|
|
25
|
+
{
|
|
26
|
+
id: 'role-1',
|
|
27
|
+
name: 'admin'
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
expect(hasPermission(user, 'read:posts')).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('should return false if role permissions are unpopulated (array of strings)', ()=>{
|
|
34
|
+
const user = {
|
|
35
|
+
id: '1',
|
|
36
|
+
roles: [
|
|
37
|
+
{
|
|
38
|
+
id: 'role-1',
|
|
39
|
+
name: 'admin',
|
|
40
|
+
permissions: [
|
|
41
|
+
'perm-id-1'
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
expect(hasPermission(user, 'read:posts')).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
it('should return true if permission is found in populated roles', ()=>{
|
|
49
|
+
const user = {
|
|
50
|
+
id: '1',
|
|
51
|
+
roles: [
|
|
52
|
+
{
|
|
53
|
+
id: 'role-1',
|
|
54
|
+
name: 'admin',
|
|
55
|
+
permissions: [
|
|
56
|
+
{
|
|
57
|
+
id: 'perm-1',
|
|
58
|
+
name: 'read:posts'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'perm-2',
|
|
62
|
+
name: 'write:posts'
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
};
|
|
68
|
+
expect(hasPermission(user, 'write:posts')).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it('should return false if permission is not found in populated roles', ()=>{
|
|
71
|
+
const user = {
|
|
72
|
+
id: '1',
|
|
73
|
+
roles: [
|
|
74
|
+
{
|
|
75
|
+
id: 'role-1',
|
|
76
|
+
name: 'editor',
|
|
77
|
+
permissions: [
|
|
78
|
+
{
|
|
79
|
+
id: 'perm-1',
|
|
80
|
+
name: 'read:posts'
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
};
|
|
86
|
+
expect(hasPermission(user, 'delete:posts')).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
it('should correctly search through multiple roles', ()=>{
|
|
89
|
+
const user = {
|
|
90
|
+
id: '1',
|
|
91
|
+
roles: [
|
|
92
|
+
{
|
|
93
|
+
id: 'role-1',
|
|
94
|
+
name: 'editor',
|
|
95
|
+
permissions: [
|
|
96
|
+
{
|
|
97
|
+
id: 'perm-1',
|
|
98
|
+
name: 'read:posts'
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'role-2',
|
|
104
|
+
name: 'manager',
|
|
105
|
+
permissions: [
|
|
106
|
+
{
|
|
107
|
+
id: 'perm-2',
|
|
108
|
+
name: 'delete:posts'
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
expect(hasPermission(user, 'delete:posts')).toBe(true);
|
|
115
|
+
expect(hasPermission(user, 'read:posts')).toBe(true);
|
|
116
|
+
expect(hasPermission(user, 'create:users')).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=hasPermission.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/hasPermission.spec.ts"],"sourcesContent":["import { hasPermission } from './hasPermission.js'\nimport type { PayloadRequest } from 'payload'\n\ndescribe('hasPermission', () => {\n it('should return false if user is null', () => {\n expect(hasPermission(null, 'read:posts')).toBe(false)\n })\n\n it('should return false if user has no roles array', () => {\n const user = { id: '1' } as PayloadRequest['user']\n expect(hasPermission(user, 'read:posts')).toBe(false)\n })\n\n it('should return false if user roles is unpopulated (array of strings)', () => {\n const user = {\n id: '1',\n roles: ['role-id-1'],\n } as unknown as PayloadRequest['user']\n expect(hasPermission(user, 'read:posts')).toBe(false)\n })\n\n it('should return false if role has no permissions', () => {\n const user = {\n id: '1',\n roles: [{ id: 'role-1', name: 'admin' }],\n } as unknown as PayloadRequest['user']\n expect(hasPermission(user, 'read:posts')).toBe(false)\n })\n\n it('should return false if role permissions are unpopulated (array of strings)', () => {\n const user = {\n id: '1',\n roles: [{ id: 'role-1', name: 'admin', permissions: ['perm-id-1'] }],\n } as unknown as PayloadRequest['user']\n expect(hasPermission(user, 'read:posts')).toBe(false)\n })\n\n it('should return true if permission is found in populated roles', () => {\n const user = {\n id: '1',\n roles: [\n {\n id: 'role-1',\n name: 'admin',\n permissions: [\n { id: 'perm-1', name: 'read:posts' },\n { id: 'perm-2', name: 'write:posts' },\n ],\n },\n ],\n } as unknown as PayloadRequest['user']\n expect(hasPermission(user, 'write:posts')).toBe(true)\n })\n\n it('should return false if permission is not found in populated roles', () => {\n const user = {\n id: '1',\n roles: [\n {\n id: 'role-1',\n name: 'editor',\n permissions: [{ id: 'perm-1', name: 'read:posts' }],\n },\n ],\n } as unknown as PayloadRequest['user']\n expect(hasPermission(user, 'delete:posts')).toBe(false)\n })\n\n it('should correctly search through multiple roles', () => {\n const user = {\n id: '1',\n roles: [\n {\n id: 'role-1',\n name: 'editor',\n permissions: [{ id: 'perm-1', name: 'read:posts' }],\n },\n {\n id: 'role-2',\n name: 'manager',\n permissions: [{ id: 'perm-2', name: 'delete:posts' }],\n },\n ],\n } as unknown as PayloadRequest['user']\n expect(hasPermission(user, 'delete:posts')).toBe(true)\n expect(hasPermission(user, 'read:posts')).toBe(true)\n expect(hasPermission(user, 'create:users')).toBe(false)\n })\n})\n"],"names":["hasPermission","describe","it","expect","toBe","user","id","roles","name","permissions"],"mappings":"AAAA,SAASA,aAAa,QAAQ,qBAAoB;AAGlDC,SAAS,iBAAiB;IACxBC,GAAG,uCAAuC;QACxCC,OAAOH,cAAc,MAAM,eAAeI,IAAI,CAAC;IACjD;IAEAF,GAAG,kDAAkD;QACnD,MAAMG,OAAO;YAAEC,IAAI;QAAI;QACvBH,OAAOH,cAAcK,MAAM,eAAeD,IAAI,CAAC;IACjD;IAEAF,GAAG,uEAAuE;QACxE,MAAMG,OAAO;YACXC,IAAI;YACJC,OAAO;gBAAC;aAAY;QACtB;QACAJ,OAAOH,cAAcK,MAAM,eAAeD,IAAI,CAAC;IACjD;IAEAF,GAAG,kDAAkD;QACnD,MAAMG,OAAO;YACXC,IAAI;YACJC,OAAO;gBAAC;oBAAED,IAAI;oBAAUE,MAAM;gBAAQ;aAAE;QAC1C;QACAL,OAAOH,cAAcK,MAAM,eAAeD,IAAI,CAAC;IACjD;IAEAF,GAAG,8EAA8E;QAC/E,MAAMG,OAAO;YACXC,IAAI;YACJC,OAAO;gBAAC;oBAAED,IAAI;oBAAUE,MAAM;oBAASC,aAAa;wBAAC;qBAAY;gBAAC;aAAE;QACtE;QACAN,OAAOH,cAAcK,MAAM,eAAeD,IAAI,CAAC;IACjD;IAEAF,GAAG,gEAAgE;QACjE,MAAMG,OAAO;YACXC,IAAI;YACJC,OAAO;gBACL;oBACED,IAAI;oBACJE,MAAM;oBACNC,aAAa;wBACX;4BAAEH,IAAI;4BAAUE,MAAM;wBAAa;wBACnC;4BAAEF,IAAI;4BAAUE,MAAM;wBAAc;qBACrC;gBACH;aACD;QACH;QACAL,OAAOH,cAAcK,MAAM,gBAAgBD,IAAI,CAAC;IAClD;IAEAF,GAAG,qEAAqE;QACtE,MAAMG,OAAO;YACXC,IAAI;YACJC,OAAO;gBACL;oBACED,IAAI;oBACJE,MAAM;oBACNC,aAAa;wBAAC;4BAAEH,IAAI;4BAAUE,MAAM;wBAAa;qBAAE;gBACrD;aACD;QACH;QACAL,OAAOH,cAAcK,MAAM,iBAAiBD,IAAI,CAAC;IACnD;IAEAF,GAAG,kDAAkD;QACnD,MAAMG,OAAO;YACXC,IAAI;YACJC,OAAO;gBACL;oBACED,IAAI;oBACJE,MAAM;oBACNC,aAAa;wBAAC;4BAAEH,IAAI;4BAAUE,MAAM;wBAAa;qBAAE;gBACrD;gBACA;oBACEF,IAAI;oBACJE,MAAM;oBACNC,aAAa;wBAAC;4BAAEH,IAAI;4BAAUE,MAAM;wBAAe;qBAAE;gBACvD;aACD;QACH;QACAL,OAAOH,cAAcK,MAAM,iBAAiBD,IAAI,CAAC;QACjDD,OAAOH,cAAcK,MAAM,eAAeD,IAAI,CAAC;QAC/CD,OAAOH,cAAcK,MAAM,iBAAiBD,IAAI,CAAC;IACnD;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/index.ts"],"sourcesContent":["export { hasPermission } from './hasPermission.js'\nexport { checkPermission } from './checkPermission.js'\n"],"names":["hasPermission","checkPermission"],"mappings":"AAAA,SAASA,aAAa,QAAQ,qBAAoB;AAClD,SAASC,eAAe,QAAQ,uBAAsB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "payload-rbac-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "RBAC plugin for payloadcms",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Mhmod-Hsn/Payload-RBAC-Plugin.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/Mhmod-Hsn/Payload-RBAC-Plugin/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Mhmod-Hsn/Payload-RBAC-Plugin#readme",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./src/index.ts",
|
|
18
|
+
"types": "./src/index.ts",
|
|
19
|
+
"default": "./src/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"import": "./src/exports/client.ts",
|
|
23
|
+
"types": "./src/exports/client.ts",
|
|
24
|
+
"default": "./src/exports/client.ts"
|
|
25
|
+
},
|
|
26
|
+
"./rsc": {
|
|
27
|
+
"import": "./src/exports/rsc.ts",
|
|
28
|
+
"types": "./src/exports/rsc.ts",
|
|
29
|
+
"default": "./src/exports/rsc.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"main": "./src/index.ts",
|
|
33
|
+
"types": "./src/index.ts",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
39
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
40
|
+
"build:types": "tsc --outDir dist --rootDir ./src",
|
|
41
|
+
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
42
|
+
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
43
|
+
"dev": "next dev dev --turbo",
|
|
44
|
+
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
45
|
+
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
46
|
+
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
47
|
+
"generate:importmap": "pnpm dev:generate-importmap",
|
|
48
|
+
"generate:types": "pnpm dev:generate-types",
|
|
49
|
+
"lint": "eslint",
|
|
50
|
+
"lint:fix": "eslint ./src --fix",
|
|
51
|
+
"test": "pnpm test:int && pnpm test:e2e",
|
|
52
|
+
"test:e2e": "playwright test",
|
|
53
|
+
"test:int": "vitest"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
57
|
+
"@payloadcms/db-mongodb": "3.84.1",
|
|
58
|
+
"@payloadcms/db-postgres": "3.84.1",
|
|
59
|
+
"@payloadcms/db-sqlite": "3.84.1",
|
|
60
|
+
"@payloadcms/eslint-config": "3.28.0",
|
|
61
|
+
"@payloadcms/next": "3.84.1",
|
|
62
|
+
"@payloadcms/richtext-lexical": "3.84.1",
|
|
63
|
+
"@payloadcms/ui": "3.84.1",
|
|
64
|
+
"@playwright/test": "1.58.2",
|
|
65
|
+
"@swc-node/register": "1.10.9",
|
|
66
|
+
"@swc/cli": "0.6.0",
|
|
67
|
+
"@types/node": "22.19.9",
|
|
68
|
+
"@types/react": "19.2.14",
|
|
69
|
+
"@types/react-dom": "19.2.3",
|
|
70
|
+
"copyfiles": "2.4.1",
|
|
71
|
+
"cross-env": "^7.0.3",
|
|
72
|
+
"eslint": "^9.23.0",
|
|
73
|
+
"eslint-config-next": "16.2.6",
|
|
74
|
+
"graphql": "^16.8.1",
|
|
75
|
+
"mongodb-memory-server": "10.1.4",
|
|
76
|
+
"next": "16.2.6",
|
|
77
|
+
"open": "^10.1.0",
|
|
78
|
+
"payload": "3.84.1",
|
|
79
|
+
"prettier": "^3.4.2",
|
|
80
|
+
"qs-esm": "8.0.1",
|
|
81
|
+
"react": "19.2.6",
|
|
82
|
+
"react-dom": "19.2.6",
|
|
83
|
+
"rimraf": "3.0.2",
|
|
84
|
+
"sharp": "0.34.2",
|
|
85
|
+
"sort-package-json": "^2.10.0",
|
|
86
|
+
"typescript": "5.7.3",
|
|
87
|
+
"vite-tsconfig-paths": "6.0.5",
|
|
88
|
+
"vitest": "^4.1.8"
|
|
89
|
+
},
|
|
90
|
+
"peerDependencies": {
|
|
91
|
+
"payload": "^3.84.1"
|
|
92
|
+
},
|
|
93
|
+
"engines": {
|
|
94
|
+
"node": "^18.20.2 || >=20.9.0",
|
|
95
|
+
"pnpm": "^9 || ^10"
|
|
96
|
+
},
|
|
97
|
+
"publishConfig": {
|
|
98
|
+
"exports": {
|
|
99
|
+
".": {
|
|
100
|
+
"import": "./dist/index.js",
|
|
101
|
+
"types": "./dist/index.d.ts",
|
|
102
|
+
"default": "./dist/index.js"
|
|
103
|
+
},
|
|
104
|
+
"./client": {
|
|
105
|
+
"import": "./dist/exports/client.js",
|
|
106
|
+
"types": "./dist/exports/client.d.ts",
|
|
107
|
+
"default": "./dist/exports/client.js"
|
|
108
|
+
},
|
|
109
|
+
"./rsc": {
|
|
110
|
+
"import": "./dist/exports/rsc.js",
|
|
111
|
+
"types": "./dist/exports/rsc.d.ts",
|
|
112
|
+
"default": "./dist/exports/rsc.js"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"main": "./dist/index.js",
|
|
116
|
+
"types": "./dist/index.d.ts"
|
|
117
|
+
},
|
|
118
|
+
"pnpm": {
|
|
119
|
+
"onlyBuiltDependencies": [
|
|
120
|
+
"sharp",
|
|
121
|
+
"esbuild",
|
|
122
|
+
"unrs-resolver"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
"registry": "https://registry.npmjs.org/"
|
|
126
|
+
}
|