payload-rbac-plugin 1.0.3-rc1 → 1.0.5-rc

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.
@@ -4,8 +4,11 @@ export const createPermissionsCollection = (options)=>{
4
4
  const customFields = options.permissionsFields || [];
5
5
  return {
6
6
  slug,
7
- access: {
8
- read: ()=>true
7
+ access: options.permissionsAccess || {
8
+ create: ()=>true,
9
+ delete: ()=>true,
10
+ read: ()=>true,
11
+ update: ()=>true
9
12
  },
10
13
  admin: {
11
14
  group: 'Access Control',
@@ -49,6 +52,75 @@ export const createPermissionsCollection = (options)=>{
49
52
  return true;
50
53
  }
51
54
  },
55
+ {
56
+ name: 'conditions',
57
+ type: 'array',
58
+ admin: {
59
+ condition: (data)=>data?.type === 'single',
60
+ description: 'Add dynamic row-level security constraints to this permission. If specified, this permission will only grant access to documents matching these conditions.'
61
+ },
62
+ fields: [
63
+ {
64
+ name: 'field',
65
+ type: 'text',
66
+ required: true,
67
+ admin: {
68
+ description: 'The document field to check (e.g. "sender" or "status").'
69
+ }
70
+ },
71
+ {
72
+ name: 'operator',
73
+ type: 'select',
74
+ required: true,
75
+ defaultValue: 'equals',
76
+ options: [
77
+ {
78
+ label: 'Equals',
79
+ value: 'equals'
80
+ },
81
+ {
82
+ label: 'Not Equals',
83
+ value: 'not_equals'
84
+ },
85
+ {
86
+ label: 'In',
87
+ value: 'in'
88
+ },
89
+ {
90
+ label: 'Not In',
91
+ value: 'not_in'
92
+ },
93
+ {
94
+ label: 'Exists',
95
+ value: 'exists'
96
+ },
97
+ {
98
+ label: 'Greater Than',
99
+ value: 'greater_than'
100
+ },
101
+ {
102
+ label: 'Less Than',
103
+ value: 'less_than'
104
+ },
105
+ {
106
+ label: 'Like',
107
+ value: 'like'
108
+ },
109
+ {
110
+ label: 'Contains',
111
+ value: 'contains'
112
+ }
113
+ ]
114
+ },
115
+ {
116
+ name: 'value',
117
+ type: 'text',
118
+ admin: {
119
+ description: 'Value to compare against. Supports {{user.id}}, {{user.roles}}, {{user.role}} variables.'
120
+ }
121
+ }
122
+ ]
123
+ },
52
124
  {
53
125
  name: 'collectionName',
54
126
  type: 'text',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/collections/Permissions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport type { PluginOptions } from '../types'\n\nimport { createBeforePermissionChangeHook } from '../hooks/beforePermissionChange'\n\nexport const createPermissionsCollection = (options: PluginOptions): CollectionConfig => {\n const slug = options.permissionsCollectionSlug || 'permissions'\n const customFields = options.permissionsFields || []\n\n return {\n slug,\n access: {\n read: () => true,\n },\n admin: {\n group: 'Access Control',\n hidden: options.hidePermissions ?? false,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'type',\n type: 'select',\n admin: {\n condition: (data) => !data?.id && !data?._id && !data?.createdAt,\n description:\n 'Choose whether to create a single custom permission or generate multiple CRUD permissions at once.',\n disableListColumn: true,\n disableListFilter: true,\n },\n defaultValue: 'single',\n options: [\n { label: 'Single Permission', value: 'single' },\n { label: 'Bulk CRUD Generator', value: 'bulk' },\n ],\n },\n {\n name: 'name',\n type: 'text',\n admin: {\n condition: (data) => data?.type === 'single',\n description: 'The unique name of the permission (e.g., \"access:admin\", \"posts:create\").',\n },\n unique: true,\n validate: (value: null | string | undefined, { data }: { data?: any }) => {\n if (data?.type === 'single' && !value) {\n return 'Name is required for single permissions.'\n }\n return true\n },\n },\n {\n name: 'collectionName',\n type: 'text',\n admin: {\n condition: (data) => data?.type === 'bulk',\n description: 'Enter a collection slug (e.g., \"posts\") to auto-generate permissions.',\n disableListColumn: true,\n disableListFilter: true,\n },\n },\n {\n type: 'row',\n fields: [\n {\n name: 'create',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n {\n name: 'read',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n {\n name: 'update',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n {\n name: 'delete',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n ],\n },\n ...customFields,\n ],\n hooks: {\n beforeChange: [createBeforePermissionChangeHook(slug)],\n },\n }\n}\n"],"names":["createBeforePermissionChangeHook","createPermissionsCollection","options","slug","permissionsCollectionSlug","customFields","permissionsFields","access","read","admin","group","hidden","hidePermissions","useAsTitle","fields","name","type","condition","data","id","_id","createdAt","description","disableListColumn","disableListFilter","defaultValue","label","value","unique","validate","width","hooks","beforeChange"],"mappings":"AAIA,SAASA,gCAAgC,QAAQ,kCAAiC;AAElF,OAAO,MAAMC,8BAA8B,CAACC;IAC1C,MAAMC,OAAOD,QAAQE,yBAAyB,IAAI;IAClD,MAAMC,eAAeH,QAAQI,iBAAiB,IAAI,EAAE;IAEpD,OAAO;QACLH;QACAI,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,OAAO;YACLC,OAAO;YACPC,QAAQT,QAAQU,eAAe,IAAI;YACnCC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLQ,WAAW,CAACC,OAAS,CAACA,MAAMC,MAAM,CAACD,MAAME,OAAO,CAACF,MAAMG;oBACvDC,aACE;oBACFC,mBAAmB;oBACnBC,mBAAmB;gBACrB;gBACAC,cAAc;gBACdvB,SAAS;oBACP;wBAAEwB,OAAO;wBAAqBC,OAAO;oBAAS;oBAC9C;wBAAED,OAAO;wBAAuBC,OAAO;oBAAO;iBAC/C;YACH;YACA;gBACEZ,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLQ,WAAW,CAACC,OAASA,MAAMF,SAAS;oBACpCM,aAAa;gBACf;gBACAM,QAAQ;gBACRC,UAAU,CAACF,OAAkC,EAAET,IAAI,EAAkB;oBACnE,IAAIA,MAAMF,SAAS,YAAY,CAACW,OAAO;wBACrC,OAAO;oBACT;oBACA,OAAO;gBACT;YACF;YACA;gBACEZ,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLQ,WAAW,CAACC,OAASA,MAAMF,SAAS;oBACpCM,aAAa;oBACbC,mBAAmB;oBACnBC,mBAAmB;gBACrB;YACF;YACA;gBACER,MAAM;gBACNF,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLQ,WAAW,CAACC,OAASA,MAAMF,SAAS;4BACpCO,mBAAmB;4BACnBC,mBAAmB;4BACnBM,OAAO;wBACT;wBACAL,cAAc;oBAChB;oBACA;wBACEV,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLQ,WAAW,CAACC,OAASA,MAAMF,SAAS;4BACpCO,mBAAmB;4BACnBC,mBAAmB;4BACnBM,OAAO;wBACT;wBACAL,cAAc;oBAChB;oBACA;wBACEV,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLQ,WAAW,CAACC,OAASA,MAAMF,SAAS;4BACpCO,mBAAmB;4BACnBC,mBAAmB;4BACnBM,OAAO;wBACT;wBACAL,cAAc;oBAChB;oBACA;wBACEV,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLQ,WAAW,CAACC,OAASA,MAAMF,SAAS;4BACpCO,mBAAmB;4BACnBC,mBAAmB;4BACnBM,OAAO;wBACT;wBACAL,cAAc;oBAChB;iBACD;YACH;eACGpB;SACJ;QACD0B,OAAO;YACLC,cAAc;gBAAChC,iCAAiCG;aAAM;QACxD;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/collections/Permissions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport type { PluginOptions } from '../types'\n\nimport { createBeforePermissionChangeHook } from '../hooks/beforePermissionChange'\n\nexport const createPermissionsCollection = (options: PluginOptions): CollectionConfig => {\n const slug = options.permissionsCollectionSlug || 'permissions'\n const customFields = options.permissionsFields || []\n\n return {\n slug,\n access: options.permissionsAccess || {\n create: () => true,\n delete: () => true,\n read: () => true,\n update: () => true,\n },\n admin: {\n group: 'Access Control',\n hidden: options.hidePermissions ?? false,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'type',\n type: 'select',\n admin: {\n condition: (data) => !data?.id && !data?._id && !data?.createdAt,\n description:\n 'Choose whether to create a single custom permission or generate multiple CRUD permissions at once.',\n disableListColumn: true,\n disableListFilter: true,\n },\n defaultValue: 'single',\n options: [\n { label: 'Single Permission', value: 'single' },\n { label: 'Bulk CRUD Generator', value: 'bulk' },\n ],\n },\n {\n name: 'name',\n type: 'text',\n admin: {\n condition: (data) => data?.type === 'single',\n description: 'The unique name of the permission (e.g., \"access:admin\", \"posts:create\").',\n },\n unique: true,\n validate: (value: null | string | undefined, { data }: { data?: any }) => {\n if (data?.type === 'single' && !value) {\n return 'Name is required for single permissions.'\n }\n return true\n },\n },\n {\n name: 'conditions',\n type: 'array',\n admin: {\n condition: (data) => data?.type === 'single',\n description:\n 'Add dynamic row-level security constraints to this permission. If specified, this permission will only grant access to documents matching these conditions.',\n },\n fields: [\n {\n name: 'field',\n type: 'text',\n required: true,\n admin: {\n description: 'The document field to check (e.g. \"sender\" or \"status\").',\n },\n },\n {\n name: 'operator',\n type: 'select',\n required: true,\n defaultValue: 'equals',\n options: [\n { label: 'Equals', value: 'equals' },\n { label: 'Not Equals', value: 'not_equals' },\n { label: 'In', value: 'in' },\n { label: 'Not In', value: 'not_in' },\n { label: 'Exists', value: 'exists' },\n { label: 'Greater Than', value: 'greater_than' },\n { label: 'Less Than', value: 'less_than' },\n { label: 'Like', value: 'like' },\n { label: 'Contains', value: 'contains' },\n ],\n },\n {\n name: 'value',\n type: 'text',\n admin: {\n description:\n 'Value to compare against. Supports {{user.id}}, {{user.roles}}, {{user.role}} variables.',\n },\n },\n ],\n },\n {\n name: 'collectionName',\n type: 'text',\n admin: {\n condition: (data) => data?.type === 'bulk',\n description: 'Enter a collection slug (e.g., \"posts\") to auto-generate permissions.',\n disableListColumn: true,\n disableListFilter: true,\n },\n },\n {\n type: 'row',\n fields: [\n {\n name: 'create',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n {\n name: 'read',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n {\n name: 'update',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n {\n name: 'delete',\n type: 'checkbox',\n admin: {\n condition: (data) => data?.type === 'bulk',\n disableListColumn: true,\n disableListFilter: true,\n width: '25%',\n },\n defaultValue: false,\n },\n ],\n },\n ...customFields,\n ],\n hooks: {\n beforeChange: [createBeforePermissionChangeHook(slug)],\n },\n }\n}\n"],"names":["createBeforePermissionChangeHook","createPermissionsCollection","options","slug","permissionsCollectionSlug","customFields","permissionsFields","access","permissionsAccess","create","delete","read","update","admin","group","hidden","hidePermissions","useAsTitle","fields","name","type","condition","data","id","_id","createdAt","description","disableListColumn","disableListFilter","defaultValue","label","value","unique","validate","required","width","hooks","beforeChange"],"mappings":"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"}
@@ -4,32 +4,35 @@ export const createRolesCollection = (options)=>{
4
4
  const customFields = options.rolesFields || [];
5
5
  return {
6
6
  slug,
7
+ access: options.rolesAccess || {
8
+ create: ()=>true,
9
+ delete: ()=>true,
10
+ read: ()=>true,
11
+ update: ()=>true
12
+ },
7
13
  admin: {
8
- useAsTitle: 'name',
9
14
  group: 'Access Control',
10
- hidden: options.hideRoles ?? false
11
- },
12
- access: {
13
- read: ()=>true
15
+ hidden: options.hideRoles ?? false,
16
+ useAsTitle: 'name'
14
17
  },
15
18
  fields: [
16
19
  {
17
20
  name: 'name',
18
21
  type: 'text',
19
- required: true,
20
- unique: true,
21
22
  admin: {
22
23
  description: 'The unique name of the role (e.g., "admin", "editor").'
23
- }
24
+ },
25
+ required: true,
26
+ unique: true
24
27
  },
25
28
  {
26
29
  name: 'permissions',
27
30
  type: 'relationship',
28
- relationTo: permissionsSlug,
29
- hasMany: true,
30
31
  admin: {
31
32
  description: 'The permissions assigned to this role.'
32
- }
33
+ },
34
+ hasMany: true,
35
+ relationTo: permissionsSlug
33
36
  },
34
37
  ...customFields
35
38
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/collections/Roles.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload';\nimport type { PluginOptions } from '../types';\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 hidden: options.hideRoles ?? false,\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","hidden","hideRoles","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;YACPC,QAAQV,QAAQW,SAAS,IAAI;QAC/B;QACAC,QAAQ;YACNC,MAAM,IAAM;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,UAAU;gBACVC,QAAQ;gBACRX,OAAO;oBACLY,aAAa;gBACf;YACF;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNI,YAAYjB;gBACZkB,SAAS;gBACTd,OAAO;oBACLY,aAAa;gBACf;YACF;eACGd;SACJ;IACH;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/collections/Roles.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport type { PluginOptions } from '../types'\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 access: options.rolesAccess || {\n create: () => true,\n delete: () => true,\n read: () => true,\n update: () => true,\n },\n admin: {\n group: 'Access Control',\n hidden: options.hideRoles ?? false,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n admin: {\n description: 'The unique name of the role (e.g., \"admin\", \"editor\").',\n },\n required: true,\n unique: true,\n },\n {\n name: 'permissions',\n type: 'relationship',\n admin: {\n description: 'The permissions assigned to this role.',\n },\n hasMany: true,\n relationTo: permissionsSlug,\n },\n ...customFields,\n ],\n }\n}\n"],"names":["createRolesCollection","options","slug","rolesCollectionSlug","permissionsSlug","permissionsCollectionSlug","customFields","rolesFields","access","rolesAccess","create","delete","read","update","admin","group","hidden","hideRoles","useAsTitle","fields","name","type","description","required","unique","hasMany","relationTo"],"mappings":"AAIA,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,QAAQP,QAAQQ,WAAW,IAAI;YAC7BC,QAAQ,IAAM;YACdC,QAAQ,IAAM;YACdC,MAAM,IAAM;YACZC,QAAQ,IAAM;QAChB;QACAC,OAAO;YACLC,OAAO;YACPC,QAAQf,QAAQgB,SAAS,IAAI;YAC7BC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLQ,aAAa;gBACf;gBACAC,UAAU;gBACVC,QAAQ;YACV;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLQ,aAAa;gBACf;gBACAG,SAAS;gBACTC,YAAYtB;YACd;eACGE;SACJ;IACH;AACF,EAAC"}
package/dist/types.d.ts CHANGED
@@ -1,47 +1,55 @@
1
1
  import type { Field } from 'payload';
2
2
  export interface PluginOptions {
3
+ /**
4
+ * The collection slug for the authentication collection (typically 'users')
5
+ * @default 'users'
6
+ */
7
+ authCollectionSlug?: string;
3
8
  /**
4
9
  * Enable or disable plugin
5
10
  * @default false
6
11
  */
7
12
  enabled?: boolean;
8
13
  /**
9
- * Override the default collection slug for Roles
10
- * @default 'roles'
14
+ * Hide the Permissions collection from the Admin panel sidebar/dashboard
15
+ * Can be a boolean or a function based on the logged-in user
16
+ * @default false
11
17
  */
12
- rolesCollectionSlug?: string;
18
+ hidePermissions?: ((args: {
19
+ user: any;
20
+ }) => boolean) | boolean;
13
21
  /**
14
- * Override the default collection slug for Permissions
15
- * @default 'permissions'
22
+ * Hide the Roles collection from the Admin panel sidebar/dashboard
23
+ * Can be a boolean or a function based on the logged-in user
24
+ * @default false
16
25
  */
17
- permissionsCollectionSlug?: string;
26
+ hideRoles?: ((args: {
27
+ user: any;
28
+ }) => boolean) | boolean;
18
29
  /**
19
- * The collection slug for the authentication collection (typically 'users')
20
- * @default 'users'
30
+ * Access control functions for the Permissions collection
21
31
  */
22
- authCollectionSlug?: string;
32
+ permissionsAccess?: any;
23
33
  /**
24
- * Inject additional custom fields into the generated Roles collection
34
+ * Override the default collection slug for Permissions
35
+ * @default 'permissions'
25
36
  */
26
- rolesFields?: Field[];
37
+ permissionsCollectionSlug?: string;
27
38
  /**
28
39
  * Inject additional custom fields into the generated Permissions collection
29
40
  */
30
41
  permissionsFields?: Field[];
31
42
  /**
32
- * Hide the Roles collection from the Admin panel sidebar/dashboard
33
- * Can be a boolean or a function based on the logged-in user
34
- * @default false
43
+ * Access control functions for the Roles collection
35
44
  */
36
- hideRoles?: boolean | ((args: {
37
- user: any;
38
- }) => boolean);
45
+ rolesAccess?: any;
39
46
  /**
40
- * Hide the Permissions collection from the Admin panel sidebar/dashboard
41
- * Can be a boolean or a function based on the logged-in user
42
- * @default false
47
+ * Override the default collection slug for Roles
48
+ * @default 'roles'
43
49
  */
44
- hidePermissions?: boolean | ((args: {
45
- user: any;
46
- }) => boolean);
50
+ rolesCollectionSlug?: string;
51
+ /**
52
+ * Inject additional custom fields into the generated Roles collection
53
+ */
54
+ rolesFields?: Field[];
47
55
  }
package/dist/types.js.map CHANGED
@@ -1 +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 /**\n * Hide the Roles collection from the Admin panel sidebar/dashboard\n * Can be a boolean or a function based on the logged-in user\n * @default false\n */\n hideRoles?: boolean | ((args: { user: any }) => boolean)\n\n /**\n * Hide the Permissions collection from the Admin panel sidebar/dashboard\n * Can be a boolean or a function based on the logged-in user\n * @default false\n */\n hidePermissions?: boolean | ((args: { user: any }) => boolean)\n}\n"],"names":[],"mappings":"AAEA,WAgDC"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\nexport interface PluginOptions {\n /**\n * The collection slug for the authentication collection (typically 'users')\n * @default 'users'\n */\n authCollectionSlug?: string\n\n /**\n * Enable or disable plugin\n * @default false\n */\n enabled?: boolean\n\n /**\n * Hide the Permissions collection from the Admin panel sidebar/dashboard\n * Can be a boolean or a function based on the logged-in user\n * @default false\n */\n hidePermissions?: ((args: { user: any }) => boolean) | boolean\n\n /**\n * Hide the Roles collection from the Admin panel sidebar/dashboard\n * Can be a boolean or a function based on the logged-in user\n * @default false\n */\n hideRoles?: ((args: { user: any }) => boolean) | boolean\n\n /**\n * Access control functions for the Permissions collection\n */\n permissionsAccess?: any\n\n /**\n * Override the default collection slug for Permissions\n * @default 'permissions'\n */\n permissionsCollectionSlug?: string\n\n /**\n * Inject additional custom fields into the generated Permissions collection\n */\n permissionsFields?: Field[]\n\n /**\n * Access control functions for the Roles collection\n */\n rolesAccess?: any\n\n /**\n * Override the default collection slug for Roles\n * @default 'roles'\n */\n rolesCollectionSlug?: string\n\n /**\n * Inject additional custom fields into the generated Roles collection\n */\n rolesFields?: Field[]\n}\n"],"names":[],"mappings":"AAEA,WA0DC"}
@@ -1,8 +1,7 @@
1
- import type { Access } from 'payload';
2
1
  /**
3
2
  * A Higher-Order Function to drop into Payload Collection access control fields.
4
3
  *
5
4
  * @param permissionName - The required permission name
6
5
  * @returns Access function
7
6
  */
8
- export declare const checkPermission: (permissionName: string) => Access;
7
+ export declare const checkPermission: (permissionName: string) => any;
@@ -1,4 +1,4 @@
1
- import { hasPermission } from './hasPermission';
1
+ import { getPermissionQuery } from './getPermissionQuery';
2
2
  /**
3
3
  * A Higher-Order Function to drop into Payload Collection access control fields.
4
4
  *
@@ -6,10 +6,10 @@ import { hasPermission } from './hasPermission';
6
6
  * @returns Access function
7
7
  */ export const checkPermission = (permissionName)=>{
8
8
  return ({ req: { user } })=>{
9
- // We can just rely on the synchronous hasPermission check.
9
+ // Use getPermissionQuery which supports both boolean checks and ABAC conditions.
10
10
  // If relations are unpopulated, it will return false. Ensure your auth
11
11
  // collection is configured with adequate depth for saveToJWT or default depth.
12
- return hasPermission(user, permissionName);
12
+ return getPermissionQuery(user, permissionName);
13
13
  };
14
14
  };
15
15
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/checkPermission.ts"],"sourcesContent":["import type { Access } from 'payload';\nimport { hasPermission } from './hasPermission';\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,kBAAkB;AAEhD;;;;;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"}
1
+ {"version":3,"sources":["../../src/utilities/checkPermission.ts"],"sourcesContent":["import { getPermissionQuery } from './getPermissionQuery';\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): any => {\n return ({ req: { user } }: any) => {\n // Use getPermissionQuery which supports both boolean checks and ABAC conditions.\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 getPermissionQuery(user, permissionName)\n }\n}\n"],"names":["getPermissionQuery","checkPermission","permissionName","req","user"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,uBAAuB;AAE1D;;;;;CAKC,GACD,OAAO,MAAMC,kBAAkB,CAACC;IAC9B,OAAO,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAO;QAC5B,iFAAiF;QACjF,uEAAuE;QACvE,+EAA+E;QAC/E,OAAOJ,mBAAmBI,MAAMF;IAClC;AACF,EAAC"}
@@ -0,0 +1,13 @@
1
+ import type { PayloadRequest, Where } from 'payload';
2
+ /**
3
+ * Checks if a user has a specific permission and evaluates its dynamic conditions.
4
+ *
5
+ * - If the user does not have the permission, returns false.
6
+ * - If the user has the permission without conditions, returns true.
7
+ * - If the user has the permission with conditions, returns a Payload `Where` object.
8
+ *
9
+ * @param user - The Payload user object from req.user
10
+ * @param requiredPermission - The name of the permission to check for
11
+ * @returns boolean | Where
12
+ */
13
+ export declare const getPermissionQuery: (user: PayloadRequest["user"] | null | undefined, requiredPermission: string) => boolean | Where;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Checks if a user has a specific permission and evaluates its dynamic conditions.
3
+ *
4
+ * - If the user does not have the permission, returns false.
5
+ * - If the user has the permission without conditions, returns true.
6
+ * - If the user has the permission with conditions, returns a Payload `Where` object.
7
+ *
8
+ * @param user - The Payload user object from req.user
9
+ * @param requiredPermission - The name of the permission to check for
10
+ * @returns boolean | Where
11
+ */ export const getPermissionQuery = (user, requiredPermission)=>{
12
+ if (!user || !user.roles || !Array.isArray(user.roles)) {
13
+ return false;
14
+ }
15
+ let hasPerm = false;
16
+ const allConditions = [];
17
+ for (const role of user.roles){
18
+ if (typeof role !== 'object' || role === null) {
19
+ continue;
20
+ }
21
+ const permissions = role.permissions;
22
+ if (!permissions || !Array.isArray(permissions)) {
23
+ continue;
24
+ }
25
+ for (const permission of permissions){
26
+ if (typeof permission === 'object' && permission !== null) {
27
+ if ('name' in permission && permission.name === requiredPermission) {
28
+ hasPerm = true;
29
+ // Collect conditions if they exist
30
+ if ('conditions' in permission && Array.isArray(permission.conditions) && permission.conditions.length > 0) {
31
+ allConditions.push(permission.conditions);
32
+ } else {
33
+ // If they have the permission WITHOUT conditions in ANY role, they get full access!
34
+ return true;
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ if (!hasPerm) {
41
+ return false;
42
+ }
43
+ // If they have the permission but ONLY with conditions, build the query.
44
+ if (allConditions.length > 0) {
45
+ const orConditions = allConditions.map((roleConditions)=>{
46
+ const andConditions = roleConditions.map((cond)=>{
47
+ let parsedValue = cond.value;
48
+ if (typeof parsedValue === 'string') {
49
+ if (parsedValue === '{{user.roles}}') {
50
+ parsedValue = user.roles?.map((r)=>typeof r === 'string' ? r : r?.id).filter(Boolean) || [];
51
+ } else if (parsedValue === '{{user.role}}') {
52
+ // Support for single role field if it exists
53
+ const singleRole = user.role;
54
+ parsedValue = typeof singleRole === 'object' && singleRole !== null ? singleRole.id : singleRole;
55
+ } else if (parsedValue === '{{user.id}}') {
56
+ parsedValue = user.id;
57
+ } else if (parsedValue === 'true') {
58
+ parsedValue = true;
59
+ } else if (parsedValue === 'false') {
60
+ parsedValue = false;
61
+ } else {
62
+ // Simple string replacement for embedded variables
63
+ parsedValue = parsedValue.replace(/\{\{user\.id\}\}/g, String(user.id));
64
+ }
65
+ }
66
+ return {
67
+ [cond.field]: {
68
+ [cond.operator]: parsedValue
69
+ }
70
+ };
71
+ });
72
+ return {
73
+ and: andConditions
74
+ };
75
+ });
76
+ if (orConditions.length === 1) {
77
+ return orConditions[0];
78
+ }
79
+ return {
80
+ or: orConditions
81
+ };
82
+ }
83
+ return false;
84
+ };
85
+
86
+ //# sourceMappingURL=getPermissionQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getPermissionQuery.ts"],"sourcesContent":["import type { PayloadRequest, Where } from 'payload'\n\n/**\n * Checks if a user has a specific permission and evaluates its dynamic conditions.\n * \n * - If the user does not have the permission, returns false.\n * - If the user has the permission without conditions, returns true.\n * - If the user has the permission with conditions, returns a Payload `Where` object.\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 | Where\n */\nexport const getPermissionQuery = (\n user: PayloadRequest['user'] | null | undefined,\n requiredPermission: string,\n): boolean | Where => {\n if (!user || !user.roles || !Array.isArray(user.roles)) {\n return false\n }\n\n let hasPerm = false\n const allConditions: any[] = []\n\n for (const role of user.roles) {\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 (typeof permission === 'object' && permission !== null) {\n if ('name' in permission && permission.name === requiredPermission) {\n hasPerm = true\n\n // Collect conditions if they exist\n if (\n 'conditions' in permission &&\n Array.isArray(permission.conditions) &&\n permission.conditions.length > 0\n ) {\n allConditions.push(permission.conditions)\n } else {\n // If they have the permission WITHOUT conditions in ANY role, they get full access!\n return true\n }\n }\n }\n }\n }\n\n if (!hasPerm) {\n return false\n }\n\n // If they have the permission but ONLY with conditions, build the query.\n if (allConditions.length > 0) {\n const orConditions: Where[] = allConditions.map((roleConditions) => {\n const andConditions = roleConditions.map((cond: any) => {\n let parsedValue: any = cond.value\n\n if (typeof parsedValue === 'string') {\n if (parsedValue === '{{user.roles}}') {\n parsedValue =\n user.roles?.map((r: any) => (typeof r === 'string' ? r : r?.id)).filter(Boolean) || []\n } else if (parsedValue === '{{user.role}}') {\n // Support for single role field if it exists\n const singleRole = (user as any).role\n parsedValue = typeof singleRole === 'object' && singleRole !== null ? singleRole.id : singleRole\n } else if (parsedValue === '{{user.id}}') {\n parsedValue = user.id\n } else if (parsedValue === 'true') {\n parsedValue = true\n } else if (parsedValue === 'false') {\n parsedValue = false\n } else {\n // Simple string replacement for embedded variables\n parsedValue = parsedValue.replace(/\\{\\{user\\.id\\}\\}/g, String(user.id))\n }\n }\n\n return {\n [cond.field]: {\n [cond.operator]: parsedValue,\n },\n } as Where\n })\n\n return { and: andConditions } as Where\n })\n\n if (orConditions.length === 1) {\n return orConditions[0] as Where\n }\n\n return { or: orConditions } as Where\n }\n\n return false\n}\n"],"names":["getPermissionQuery","user","requiredPermission","roles","Array","isArray","hasPerm","allConditions","role","permissions","permission","name","conditions","length","push","orConditions","map","roleConditions","andConditions","cond","parsedValue","value","r","id","filter","Boolean","singleRole","replace","String","field","operator","and","or"],"mappings":"AAEA;;;;;;;;;;CAUC,GACD,OAAO,MAAMA,qBAAqB,CAChCC,MACAC;IAEA,IAAI,CAACD,QAAQ,CAACA,KAAKE,KAAK,IAAI,CAACC,MAAMC,OAAO,CAACJ,KAAKE,KAAK,GAAG;QACtD,OAAO;IACT;IAEA,IAAIG,UAAU;IACd,MAAMC,gBAAuB,EAAE;IAE/B,KAAK,MAAMC,QAAQP,KAAKE,KAAK,CAAE;QAC7B,IAAI,OAAOK,SAAS,YAAYA,SAAS,MAAM;YAC7C;QACF;QAEA,MAAMC,cAAcD,KAAKC,WAAW;QACpC,IAAI,CAACA,eAAe,CAACL,MAAMC,OAAO,CAACI,cAAc;YAC/C;QACF;QAEA,KAAK,MAAMC,cAAcD,YAAa;YACpC,IAAI,OAAOC,eAAe,YAAYA,eAAe,MAAM;gBACzD,IAAI,UAAUA,cAAcA,WAAWC,IAAI,KAAKT,oBAAoB;oBAClEI,UAAU;oBAEV,mCAAmC;oBACnC,IACE,gBAAgBI,cAChBN,MAAMC,OAAO,CAACK,WAAWE,UAAU,KACnCF,WAAWE,UAAU,CAACC,MAAM,GAAG,GAC/B;wBACAN,cAAcO,IAAI,CAACJ,WAAWE,UAAU;oBAC1C,OAAO;wBACL,oFAAoF;wBACpF,OAAO;oBACT;gBACF;YACF;QACF;IACF;IAEA,IAAI,CAACN,SAAS;QACZ,OAAO;IACT;IAEA,yEAAyE;IACzE,IAAIC,cAAcM,MAAM,GAAG,GAAG;QAC5B,MAAME,eAAwBR,cAAcS,GAAG,CAAC,CAACC;YAC/C,MAAMC,gBAAgBD,eAAeD,GAAG,CAAC,CAACG;gBACxC,IAAIC,cAAmBD,KAAKE,KAAK;gBAEjC,IAAI,OAAOD,gBAAgB,UAAU;oBACnC,IAAIA,gBAAgB,kBAAkB;wBACpCA,cACEnB,KAAKE,KAAK,EAAEa,IAAI,CAACM,IAAY,OAAOA,MAAM,WAAWA,IAAIA,GAAGC,IAAKC,OAAOC,YAAY,EAAE;oBAC1F,OAAO,IAAIL,gBAAgB,iBAAiB;wBAC1C,6CAA6C;wBAC7C,MAAMM,aAAa,AAACzB,KAAaO,IAAI;wBACrCY,cAAc,OAAOM,eAAe,YAAYA,eAAe,OAAOA,WAAWH,EAAE,GAAGG;oBACxF,OAAO,IAAIN,gBAAgB,eAAe;wBACxCA,cAAcnB,KAAKsB,EAAE;oBACvB,OAAO,IAAIH,gBAAgB,QAAQ;wBACjCA,cAAc;oBAChB,OAAO,IAAIA,gBAAgB,SAAS;wBAClCA,cAAc;oBAChB,OAAO;wBACL,mDAAmD;wBACnDA,cAAcA,YAAYO,OAAO,CAAC,qBAAqBC,OAAO3B,KAAKsB,EAAE;oBACvE;gBACF;gBAEA,OAAO;oBACL,CAACJ,KAAKU,KAAK,CAAC,EAAE;wBACZ,CAACV,KAAKW,QAAQ,CAAC,EAAEV;oBACnB;gBACF;YACF;YAEA,OAAO;gBAAEW,KAAKb;YAAc;QAC9B;QAEA,IAAIH,aAAaF,MAAM,KAAK,GAAG;YAC7B,OAAOE,YAAY,CAAC,EAAE;QACxB;QAEA,OAAO;YAAEiB,IAAIjB;QAAa;IAC5B;IAEA,OAAO;AACT,EAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,244 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getPermissionQuery } from './getPermissionQuery';
3
+ describe('getPermissionQuery', ()=>{
4
+ it('should return false if user is null', ()=>{
5
+ expect(getPermissionQuery(null, 'read:posts')).toBe(false);
6
+ });
7
+ it('should return false if user has no roles array', ()=>{
8
+ const user = {
9
+ id: '1'
10
+ };
11
+ expect(getPermissionQuery(user, 'read:posts')).toBe(false);
12
+ });
13
+ it('should return false if user roles is unpopulated', ()=>{
14
+ const user = {
15
+ id: '1',
16
+ roles: [
17
+ 'role-id-1'
18
+ ]
19
+ };
20
+ expect(getPermissionQuery(user, 'read:posts')).toBe(false);
21
+ });
22
+ it('should return false if role has no permissions', ()=>{
23
+ const user = {
24
+ id: '1',
25
+ roles: [
26
+ {
27
+ id: 'role-1',
28
+ name: 'admin'
29
+ }
30
+ ]
31
+ };
32
+ expect(getPermissionQuery(user, 'read:posts')).toBe(false);
33
+ });
34
+ it('should return true if permission is found without conditions', ()=>{
35
+ const user = {
36
+ id: '1',
37
+ roles: [
38
+ {
39
+ id: 'role-1',
40
+ name: 'admin',
41
+ permissions: [
42
+ {
43
+ id: 'perm-1',
44
+ name: 'read:posts'
45
+ },
46
+ {
47
+ id: 'perm-2',
48
+ name: 'write:posts'
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ };
54
+ expect(getPermissionQuery(user, 'read:posts')).toBe(true);
55
+ });
56
+ it('should return a Where object if permission has conditions', ()=>{
57
+ const user = {
58
+ id: 'user-1',
59
+ roles: [
60
+ {
61
+ id: 'role-1',
62
+ name: 'editor',
63
+ permissions: [
64
+ {
65
+ id: 'perm-1',
66
+ name: 'read:posts',
67
+ conditions: [
68
+ {
69
+ field: 'sender',
70
+ operator: 'equals',
71
+ value: '{{user.id}}'
72
+ }
73
+ ]
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ };
79
+ const query = getPermissionQuery(user, 'read:posts');
80
+ expect(query).toEqual({
81
+ and: [
82
+ {
83
+ sender: {
84
+ equals: 'user-1'
85
+ }
86
+ }
87
+ ]
88
+ });
89
+ });
90
+ it('should return true if one role has conditions but another role gives full access', ()=>{
91
+ const user = {
92
+ id: 'user-1',
93
+ roles: [
94
+ {
95
+ id: 'role-1',
96
+ name: 'editor',
97
+ permissions: [
98
+ {
99
+ id: 'perm-1',
100
+ name: 'read:posts',
101
+ conditions: [
102
+ {
103
+ field: 'sender',
104
+ operator: 'equals',
105
+ value: '{{user.id}}'
106
+ }
107
+ ]
108
+ }
109
+ ]
110
+ },
111
+ {
112
+ id: 'role-2',
113
+ name: 'admin',
114
+ permissions: [
115
+ {
116
+ id: 'perm-2',
117
+ name: 'read:posts'
118
+ }
119
+ ]
120
+ }
121
+ ]
122
+ };
123
+ // The user has full 'read:posts' from admin role, so they get full access (true)
124
+ const query = getPermissionQuery(user, 'read:posts');
125
+ expect(query).toBe(true);
126
+ });
127
+ it('should return an OR Where object if multiple roles have different conditions', ()=>{
128
+ const user = {
129
+ id: 'user-1',
130
+ roles: [
131
+ {
132
+ id: 'role-1',
133
+ name: 'editor',
134
+ permissions: [
135
+ {
136
+ id: 'perm-1',
137
+ name: 'read:posts',
138
+ conditions: [
139
+ {
140
+ field: 'author',
141
+ operator: 'equals',
142
+ value: '{{user.id}}'
143
+ }
144
+ ]
145
+ }
146
+ ]
147
+ },
148
+ {
149
+ id: 'role-2',
150
+ name: 'manager',
151
+ permissions: [
152
+ {
153
+ id: 'perm-2',
154
+ name: 'read:posts',
155
+ conditions: [
156
+ {
157
+ field: 'status',
158
+ operator: 'equals',
159
+ value: 'published'
160
+ }
161
+ ]
162
+ }
163
+ ]
164
+ }
165
+ ]
166
+ };
167
+ const query = getPermissionQuery(user, 'read:posts');
168
+ expect(query).toEqual({
169
+ or: [
170
+ {
171
+ and: [
172
+ {
173
+ author: {
174
+ equals: 'user-1'
175
+ }
176
+ }
177
+ ]
178
+ },
179
+ {
180
+ and: [
181
+ {
182
+ status: {
183
+ equals: 'published'
184
+ }
185
+ }
186
+ ]
187
+ }
188
+ ]
189
+ });
190
+ });
191
+ it('should correctly map {{user.roles}} and boolean string values', ()=>{
192
+ const user = {
193
+ id: 'user-1',
194
+ roles: [
195
+ {
196
+ id: 'role-1',
197
+ name: 'editor',
198
+ permissions: [
199
+ {
200
+ id: 'perm-1',
201
+ name: 'read:posts',
202
+ conditions: [
203
+ {
204
+ field: 'role',
205
+ operator: 'in',
206
+ value: '{{user.roles}}'
207
+ },
208
+ {
209
+ field: 'isPublic',
210
+ operator: 'equals',
211
+ value: 'true'
212
+ }
213
+ ]
214
+ }
215
+ ]
216
+ },
217
+ {
218
+ id: 'role-2',
219
+ name: 'manager'
220
+ }
221
+ ]
222
+ };
223
+ const query = getPermissionQuery(user, 'read:posts');
224
+ expect(query).toEqual({
225
+ and: [
226
+ {
227
+ role: {
228
+ in: [
229
+ 'role-1',
230
+ 'role-2'
231
+ ]
232
+ }
233
+ },
234
+ {
235
+ isPublic: {
236
+ equals: true
237
+ }
238
+ }
239
+ ]
240
+ });
241
+ });
242
+ });
243
+
244
+ //# sourceMappingURL=getPermissionQuery.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getPermissionQuery.spec.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport { describe, expect, it } from 'vitest'\nimport { getPermissionQuery } from './getPermissionQuery'\n\ndescribe('getPermissionQuery', () => {\n it('should return false if user is null', () => {\n expect(getPermissionQuery(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(getPermissionQuery(user, 'read:posts')).toBe(false)\n })\n\n it('should return false if user roles is unpopulated', () => {\n const user = {\n id: '1',\n roles: ['role-id-1'],\n } as unknown as PayloadRequest['user']\n expect(getPermissionQuery(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(getPermissionQuery(user, 'read:posts')).toBe(false)\n })\n\n it('should return true if permission is found without conditions', () => {\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(getPermissionQuery(user, 'read:posts')).toBe(true)\n })\n\n it('should return a Where object if permission has conditions', () => {\n const user = {\n id: 'user-1',\n roles: [\n {\n id: 'role-1',\n name: 'editor',\n permissions: [\n {\n id: 'perm-1',\n name: 'read:posts',\n conditions: [\n { field: 'sender', operator: 'equals', value: '{{user.id}}' },\n ],\n },\n ],\n },\n ],\n } as unknown as PayloadRequest['user']\n\n const query = getPermissionQuery(user, 'read:posts')\n expect(query).toEqual({\n and: [\n {\n sender: { equals: 'user-1' },\n },\n ],\n })\n })\n\n it('should return true if one role has conditions but another role gives full access', () => {\n const user = {\n id: 'user-1',\n roles: [\n {\n id: 'role-1',\n name: 'editor',\n permissions: [\n {\n id: 'perm-1',\n name: 'read:posts',\n conditions: [\n { field: 'sender', operator: 'equals', value: '{{user.id}}' },\n ],\n },\n ],\n },\n {\n id: 'role-2',\n name: 'admin',\n permissions: [{ id: 'perm-2', name: 'read:posts' }],\n },\n ],\n } as unknown as PayloadRequest['user']\n\n // The user has full 'read:posts' from admin role, so they get full access (true)\n const query = getPermissionQuery(user, 'read:posts')\n expect(query).toBe(true)\n })\n\n it('should return an OR Where object if multiple roles have different conditions', () => {\n const user = {\n id: 'user-1',\n roles: [\n {\n id: 'role-1',\n name: 'editor',\n permissions: [\n {\n id: 'perm-1',\n name: 'read:posts',\n conditions: [\n { field: 'author', operator: 'equals', value: '{{user.id}}' },\n ],\n },\n ],\n },\n {\n id: 'role-2',\n name: 'manager',\n permissions: [\n {\n id: 'perm-2',\n name: 'read:posts',\n conditions: [\n { field: 'status', operator: 'equals', value: 'published' },\n ],\n },\n ],\n },\n ],\n } as unknown as PayloadRequest['user']\n\n const query = getPermissionQuery(user, 'read:posts')\n expect(query).toEqual({\n or: [\n {\n and: [\n { author: { equals: 'user-1' } },\n ],\n },\n {\n and: [\n { status: { equals: 'published' } },\n ],\n },\n ],\n })\n })\n\n it('should correctly map {{user.roles}} and boolean string values', () => {\n const user = {\n id: 'user-1',\n roles: [\n { \n id: 'role-1', \n name: 'editor',\n permissions: [\n {\n id: 'perm-1',\n name: 'read:posts',\n conditions: [\n { field: 'role', operator: 'in', value: '{{user.roles}}' },\n { field: 'isPublic', operator: 'equals', value: 'true' },\n ],\n },\n ]\n },\n { id: 'role-2', name: 'manager' }\n ],\n } as unknown as PayloadRequest['user']\n\n const query = getPermissionQuery(user, 'read:posts')\n expect(query).toEqual({\n and: [\n { role: { in: ['role-1', 'role-2'] } },\n { isPublic: { equals: true } },\n ],\n })\n })\n})\n"],"names":["describe","expect","it","getPermissionQuery","toBe","user","id","roles","name","permissions","conditions","field","operator","value","query","toEqual","and","sender","equals","or","author","status","role","in","isPublic"],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  export { checkPermission } from './checkPermission';
2
+ export { getPermissionQuery } from './getPermissionQuery';
2
3
  export { hasPermission } from './hasPermission';
@@ -1,4 +1,5 @@
1
1
  export { checkPermission } from './checkPermission';
2
+ export { getPermissionQuery } from './getPermissionQuery';
2
3
  export { hasPermission } from './hasPermission';
3
4
 
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/index.ts"],"sourcesContent":["export { checkPermission } from './checkPermission';\nexport { hasPermission } from './hasPermission';\n\n"],"names":["checkPermission","hasPermission"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AACpD,SAASC,aAAa,QAAQ,kBAAkB"}
1
+ {"version":3,"sources":["../../src/utilities/index.ts"],"sourcesContent":["export { checkPermission } from './checkPermission';\nexport { getPermissionQuery } from './getPermissionQuery';\nexport { hasPermission } from './hasPermission';\n\n"],"names":["checkPermission","getPermissionQuery","hasPermission"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AACpD,SAASC,kBAAkB,QAAQ,uBAAuB;AAC1D,SAASC,aAAa,QAAQ,kBAAkB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-rbac-plugin",
3
- "version": "1.0.3rc1",
3
+ "version": "1.0.5rc",
4
4
  "description": "RBAC plugin for payloadcms",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,13 +52,13 @@
52
52
  ],
53
53
  "devDependencies": {
54
54
  "@eslint/eslintrc": "^3.2.0",
55
- "@payloadcms/db-mongodb": "3.84.1",
56
- "@payloadcms/db-postgres": "3.84.1",
57
- "@payloadcms/db-sqlite": "3.84.1",
55
+ "@payloadcms/db-mongodb": "^3.85.0",
56
+ "@payloadcms/db-postgres": "^3.85.0",
57
+ "@payloadcms/db-sqlite": "^3.85.0",
58
58
  "@payloadcms/eslint-config": "3.28.0",
59
- "@payloadcms/next": "3.84.1",
60
- "@payloadcms/richtext-lexical": "3.84.1",
61
- "@payloadcms/ui": "3.84.1",
59
+ "@payloadcms/next": "^3.85.0",
60
+ "@payloadcms/richtext-lexical": "^3.85.0",
61
+ "@payloadcms/ui": "^3.85.0",
62
62
  "@playwright/test": "1.58.2",
63
63
  "@swc-node/register": "1.10.9",
64
64
  "@swc/cli": "0.6.0",
@@ -73,7 +73,7 @@
73
73
  "mongodb-memory-server": "10.1.4",
74
74
  "next": "16.2.6",
75
75
  "open": "^10.1.0",
76
- "payload": "3.84.1",
76
+ "payload": "^3.85.0",
77
77
  "prettier": "^3.4.2",
78
78
  "qs-esm": "8.0.1",
79
79
  "react": "19.2.6",
@@ -86,7 +86,7 @@
86
86
  "vitest": "^4.1.8"
87
87
  },
88
88
  "peerDependencies": {
89
- "payload": "^3.84.1"
89
+ "payload": "^3.85.0"
90
90
  },
91
91
  "engines": {
92
92
  "node": "^18.20.2 || >=20.9.0",