@payloadcms/plugin-multi-tenant 3.89.0-internal.95c9cba → 3.90.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/dist/fields/tenantField/index.d.ts +5 -3
- package/dist/fields/tenantField/index.d.ts.map +1 -1
- package/dist/fields/tenantField/index.js +40 -11
- package/dist/fields/tenantField/index.js.map +1 -1
- package/dist/fields/tenantField/index.spec.js +54 -0
- package/dist/fields/tenantField/index.spec.js.map +1 -0
- package/dist/hooks/enforceTenantMembership.d.ts +20 -0
- package/dist/hooks/enforceTenantMembership.d.ts.map +1 -0
- package/dist/hooks/enforceTenantMembership.js +46 -0
- package/dist/hooks/enforceTenantMembership.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/dist/utilities/getTenantIDsFromValue.d.ts +2 -0
- package/dist/utilities/getTenantIDsFromValue.d.ts.map +1 -0
- package/dist/utilities/getTenantIDsFromValue.js +14 -0
- package/dist/utilities/getTenantIDsFromValue.js.map +1 -0
- package/dist/utilities/getUserTenantIDs.d.ts.map +1 -1
- package/dist/utilities/getUserTenantIDs.js +3 -2
- package/dist/utilities/getUserTenantIDs.js.map +1 -1
- package/dist/utilities/isValidTenantAssignment.d.ts +23 -0
- package/dist/utilities/isValidTenantAssignment.d.ts.map +1 -0
- package/dist/utilities/isValidTenantAssignment.js +38 -0
- package/dist/utilities/isValidTenantAssignment.js.map +1 -0
- package/dist/utilities/isValidTenantAssignment.spec.js +117 -0
- package/dist/utilities/isValidTenantAssignment.spec.js.map +1 -0
- package/package.json +6 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SingleRelationshipField } from 'payload';
|
|
2
|
-
import type { RootTenantFieldConfigOverrides } from '../../types.js';
|
|
3
|
-
type Args = {
|
|
2
|
+
import type { MultiTenantPluginConfig, RootTenantFieldConfigOverrides } from '../../types.js';
|
|
3
|
+
type Args<ConfigType = unknown> = {
|
|
4
|
+
adminUsersSlug?: string;
|
|
4
5
|
debug?: boolean;
|
|
5
6
|
isAutosaveEnabled?: boolean;
|
|
6
7
|
name: string;
|
|
@@ -9,7 +10,8 @@ type Args = {
|
|
|
9
10
|
tenantsArrayTenantFieldName: string;
|
|
10
11
|
tenantsCollectionSlug: string;
|
|
11
12
|
unique: boolean;
|
|
13
|
+
userHasAccessToAllTenants?: MultiTenantPluginConfig<ConfigType>['userHasAccessToAllTenants'];
|
|
12
14
|
};
|
|
13
|
-
export declare const tenantField: ({ name, debug, isAutosaveEnabled, overrides: _overrides, tenantsArrayFieldName, tenantsArrayTenantFieldName, tenantsCollectionSlug, unique, }: Args) => SingleRelationshipField;
|
|
15
|
+
export declare const tenantField: <ConfigType = unknown>({ name, adminUsersSlug, debug, isAutosaveEnabled, overrides: _overrides, tenantsArrayFieldName, tenantsArrayTenantFieldName, tenantsCollectionSlug, unique, userHasAccessToAllTenants, }: Args<ConfigType>) => SingleRelationshipField;
|
|
14
16
|
export {};
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/tenantField/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAEnF,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fields/tenantField/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAEnF,OAAO,KAAK,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAA;AAwD7F,KAAK,IAAI,CAAC,UAAU,GAAG,OAAO,IAAI;IAChC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,8BAA8B,CAAA;IAC1C,qBAAqB,EAAE,MAAM,CAAA;IAC7B,2BAA2B,EAAE,MAAM,CAAA;IACnC,qBAAqB,EAAE,MAAM,CAAA;IAC7B,MAAM,EAAE,OAAO,CAAA;IACf,yBAAyB,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC,2BAA2B,CAAC,CAAA;CAC7F,CAAA;AACD,eAAO,MAAM,WAAW,GAAI,UAAU,sMAWnC,IAAI,CAAC,UAAU,CAAC,KAAG,uBAsHrB,CAAA"}
|
|
@@ -2,9 +2,10 @@ import { defaults } from '../../defaults.js';
|
|
|
2
2
|
import { getCollectionIDType } from '../../utilities/getCollectionIDType.js';
|
|
3
3
|
import { getTenantFromCookie } from '../../utilities/getTenantFromCookie.js';
|
|
4
4
|
import { getUserTenantIDs } from '../../utilities/getUserTenantIDs.js';
|
|
5
|
-
|
|
5
|
+
import { isValidTenantAssignment } from '../../utilities/isValidTenantAssignment.js';
|
|
6
|
+
const fieldValidation = ({ tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, validateFunction })=>async (value, options)=>{
|
|
6
7
|
if (validateFunction) {
|
|
7
|
-
const result = validateFunction(value, options);
|
|
8
|
+
const result = await validateFunction(value, options);
|
|
8
9
|
if (result !== true) {
|
|
9
10
|
return result;
|
|
10
11
|
}
|
|
@@ -18,15 +19,35 @@ const fieldValidation = (validateFunction)=>(value, options)=>{
|
|
|
18
19
|
return options.req.t('validation:required');
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
const { req } = options;
|
|
23
|
+
if (isValidTenantAssignment({
|
|
24
|
+
previousValue: options.previousValue,
|
|
25
|
+
req,
|
|
26
|
+
tenantsArrayFieldName,
|
|
27
|
+
tenantsArrayTenantFieldName,
|
|
28
|
+
userHasAccessToAllTenants,
|
|
29
|
+
value
|
|
30
|
+
})) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return req.t('validation:invalidSelection');
|
|
22
34
|
};
|
|
23
|
-
export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosaveEnabled, overrides: _overrides = {}, tenantsArrayFieldName = defaults.tenantsArrayFieldName, tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName, tenantsCollectionSlug = defaults.tenantCollectionSlug, unique })=>{
|
|
35
|
+
export const tenantField = ({ name = defaults.tenantFieldName, adminUsersSlug, debug, isAutosaveEnabled, overrides: _overrides = {}, tenantsArrayFieldName = defaults.tenantsArrayFieldName, tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName, tenantsCollectionSlug = defaults.tenantCollectionSlug, unique, userHasAccessToAllTenants })=>{
|
|
24
36
|
const { hasMany = false, validate, ...overrides } = _overrides || {};
|
|
25
37
|
return {
|
|
26
38
|
...overrides || {},
|
|
27
39
|
name,
|
|
28
40
|
type: 'relationship',
|
|
29
|
-
|
|
41
|
+
// Only users of the tenant-managed collection may write the tenant field. Other auth
|
|
42
|
+
// collections carry no tenants, so they must not set or change it. Skipped when
|
|
43
|
+
// `adminUsersSlug` is absent (a config placing this field through `customTenantField`).
|
|
44
|
+
access: {
|
|
45
|
+
...adminUsersSlug ? {
|
|
46
|
+
create: ({ req })=>Boolean(req.user && req.user.collection === adminUsersSlug),
|
|
47
|
+
update: ({ req })=>Boolean(req.user && req.user.collection === adminUsersSlug)
|
|
48
|
+
} : {},
|
|
49
|
+
...overrides.access || {}
|
|
50
|
+
},
|
|
30
51
|
admin: {
|
|
31
52
|
allowCreate: false,
|
|
32
53
|
allowEdit: false,
|
|
@@ -55,7 +76,7 @@ export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosave
|
|
|
55
76
|
});
|
|
56
77
|
const tenantFromCookie = getTenantFromCookie(req.headers, idType);
|
|
57
78
|
if (tenantFromCookie) {
|
|
58
|
-
const
|
|
79
|
+
const { totalDocs } = await req.payload.count({
|
|
59
80
|
collection: tenantsCollectionSlug,
|
|
60
81
|
overrideAccess: false,
|
|
61
82
|
req,
|
|
@@ -68,7 +89,7 @@ export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosave
|
|
|
68
89
|
}
|
|
69
90
|
}
|
|
70
91
|
});
|
|
71
|
-
return
|
|
92
|
+
return totalDocs > 0 ? tenantFromCookie : null;
|
|
72
93
|
}
|
|
73
94
|
if (req.user && isAutosaveEnabled) {
|
|
74
95
|
const userTenants = getUserTenantIDs(req.user, {
|
|
@@ -100,12 +121,20 @@ export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosave
|
|
|
100
121
|
unique,
|
|
101
122
|
...hasMany ? {
|
|
102
123
|
hasMany: true,
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
validate: fieldValidation({
|
|
125
|
+
tenantsArrayFieldName,
|
|
126
|
+
tenantsArrayTenantFieldName,
|
|
127
|
+
userHasAccessToAllTenants,
|
|
128
|
+
validateFunction: validate
|
|
129
|
+
})
|
|
105
130
|
} : {
|
|
106
131
|
hasMany: false,
|
|
107
|
-
|
|
108
|
-
|
|
132
|
+
validate: fieldValidation({
|
|
133
|
+
tenantsArrayFieldName,
|
|
134
|
+
tenantsArrayTenantFieldName,
|
|
135
|
+
userHasAccessToAllTenants,
|
|
136
|
+
validateFunction: validate
|
|
137
|
+
})
|
|
109
138
|
},
|
|
110
139
|
// @ts-expect-error translations are not typed for this plugin
|
|
111
140
|
label: overrides.label || (({ t })=>t('plugin-multi-tenant:field-assignedTenant-label'))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/fields/tenantField/index.ts"],"sourcesContent":["import type { RelationshipFieldValidation, SingleRelationshipField } from 'payload'\n\nimport type { RootTenantFieldConfigOverrides } from '../../types.js'\n\nimport { defaults } from '../../defaults.js'\nimport { getCollectionIDType } from '../../utilities/getCollectionIDType.js'\nimport { getTenantFromCookie } from '../../utilities/getTenantFromCookie.js'\nimport { getUserTenantIDs } from '../../utilities/getUserTenantIDs.js'\n\nconst fieldValidation =\n (validateFunction?: RelationshipFieldValidation): RelationshipFieldValidation =>\n (value, options) => {\n if (validateFunction) {\n const result = validateFunction(value, options)\n if (result !== true) {\n return result\n }\n }\n\n if (options.hasMany) {\n if (!value || (Array.isArray(value) && value.length === 0)) {\n return options.req.t('validation:required')\n }\n } else {\n if (!value) {\n return options.req.t('validation:required')\n }\n }\n\n return true\n }\n\ntype Args = {\n debug?: boolean\n isAutosaveEnabled?: boolean\n name: string\n overrides?: RootTenantFieldConfigOverrides\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n tenantsCollectionSlug: string\n unique: boolean\n}\nexport const tenantField = ({\n name = defaults.tenantFieldName,\n debug,\n isAutosaveEnabled,\n overrides: _overrides = {},\n tenantsArrayFieldName = defaults.tenantsArrayFieldName,\n tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName,\n tenantsCollectionSlug = defaults.tenantCollectionSlug,\n unique,\n}: Args): SingleRelationshipField => {\n const { hasMany = false, validate, ...overrides } = _overrides || {}\n return {\n ...(overrides || {}),\n name,\n type: 'relationship',\n access: overrides.access || {},\n admin: {\n allowCreate: false,\n allowEdit: false,\n disableGroupBy: true,\n disableListColumn: true,\n disableListFilter: true,\n position: 'sidebar',\n ...(overrides.admin || {}),\n components: {\n ...(overrides.admin?.components || {}),\n Field: {\n path: '@payloadcms/plugin-multi-tenant/client#TenantField',\n ...(typeof overrides.admin?.components?.Field !== 'string'\n ? overrides.admin?.components?.Field || {}\n : {}),\n clientProps: {\n ...(typeof overrides.admin?.components?.Field !== 'string'\n ? (overrides.admin?.components?.Field || {})?.clientProps\n : {}),\n debug,\n unique,\n },\n },\n },\n },\n defaultValue:\n overrides.defaultValue ||\n (async ({ req }) => {\n const idType = getCollectionIDType({\n collectionSlug: tenantsCollectionSlug,\n payload: req.payload,\n })\n const tenantFromCookie = getTenantFromCookie(req.headers, idType)\n if (tenantFromCookie) {\n const isValidTenant = await req.payload.count({\n collection: tenantsCollectionSlug,\n overrideAccess: false,\n req,\n user: req.user,\n where: {\n id: {\n in: [tenantFromCookie],\n },\n },\n })\n return isValidTenant ? tenantFromCookie : null\n }\n if (req.user && isAutosaveEnabled) {\n const userTenants = getUserTenantIDs(req.user, {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n })\n if (userTenants.length > 0) {\n return userTenants[0]\n }\n }\n return null\n }),\n filterOptions:\n overrides.filterOptions ||\n (({ req }) => {\n const userAssignedTenants = getUserTenantIDs(req.user, {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n })\n if (userAssignedTenants.length > 0) {\n return {\n id: {\n in: userAssignedTenants,\n },\n }\n }\n\n return true\n }),\n index: true,\n relationTo: tenantsCollectionSlug,\n unique,\n ...(hasMany\n ? {\n hasMany: true,\n // TODO: V4 - replace validation with required: true\n validate: fieldValidation(validate as RelationshipFieldValidation),\n }\n : {\n hasMany: false,\n // TODO: V4 - replace validation with required: true\n validate: fieldValidation(validate as RelationshipFieldValidation),\n }),\n // @ts-expect-error translations are not typed for this plugin\n label: overrides.label || (({ t }) => t('plugin-multi-tenant:field-assignedTenant-label')),\n }\n}\n"],"names":["defaults","getCollectionIDType","getTenantFromCookie","getUserTenantIDs","fieldValidation","validateFunction","value","options","result","hasMany","Array","isArray","length","req","t","tenantField","name","tenantFieldName","debug","isAutosaveEnabled","overrides","_overrides","tenantsArrayFieldName","tenantsArrayTenantFieldName","tenantsCollectionSlug","tenantCollectionSlug","unique","validate","type","access","admin","allowCreate","allowEdit","disableGroupBy","disableListColumn","disableListFilter","position","components","Field","path","clientProps","defaultValue","idType","collectionSlug","payload","tenantFromCookie","headers","isValidTenant","count","collection","overrideAccess","user","where","id","in","userTenants","filterOptions","userAssignedTenants","index","relationTo","label"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/fields/tenantField/index.ts"],"sourcesContent":["import type { RelationshipFieldValidation, SingleRelationshipField } from 'payload'\n\nimport type { MultiTenantPluginConfig, RootTenantFieldConfigOverrides } from '../../types.js'\n\nimport { defaults } from '../../defaults.js'\nimport { getCollectionIDType } from '../../utilities/getCollectionIDType.js'\nimport { getTenantFromCookie } from '../../utilities/getTenantFromCookie.js'\nimport { getUserTenantIDs } from '../../utilities/getUserTenantIDs.js'\nimport { isValidTenantAssignment } from '../../utilities/isValidTenantAssignment.js'\n\nconst fieldValidation =\n <ConfigType = unknown>({\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n validateFunction,\n }: {\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n userHasAccessToAllTenants?: MultiTenantPluginConfig<ConfigType>['userHasAccessToAllTenants']\n validateFunction?: RelationshipFieldValidation\n }): RelationshipFieldValidation =>\n async (value, options) => {\n if (validateFunction) {\n const result = await validateFunction(value, options)\n if (result !== true) {\n return result\n }\n }\n\n if (options.hasMany) {\n if (!value || (Array.isArray(value) && value.length === 0)) {\n return options.req.t('validation:required')\n }\n } else {\n if (!value) {\n return options.req.t('validation:required')\n }\n }\n\n const { req } = options\n\n if (\n isValidTenantAssignment<ConfigType>({\n previousValue: options.previousValue,\n req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n value,\n })\n ) {\n return true\n }\n\n return req.t('validation:invalidSelection')\n }\n\ntype Args<ConfigType = unknown> = {\n adminUsersSlug?: string\n debug?: boolean\n isAutosaveEnabled?: boolean\n name: string\n overrides?: RootTenantFieldConfigOverrides\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n tenantsCollectionSlug: string\n unique: boolean\n userHasAccessToAllTenants?: MultiTenantPluginConfig<ConfigType>['userHasAccessToAllTenants']\n}\nexport const tenantField = <ConfigType = unknown>({\n name = defaults.tenantFieldName,\n adminUsersSlug,\n debug,\n isAutosaveEnabled,\n overrides: _overrides = {},\n tenantsArrayFieldName = defaults.tenantsArrayFieldName,\n tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName,\n tenantsCollectionSlug = defaults.tenantCollectionSlug,\n unique,\n userHasAccessToAllTenants,\n}: Args<ConfigType>): SingleRelationshipField => {\n const { hasMany = false, validate, ...overrides } = _overrides || {}\n return {\n ...(overrides || {}),\n name,\n type: 'relationship',\n // Only users of the tenant-managed collection may write the tenant field. Other auth\n // collections carry no tenants, so they must not set or change it. Skipped when\n // `adminUsersSlug` is absent (a config placing this field through `customTenantField`).\n access: {\n ...(adminUsersSlug\n ? {\n create: ({ req }) => Boolean(req.user && req.user.collection === adminUsersSlug),\n update: ({ req }) => Boolean(req.user && req.user.collection === adminUsersSlug),\n }\n : {}),\n ...(overrides.access || {}),\n },\n admin: {\n allowCreate: false,\n allowEdit: false,\n disableGroupBy: true,\n disableListColumn: true,\n disableListFilter: true,\n position: 'sidebar',\n ...(overrides.admin || {}),\n components: {\n ...(overrides.admin?.components || {}),\n Field: {\n path: '@payloadcms/plugin-multi-tenant/client#TenantField',\n ...(typeof overrides.admin?.components?.Field !== 'string'\n ? overrides.admin?.components?.Field || {}\n : {}),\n clientProps: {\n ...(typeof overrides.admin?.components?.Field !== 'string'\n ? (overrides.admin?.components?.Field || {})?.clientProps\n : {}),\n debug,\n unique,\n },\n },\n },\n },\n defaultValue:\n overrides.defaultValue ||\n (async ({ req }) => {\n const idType = getCollectionIDType({\n collectionSlug: tenantsCollectionSlug,\n payload: req.payload,\n })\n const tenantFromCookie = getTenantFromCookie(req.headers, idType)\n if (tenantFromCookie) {\n const { totalDocs } = await req.payload.count({\n collection: tenantsCollectionSlug,\n overrideAccess: false,\n req,\n user: req.user,\n where: {\n id: {\n in: [tenantFromCookie],\n },\n },\n })\n return totalDocs > 0 ? tenantFromCookie : null\n }\n if (req.user && isAutosaveEnabled) {\n const userTenants = getUserTenantIDs(req.user, {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n })\n if (userTenants.length > 0) {\n return userTenants[0]\n }\n }\n return null\n }),\n filterOptions:\n overrides.filterOptions ||\n (({ req }) => {\n const userAssignedTenants = getUserTenantIDs(req.user, {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n })\n if (userAssignedTenants.length > 0) {\n return {\n id: {\n in: userAssignedTenants,\n },\n }\n }\n\n return true\n }),\n index: true,\n relationTo: tenantsCollectionSlug,\n unique,\n ...(hasMany\n ? {\n hasMany: true,\n validate: fieldValidation<ConfigType>({\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n validateFunction: validate as RelationshipFieldValidation,\n }),\n }\n : {\n hasMany: false,\n validate: fieldValidation<ConfigType>({\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n validateFunction: validate as RelationshipFieldValidation,\n }),\n }),\n // @ts-expect-error translations are not typed for this plugin\n label: overrides.label || (({ t }) => t('plugin-multi-tenant:field-assignedTenant-label')),\n }\n}\n"],"names":["defaults","getCollectionIDType","getTenantFromCookie","getUserTenantIDs","isValidTenantAssignment","fieldValidation","tenantsArrayFieldName","tenantsArrayTenantFieldName","userHasAccessToAllTenants","validateFunction","value","options","result","hasMany","Array","isArray","length","req","t","previousValue","tenantField","name","tenantFieldName","adminUsersSlug","debug","isAutosaveEnabled","overrides","_overrides","tenantsCollectionSlug","tenantCollectionSlug","unique","validate","type","access","create","Boolean","user","collection","update","admin","allowCreate","allowEdit","disableGroupBy","disableListColumn","disableListFilter","position","components","Field","path","clientProps","defaultValue","idType","collectionSlug","payload","tenantFromCookie","headers","totalDocs","count","overrideAccess","where","id","in","userTenants","filterOptions","userAssignedTenants","index","relationTo","label"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { tenantField } from './index.js';
|
|
3
|
+
const baseArgs = {
|
|
4
|
+
adminUsersSlug: 'users',
|
|
5
|
+
name: 'tenant',
|
|
6
|
+
tenantsArrayFieldName: 'tenants',
|
|
7
|
+
tenantsArrayTenantFieldName: 'tenant',
|
|
8
|
+
tenantsCollectionSlug: 'tenants',
|
|
9
|
+
unique: false
|
|
10
|
+
};
|
|
11
|
+
describe('tenantField access', ()=>{
|
|
12
|
+
it('should let the admin users collection write the tenant field', ()=>{
|
|
13
|
+
const field = tenantField(baseArgs);
|
|
14
|
+
const req = {
|
|
15
|
+
user: {
|
|
16
|
+
collection: 'users'
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
expect(field.access.create({
|
|
20
|
+
req
|
|
21
|
+
})).toBe(true);
|
|
22
|
+
expect(field.access.update({
|
|
23
|
+
req
|
|
24
|
+
})).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it('should block other auth collections from writing the tenant field', ()=>{
|
|
27
|
+
const field = tenantField(baseArgs);
|
|
28
|
+
const req = {
|
|
29
|
+
user: {
|
|
30
|
+
collection: 'customers'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
expect(field.access.create({
|
|
34
|
+
req
|
|
35
|
+
})).toBe(false);
|
|
36
|
+
expect(field.access.update({
|
|
37
|
+
req
|
|
38
|
+
})).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('should block an unauthenticated request from writing the tenant field', ()=>{
|
|
41
|
+
const field = tenantField(baseArgs);
|
|
42
|
+
const req = {
|
|
43
|
+
user: null
|
|
44
|
+
};
|
|
45
|
+
expect(field.access.create({
|
|
46
|
+
req
|
|
47
|
+
})).toBe(false);
|
|
48
|
+
expect(field.access.update({
|
|
49
|
+
req
|
|
50
|
+
})).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=index.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/fields/tenantField/index.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { tenantField } from './index.js'\n\nconst baseArgs = {\n adminUsersSlug: 'users',\n name: 'tenant',\n tenantsArrayFieldName: 'tenants',\n tenantsArrayTenantFieldName: 'tenant',\n tenantsCollectionSlug: 'tenants',\n unique: false,\n}\n\ndescribe('tenantField access', () => {\n it('should let the admin users collection write the tenant field', () => {\n const field = tenantField(baseArgs)\n const req = { user: { collection: 'users' } }\n\n expect(field.access!.create!({ req } as never)).toBe(true)\n expect(field.access!.update!({ req } as never)).toBe(true)\n })\n\n it('should block other auth collections from writing the tenant field', () => {\n const field = tenantField(baseArgs)\n const req = { user: { collection: 'customers' } }\n\n expect(field.access!.create!({ req } as never)).toBe(false)\n expect(field.access!.update!({ req } as never)).toBe(false)\n })\n\n it('should block an unauthenticated request from writing the tenant field', () => {\n const field = tenantField(baseArgs)\n const req = { user: null }\n\n expect(field.access!.create!({ req } as never)).toBe(false)\n expect(field.access!.update!({ req } as never)).toBe(false)\n })\n})\n"],"names":["describe","expect","it","tenantField","baseArgs","adminUsersSlug","name","tenantsArrayFieldName","tenantsArrayTenantFieldName","tenantsCollectionSlug","unique","field","req","user","collection","access","create","toBe","update"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,WAAW,QAAQ,aAAY;AAExC,MAAMC,WAAW;IACfC,gBAAgB;IAChBC,MAAM;IACNC,uBAAuB;IACvBC,6BAA6B;IAC7BC,uBAAuB;IACvBC,QAAQ;AACV;AAEAV,SAAS,sBAAsB;IAC7BE,GAAG,gEAAgE;QACjE,MAAMS,QAAQR,YAAYC;QAC1B,MAAMQ,MAAM;YAAEC,MAAM;gBAAEC,YAAY;YAAQ;QAAE;QAE5Cb,OAAOU,MAAMI,MAAM,CAAEC,MAAM,CAAE;YAAEJ;QAAI,IAAaK,IAAI,CAAC;QACrDhB,OAAOU,MAAMI,MAAM,CAAEG,MAAM,CAAE;YAAEN;QAAI,IAAaK,IAAI,CAAC;IACvD;IAEAf,GAAG,qEAAqE;QACtE,MAAMS,QAAQR,YAAYC;QAC1B,MAAMQ,MAAM;YAAEC,MAAM;gBAAEC,YAAY;YAAY;QAAE;QAEhDb,OAAOU,MAAMI,MAAM,CAAEC,MAAM,CAAE;YAAEJ;QAAI,IAAaK,IAAI,CAAC;QACrDhB,OAAOU,MAAMI,MAAM,CAAEG,MAAM,CAAE;YAAEN;QAAI,IAAaK,IAAI,CAAC;IACvD;IAEAf,GAAG,yEAAyE;QAC1E,MAAMS,QAAQR,YAAYC;QAC1B,MAAMQ,MAAM;YAAEC,MAAM;QAAK;QAEzBZ,OAAOU,MAAMI,MAAM,CAAEC,MAAM,CAAE;YAAEJ;QAAI,IAAaK,IAAI,CAAC;QACrDhB,OAAOU,MAAMI,MAAM,CAAEG,MAAM,CAAE;YAAEN;QAAI,IAAaK,IAAI,CAAC;IACvD;AACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload';
|
|
2
|
+
import type { MultiTenantPluginConfig } from '../types.js';
|
|
3
|
+
type Args<ConfigType = unknown> = {
|
|
4
|
+
collection: CollectionConfig;
|
|
5
|
+
tenantFieldName: string;
|
|
6
|
+
tenantsArrayFieldName: string;
|
|
7
|
+
tenantsArrayTenantFieldName: string;
|
|
8
|
+
userHasAccessToAllTenants: Required<MultiTenantPluginConfig<ConfigType>>['userHasAccessToAllTenants'];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Reject writes that assign a tenant the user is not a member of.
|
|
12
|
+
*
|
|
13
|
+
* The tenant field's own `validate` covers this on ordinary writes and gives the
|
|
14
|
+
* admin panel an inline field error, but Payload skips field validation for draft,
|
|
15
|
+
* autosave, trash and restore-version writes. A collection `beforeValidate` hook
|
|
16
|
+
* always runs, so this is the check that actually closes those paths.
|
|
17
|
+
*/
|
|
18
|
+
export declare const addCollectionBeforeValidateHook: <ConfigType = unknown>({ collection, tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, }: Args<ConfigType>) => void;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=enforceTenantMembership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforceTenantMembership.d.ts","sourceRoot":"","sources":["../../src/hooks/enforceTenantMembership.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgC,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI7E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAI1D,KAAK,IAAI,CAAC,UAAU,GAAG,OAAO,IAAI;IAChC,UAAU,EAAE,gBAAgB,CAAA;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,2BAA2B,EAAE,MAAM,CAAA;IACnC,yBAAyB,EAAE,QAAQ,CACjC,uBAAuB,CAAC,UAAU,CAAC,CACpC,CAAC,2BAA2B,CAAC,CAAA;CAC/B,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAAI,UAAU,6HAMvD,IAAI,CAAC,UAAU,CAAC,SAYlB,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
import { isValidTenantAssignment } from '../utilities/isValidTenantAssignment.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reject writes that assign a tenant the user is not a member of.
|
|
5
|
+
*
|
|
6
|
+
* The tenant field's own `validate` covers this on ordinary writes and gives the
|
|
7
|
+
* admin panel an inline field error, but Payload skips field validation for draft,
|
|
8
|
+
* autosave, trash and restore-version writes. A collection `beforeValidate` hook
|
|
9
|
+
* always runs, so this is the check that actually closes those paths.
|
|
10
|
+
*/ export const addCollectionBeforeValidateHook = ({ collection, tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants })=>{
|
|
11
|
+
collection.hooks ??= {};
|
|
12
|
+
collection.hooks.beforeValidate ??= [];
|
|
13
|
+
collection.hooks.beforeValidate.push(enforceTenantMembership({
|
|
14
|
+
collectionSlug: collection.slug,
|
|
15
|
+
tenantFieldName,
|
|
16
|
+
tenantsArrayFieldName,
|
|
17
|
+
tenantsArrayTenantFieldName,
|
|
18
|
+
userHasAccessToAllTenants
|
|
19
|
+
}));
|
|
20
|
+
};
|
|
21
|
+
const enforceTenantMembership = ({ collectionSlug, tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants })=>({ data, originalDoc, req })=>{
|
|
22
|
+
if (!data || !(tenantFieldName in data)) {
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
if (!isValidTenantAssignment({
|
|
26
|
+
previousValue: originalDoc?.[tenantFieldName],
|
|
27
|
+
req,
|
|
28
|
+
tenantsArrayFieldName,
|
|
29
|
+
tenantsArrayTenantFieldName,
|
|
30
|
+
userHasAccessToAllTenants,
|
|
31
|
+
value: data[tenantFieldName]
|
|
32
|
+
})) {
|
|
33
|
+
throw new ValidationError({
|
|
34
|
+
collection: collectionSlug,
|
|
35
|
+
errors: [
|
|
36
|
+
{
|
|
37
|
+
message: req.t('validation:invalidSelection'),
|
|
38
|
+
path: tenantFieldName
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}, req.t);
|
|
42
|
+
}
|
|
43
|
+
return data;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=enforceTenantMembership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/enforceTenantMembership.ts"],"sourcesContent":["import type { CollectionBeforeValidateHook, CollectionConfig } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { MultiTenantPluginConfig } from '../types.js'\n\nimport { isValidTenantAssignment } from '../utilities/isValidTenantAssignment.js'\n\ntype Args<ConfigType = unknown> = {\n collection: CollectionConfig\n tenantFieldName: string\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n userHasAccessToAllTenants: Required<\n MultiTenantPluginConfig<ConfigType>\n >['userHasAccessToAllTenants']\n}\n\n/**\n * Reject writes that assign a tenant the user is not a member of.\n *\n * The tenant field's own `validate` covers this on ordinary writes and gives the\n * admin panel an inline field error, but Payload skips field validation for draft,\n * autosave, trash and restore-version writes. A collection `beforeValidate` hook\n * always runs, so this is the check that actually closes those paths.\n */\nexport const addCollectionBeforeValidateHook = <ConfigType = unknown>({\n collection,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n}: Args<ConfigType>) => {\n collection.hooks ??= {}\n collection.hooks.beforeValidate ??= []\n collection.hooks.beforeValidate.push(\n enforceTenantMembership<ConfigType>({\n collectionSlug: collection.slug,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n }),\n )\n}\n\nconst enforceTenantMembership =\n <ConfigType = unknown>({\n collectionSlug,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n }: { collectionSlug: string } & Omit<\n Args<ConfigType>,\n 'collection'\n >): CollectionBeforeValidateHook =>\n ({ data, originalDoc, req }) => {\n if (!data || !(tenantFieldName in data)) {\n return data\n }\n\n if (\n !isValidTenantAssignment<ConfigType>({\n previousValue: originalDoc?.[tenantFieldName],\n req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n value: (data as Record<string, unknown>)[tenantFieldName],\n })\n ) {\n throw new ValidationError(\n {\n collection: collectionSlug,\n errors: [{ message: req.t('validation:invalidSelection'), path: tenantFieldName }],\n },\n req.t,\n )\n }\n\n return data\n }\n"],"names":["ValidationError","isValidTenantAssignment","addCollectionBeforeValidateHook","collection","tenantFieldName","tenantsArrayFieldName","tenantsArrayTenantFieldName","userHasAccessToAllTenants","hooks","beforeValidate","push","enforceTenantMembership","collectionSlug","slug","data","originalDoc","req","previousValue","value","errors","message","t","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAIzC,SAASC,uBAAuB,QAAQ,0CAAyC;AAYjF;;;;;;;CAOC,GACD,OAAO,MAAMC,kCAAkC,CAAuB,EACpEC,UAAU,EACVC,eAAe,EACfC,qBAAqB,EACrBC,2BAA2B,EAC3BC,yBAAyB,EACR;IACjBJ,WAAWK,KAAK,KAAK,CAAC;IACtBL,WAAWK,KAAK,CAACC,cAAc,KAAK,EAAE;IACtCN,WAAWK,KAAK,CAACC,cAAc,CAACC,IAAI,CAClCC,wBAAoC;QAClCC,gBAAgBT,WAAWU,IAAI;QAC/BT;QACAC;QACAC;QACAC;IACF;AAEJ,EAAC;AAED,MAAMI,0BACJ,CAAuB,EACrBC,cAAc,EACdR,eAAe,EACfC,qBAAqB,EACrBC,2BAA2B,EAC3BC,yBAAyB,EAI1B,GACD,CAAC,EAAEO,IAAI,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACzB,IAAI,CAACF,QAAQ,CAAEV,CAAAA,mBAAmBU,IAAG,GAAI;YACvC,OAAOA;QACT;QAEA,IACE,CAACb,wBAAoC;YACnCgB,eAAeF,aAAa,CAACX,gBAAgB;YAC7CY;YACAX;YACAC;YACAC;YACAW,OAAO,AAACJ,IAAgC,CAACV,gBAAgB;QAC3D,IACA;YACA,MAAM,IAAIJ,gBACR;gBACEG,YAAYS;gBACZO,QAAQ;oBAAC;wBAAEC,SAASJ,IAAIK,CAAC,CAAC;wBAAgCC,MAAMlB;oBAAgB;iBAAE;YACpF,GACAY,IAAIK,CAAC;QAET;QAEA,OAAOP;IACT"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,MAAM,EAAa,MAAM,SAAS,CAAA;AAMlE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,MAAM,EAAa,MAAM,SAAS,CAAA;AAMlE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAezD,eAAO,MAAM,iBAAiB,GAC3B,UAAU,gBAAgB,uBAAuB,CAAC,UAAU,CAAC,sBAC7C,MAAM,KAAG,MAkjBzB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { tenantField } from './fields/tenantField/index.js';
|
|
|
5
5
|
import { tenantsArrayField } from './fields/tenantsArrayField/index.js';
|
|
6
6
|
import { filterDocumentsByTenants } from './filters/filterDocumentsByTenants.js';
|
|
7
7
|
import { addTenantCleanup } from './hooks/afterTenantDelete.js';
|
|
8
|
+
import { addCollectionBeforeValidateHook } from './hooks/enforceTenantMembership.js';
|
|
8
9
|
import { translations } from './translations/index.js';
|
|
9
10
|
import { addCollectionAccess } from './utilities/addCollectionAccess.js';
|
|
10
11
|
import { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js';
|
|
@@ -146,15 +147,27 @@ export const multiTenantPlugin = (pluginConfig)=>(incomingConfig)=>{
|
|
|
146
147
|
* Add tenant field to enabled collections
|
|
147
148
|
*/ collection.fields.unshift(tenantField({
|
|
148
149
|
name: tenantFieldName,
|
|
150
|
+
adminUsersSlug: adminUsersCollection.slug,
|
|
149
151
|
debug: pluginConfig.debug,
|
|
150
152
|
isAutosaveEnabled: hasAutosaveEnabled(collection),
|
|
151
153
|
overrides: pluginConfig.collections[collection.slug]?.tenantFieldOverrides ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides : pluginConfig.tenantField || {},
|
|
152
154
|
tenantsArrayFieldName,
|
|
153
155
|
tenantsArrayTenantFieldName,
|
|
154
156
|
tenantsCollectionSlug,
|
|
155
|
-
unique: false
|
|
157
|
+
unique: false,
|
|
158
|
+
userHasAccessToAllTenants
|
|
156
159
|
}));
|
|
157
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Reject writes that assign a tenant the user is not a member of
|
|
163
|
+
* - covers drafts, autosave and restore-version, which skip field validation
|
|
164
|
+
*/ addCollectionBeforeValidateHook({
|
|
165
|
+
collection,
|
|
166
|
+
tenantFieldName,
|
|
167
|
+
tenantsArrayFieldName,
|
|
168
|
+
tenantsArrayTenantFieldName,
|
|
169
|
+
userHasAccessToAllTenants
|
|
170
|
+
});
|
|
158
171
|
const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {};
|
|
159
172
|
if (useBaseFilter ?? useBaseListFilter ?? true) {
|
|
160
173
|
/**
|
|
@@ -311,15 +324,27 @@ export const multiTenantPlugin = (pluginConfig)=>(incomingConfig)=>{
|
|
|
311
324
|
* Add tenant field to enabled collections
|
|
312
325
|
*/ collection.fields.unshift(tenantField({
|
|
313
326
|
name: tenantFieldName,
|
|
327
|
+
adminUsersSlug: adminUsersCollection.slug,
|
|
314
328
|
debug: pluginConfig.debug,
|
|
315
329
|
isAutosaveEnabled: hasAutosaveEnabled(collection),
|
|
316
330
|
overrides: pluginConfig.collections[collection.slug]?.tenantFieldOverrides ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides : pluginConfig.tenantField || {},
|
|
317
331
|
tenantsArrayFieldName,
|
|
318
332
|
tenantsArrayTenantFieldName,
|
|
319
333
|
tenantsCollectionSlug,
|
|
320
|
-
unique: isGlobal
|
|
334
|
+
unique: isGlobal,
|
|
335
|
+
userHasAccessToAllTenants
|
|
321
336
|
}));
|
|
322
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Reject writes that assign a tenant the user is not a member of
|
|
340
|
+
* - covers drafts, autosave and restore-version, which skip field validation
|
|
341
|
+
*/ addCollectionBeforeValidateHook({
|
|
342
|
+
collection,
|
|
343
|
+
tenantFieldName,
|
|
344
|
+
tenantsArrayFieldName,
|
|
345
|
+
tenantsArrayTenantFieldName,
|
|
346
|
+
userHasAccessToAllTenants
|
|
347
|
+
});
|
|
323
348
|
const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {};
|
|
324
349
|
if (useBaseFilter ?? useBaseListFilter ?? true) {
|
|
325
350
|
/**
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { AcceptedLanguages } from '@payloadcms/translations'\nimport type { CollectionConfig, Config, TypedUser } from 'payload'\n\nimport chalk from 'chalk'\nimport { hasAutosaveEnabled } from 'payload/shared'\n\nimport type { PluginDefaultTranslationsObject } from './translations/types.js'\nimport type { MultiTenantPluginConfig } from './types.js'\n\nimport { defaults } from './defaults.js'\nimport { getTenantOptionsEndpoint } from './endpoints/getTenantOptionsEndpoint.js'\nimport { tenantField } from './fields/tenantField/index.js'\nimport { tenantsArrayField } from './fields/tenantsArrayField/index.js'\nimport { filterDocumentsByTenants } from './filters/filterDocumentsByTenants.js'\nimport { addTenantCleanup } from './hooks/afterTenantDelete.js'\nimport { translations } from './translations/index.js'\nimport { addCollectionAccess } from './utilities/addCollectionAccess.js'\nimport { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js'\nimport { combineFilters } from './utilities/combineFilters.js'\nimport { miniChalk } from './utilities/miniChalk.js'\n\nexport const multiTenantPlugin =\n <ConfigType>(pluginConfig: MultiTenantPluginConfig<ConfigType>) =>\n (incomingConfig: Config): Config => {\n if (pluginConfig.enabled === false) {\n return incomingConfig\n }\n\n /**\n * Set defaults\n */\n const userHasAccessToAllTenants: Required<\n MultiTenantPluginConfig<ConfigType>\n >['userHasAccessToAllTenants'] =\n typeof pluginConfig.userHasAccessToAllTenants === 'function'\n ? pluginConfig.userHasAccessToAllTenants\n : () => false\n const tenantsCollectionSlug = (pluginConfig.tenantsSlug =\n pluginConfig.tenantsSlug || defaults.tenantCollectionSlug)\n const tenantFieldName = pluginConfig?.tenantField?.name || defaults.tenantFieldName\n const tenantsArrayFieldName =\n pluginConfig?.tenantsArrayField?.arrayFieldName || defaults.tenantsArrayFieldName\n const tenantsArrayTenantFieldName =\n pluginConfig?.tenantsArrayField?.arrayTenantFieldName || defaults.tenantsArrayTenantFieldName\n\n /**\n * Add defaults for admin properties\n */\n if (!incomingConfig.admin) {\n incomingConfig.admin = {}\n }\n if (!incomingConfig.admin?.components) {\n incomingConfig.admin.components = {\n actions: [],\n beforeNav: [],\n providers: [],\n }\n }\n if (!incomingConfig.admin.components?.providers) {\n incomingConfig.admin.components.providers = []\n }\n if (!incomingConfig.admin.components?.actions) {\n incomingConfig.admin.components.actions = []\n }\n if (!incomingConfig.admin.components?.beforeNav) {\n incomingConfig.admin.components.beforeNav = []\n }\n if (!incomingConfig.collections) {\n incomingConfig.collections = []\n }\n\n /**\n * Add tenants array field to users collection\n */\n const adminUsersCollection = incomingConfig.collections.find(({ slug, auth }) => {\n if (incomingConfig.admin?.user) {\n return slug === incomingConfig.admin.user\n } else if (auth) {\n return true\n }\n })\n\n if (!adminUsersCollection) {\n throw Error('An auth enabled collection was not found')\n }\n\n /**\n * Add tenants array field to users collection\n */\n if (pluginConfig?.tenantsArrayField?.includeDefaultField !== false) {\n adminUsersCollection.fields.push(\n tenantsArrayField({\n ...(pluginConfig?.tenantsArrayField || {}),\n arrayFieldAccess: {\n create: ({ req }) =>\n Boolean(\n req.user &&\n userHasAccessToAllTenants(\n req.user as ConfigType extends { user: unknown }\n ? ConfigType['user']\n : TypedUser,\n ),\n ),\n update: ({ req }) =>\n Boolean(\n req.user &&\n userHasAccessToAllTenants(\n req.user as ConfigType extends { user: unknown }\n ? ConfigType['user']\n : TypedUser,\n ),\n ),\n ...(pluginConfig?.tenantsArrayField?.arrayFieldAccess || {}),\n },\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n }),\n )\n }\n\n addCollectionAccess({\n accessResultCallback: pluginConfig.usersAccessResultOverride,\n adminUsersSlug: adminUsersCollection.slug,\n collection: adminUsersCollection,\n fieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n\n if (pluginConfig.useUsersTenantFilter !== false) {\n if (!adminUsersCollection.admin) {\n adminUsersCollection.admin = {}\n }\n\n const baseFilter =\n adminUsersCollection.admin?.baseFilter ?? adminUsersCollection.admin?.baseListFilter\n adminUsersCollection.admin.baseFilter = combineFilters({\n baseFilter,\n customFilter: (args) =>\n filterDocumentsByTenants<ConfigType>({\n filterFieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n let tenantCollection: CollectionConfig | undefined\n\n const [collectionSlugs, globalCollectionSlugs] = Object.keys(pluginConfig.collections).reduce<\n [string[], string[]]\n >(\n (acc, slug) => {\n if (slug === adminUsersCollection.slug) {\n return acc\n }\n if (pluginConfig?.collections?.[slug]?.isGlobal) {\n acc[1].push(slug)\n } else {\n acc[0].push(slug)\n }\n\n return acc\n },\n [[], []],\n )\n\n // used to track and not duplicate filterOptions on referenced blocks\n const blockReferencesWithFilters: string[] = []\n\n // used to validate enabled collection slugs\n const multiTenantCollectionsFound: string[] = []\n\n /**\n * The folders collection is added AFTER the plugin is initialized\n * so if they added the folder slug to the plugin collections,\n * we can assume that they have folders enabled\n */\n const foldersSlug = incomingConfig.folders\n ? incomingConfig.folders.slug || 'payload-folders'\n : 'payload-folders'\n\n if (collectionSlugs.includes(foldersSlug)) {\n multiTenantCollectionsFound.push(foldersSlug)\n incomingConfig.folders = incomingConfig.folders || {}\n incomingConfig.folders.collectionOverrides = incomingConfig.folders.collectionOverrides || []\n incomingConfig.folders.collectionOverrides.push(({ collection }) => {\n /**\n * Add filter options to all relationship fields\n */\n collection.fields = addFilterOptionsToFields({\n blockReferencesWithFilters,\n config: incomingConfig,\n fields: collection.fields,\n tenantEnabledCollectionSlugs: collectionSlugs,\n tenantEnabledGlobalSlugs: globalCollectionSlugs,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n })\n\n if (pluginConfig.collections[foldersSlug]?.customTenantField !== true) {\n /**\n * Add tenant field to enabled collections\n */\n collection.fields.unshift(\n tenantField({\n name: tenantFieldName,\n debug: pluginConfig.debug,\n isAutosaveEnabled: hasAutosaveEnabled(collection),\n overrides: pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n : pluginConfig.tenantField || {},\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n unique: false,\n }),\n )\n }\n\n const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {}\n if (useBaseFilter ?? useBaseListFilter ?? true) {\n /**\n * Add list filter to enabled collections\n * - filters results by selected tenant\n */\n collection.admin = collection.admin || {}\n collection.admin.baseFilter = combineFilters({\n baseFilter: collection.admin?.baseFilter ?? collection.admin?.baseListFilter,\n customFilter: (args) =>\n filterDocumentsByTenants<ConfigType>({\n filterFieldName: tenantFieldName,\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n if (pluginConfig.collections[foldersSlug]?.useTenantAccess !== false) {\n /**\n * Add access control constraint to tenant enabled folders collection\n */\n addCollectionAccess({\n accessResultCallback: pluginConfig.collections[foldersSlug]?.accessResultOverride,\n adminUsersSlug: adminUsersCollection.slug,\n collection,\n fieldName: tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n }\n\n return collection\n })\n }\n\n /**\n * Modify collections\n */\n incomingConfig.collections.forEach((collection) => {\n /**\n * Modify tenants collection\n */\n if (collection.slug === tenantsCollectionSlug) {\n tenantCollection = collection\n\n if (pluginConfig.useTenantsCollectionAccess !== false) {\n /**\n * Add access control constraint to tenants collection\n * - constrains access a users assigned tenants\n */\n addCollectionAccess({\n adminUsersSlug: adminUsersCollection.slug,\n collection,\n fieldName: 'id',\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n }\n\n if (pluginConfig.useTenantsListFilter !== false) {\n /**\n * Add list filter to tenants collection\n * - filter by selected tenant\n */\n if (!collection.admin) {\n collection.admin = {}\n }\n\n const baseFilter = collection.admin?.baseFilter ?? collection.admin?.baseListFilter\n collection.admin.baseFilter = combineFilters({\n baseFilter,\n customFilter: (args) =>\n filterDocumentsByTenants({\n filterFieldName: 'id',\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n if (pluginConfig.cleanupAfterTenantDelete !== false) {\n /**\n * Add cleanup logic when tenant is deleted\n * - delete documents related to tenant\n * - remove tenant from users\n */\n addTenantCleanup({\n collection,\n enabledSlugs: [...collectionSlugs, ...globalCollectionSlugs],\n tenantFieldName,\n tenantsCollectionSlug,\n usersSlug: adminUsersCollection.slug,\n usersTenantsArrayFieldName: tenantsArrayFieldName,\n usersTenantsArrayTenantFieldName: tenantsArrayTenantFieldName,\n })\n }\n\n /**\n * Add custom tenant field that watches and dispatches updates to the selector\n */\n collection.fields.push({\n name: '_watchTenant',\n type: 'ui',\n admin: {\n components: {\n Field: {\n path: '@payloadcms/plugin-multi-tenant/client#WatchTenantCollection',\n },\n },\n disableBulkEdit: true,\n disableListColumn: true,\n },\n })\n\n collection.endpoints = [\n ...(collection.endpoints || []),\n getTenantOptionsEndpoint({\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n useAsTitle: tenantCollection.admin?.useAsTitle || 'id',\n userHasAccessToAllTenants,\n }),\n ]\n } else if (pluginConfig.collections?.[collection.slug]) {\n if (collection.slug === adminUsersCollection.slug) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugin-multi-tenant] The admin users collection \"${collection.slug}\" should not be listed in pluginConfig.collections — it is already handled by the plugin. Skipping tenant-field processing for this collection to avoid double access control and validation errors.`,\n )\n return\n }\n multiTenantCollectionsFound.push(collection.slug)\n const isGlobal = Boolean(pluginConfig.collections[collection.slug]?.isGlobal)\n\n if (isGlobal) {\n collection.disableDuplicate = true\n }\n\n if (!pluginConfig.debug && !isGlobal) {\n collection.admin ??= {}\n collection.admin.components ??= {}\n collection.admin.components.edit ??= {}\n collection.admin.components.edit.editMenuItems ??= []\n collection.admin.components.edit.editMenuItems.push({\n path: '@payloadcms/plugin-multi-tenant/client#AssignTenantFieldTrigger',\n })\n }\n\n /**\n * Add filter options to all relationship fields\n */\n collection.fields = addFilterOptionsToFields({\n blockReferencesWithFilters,\n config: incomingConfig,\n fields: collection.fields,\n tenantEnabledCollectionSlugs: collectionSlugs,\n tenantEnabledGlobalSlugs: globalCollectionSlugs,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n })\n\n if (pluginConfig.collections[collection.slug]?.customTenantField !== true) {\n /**\n * Add tenant field to enabled collections\n */\n collection.fields.unshift(\n tenantField({\n name: tenantFieldName,\n debug: pluginConfig.debug,\n isAutosaveEnabled: hasAutosaveEnabled(collection),\n overrides: pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n : pluginConfig.tenantField || {},\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n unique: isGlobal,\n }),\n )\n }\n\n const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {}\n if (useBaseFilter ?? useBaseListFilter ?? true) {\n /**\n * Add list filter to enabled collections\n * - filters results by selected tenant\n */\n collection.admin = collection.admin || {}\n collection.admin.baseFilter = combineFilters({\n baseFilter: collection.admin?.baseFilter ?? collection.admin?.baseListFilter,\n customFilter: (args) =>\n filterDocumentsByTenants({\n filterFieldName: tenantFieldName,\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n if (pluginConfig.collections[collection.slug]?.useTenantAccess !== false) {\n /**\n * Add access control constraint to tenant enabled collection\n */\n addCollectionAccess({\n accessResultCallback: pluginConfig.collections[collection.slug]?.accessResultOverride,\n adminUsersSlug: adminUsersCollection.slug,\n collection,\n fieldName: tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n }\n }\n })\n\n if (!tenantCollection) {\n throw new Error(`Tenants collection not found with slug: ${tenantsCollectionSlug}`)\n }\n\n if (\n multiTenantCollectionsFound.length !==\n collectionSlugs.length + globalCollectionSlugs.length\n ) {\n const missingSlugs = [...collectionSlugs, ...globalCollectionSlugs].filter(\n (slug) => !multiTenantCollectionsFound.includes(slug),\n )\n // eslint-disable-next-line no-console\n console.error(\n miniChalk.yellowBold('WARNING (plugin-multi-tenant)'),\n 'missing collections',\n missingSlugs,\n 'try placing the multi-tenant plugin after other plugins.',\n )\n }\n\n /**\n * Add TenantSelectionProvider to admin providers\n */\n incomingConfig.admin.components.providers.push({\n clientProps: {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug: tenantCollection.slug,\n useAsTitle: tenantCollection.admin?.useAsTitle || 'id',\n userHasAccessToAllTenants,\n },\n path: '@payloadcms/plugin-multi-tenant/rsc#TenantSelectionProvider',\n })\n\n /**\n * Add global redirect action\n */\n if (globalCollectionSlugs.length) {\n incomingConfig.admin.components.actions.push({\n path: '@payloadcms/plugin-multi-tenant/rsc#GlobalViewRedirect',\n serverProps: {\n globalSlugs: globalCollectionSlugs,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n useAsTitle: tenantCollection.admin?.useAsTitle || 'id',\n userHasAccessToAllTenants,\n },\n })\n }\n\n /**\n * Add tenant selector to admin UI\n */\n incomingConfig.admin.components.beforeNav.push({\n clientProps: {\n enabledSlugs: [\n ...collectionSlugs,\n ...globalCollectionSlugs,\n adminUsersCollection.slug,\n tenantCollection.slug,\n ],\n label: pluginConfig.tenantSelectorLabel || undefined,\n },\n path: '@payloadcms/plugin-multi-tenant/rsc#TenantSelector',\n })\n\n /**\n * Merge plugin translations\n */\n if (!incomingConfig.i18n) {\n incomingConfig.i18n = {}\n }\n Object.entries(translations).forEach(([locale, pluginI18nObject]) => {\n const typedLocale = locale as AcceptedLanguages\n if (!incomingConfig.i18n!.translations) {\n incomingConfig.i18n!.translations = {}\n }\n if (!(typedLocale in incomingConfig.i18n!.translations)) {\n incomingConfig.i18n!.translations[typedLocale] = {}\n }\n if (!('plugin-multi-tenant' in incomingConfig.i18n!.translations[typedLocale]!)) {\n ;(incomingConfig.i18n!.translations[typedLocale] as PluginDefaultTranslationsObject)[\n 'plugin-multi-tenant'\n ] = {} as PluginDefaultTranslationsObject['plugin-multi-tenant']\n }\n\n ;(incomingConfig.i18n!.translations[typedLocale] as PluginDefaultTranslationsObject)[\n 'plugin-multi-tenant'\n ] = {\n ...pluginI18nObject.translations['plugin-multi-tenant'],\n ...(pluginConfig.i18n?.translations?.[typedLocale] || {}),\n }\n })\n\n return incomingConfig\n }\n"],"names":["hasAutosaveEnabled","defaults","getTenantOptionsEndpoint","tenantField","tenantsArrayField","filterDocumentsByTenants","addTenantCleanup","translations","addCollectionAccess","addFilterOptionsToFields","combineFilters","miniChalk","multiTenantPlugin","pluginConfig","incomingConfig","enabled","userHasAccessToAllTenants","tenantsCollectionSlug","tenantsSlug","tenantCollectionSlug","tenantFieldName","name","tenantsArrayFieldName","arrayFieldName","tenantsArrayTenantFieldName","arrayTenantFieldName","admin","components","actions","beforeNav","providers","collections","adminUsersCollection","find","slug","auth","user","Error","includeDefaultField","fields","push","arrayFieldAccess","create","req","Boolean","update","accessResultCallback","usersAccessResultOverride","adminUsersSlug","collection","fieldName","useUsersTenantFilter","baseFilter","baseListFilter","customFilter","args","filterFieldName","tenantCollection","collectionSlugs","globalCollectionSlugs","Object","keys","reduce","acc","isGlobal","blockReferencesWithFilters","multiTenantCollectionsFound","foldersSlug","folders","includes","collectionOverrides","config","tenantEnabledCollectionSlugs","tenantEnabledGlobalSlugs","customTenantField","unshift","debug","isAutosaveEnabled","overrides","tenantFieldOverrides","unique","useBaseFilter","useBaseListFilter","useTenantAccess","accessResultOverride","forEach","useTenantsCollectionAccess","useTenantsListFilter","cleanupAfterTenantDelete","enabledSlugs","usersSlug","usersTenantsArrayFieldName","usersTenantsArrayTenantFieldName","type","Field","path","disableBulkEdit","disableListColumn","endpoints","useAsTitle","console","warn","disableDuplicate","edit","editMenuItems","length","missingSlugs","filter","error","yellowBold","clientProps","serverProps","globalSlugs","label","tenantSelectorLabel","undefined","i18n","entries","locale","pluginI18nObject","typedLocale"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { AcceptedLanguages } from '@payloadcms/translations'\nimport type { CollectionConfig, Config, TypedUser } from 'payload'\n\nimport chalk from 'chalk'\nimport { hasAutosaveEnabled } from 'payload/shared'\n\nimport type { PluginDefaultTranslationsObject } from './translations/types.js'\nimport type { MultiTenantPluginConfig } from './types.js'\n\nimport { defaults } from './defaults.js'\nimport { getTenantOptionsEndpoint } from './endpoints/getTenantOptionsEndpoint.js'\nimport { tenantField } from './fields/tenantField/index.js'\nimport { tenantsArrayField } from './fields/tenantsArrayField/index.js'\nimport { filterDocumentsByTenants } from './filters/filterDocumentsByTenants.js'\nimport { addTenantCleanup } from './hooks/afterTenantDelete.js'\nimport { addCollectionBeforeValidateHook } from './hooks/enforceTenantMembership.js'\nimport { translations } from './translations/index.js'\nimport { addCollectionAccess } from './utilities/addCollectionAccess.js'\nimport { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js'\nimport { combineFilters } from './utilities/combineFilters.js'\nimport { miniChalk } from './utilities/miniChalk.js'\n\nexport const multiTenantPlugin =\n <ConfigType>(pluginConfig: MultiTenantPluginConfig<ConfigType>) =>\n (incomingConfig: Config): Config => {\n if (pluginConfig.enabled === false) {\n return incomingConfig\n }\n\n /**\n * Set defaults\n */\n const userHasAccessToAllTenants: Required<\n MultiTenantPluginConfig<ConfigType>\n >['userHasAccessToAllTenants'] =\n typeof pluginConfig.userHasAccessToAllTenants === 'function'\n ? pluginConfig.userHasAccessToAllTenants\n : () => false\n const tenantsCollectionSlug = (pluginConfig.tenantsSlug =\n pluginConfig.tenantsSlug || defaults.tenantCollectionSlug)\n const tenantFieldName = pluginConfig?.tenantField?.name || defaults.tenantFieldName\n const tenantsArrayFieldName =\n pluginConfig?.tenantsArrayField?.arrayFieldName || defaults.tenantsArrayFieldName\n const tenantsArrayTenantFieldName =\n pluginConfig?.tenantsArrayField?.arrayTenantFieldName || defaults.tenantsArrayTenantFieldName\n\n /**\n * Add defaults for admin properties\n */\n if (!incomingConfig.admin) {\n incomingConfig.admin = {}\n }\n if (!incomingConfig.admin?.components) {\n incomingConfig.admin.components = {\n actions: [],\n beforeNav: [],\n providers: [],\n }\n }\n if (!incomingConfig.admin.components?.providers) {\n incomingConfig.admin.components.providers = []\n }\n if (!incomingConfig.admin.components?.actions) {\n incomingConfig.admin.components.actions = []\n }\n if (!incomingConfig.admin.components?.beforeNav) {\n incomingConfig.admin.components.beforeNav = []\n }\n if (!incomingConfig.collections) {\n incomingConfig.collections = []\n }\n\n /**\n * Add tenants array field to users collection\n */\n const adminUsersCollection = incomingConfig.collections.find(({ slug, auth }) => {\n if (incomingConfig.admin?.user) {\n return slug === incomingConfig.admin.user\n } else if (auth) {\n return true\n }\n })\n\n if (!adminUsersCollection) {\n throw Error('An auth enabled collection was not found')\n }\n\n /**\n * Add tenants array field to users collection\n */\n if (pluginConfig?.tenantsArrayField?.includeDefaultField !== false) {\n adminUsersCollection.fields.push(\n tenantsArrayField({\n ...(pluginConfig?.tenantsArrayField || {}),\n arrayFieldAccess: {\n create: ({ req }) =>\n Boolean(\n req.user &&\n userHasAccessToAllTenants(\n req.user as ConfigType extends { user: unknown }\n ? ConfigType['user']\n : TypedUser,\n ),\n ),\n update: ({ req }) =>\n Boolean(\n req.user &&\n userHasAccessToAllTenants(\n req.user as ConfigType extends { user: unknown }\n ? ConfigType['user']\n : TypedUser,\n ),\n ),\n ...(pluginConfig?.tenantsArrayField?.arrayFieldAccess || {}),\n },\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n }),\n )\n }\n\n addCollectionAccess({\n accessResultCallback: pluginConfig.usersAccessResultOverride,\n adminUsersSlug: adminUsersCollection.slug,\n collection: adminUsersCollection,\n fieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n\n if (pluginConfig.useUsersTenantFilter !== false) {\n if (!adminUsersCollection.admin) {\n adminUsersCollection.admin = {}\n }\n\n const baseFilter =\n adminUsersCollection.admin?.baseFilter ?? adminUsersCollection.admin?.baseListFilter\n adminUsersCollection.admin.baseFilter = combineFilters({\n baseFilter,\n customFilter: (args) =>\n filterDocumentsByTenants<ConfigType>({\n filterFieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n let tenantCollection: CollectionConfig | undefined\n\n const [collectionSlugs, globalCollectionSlugs] = Object.keys(pluginConfig.collections).reduce<\n [string[], string[]]\n >(\n (acc, slug) => {\n if (slug === adminUsersCollection.slug) {\n return acc\n }\n if (pluginConfig?.collections?.[slug]?.isGlobal) {\n acc[1].push(slug)\n } else {\n acc[0].push(slug)\n }\n\n return acc\n },\n [[], []],\n )\n\n // used to track and not duplicate filterOptions on referenced blocks\n const blockReferencesWithFilters: string[] = []\n\n // used to validate enabled collection slugs\n const multiTenantCollectionsFound: string[] = []\n\n /**\n * The folders collection is added AFTER the plugin is initialized\n * so if they added the folder slug to the plugin collections,\n * we can assume that they have folders enabled\n */\n const foldersSlug = incomingConfig.folders\n ? incomingConfig.folders.slug || 'payload-folders'\n : 'payload-folders'\n\n if (collectionSlugs.includes(foldersSlug)) {\n multiTenantCollectionsFound.push(foldersSlug)\n incomingConfig.folders = incomingConfig.folders || {}\n incomingConfig.folders.collectionOverrides = incomingConfig.folders.collectionOverrides || []\n incomingConfig.folders.collectionOverrides.push(({ collection }) => {\n /**\n * Add filter options to all relationship fields\n */\n collection.fields = addFilterOptionsToFields({\n blockReferencesWithFilters,\n config: incomingConfig,\n fields: collection.fields,\n tenantEnabledCollectionSlugs: collectionSlugs,\n tenantEnabledGlobalSlugs: globalCollectionSlugs,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n })\n\n if (pluginConfig.collections[foldersSlug]?.customTenantField !== true) {\n /**\n * Add tenant field to enabled collections\n */\n collection.fields.unshift(\n tenantField<ConfigType>({\n name: tenantFieldName,\n adminUsersSlug: adminUsersCollection.slug,\n debug: pluginConfig.debug,\n isAutosaveEnabled: hasAutosaveEnabled(collection),\n overrides: pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n : pluginConfig.tenantField || {},\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n unique: false,\n userHasAccessToAllTenants,\n }),\n )\n }\n\n /**\n * Reject writes that assign a tenant the user is not a member of\n * - covers drafts, autosave and restore-version, which skip field validation\n */\n addCollectionBeforeValidateHook<ConfigType>({\n collection,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n\n const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {}\n if (useBaseFilter ?? useBaseListFilter ?? true) {\n /**\n * Add list filter to enabled collections\n * - filters results by selected tenant\n */\n collection.admin = collection.admin || {}\n collection.admin.baseFilter = combineFilters({\n baseFilter: collection.admin?.baseFilter ?? collection.admin?.baseListFilter,\n customFilter: (args) =>\n filterDocumentsByTenants<ConfigType>({\n filterFieldName: tenantFieldName,\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n if (pluginConfig.collections[foldersSlug]?.useTenantAccess !== false) {\n /**\n * Add access control constraint to tenant enabled folders collection\n */\n addCollectionAccess({\n accessResultCallback: pluginConfig.collections[foldersSlug]?.accessResultOverride,\n adminUsersSlug: adminUsersCollection.slug,\n collection,\n fieldName: tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n }\n\n return collection\n })\n }\n\n /**\n * Modify collections\n */\n incomingConfig.collections.forEach((collection) => {\n /**\n * Modify tenants collection\n */\n if (collection.slug === tenantsCollectionSlug) {\n tenantCollection = collection\n\n if (pluginConfig.useTenantsCollectionAccess !== false) {\n /**\n * Add access control constraint to tenants collection\n * - constrains access a users assigned tenants\n */\n addCollectionAccess({\n adminUsersSlug: adminUsersCollection.slug,\n collection,\n fieldName: 'id',\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n }\n\n if (pluginConfig.useTenantsListFilter !== false) {\n /**\n * Add list filter to tenants collection\n * - filter by selected tenant\n */\n if (!collection.admin) {\n collection.admin = {}\n }\n\n const baseFilter = collection.admin?.baseFilter ?? collection.admin?.baseListFilter\n collection.admin.baseFilter = combineFilters({\n baseFilter,\n customFilter: (args) =>\n filterDocumentsByTenants({\n filterFieldName: 'id',\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n if (pluginConfig.cleanupAfterTenantDelete !== false) {\n /**\n * Add cleanup logic when tenant is deleted\n * - delete documents related to tenant\n * - remove tenant from users\n */\n addTenantCleanup({\n collection,\n enabledSlugs: [...collectionSlugs, ...globalCollectionSlugs],\n tenantFieldName,\n tenantsCollectionSlug,\n usersSlug: adminUsersCollection.slug,\n usersTenantsArrayFieldName: tenantsArrayFieldName,\n usersTenantsArrayTenantFieldName: tenantsArrayTenantFieldName,\n })\n }\n\n /**\n * Add custom tenant field that watches and dispatches updates to the selector\n */\n collection.fields.push({\n name: '_watchTenant',\n type: 'ui',\n admin: {\n components: {\n Field: {\n path: '@payloadcms/plugin-multi-tenant/client#WatchTenantCollection',\n },\n },\n disableBulkEdit: true,\n disableListColumn: true,\n },\n })\n\n collection.endpoints = [\n ...(collection.endpoints || []),\n getTenantOptionsEndpoint({\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n useAsTitle: tenantCollection.admin?.useAsTitle || 'id',\n userHasAccessToAllTenants,\n }),\n ]\n } else if (pluginConfig.collections?.[collection.slug]) {\n if (collection.slug === adminUsersCollection.slug) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugin-multi-tenant] The admin users collection \"${collection.slug}\" should not be listed in pluginConfig.collections — it is already handled by the plugin. Skipping tenant-field processing for this collection to avoid double access control and validation errors.`,\n )\n return\n }\n multiTenantCollectionsFound.push(collection.slug)\n const isGlobal = Boolean(pluginConfig.collections[collection.slug]?.isGlobal)\n\n if (isGlobal) {\n collection.disableDuplicate = true\n }\n\n if (!pluginConfig.debug && !isGlobal) {\n collection.admin ??= {}\n collection.admin.components ??= {}\n collection.admin.components.edit ??= {}\n collection.admin.components.edit.editMenuItems ??= []\n collection.admin.components.edit.editMenuItems.push({\n path: '@payloadcms/plugin-multi-tenant/client#AssignTenantFieldTrigger',\n })\n }\n\n /**\n * Add filter options to all relationship fields\n */\n collection.fields = addFilterOptionsToFields({\n blockReferencesWithFilters,\n config: incomingConfig,\n fields: collection.fields,\n tenantEnabledCollectionSlugs: collectionSlugs,\n tenantEnabledGlobalSlugs: globalCollectionSlugs,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n })\n\n if (pluginConfig.collections[collection.slug]?.customTenantField !== true) {\n /**\n * Add tenant field to enabled collections\n */\n collection.fields.unshift(\n tenantField<ConfigType>({\n name: tenantFieldName,\n adminUsersSlug: adminUsersCollection.slug,\n debug: pluginConfig.debug,\n isAutosaveEnabled: hasAutosaveEnabled(collection),\n overrides: pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides\n : pluginConfig.tenantField || {},\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n unique: isGlobal,\n userHasAccessToAllTenants,\n }),\n )\n }\n\n /**\n * Reject writes that assign a tenant the user is not a member of\n * - covers drafts, autosave and restore-version, which skip field validation\n */\n addCollectionBeforeValidateHook<ConfigType>({\n collection,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n\n const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {}\n if (useBaseFilter ?? useBaseListFilter ?? true) {\n /**\n * Add list filter to enabled collections\n * - filters results by selected tenant\n */\n collection.admin = collection.admin || {}\n collection.admin.baseFilter = combineFilters({\n baseFilter: collection.admin?.baseFilter ?? collection.admin?.baseListFilter,\n customFilter: (args) =>\n filterDocumentsByTenants({\n filterFieldName: tenantFieldName,\n req: args.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n userHasAccessToAllTenants,\n }),\n })\n }\n\n if (pluginConfig.collections[collection.slug]?.useTenantAccess !== false) {\n /**\n * Add access control constraint to tenant enabled collection\n */\n addCollectionAccess({\n accessResultCallback: pluginConfig.collections[collection.slug]?.accessResultOverride,\n adminUsersSlug: adminUsersCollection.slug,\n collection,\n fieldName: tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n })\n }\n }\n })\n\n if (!tenantCollection) {\n throw new Error(`Tenants collection not found with slug: ${tenantsCollectionSlug}`)\n }\n\n if (\n multiTenantCollectionsFound.length !==\n collectionSlugs.length + globalCollectionSlugs.length\n ) {\n const missingSlugs = [...collectionSlugs, ...globalCollectionSlugs].filter(\n (slug) => !multiTenantCollectionsFound.includes(slug),\n )\n // eslint-disable-next-line no-console\n console.error(\n miniChalk.yellowBold('WARNING (plugin-multi-tenant)'),\n 'missing collections',\n missingSlugs,\n 'try placing the multi-tenant plugin after other plugins.',\n )\n }\n\n /**\n * Add TenantSelectionProvider to admin providers\n */\n incomingConfig.admin.components.providers.push({\n clientProps: {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug: tenantCollection.slug,\n useAsTitle: tenantCollection.admin?.useAsTitle || 'id',\n userHasAccessToAllTenants,\n },\n path: '@payloadcms/plugin-multi-tenant/rsc#TenantSelectionProvider',\n })\n\n /**\n * Add global redirect action\n */\n if (globalCollectionSlugs.length) {\n incomingConfig.admin.components.actions.push({\n path: '@payloadcms/plugin-multi-tenant/rsc#GlobalViewRedirect',\n serverProps: {\n globalSlugs: globalCollectionSlugs,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n useAsTitle: tenantCollection.admin?.useAsTitle || 'id',\n userHasAccessToAllTenants,\n },\n })\n }\n\n /**\n * Add tenant selector to admin UI\n */\n incomingConfig.admin.components.beforeNav.push({\n clientProps: {\n enabledSlugs: [\n ...collectionSlugs,\n ...globalCollectionSlugs,\n adminUsersCollection.slug,\n tenantCollection.slug,\n ],\n label: pluginConfig.tenantSelectorLabel || undefined,\n },\n path: '@payloadcms/plugin-multi-tenant/rsc#TenantSelector',\n })\n\n /**\n * Merge plugin translations\n */\n if (!incomingConfig.i18n) {\n incomingConfig.i18n = {}\n }\n Object.entries(translations).forEach(([locale, pluginI18nObject]) => {\n const typedLocale = locale as AcceptedLanguages\n if (!incomingConfig.i18n!.translations) {\n incomingConfig.i18n!.translations = {}\n }\n if (!(typedLocale in incomingConfig.i18n!.translations)) {\n incomingConfig.i18n!.translations[typedLocale] = {}\n }\n if (!('plugin-multi-tenant' in incomingConfig.i18n!.translations[typedLocale]!)) {\n ;(incomingConfig.i18n!.translations[typedLocale] as PluginDefaultTranslationsObject)[\n 'plugin-multi-tenant'\n ] = {} as PluginDefaultTranslationsObject['plugin-multi-tenant']\n }\n\n ;(incomingConfig.i18n!.translations[typedLocale] as PluginDefaultTranslationsObject)[\n 'plugin-multi-tenant'\n ] = {\n ...pluginI18nObject.translations['plugin-multi-tenant'],\n ...(pluginConfig.i18n?.translations?.[typedLocale] || {}),\n }\n })\n\n return incomingConfig\n }\n"],"names":["hasAutosaveEnabled","defaults","getTenantOptionsEndpoint","tenantField","tenantsArrayField","filterDocumentsByTenants","addTenantCleanup","addCollectionBeforeValidateHook","translations","addCollectionAccess","addFilterOptionsToFields","combineFilters","miniChalk","multiTenantPlugin","pluginConfig","incomingConfig","enabled","userHasAccessToAllTenants","tenantsCollectionSlug","tenantsSlug","tenantCollectionSlug","tenantFieldName","name","tenantsArrayFieldName","arrayFieldName","tenantsArrayTenantFieldName","arrayTenantFieldName","admin","components","actions","beforeNav","providers","collections","adminUsersCollection","find","slug","auth","user","Error","includeDefaultField","fields","push","arrayFieldAccess","create","req","Boolean","update","accessResultCallback","usersAccessResultOverride","adminUsersSlug","collection","fieldName","useUsersTenantFilter","baseFilter","baseListFilter","customFilter","args","filterFieldName","tenantCollection","collectionSlugs","globalCollectionSlugs","Object","keys","reduce","acc","isGlobal","blockReferencesWithFilters","multiTenantCollectionsFound","foldersSlug","folders","includes","collectionOverrides","config","tenantEnabledCollectionSlugs","tenantEnabledGlobalSlugs","customTenantField","unshift","debug","isAutosaveEnabled","overrides","tenantFieldOverrides","unique","useBaseFilter","useBaseListFilter","useTenantAccess","accessResultOverride","forEach","useTenantsCollectionAccess","useTenantsListFilter","cleanupAfterTenantDelete","enabledSlugs","usersSlug","usersTenantsArrayFieldName","usersTenantsArrayTenantFieldName","type","Field","path","disableBulkEdit","disableListColumn","endpoints","useAsTitle","console","warn","disableDuplicate","edit","editMenuItems","length","missingSlugs","filter","error","yellowBold","clientProps","serverProps","globalSlugs","label","tenantSelectorLabel","undefined","i18n","entries","locale","pluginI18nObject","typedLocale"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getTenantIDsFromValue.d.ts","sourceRoot":"","sources":["../../src/utilities/getTenantIDsFromValue.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,UAAW,OAAO,KAAG,MAAM,EAU5D,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { extractID } from './extractID.js';
|
|
2
|
+
export const getTenantIDsFromValue = (value)=>{
|
|
3
|
+
if (value === undefined || value === null) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
return (Array.isArray(value) ? value : [
|
|
7
|
+
value
|
|
8
|
+
]).map((entry)=>{
|
|
9
|
+
const id = entry === null || entry === undefined ? undefined : extractID(entry);
|
|
10
|
+
return typeof id === 'number' || typeof id === 'string' ? String(id) : '';
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=getTenantIDsFromValue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/getTenantIDsFromValue.ts"],"sourcesContent":["import type { Tenant } from '../types.js'\n\nimport { extractID } from './extractID.js'\n\nexport const getTenantIDsFromValue = (value: unknown): string[] => {\n if (value === undefined || value === null) {\n return []\n }\n\n return (Array.isArray(value) ? value : [value]).map((entry) => {\n const id = entry === null || entry === undefined ? undefined : extractID(entry as Tenant)\n\n return typeof id === 'number' || typeof id === 'string' ? String(id) : ''\n })\n}\n"],"names":["extractID","getTenantIDsFromValue","value","undefined","Array","isArray","map","entry","id","String"],"mappings":"AAEA,SAASA,SAAS,QAAQ,iBAAgB;AAE1C,OAAO,MAAMC,wBAAwB,CAACC;IACpC,IAAIA,UAAUC,aAAaD,UAAU,MAAM;QACzC,OAAO,EAAE;IACX;IAEA,OAAO,AAACE,CAAAA,MAAMC,OAAO,CAACH,SAASA,QAAQ;QAACA;KAAM,AAAD,EAAGI,GAAG,CAAC,CAACC;QACnD,MAAMC,KAAKD,UAAU,QAAQA,UAAUJ,YAAYA,YAAYH,UAAUO;QAEzE,OAAO,OAAOC,OAAO,YAAY,OAAOA,OAAO,WAAWC,OAAOD,MAAM;IACzE;AACF,EAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getUserTenantIDs.d.ts","sourceRoot":"","sources":["../../src/utilities/getUserTenantIDs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAK/D;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,MAAM,GAAG,MAAM,QACvD,IAAI,GAAG,oBAAoB,YACvB;IACR,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,2BAA2B,CAAC,EAAE,MAAM,CAAA;CACrC,KACA,MAAM,
|
|
1
|
+
{"version":3,"file":"getUserTenantIDs.d.ts","sourceRoot":"","sources":["../../src/utilities/getUserTenantIDs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAK/D;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,MAAM,GAAG,MAAM,QACvD,IAAI,GAAG,oBAAoB,YACvB;IACR,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,2BAA2B,CAAC,EAAE,MAAM,CAAA;CACrC,KACA,MAAM,EAqBR,CAAA"}
|
|
@@ -11,8 +11,9 @@ import { extractID } from './extractID.js';
|
|
|
11
11
|
const tenantsArrayFieldName = options?.tenantsArrayFieldName || defaults.tenantsArrayFieldName;
|
|
12
12
|
const tenantsArrayTenantFieldName = options?.tenantsArrayTenantFieldName || defaults.tenantsArrayTenantFieldName;
|
|
13
13
|
return (Array.isArray(user[tenantsArrayFieldName]) ? user[tenantsArrayFieldName] : [])?.reduce((acc, row)=>{
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const tenantRow = row[tenantsArrayTenantFieldName];
|
|
15
|
+
if (tenantRow !== undefined && tenantRow !== null) {
|
|
16
|
+
acc.push(extractID(tenantRow));
|
|
16
17
|
}
|
|
17
18
|
return acc;
|
|
18
19
|
}, []) || [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/getUserTenantIDs.ts"],"sourcesContent":["import type { Tenant, UserWithTenantsField } from '../types.js'\n\nimport { defaults } from '../defaults.js'\nimport { extractID } from './extractID.js'\n\n/**\n * Returns array of all tenant IDs assigned to a user\n *\n * @param user - User object with tenants field\n */\nexport const getUserTenantIDs = <IDType extends number | string>(\n user: null | UserWithTenantsField,\n options?: {\n tenantsArrayFieldName?: string\n tenantsArrayTenantFieldName?: string\n },\n): IDType[] => {\n if (!user) {\n return []\n }\n\n const tenantsArrayFieldName = options?.tenantsArrayFieldName || defaults.tenantsArrayFieldName\n const tenantsArrayTenantFieldName =\n options?.tenantsArrayTenantFieldName || defaults.tenantsArrayTenantFieldName\n\n return (\n (Array.isArray(user[tenantsArrayFieldName]) ? user[tenantsArrayFieldName] : [])?.reduce<\n IDType[]\n >((acc, row) => {\n
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/getUserTenantIDs.ts"],"sourcesContent":["import type { Tenant, UserWithTenantsField } from '../types.js'\n\nimport { defaults } from '../defaults.js'\nimport { extractID } from './extractID.js'\n\n/**\n * Returns array of all tenant IDs assigned to a user\n *\n * @param user - User object with tenants field\n */\nexport const getUserTenantIDs = <IDType extends number | string>(\n user: null | UserWithTenantsField,\n options?: {\n tenantsArrayFieldName?: string\n tenantsArrayTenantFieldName?: string\n },\n): IDType[] => {\n if (!user) {\n return []\n }\n\n const tenantsArrayFieldName = options?.tenantsArrayFieldName || defaults.tenantsArrayFieldName\n const tenantsArrayTenantFieldName =\n options?.tenantsArrayTenantFieldName || defaults.tenantsArrayTenantFieldName\n\n return (\n (Array.isArray(user[tenantsArrayFieldName]) ? user[tenantsArrayFieldName] : [])?.reduce<\n IDType[]\n >((acc, row) => {\n const tenantRow = row[tenantsArrayTenantFieldName] as Tenant<IDType>\n if (tenantRow !== undefined && tenantRow !== null) {\n acc.push(extractID(tenantRow))\n }\n\n return acc\n }, []) || []\n )\n}\n"],"names":["defaults","extractID","getUserTenantIDs","user","options","tenantsArrayFieldName","tenantsArrayTenantFieldName","Array","isArray","reduce","acc","row","tenantRow","undefined","push"],"mappings":"AAEA,SAASA,QAAQ,QAAQ,iBAAgB;AACzC,SAASC,SAAS,QAAQ,iBAAgB;AAE1C;;;;CAIC,GACD,OAAO,MAAMC,mBAAmB,CAC9BC,MACAC;IAKA,IAAI,CAACD,MAAM;QACT,OAAO,EAAE;IACX;IAEA,MAAME,wBAAwBD,SAASC,yBAAyBL,SAASK,qBAAqB;IAC9F,MAAMC,8BACJF,SAASE,+BAA+BN,SAASM,2BAA2B;IAE9E,OACE,AAACC,CAAAA,MAAMC,OAAO,CAACL,IAAI,CAACE,sBAAsB,IAAIF,IAAI,CAACE,sBAAsB,GAAG,EAAE,AAAD,GAAII,OAE/E,CAACC,KAAKC;QACN,MAAMC,YAAYD,GAAG,CAACL,4BAA4B;QAClD,IAAIM,cAAcC,aAAaD,cAAc,MAAM;YACjDF,IAAII,IAAI,CAACb,UAAUW;QACrB;QAEA,OAAOF;IACT,GAAG,EAAE,KAAK,EAAE;AAEhB,EAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { MultiTenantPluginConfig } from '../types.js';
|
|
3
|
+
type Args<ConfigType = unknown> = {
|
|
4
|
+
/**
|
|
5
|
+
* The tenant already stored on the document, when there is one.
|
|
6
|
+
*/
|
|
7
|
+
previousValue?: unknown;
|
|
8
|
+
req: PayloadRequest;
|
|
9
|
+
tenantsArrayFieldName: string;
|
|
10
|
+
tenantsArrayTenantFieldName: string;
|
|
11
|
+
userHasAccessToAllTenants?: MultiTenantPluginConfig<ConfigType>['userHasAccessToAllTenants'];
|
|
12
|
+
value: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Whether the submitted tenant value is one the user is allowed to assign.
|
|
16
|
+
*
|
|
17
|
+
* Reads the user's assignments from `req` rather than querying, so it can't conflict with the
|
|
18
|
+
* write's own transaction. Allows all-tenants users, unchanged values, and userless Local API
|
|
19
|
+
* writes (seeds, migrations); rejects assigning an unassigned tenant or clearing an existing one.
|
|
20
|
+
*/
|
|
21
|
+
export declare const isValidTenantAssignment: <ConfigType = unknown>({ previousValue, req, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, value, }: Args<ConfigType>) => boolean;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=isValidTenantAssignment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isValidTenantAssignment.d.ts","sourceRoot":"","sources":["../../src/utilities/isValidTenantAssignment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAa,MAAM,SAAS,CAAA;AAExD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAK1D,KAAK,IAAI,CAAC,UAAU,GAAG,OAAO,IAAI;IAChC;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;IACnB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,2BAA2B,EAAE,MAAM,CAAA;IACnC,yBAAyB,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC,2BAA2B,CAAC,CAAA;IAC5F,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAI,UAAU,2HAO/C,IAAI,CAAC,UAAU,CAAC,KAAG,OAwCrB,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getTenantIDsFromValue } from './getTenantIDsFromValue.js';
|
|
2
|
+
import { getUserTenantIDs } from './getUserTenantIDs.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the submitted tenant value is one the user is allowed to assign.
|
|
5
|
+
*
|
|
6
|
+
* Reads the user's assignments from `req` rather than querying, so it can't conflict with the
|
|
7
|
+
* write's own transaction. Allows all-tenants users, unchanged values, and userless Local API
|
|
8
|
+
* writes (seeds, migrations); rejects assigning an unassigned tenant or clearing an existing one.
|
|
9
|
+
*/ export const isValidTenantAssignment = ({ previousValue, req, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, value })=>{
|
|
10
|
+
const { user } = req;
|
|
11
|
+
if (user) {
|
|
12
|
+
if (userHasAccessToAllTenants?.(user)) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
} else if (req.payloadAPI === 'local') {
|
|
16
|
+
// Seeds, migrations and scripts. There is no user to check against, and this is
|
|
17
|
+
// server-side code rather than an inbound request. An unauthenticated REST or
|
|
18
|
+
// GraphQL request keeps `payloadAPI` set to that API, so it is still checked.
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const submittedDocTenantIDs = getTenantIDsFromValue(value);
|
|
22
|
+
const storedDocTenantIDs = getTenantIDsFromValue(previousValue);
|
|
23
|
+
if (!hasTenantValueChanged(submittedDocTenantIDs, storedDocTenantIDs)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (submittedDocTenantIDs.length === 0) {
|
|
27
|
+
// deny clearing an existing tenant
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const userAssignedTenantIDs = getUserTenantIDs(user, {
|
|
31
|
+
tenantsArrayFieldName,
|
|
32
|
+
tenantsArrayTenantFieldName
|
|
33
|
+
});
|
|
34
|
+
return submittedDocTenantIDs.every((tenantID)=>userAssignedTenantIDs.some((userAssignedTenantID)=>String(userAssignedTenantID) === String(tenantID)));
|
|
35
|
+
};
|
|
36
|
+
const hasTenantValueChanged = (submittedTenantIDs, storedTenantIDs)=>submittedTenantIDs.length !== storedTenantIDs.length || submittedTenantIDs.some((tenantID)=>!storedTenantIDs.includes(tenantID));
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=isValidTenantAssignment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/isValidTenantAssignment.ts"],"sourcesContent":["import type { PayloadRequest, TypedUser } from 'payload'\n\nimport type { MultiTenantPluginConfig } from '../types.js'\n\nimport { getTenantIDsFromValue } from './getTenantIDsFromValue.js'\nimport { getUserTenantIDs } from './getUserTenantIDs.js'\n\ntype Args<ConfigType = unknown> = {\n /**\n * The tenant already stored on the document, when there is one.\n */\n previousValue?: unknown\n req: PayloadRequest\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n userHasAccessToAllTenants?: MultiTenantPluginConfig<ConfigType>['userHasAccessToAllTenants']\n value: unknown\n}\n\n/**\n * Whether the submitted tenant value is one the user is allowed to assign.\n *\n * Reads the user's assignments from `req` rather than querying, so it can't conflict with the\n * write's own transaction. Allows all-tenants users, unchanged values, and userless Local API\n * writes (seeds, migrations); rejects assigning an unassigned tenant or clearing an existing one.\n */\nexport const isValidTenantAssignment = <ConfigType = unknown>({\n previousValue,\n req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n value,\n}: Args<ConfigType>): boolean => {\n const { user } = req\n\n if (user) {\n if (\n userHasAccessToAllTenants?.(\n user as ConfigType extends { user: unknown } ? ConfigType['user'] : TypedUser,\n )\n ) {\n return true\n }\n } else if (req.payloadAPI === 'local') {\n // Seeds, migrations and scripts. There is no user to check against, and this is\n // server-side code rather than an inbound request. An unauthenticated REST or\n // GraphQL request keeps `payloadAPI` set to that API, so it is still checked.\n return true\n }\n\n const submittedDocTenantIDs = getTenantIDsFromValue(value)\n const storedDocTenantIDs = getTenantIDsFromValue(previousValue)\n\n if (!hasTenantValueChanged(submittedDocTenantIDs, storedDocTenantIDs)) {\n return true\n }\n\n if (submittedDocTenantIDs.length === 0) {\n // deny clearing an existing tenant\n return false\n }\n\n const userAssignedTenantIDs = getUserTenantIDs(user, {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n })\n\n return submittedDocTenantIDs.every((tenantID) =>\n userAssignedTenantIDs.some(\n (userAssignedTenantID) => String(userAssignedTenantID) === String(tenantID),\n ),\n )\n}\n\nconst hasTenantValueChanged = (\n submittedTenantIDs: (number | string)[],\n storedTenantIDs: (number | string)[],\n): boolean =>\n submittedTenantIDs.length !== storedTenantIDs.length ||\n submittedTenantIDs.some((tenantID) => !storedTenantIDs.includes(tenantID))\n"],"names":["getTenantIDsFromValue","getUserTenantIDs","isValidTenantAssignment","previousValue","req","tenantsArrayFieldName","tenantsArrayTenantFieldName","userHasAccessToAllTenants","value","user","payloadAPI","submittedDocTenantIDs","storedDocTenantIDs","hasTenantValueChanged","length","userAssignedTenantIDs","every","tenantID","some","userAssignedTenantID","String","submittedTenantIDs","storedTenantIDs","includes"],"mappings":"AAIA,SAASA,qBAAqB,QAAQ,6BAA4B;AAClE,SAASC,gBAAgB,QAAQ,wBAAuB;AAcxD;;;;;;CAMC,GACD,OAAO,MAAMC,0BAA0B,CAAuB,EAC5DC,aAAa,EACbC,GAAG,EACHC,qBAAqB,EACrBC,2BAA2B,EAC3BC,yBAAyB,EACzBC,KAAK,EACY;IACjB,MAAM,EAAEC,IAAI,EAAE,GAAGL;IAEjB,IAAIK,MAAM;QACR,IACEF,4BACEE,OAEF;YACA,OAAO;QACT;IACF,OAAO,IAAIL,IAAIM,UAAU,KAAK,SAAS;QACrC,gFAAgF;QAChF,8EAA8E;QAC9E,8EAA8E;QAC9E,OAAO;IACT;IAEA,MAAMC,wBAAwBX,sBAAsBQ;IACpD,MAAMI,qBAAqBZ,sBAAsBG;IAEjD,IAAI,CAACU,sBAAsBF,uBAAuBC,qBAAqB;QACrE,OAAO;IACT;IAEA,IAAID,sBAAsBG,MAAM,KAAK,GAAG;QACtC,mCAAmC;QACnC,OAAO;IACT;IAEA,MAAMC,wBAAwBd,iBAAiBQ,MAAM;QACnDJ;QACAC;IACF;IAEA,OAAOK,sBAAsBK,KAAK,CAAC,CAACC,WAClCF,sBAAsBG,IAAI,CACxB,CAACC,uBAAyBC,OAAOD,0BAA0BC,OAAOH;AAGxE,EAAC;AAED,MAAMJ,wBAAwB,CAC5BQ,oBACAC,kBAEAD,mBAAmBP,MAAM,KAAKQ,gBAAgBR,MAAM,IACpDO,mBAAmBH,IAAI,CAAC,CAACD,WAAa,CAACK,gBAAgBC,QAAQ,CAACN"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { isValidTenantAssignment } from './isValidTenantAssignment.js';
|
|
3
|
+
const tenantsArrayFieldName = 'tenants';
|
|
4
|
+
const tenantsArrayTenantFieldName = 'tenant';
|
|
5
|
+
const memberOfTenantA = {
|
|
6
|
+
id: 'user-1',
|
|
7
|
+
collection: 'users',
|
|
8
|
+
tenants: [
|
|
9
|
+
{
|
|
10
|
+
tenant: 'tenant-a'
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
};
|
|
14
|
+
const check = ({ payloadAPI = 'REST', previousValue, user = memberOfTenantA, userHasAccessToAllTenants, value })=>isValidTenantAssignment({
|
|
15
|
+
previousValue,
|
|
16
|
+
req: {
|
|
17
|
+
payloadAPI,
|
|
18
|
+
user
|
|
19
|
+
},
|
|
20
|
+
tenantsArrayFieldName,
|
|
21
|
+
tenantsArrayTenantFieldName,
|
|
22
|
+
userHasAccessToAllTenants,
|
|
23
|
+
value
|
|
24
|
+
});
|
|
25
|
+
describe('isValidTenantAssignment', ()=>{
|
|
26
|
+
it('should allow a tenant the user is assigned to', ()=>{
|
|
27
|
+
expect(check({
|
|
28
|
+
value: 'tenant-a'
|
|
29
|
+
})).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
it('should reject a tenant the user is not assigned to', ()=>{
|
|
32
|
+
expect(check({
|
|
33
|
+
value: 'tenant-b'
|
|
34
|
+
})).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it('should allow an unchanged tenant even when it sits outside the user list', ()=>{
|
|
37
|
+
expect(check({
|
|
38
|
+
previousValue: 'tenant-b',
|
|
39
|
+
value: 'tenant-b'
|
|
40
|
+
})).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it('should reject clearing an existing tenant', ()=>{
|
|
43
|
+
expect(check({
|
|
44
|
+
previousValue: 'tenant-a',
|
|
45
|
+
value: null
|
|
46
|
+
})).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
it('should allow a userless Local API write', ()=>{
|
|
49
|
+
expect(check({
|
|
50
|
+
payloadAPI: 'local',
|
|
51
|
+
user: null,
|
|
52
|
+
value: 'tenant-b'
|
|
53
|
+
})).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it('should reject a userless request that names a tenant', ()=>{
|
|
56
|
+
expect(check({
|
|
57
|
+
user: null,
|
|
58
|
+
value: 'tenant-b'
|
|
59
|
+
})).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it('should allow any tenant for a user with access to all tenants', ()=>{
|
|
62
|
+
expect(check({
|
|
63
|
+
userHasAccessToAllTenants: ()=>true,
|
|
64
|
+
value: 'tenant-b'
|
|
65
|
+
})).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it('should never call userHasAccessToAllTenants with a null user', ()=>{
|
|
68
|
+
// A config predicate may dereference `user`; a userless write must not reach it.
|
|
69
|
+
const predicate = (user)=>user.roles.includes('a');
|
|
70
|
+
expect(()=>check({
|
|
71
|
+
payloadAPI: 'local',
|
|
72
|
+
user: null,
|
|
73
|
+
userHasAccessToAllTenants: predicate,
|
|
74
|
+
value: null
|
|
75
|
+
})).not.toThrow();
|
|
76
|
+
});
|
|
77
|
+
it('should reject a hasMany value that mixes assigned and unassigned tenants', ()=>{
|
|
78
|
+
expect(check({
|
|
79
|
+
value: [
|
|
80
|
+
'tenant-a',
|
|
81
|
+
'tenant-b'
|
|
82
|
+
]
|
|
83
|
+
})).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
it('should treat a numeric ID and its string form as the same tenant', ()=>{
|
|
86
|
+
const user = {
|
|
87
|
+
id: 1,
|
|
88
|
+
collection: 'users',
|
|
89
|
+
tenants: [
|
|
90
|
+
{
|
|
91
|
+
tenant: 1
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
expect(check({
|
|
96
|
+
user,
|
|
97
|
+
value: '1'
|
|
98
|
+
})).toBe(true);
|
|
99
|
+
expect(check({
|
|
100
|
+
user,
|
|
101
|
+
value: 1
|
|
102
|
+
})).toBe(true);
|
|
103
|
+
expect(check({
|
|
104
|
+
user,
|
|
105
|
+
value: '2'
|
|
106
|
+
})).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
it('should reject a tenant value it cannot read rather than throwing', ()=>{
|
|
109
|
+
expect(check({
|
|
110
|
+
value: [
|
|
111
|
+
null
|
|
112
|
+
]
|
|
113
|
+
})).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=isValidTenantAssignment.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/isValidTenantAssignment.spec.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { describe, expect, it } from 'vitest'\n\nimport { isValidTenantAssignment } from './isValidTenantAssignment.js'\n\nconst tenantsArrayFieldName = 'tenants'\nconst tenantsArrayTenantFieldName = 'tenant'\n\nconst memberOfTenantA = {\n id: 'user-1',\n collection: 'users',\n tenants: [{ tenant: 'tenant-a' }],\n}\n\ntype CheckArgs = {\n payloadAPI?: string\n previousValue?: unknown\n user?: unknown\n userHasAccessToAllTenants?: (user: unknown) => boolean\n value: unknown\n}\n\nconst check = ({\n payloadAPI = 'REST',\n previousValue,\n user = memberOfTenantA,\n userHasAccessToAllTenants,\n value,\n}: CheckArgs): boolean =>\n isValidTenantAssignment({\n previousValue,\n req: { payloadAPI, user } as unknown as PayloadRequest,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n value,\n })\n\ndescribe('isValidTenantAssignment', () => {\n it('should allow a tenant the user is assigned to', () => {\n expect(check({ value: 'tenant-a' })).toBe(true)\n })\n\n it('should reject a tenant the user is not assigned to', () => {\n expect(check({ value: 'tenant-b' })).toBe(false)\n })\n\n it('should allow an unchanged tenant even when it sits outside the user list', () => {\n expect(check({ previousValue: 'tenant-b', value: 'tenant-b' })).toBe(true)\n })\n\n it('should reject clearing an existing tenant', () => {\n expect(check({ previousValue: 'tenant-a', value: null })).toBe(false)\n })\n\n it('should allow a userless Local API write', () => {\n expect(check({ payloadAPI: 'local', user: null, value: 'tenant-b' })).toBe(true)\n })\n\n it('should reject a userless request that names a tenant', () => {\n expect(check({ user: null, value: 'tenant-b' })).toBe(false)\n })\n\n it('should allow any tenant for a user with access to all tenants', () => {\n expect(check({ userHasAccessToAllTenants: () => true, value: 'tenant-b' })).toBe(true)\n })\n\n it('should never call userHasAccessToAllTenants with a null user', () => {\n // A config predicate may dereference `user`; a userless write must not reach it.\n const predicate = (user: unknown): boolean => (user as { roles: string[] }).roles.includes('a')\n\n expect(() =>\n check({ payloadAPI: 'local', user: null, userHasAccessToAllTenants: predicate, value: null }),\n ).not.toThrow()\n })\n\n it('should reject a hasMany value that mixes assigned and unassigned tenants', () => {\n expect(check({ value: ['tenant-a', 'tenant-b'] })).toBe(false)\n })\n\n it('should treat a numeric ID and its string form as the same tenant', () => {\n const user = { id: 1, collection: 'users', tenants: [{ tenant: 1 }] }\n\n expect(check({ user, value: '1' })).toBe(true)\n expect(check({ user, value: 1 })).toBe(true)\n expect(check({ user, value: '2' })).toBe(false)\n })\n\n it('should reject a tenant value it cannot read rather than throwing', () => {\n expect(check({ value: [null] })).toBe(false)\n })\n})\n"],"names":["describe","expect","it","isValidTenantAssignment","tenantsArrayFieldName","tenantsArrayTenantFieldName","memberOfTenantA","id","collection","tenants","tenant","check","payloadAPI","previousValue","user","userHasAccessToAllTenants","value","req","toBe","predicate","roles","includes","not","toThrow"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,uBAAuB,QAAQ,+BAA8B;AAEtE,MAAMC,wBAAwB;AAC9B,MAAMC,8BAA8B;AAEpC,MAAMC,kBAAkB;IACtBC,IAAI;IACJC,YAAY;IACZC,SAAS;QAAC;YAAEC,QAAQ;QAAW;KAAE;AACnC;AAUA,MAAMC,QAAQ,CAAC,EACbC,aAAa,MAAM,EACnBC,aAAa,EACbC,OAAOR,eAAe,EACtBS,yBAAyB,EACzBC,KAAK,EACK,GACVb,wBAAwB;QACtBU;QACAI,KAAK;YAAEL;YAAYE;QAAK;QACxBV;QACAC;QACAU;QACAC;IACF;AAEFhB,SAAS,2BAA2B;IAClCE,GAAG,iDAAiD;QAClDD,OAAOU,MAAM;YAAEK,OAAO;QAAW,IAAIE,IAAI,CAAC;IAC5C;IAEAhB,GAAG,sDAAsD;QACvDD,OAAOU,MAAM;YAAEK,OAAO;QAAW,IAAIE,IAAI,CAAC;IAC5C;IAEAhB,GAAG,4EAA4E;QAC7ED,OAAOU,MAAM;YAAEE,eAAe;YAAYG,OAAO;QAAW,IAAIE,IAAI,CAAC;IACvE;IAEAhB,GAAG,6CAA6C;QAC9CD,OAAOU,MAAM;YAAEE,eAAe;YAAYG,OAAO;QAAK,IAAIE,IAAI,CAAC;IACjE;IAEAhB,GAAG,2CAA2C;QAC5CD,OAAOU,MAAM;YAAEC,YAAY;YAASE,MAAM;YAAME,OAAO;QAAW,IAAIE,IAAI,CAAC;IAC7E;IAEAhB,GAAG,wDAAwD;QACzDD,OAAOU,MAAM;YAAEG,MAAM;YAAME,OAAO;QAAW,IAAIE,IAAI,CAAC;IACxD;IAEAhB,GAAG,iEAAiE;QAClED,OAAOU,MAAM;YAAEI,2BAA2B,IAAM;YAAMC,OAAO;QAAW,IAAIE,IAAI,CAAC;IACnF;IAEAhB,GAAG,gEAAgE;QACjE,iFAAiF;QACjF,MAAMiB,YAAY,CAACL,OAA2B,AAACA,KAA6BM,KAAK,CAACC,QAAQ,CAAC;QAE3FpB,OAAO,IACLU,MAAM;gBAAEC,YAAY;gBAASE,MAAM;gBAAMC,2BAA2BI;gBAAWH,OAAO;YAAK,IAC3FM,GAAG,CAACC,OAAO;IACf;IAEArB,GAAG,4EAA4E;QAC7ED,OAAOU,MAAM;YAAEK,OAAO;gBAAC;gBAAY;aAAW;QAAC,IAAIE,IAAI,CAAC;IAC1D;IAEAhB,GAAG,oEAAoE;QACrE,MAAMY,OAAO;YAAEP,IAAI;YAAGC,YAAY;YAASC,SAAS;gBAAC;oBAAEC,QAAQ;gBAAE;aAAE;QAAC;QAEpET,OAAOU,MAAM;YAAEG;YAAME,OAAO;QAAI,IAAIE,IAAI,CAAC;QACzCjB,OAAOU,MAAM;YAAEG;YAAME,OAAO;QAAE,IAAIE,IAAI,CAAC;QACvCjB,OAAOU,MAAM;YAAEG;YAAME,OAAO;QAAI,IAAIE,IAAI,CAAC;IAC3C;IAEAhB,GAAG,oEAAoE;QACrED,OAAOU,MAAM;YAAEK,OAAO;gBAAC;aAAK;QAAC,IAAIE,IAAI,CAAC;IACxC;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/plugin-multi-tenant",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.90.0",
|
|
4
4
|
"description": "Multi Tenant plugin for Payload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
"types.d.ts"
|
|
81
81
|
],
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@payloadcms/ui": "3.89.0-internal.95c9cba",
|
|
84
|
-
"payload": "3.89.0-internal.95c9cba",
|
|
85
83
|
"@payloadcms/eslint-config": "3.28.0",
|
|
86
|
-
"@payloadcms/
|
|
84
|
+
"@payloadcms/ui": "3.90.0",
|
|
85
|
+
"@payloadcms/translations": "3.90.0",
|
|
86
|
+
"payload": "3.90.0"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"@payloadcms/ui": "3.
|
|
90
|
-
"payload": "3.
|
|
89
|
+
"@payloadcms/ui": "3.90.0",
|
|
90
|
+
"payload": "3.90.0"
|
|
91
91
|
},
|
|
92
92
|
"homepage:": "https://payloadcms.com",
|
|
93
93
|
"scripts": {
|