@payloadcms/plugin-multi-tenant 4.0.0-internal.af6aad0 → 4.0.0-internal.b8a92d2
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 +4 -2
- package/dist/fields/tenantField/index.d.ts.map +1 -1
- package/dist/fields/tenantField/index.js +36 -9
- package/dist/fields/tenantField/index.js.map +1 -1
- package/dist/hooks/enforceTenantMembership.d.ts +15 -0
- package/dist/hooks/enforceTenantMembership.d.ts.map +1 -0
- package/dist/hooks/enforceTenantMembership.js +39 -0
- package/dist/hooks/enforceTenantMembership.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/TenantSelectionProvider/index.client.d.ts.map +1 -1
- package/dist/providers/TenantSelectionProvider/index.client.js +22 -24
- package/dist/providers/TenantSelectionProvider/index.client.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 +13 -0
- package/dist/utilities/isValidTenantAssignment.d.ts.map +1 -0
- package/dist/utilities/isValidTenantAssignment.js +28 -0
- package/dist/utilities/isValidTenantAssignment.js.map +1 -0
- package/dist/utilities/shouldRefreshTenantSelection.d.ts +5 -0
- package/dist/utilities/shouldRefreshTenantSelection.d.ts.map +1 -0
- package/dist/utilities/shouldRefreshTenantSelection.js +3 -0
- package/dist/utilities/shouldRefreshTenantSelection.js.map +1 -0
- package/package.json +7 -7
- package/dist/index.spec.js +0 -70
- package/dist/index.spec.js.map +0 -1
- package/dist/utilities/addCollectionAccess.spec.js +0 -134
- package/dist/utilities/addCollectionAccess.spec.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SingleRelationshipField } from 'payload';
|
|
2
|
-
import type { RootTenantFieldConfigOverrides } from '../../types.js';
|
|
2
|
+
import type { MultiTenantPluginConfig, RootTenantFieldConfigOverrides } from '../../types.js';
|
|
3
3
|
type Args = {
|
|
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['userHasAccessToAllTenants'];
|
|
12
14
|
};
|
|
13
|
-
export declare const tenantField: ({ name, debug, isAutosaveEnabled, overrides: _overrides, tenantsArrayFieldName, tenantsArrayTenantFieldName, tenantsCollectionSlug, unique, }: Args) => SingleRelationshipField;
|
|
15
|
+
export declare const tenantField: ({ name, adminUsersSlug, debug, isAutosaveEnabled, overrides: _overrides, tenantsArrayFieldName, tenantsArrayTenantFieldName, tenantsCollectionSlug, unique, userHasAccessToAllTenants, }: Args) => 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;AAsD7F,KAAK,IAAI,GAAG;IACV,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,2BAA2B,CAAC,CAAA;CACjF,CAAA;AACD,eAAO,MAAM,WAAW,6LAWrB,IAAI,KAAG,uBAmHT,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,31 @@ const fieldValidation = (validateFunction)=>(value, options)=>{
|
|
|
18
19
|
return options.req.t('validation:required');
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
if (isValidTenantAssignment({
|
|
23
|
+
previousValue: options.previousValue,
|
|
24
|
+
req: options.req,
|
|
25
|
+
tenantsArrayFieldName,
|
|
26
|
+
tenantsArrayTenantFieldName,
|
|
27
|
+
userHasAccessToAllTenants,
|
|
28
|
+
value
|
|
29
|
+
})) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return options.req.t('validation:invalidSelection');
|
|
22
33
|
};
|
|
23
|
-
export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosaveEnabled, overrides: _overrides = {}, tenantsArrayFieldName = defaults.tenantsArrayFieldName, tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName, tenantsCollectionSlug = defaults.tenantCollectionSlug, unique })=>{
|
|
34
|
+
export const tenantField = ({ name = defaults.tenantFieldName, adminUsersSlug, debug, isAutosaveEnabled, overrides: _overrides = {}, tenantsArrayFieldName = defaults.tenantsArrayFieldName, tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName, tenantsCollectionSlug = defaults.tenantCollectionSlug, unique, userHasAccessToAllTenants })=>{
|
|
24
35
|
const { hasMany = false, validate, ...overrides } = _overrides || {};
|
|
25
36
|
return {
|
|
26
37
|
...overrides || {},
|
|
27
38
|
name,
|
|
28
39
|
type: 'relationship',
|
|
29
|
-
access:
|
|
40
|
+
access: {
|
|
41
|
+
...adminUsersSlug ? {
|
|
42
|
+
create: ({ req })=>Boolean(req.user && req.user.collection === adminUsersSlug),
|
|
43
|
+
update: ({ req })=>Boolean(req.user && req.user.collection === adminUsersSlug)
|
|
44
|
+
} : {},
|
|
45
|
+
...overrides.access || {}
|
|
46
|
+
},
|
|
30
47
|
admin: {
|
|
31
48
|
allowCreate: false,
|
|
32
49
|
allowEdit: false,
|
|
@@ -57,7 +74,7 @@ export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosave
|
|
|
57
74
|
});
|
|
58
75
|
const tenantFromCookie = getTenantFromCookie(req.headers, idType);
|
|
59
76
|
if (tenantFromCookie) {
|
|
60
|
-
const
|
|
77
|
+
const { totalDocs } = await req.payload.count({
|
|
61
78
|
collection: tenantsCollectionSlug,
|
|
62
79
|
overrideAccess: false,
|
|
63
80
|
req,
|
|
@@ -70,7 +87,7 @@ export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosave
|
|
|
70
87
|
}
|
|
71
88
|
}
|
|
72
89
|
});
|
|
73
|
-
return
|
|
90
|
+
return totalDocs > 0 ? tenantFromCookie : null;
|
|
74
91
|
}
|
|
75
92
|
if (req.user && isAutosaveEnabled) {
|
|
76
93
|
const userTenants = getUserTenantIDs(req.user, {
|
|
@@ -103,11 +120,21 @@ export const tenantField = ({ name = defaults.tenantFieldName, debug, isAutosave
|
|
|
103
120
|
...hasMany ? {
|
|
104
121
|
hasMany: true,
|
|
105
122
|
// TODO: V4 - replace validation with required: true
|
|
106
|
-
validate: fieldValidation(
|
|
123
|
+
validate: fieldValidation({
|
|
124
|
+
tenantsArrayFieldName,
|
|
125
|
+
tenantsArrayTenantFieldName,
|
|
126
|
+
userHasAccessToAllTenants,
|
|
127
|
+
validateFunction: validate
|
|
128
|
+
})
|
|
107
129
|
} : {
|
|
108
130
|
hasMany: false,
|
|
109
131
|
// TODO: V4 - replace validation with required: true
|
|
110
|
-
validate: fieldValidation(
|
|
132
|
+
validate: fieldValidation({
|
|
133
|
+
tenantsArrayFieldName,
|
|
134
|
+
tenantsArrayTenantFieldName,
|
|
135
|
+
userHasAccessToAllTenants,
|
|
136
|
+
validateFunction: validate
|
|
137
|
+
})
|
|
111
138
|
},
|
|
112
139
|
// @ts-expect-error translations are not typed for this plugin
|
|
113
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 disabled: { column: true, filter: true, groupBy: 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","disabled","column","filter","groupBy","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":"AAIA,SAASA,QAAQ,QAAQ,oBAAmB;AAC5C,SAASC,mBAAmB,QAAQ,yCAAwC;AAC5E,SAASC,mBAAmB,QAAQ,yCAAwC;AAC5E,SAASC,gBAAgB,QAAQ,sCAAqC;AAEtE,MAAMC,kBACJ,CAACC,mBACD,CAACC,OAAOC;QACN,IAAIF,kBAAkB;YACpB,MAAMG,SAASH,iBAAiBC,OAAOC;YACvC,IAAIC,WAAW,MAAM;gBACnB,OAAOA;YACT;QACF;QAEA,IAAID,QAAQE,OAAO,EAAE;YACnB,IAAI,CAACH,SAAUI,MAAMC,OAAO,CAACL,UAAUA,MAAMM,MAAM,KAAK,GAAI;gBAC1D,OAAOL,QAAQM,GAAG,CAACC,CAAC,CAAC;YACvB;QACF,OAAO;YACL,IAAI,CAACR,OAAO;gBACV,OAAOC,QAAQM,GAAG,CAACC,CAAC,CAAC;YACvB;QACF;QAEA,OAAO;IACT;AAYF,OAAO,MAAMC,cAAc,CAAC,EAC1BC,OAAOhB,SAASiB,eAAe,EAC/BC,KAAK,EACLC,iBAAiB,EACjBC,WAAWC,aAAa,CAAC,CAAC,EAC1BC,wBAAwBtB,SAASsB,qBAAqB,EACtDC,8BAA8BvB,SAASuB,2BAA2B,EAClEC,wBAAwBxB,SAASyB,oBAAoB,EACrDC,MAAM,EACD;IACL,MAAM,EAAEjB,UAAU,KAAK,EAAEkB,QAAQ,EAAE,GAAGP,WAAW,GAAGC,cAAc,CAAC;IACnE,OAAO;QACL,GAAID,aAAa,CAAC,CAAC;QACnBJ;QACAY,MAAM;QACNC,QAAQT,UAAUS,MAAM,IAAI,CAAC;QAC7BC,OAAO;YACLC,aAAa;YACbC,WAAW;YACXC,UAAU;gBAAEC,QAAQ;gBAAMC,QAAQ;gBAAMC,SAAS;YAAK;YACtDC,UAAU;YACV,GAAIjB,UAAUU,KAAK,IAAI,CAAC,CAAC;YACzBQ,YAAY;gBACV,GAAIlB,UAAUU,KAAK,EAAEQ,cAAc,CAAC,CAAC;gBACrCC,OAAO;oBACLC,MAAM;oBACN,GAAI,OAAOpB,UAAUU,KAAK,EAAEQ,YAAYC,UAAU,WAC9CnB,UAAUU,KAAK,EAAEQ,YAAYC,SAAS,CAAC,IACvC,CAAC,CAAC;oBACNE,aAAa;wBACX,GAAI,OAAOrB,UAAUU,KAAK,EAAEQ,YAAYC,UAAU,WAC7CnB,CAAAA,UAAUU,KAAK,EAAEQ,YAAYC,SAAS,CAAC,CAAA,GAAIE,cAC5C,CAAC,CAAC;wBACNvB;wBACAQ;oBACF;gBACF;YACF;QACF;QACAgB,cACEtB,UAAUsB,YAAY,IACrB,CAAA,OAAO,EAAE7B,GAAG,EAAE;YACb,MAAM8B,SAAS1C,oBAAoB;gBACjC2C,gBAAgBpB;gBAChBqB,SAAShC,IAAIgC,OAAO;YACtB;YACA,MAAMC,mBAAmB5C,oBAAoBW,IAAIkC,OAAO,EAAEJ;YAC1D,IAAIG,kBAAkB;gBACpB,MAAME,gBAAgB,MAAMnC,IAAIgC,OAAO,CAACI,KAAK,CAAC;oBAC5CC,YAAY1B;oBACZ2B,gBAAgB;oBAChBtC;oBACAuC,MAAMvC,IAAIuC,IAAI;oBACdC,OAAO;wBACLC,IAAI;4BACFC,IAAI;gCAACT;6BAAiB;wBACxB;oBACF;gBACF;gBACA,OAAOE,gBAAgBF,mBAAmB;YAC5C;YACA,IAAIjC,IAAIuC,IAAI,IAAIjC,mBAAmB;gBACjC,MAAMqC,cAAcrD,iBAAiBU,IAAIuC,IAAI,EAAE;oBAC7C9B;oBACAC;gBACF;gBACA,IAAIiC,YAAY5C,MAAM,GAAG,GAAG;oBAC1B,OAAO4C,WAAW,CAAC,EAAE;gBACvB;YACF;YACA,OAAO;QACT,CAAA;QACFC,eACErC,UAAUqC,aAAa,IACtB,CAAA,CAAC,EAAE5C,GAAG,EAAE;YACP,MAAM6C,sBAAsBvD,iBAAiBU,IAAIuC,IAAI,EAAE;gBACrD9B;gBACAC;YACF;YACA,IAAImC,oBAAoB9C,MAAM,GAAG,GAAG;gBAClC,OAAO;oBACL0C,IAAI;wBACFC,IAAIG;oBACN;gBACF;YACF;YAEA,OAAO;QACT,CAAA;QACFC,OAAO;QACPC,YAAYpC;QACZE;QACA,GAAIjB,UACA;YACEA,SAAS;YACT,oDAAoD;YACpDkB,UAAUvB,gBAAgBuB;QAC5B,IACA;YACElB,SAAS;YACT,oDAAoD;YACpDkB,UAAUvB,gBAAgBuB;QAC5B,CAAC;QACL,8DAA8D;QAC9DkC,OAAOzC,UAAUyC,KAAK,IAAK,CAAA,CAAC,EAAE/C,CAAC,EAAE,GAAKA,EAAE,iDAAgD;IAC1F;AACF,EAAC"}
|
|
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 ({\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n validateFunction,\n }: {\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n userHasAccessToAllTenants?: MultiTenantPluginConfig['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 if (\n isValidTenantAssignment({\n previousValue: options.previousValue,\n req: options.req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n value,\n })\n ) {\n return true\n }\n\n return options.req.t('validation:invalidSelection')\n }\n\ntype Args = {\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['userHasAccessToAllTenants']\n}\nexport const tenantField = ({\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): SingleRelationshipField => {\n const { hasMany = false, validate, ...overrides } = _overrides || {}\n return {\n ...(overrides || {}),\n name,\n type: 'relationship',\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 disabled: { column: true, filter: true, groupBy: 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 // TODO: V4 - replace validation with required: true\n validate: fieldValidation({\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n validateFunction: validate as RelationshipFieldValidation,\n }),\n }\n : {\n hasMany: false,\n // TODO: V4 - replace validation with required: true\n validate: fieldValidation({\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","disabled","column","filter","groupBy","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,15 @@
|
|
|
1
|
+
import type { CollectionBeforeValidateHook, CollectionConfig } from 'payload';
|
|
2
|
+
import type { MultiTenantPluginConfig } from '../types.js';
|
|
3
|
+
type Args = {
|
|
4
|
+
collection: CollectionConfig;
|
|
5
|
+
tenantFieldName: string;
|
|
6
|
+
tenantsArrayFieldName: string;
|
|
7
|
+
tenantsArrayTenantFieldName: string;
|
|
8
|
+
userHasAccessToAllTenants: Required<MultiTenantPluginConfig>['userHasAccessToAllTenants'];
|
|
9
|
+
};
|
|
10
|
+
export declare const addCollectionBeforeValidateHook: ({ collection, tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, }: Args) => void;
|
|
11
|
+
export declare const enforceTenantMembership: ({ collectionSlug, tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, }: {
|
|
12
|
+
collectionSlug: string;
|
|
13
|
+
} & Omit<Args, 'collection'>) => CollectionBeforeValidateHook;
|
|
14
|
+
export {};
|
|
15
|
+
//# 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,EAAE,4BAA4B,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI7E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAI1D,KAAK,IAAI,GAAG;IACV,UAAU,EAAE,gBAAgB,CAAA;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,2BAA2B,EAAE,MAAM,CAAA;IACnC,yBAAyB,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,2BAA2B,CAAC,CAAA;CAC1F,CAAA;AAED,eAAO,MAAM,+BAA+B,oHAMzC,IAAI,SAYN,CAAA;AAED,eAAO,MAAM,uBAAuB,wHAO/B;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,KAAG,4BA0B1D,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
import { isValidTenantAssignment } from '../utilities/isValidTenantAssignment.js';
|
|
3
|
+
export const addCollectionBeforeValidateHook = ({ collection, tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants })=>{
|
|
4
|
+
collection.hooks ??= {};
|
|
5
|
+
collection.hooks.beforeValidate ??= [];
|
|
6
|
+
collection.hooks.beforeValidate.push(enforceTenantMembership({
|
|
7
|
+
collectionSlug: collection.slug,
|
|
8
|
+
tenantFieldName,
|
|
9
|
+
tenantsArrayFieldName,
|
|
10
|
+
tenantsArrayTenantFieldName,
|
|
11
|
+
userHasAccessToAllTenants
|
|
12
|
+
}));
|
|
13
|
+
};
|
|
14
|
+
export const enforceTenantMembership = ({ collectionSlug, tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants })=>({ data, originalDoc, req })=>{
|
|
15
|
+
if (!data || !(tenantFieldName in data)) {
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
18
|
+
if (!isValidTenantAssignment({
|
|
19
|
+
previousValue: originalDoc?.[tenantFieldName],
|
|
20
|
+
req,
|
|
21
|
+
tenantsArrayFieldName,
|
|
22
|
+
tenantsArrayTenantFieldName,
|
|
23
|
+
userHasAccessToAllTenants,
|
|
24
|
+
value: data[tenantFieldName]
|
|
25
|
+
})) {
|
|
26
|
+
throw new ValidationError({
|
|
27
|
+
collection: collectionSlug,
|
|
28
|
+
errors: [
|
|
29
|
+
{
|
|
30
|
+
message: req.t('validation:invalidSelection'),
|
|
31
|
+
path: tenantFieldName
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}, req.t);
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
//# 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 = {\n collection: CollectionConfig\n tenantFieldName: string\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n userHasAccessToAllTenants: Required<MultiTenantPluginConfig>['userHasAccessToAllTenants']\n}\n\nexport const addCollectionBeforeValidateHook = ({\n collection,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n}: Args) => {\n collection.hooks ??= {}\n collection.hooks.beforeValidate ??= []\n collection.hooks.beforeValidate.push(\n enforceTenantMembership({\n collectionSlug: collection.slug,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n }),\n )\n}\n\nexport const enforceTenantMembership =\n ({\n collectionSlug,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n }: { collectionSlug: string } & Omit<Args, 'collection'>): CollectionBeforeValidateHook =>\n ({ data, originalDoc, req }) => {\n if (!data || !(tenantFieldName in data)) {\n return data\n }\n\n if (\n !isValidTenantAssignment({\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;AAUjF,OAAO,MAAMC,kCAAkC,CAAC,EAC9CC,UAAU,EACVC,eAAe,EACfC,qBAAqB,EACrBC,2BAA2B,EAC3BC,yBAAyB,EACpB;IACLJ,WAAWK,KAAK,KAAK,CAAC;IACtBL,WAAWK,KAAK,CAACC,cAAc,KAAK,EAAE;IACtCN,WAAWK,KAAK,CAACC,cAAc,CAACC,IAAI,CAClCC,wBAAwB;QACtBC,gBAAgBT,WAAWU,IAAI;QAC/BT;QACAC;QACAC;QACAC;IACF;AAEJ,EAAC;AAED,OAAO,MAAMI,0BACX,CAAC,EACCC,cAAc,EACdR,eAAe,EACfC,qBAAqB,EACrBC,2BAA2B,EAC3BC,yBAAyB,EAC6B,GACxD,CAAC,EAAEO,IAAI,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACzB,IAAI,CAACF,QAAQ,CAAEV,CAAAA,mBAAmBU,IAAG,GAAI;YACvC,OAAOA;QACT;QAEA,IACE,CAACb,wBAAwB;YACvBgB,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,EAAC"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAezD,eAAO,MAAM,iBAAiB,gEA6b5B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { tenantField } from './fields/tenantField/index.js';
|
|
|
6
6
|
import { tenantsArrayField } from './fields/tenantsArrayField/index.js';
|
|
7
7
|
import { filterDocumentsByTenants } from './filters/filterDocumentsByTenants.js';
|
|
8
8
|
import { addTenantCleanup } from './hooks/afterTenantDelete.js';
|
|
9
|
+
import { enforceTenantMembership } from './hooks/enforceTenantMembership.js';
|
|
9
10
|
import { translations } from './translations/index.js';
|
|
10
11
|
import { addCollectionAccess } from './utilities/addCollectionAccess.js';
|
|
11
12
|
import { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js';
|
|
@@ -245,15 +246,26 @@ export const multiTenantPlugin = definePlugin({
|
|
|
245
246
|
* Add tenant field to enabled collections
|
|
246
247
|
*/ collection.fields.unshift(tenantField({
|
|
247
248
|
name: tenantFieldName,
|
|
249
|
+
adminUsersSlug: adminUsersCollection.slug,
|
|
248
250
|
debug: pluginConfig.debug,
|
|
249
251
|
isAutosaveEnabled: hasAutosaveEnabled(collection),
|
|
250
252
|
overrides: pluginConfig.collections[collection.slug]?.tenantFieldOverrides ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides : pluginConfig.tenantField || {},
|
|
251
253
|
tenantsArrayFieldName,
|
|
252
254
|
tenantsArrayTenantFieldName,
|
|
253
255
|
tenantsCollectionSlug,
|
|
254
|
-
unique: isGlobal
|
|
256
|
+
unique: isGlobal,
|
|
257
|
+
userHasAccessToAllTenants
|
|
255
258
|
}));
|
|
256
259
|
}
|
|
260
|
+
collection.hooks ??= {};
|
|
261
|
+
collection.hooks.beforeValidate ??= [];
|
|
262
|
+
collection.hooks.beforeValidate.push(enforceTenantMembership({
|
|
263
|
+
collectionSlug: collection.slug,
|
|
264
|
+
tenantFieldName,
|
|
265
|
+
tenantsArrayFieldName,
|
|
266
|
+
tenantsArrayTenantFieldName,
|
|
267
|
+
userHasAccessToAllTenants
|
|
268
|
+
}));
|
|
257
269
|
const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {};
|
|
258
270
|
if (useBaseFilter ?? useBaseListFilter ?? true) {
|
|
259
271
|
/**
|
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 } from 'payload'\n\nimport { definePlugin } from 'payload'\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, type TenantAccessConfig } 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 = definePlugin<MultiTenantPluginConfig>({\n slug: '@payloadcms/plugin-multi-tenant',\n plugin: ({ config: incomingConfig, options: pluginConfig }) => {\n if (pluginConfig.enabled === false) {\n return incomingConfig\n }\n\n /**\n * Set defaults\n */\n const userHasAccessToAllTenants: Required<MultiTenantPluginConfig>['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 const tenantAccessScopes: TenantAccessConfig[] = []\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 }) => Boolean(req.user && userHasAccessToAllTenants(req.user)),\n update: ({ req }) => Boolean(req.user && userHasAccessToAllTenants(req.user)),\n ...(pluginConfig?.tenantsArrayField?.arrayFieldAccess || {}),\n },\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n }),\n )\n }\n\n tenantAccessScopes.push({\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({\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 * 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 tenantAccessScopes.push({\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 disabled: { bulkEdit: true, column: 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 tenantAccessScopes.push({\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 addCollectionAccess({ config: incomingConfig, scopes: tenantAccessScopes })\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})\n"],"names":["definePlugin","hasAutosaveEnabled","defaults","getTenantOptionsEndpoint","tenantField","tenantsArrayField","filterDocumentsByTenants","addTenantCleanup","translations","addCollectionAccess","addFilterOptionsToFields","combineFilters","miniChalk","multiTenantPlugin","slug","plugin","config","incomingConfig","options","pluginConfig","enabled","userHasAccessToAllTenants","tenantsCollectionSlug","tenantsSlug","tenantCollectionSlug","tenantFieldName","name","tenantsArrayFieldName","arrayFieldName","tenantsArrayTenantFieldName","arrayTenantFieldName","tenantAccessScopes","admin","components","actions","beforeNav","providers","collections","adminUsersCollection","find","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","forEach","useTenantsCollectionAccess","useTenantsListFilter","cleanupAfterTenantDelete","enabledSlugs","usersSlug","usersTenantsArrayFieldName","usersTenantsArrayTenantFieldName","type","Field","path","disabled","bulkEdit","column","endpoints","useAsTitle","console","warn","disableDuplicate","debug","edit","editMenuItems","tenantEnabledCollectionSlugs","tenantEnabledGlobalSlugs","customTenantField","unshift","isAutosaveEnabled","overrides","tenantFieldOverrides","unique","useBaseFilter","useBaseListFilter","useTenantAccess","accessResultOverride","length","missingSlugs","filter","includes","error","yellowBold","scopes","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 } from 'payload'\n\nimport { definePlugin } from 'payload'\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 { enforceTenantMembership } from './hooks/enforceTenantMembership.js'\nimport { translations } from './translations/index.js'\nimport { addCollectionAccess, type TenantAccessConfig } 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 = definePlugin<MultiTenantPluginConfig>({\n slug: '@payloadcms/plugin-multi-tenant',\n plugin: ({ config: incomingConfig, options: pluginConfig }) => {\n if (pluginConfig.enabled === false) {\n return incomingConfig\n }\n\n /**\n * Set defaults\n */\n const userHasAccessToAllTenants: Required<MultiTenantPluginConfig>['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 const tenantAccessScopes: TenantAccessConfig[] = []\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 }) => Boolean(req.user && userHasAccessToAllTenants(req.user)),\n update: ({ req }) => Boolean(req.user && userHasAccessToAllTenants(req.user)),\n ...(pluginConfig?.tenantsArrayField?.arrayFieldAccess || {}),\n },\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n tenantsCollectionSlug,\n }),\n )\n }\n\n tenantAccessScopes.push({\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({\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 * 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 tenantAccessScopes.push({\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 disabled: { bulkEdit: true, column: 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 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 collection.hooks ??= {}\n collection.hooks.beforeValidate ??= []\n collection.hooks.beforeValidate.push(\n enforceTenantMembership({\n collectionSlug: collection.slug,\n tenantFieldName,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\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 tenantAccessScopes.push({\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 addCollectionAccess({ config: incomingConfig, scopes: tenantAccessScopes })\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})\n"],"names":["definePlugin","hasAutosaveEnabled","defaults","getTenantOptionsEndpoint","tenantField","tenantsArrayField","filterDocumentsByTenants","addTenantCleanup","enforceTenantMembership","translations","addCollectionAccess","addFilterOptionsToFields","combineFilters","miniChalk","multiTenantPlugin","slug","plugin","config","incomingConfig","options","pluginConfig","enabled","userHasAccessToAllTenants","tenantsCollectionSlug","tenantsSlug","tenantCollectionSlug","tenantFieldName","name","tenantsArrayFieldName","arrayFieldName","tenantsArrayTenantFieldName","arrayTenantFieldName","tenantAccessScopes","admin","components","actions","beforeNav","providers","collections","adminUsersCollection","find","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","forEach","useTenantsCollectionAccess","useTenantsListFilter","cleanupAfterTenantDelete","enabledSlugs","usersSlug","usersTenantsArrayFieldName","usersTenantsArrayTenantFieldName","type","Field","path","disabled","bulkEdit","column","endpoints","useAsTitle","console","warn","disableDuplicate","debug","edit","editMenuItems","tenantEnabledCollectionSlugs","tenantEnabledGlobalSlugs","customTenantField","unshift","isAutosaveEnabled","overrides","tenantFieldOverrides","unique","hooks","beforeValidate","collectionSlug","useBaseFilter","useBaseListFilter","useTenantAccess","accessResultOverride","length","missingSlugs","filter","includes","error","yellowBold","scopes","clientProps","serverProps","globalSlugs","label","tenantSelectorLabel","undefined","i18n","entries","locale","pluginI18nObject","typedLocale"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../../../src/providers/TenantSelectionProvider/index.client.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAI3C,OAAO,KAAwB,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../../../src/providers/TenantSelectionProvider/index.client.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAI3C,OAAO,KAAwB,MAAM,OAAO,CAAA;AAK5C,KAAK,WAAW,GAAG;IACjB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAA;IAClC;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;OAEG;IACH,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB;;OAEG;IACH,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAC7C;;OAEG;IACH,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAA;IACtF;;OAEG;IACH,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IAC1D;;;;;OAKG;IACH,SAAS,EAAE,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IACjF;;OAEG;IACH,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC;;OAEG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACtE,CAAA;AA+CD,eAAO,MAAM,6BAA6B,6EAKvC;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,oBAAoB,EAAE,YAAY,EAAE,CAAA;IACpC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,qBAAqB,EAAE,MAAM,CAAA;CAC9B,sBAkOA,CAAA;AAED,eAAO,MAAM,kBAAkB,mBAA2B,CAAA"}
|
|
@@ -4,6 +4,7 @@ import { toast, useAuth, useConfig, useRouter } from '@payloadcms/ui';
|
|
|
4
4
|
import { formatAdminURL } from 'payload/shared';
|
|
5
5
|
import React, { createContext } from 'react';
|
|
6
6
|
import { generateCookie } from '../../utilities/generateCookie.js';
|
|
7
|
+
import { shouldRefreshTenantSelection } from '../../utilities/shouldRefreshTenantSelection.js';
|
|
7
8
|
const Context = /*#__PURE__*/ createContext({
|
|
8
9
|
entityType: undefined,
|
|
9
10
|
options: [],
|
|
@@ -176,17 +177,18 @@ export const TenantSelectionProviderClient = ({ children, initialTenantOptions,
|
|
|
176
177
|
initialTenantOptions
|
|
177
178
|
]);
|
|
178
179
|
React.useEffect(()=>{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
180
|
+
const hasTenantSelectionMismatch = initialValue && String(initialValue) !== getTenantCookie();
|
|
181
|
+
if (userID && (userChanged || tenantOptions.length === 0 || hasTenantSelectionMismatch)) {
|
|
182
|
+
// Sync on login, and when the provider remounts after login without tenant options.
|
|
183
|
+
void syncTenants();
|
|
184
|
+
} else if (!userID && (userChanged || hasTenantSelectionMismatch)) {
|
|
185
|
+
// user logging out
|
|
186
|
+
setSelectedTenantID(undefined);
|
|
187
|
+
deleteTenantCookie();
|
|
188
|
+
setTenantOptions((prev)=>prev.length > 0 ? [] : prev);
|
|
189
|
+
router.refresh();
|
|
190
|
+
}
|
|
191
|
+
if (userChanged) {
|
|
190
192
|
prevUserID.current = userID;
|
|
191
193
|
}
|
|
192
194
|
}, [
|
|
@@ -194,7 +196,8 @@ export const TenantSelectionProviderClient = ({ children, initialTenantOptions,
|
|
|
194
196
|
userChanged,
|
|
195
197
|
syncTenants,
|
|
196
198
|
initialValue,
|
|
197
|
-
router
|
|
199
|
+
router,
|
|
200
|
+
tenantOptions.length
|
|
198
201
|
]);
|
|
199
202
|
/**
|
|
200
203
|
* Populate tenant options when the provider mounts already-authenticated but
|
|
@@ -219,21 +222,16 @@ export const TenantSelectionProviderClient = ({ children, initialTenantOptions,
|
|
|
219
222
|
syncTenants
|
|
220
223
|
]);
|
|
221
224
|
/**
|
|
222
|
-
* If there is no initial value
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* Only refresh when there is actually a stale cookie to clear: when no cookie
|
|
226
|
-
* is present the server already rendered tenant-less, so a refresh is
|
|
227
|
-
* redundant. This also avoids an infinite loop on framework adapters whose
|
|
228
|
-
* `router.refresh()` remounts this provider (e.g. TanStack Start) — an
|
|
229
|
-
* unconditional refresh would re-fire this mount effect and refresh again,
|
|
230
|
-
* whereas guarding on the (persistent) cookie self-terminates after the one
|
|
231
|
-
* refresh that clears it. The cookie is cleared either way via `setTenant`.
|
|
225
|
+
* If there is no initial value and a stale tenant cookie exists, clear the
|
|
226
|
+
* tenant and refresh the router.
|
|
232
227
|
*/ React.useEffect(()=>{
|
|
233
|
-
if (
|
|
228
|
+
if (shouldRefreshTenantSelection({
|
|
229
|
+
initialValue,
|
|
230
|
+
tenantCookie: getTenantCookie()
|
|
231
|
+
})) {
|
|
234
232
|
setTenant({
|
|
235
233
|
id: undefined,
|
|
236
|
-
refresh:
|
|
234
|
+
refresh: true
|
|
237
235
|
});
|
|
238
236
|
}
|
|
239
237
|
}, [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/providers/TenantSelectionProvider/index.client.tsx"],"sourcesContent":["'use client'\n\nimport type { OptionObject } from 'payload'\n\nimport { toast, useAuth, useConfig, useRouter } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport React, { createContext } from 'react'\n\nimport { generateCookie } from '../../utilities/generateCookie.js'\n\ntype ContextType = {\n /**\n * What is the context of the selector? It is either 'document' | 'global' | undefined.\n *\n * - 'document' means you are viewing a document in the context of a tenant\n * - 'global' means you are viewing a \"global\" (globals are collection documents but prevent you from viewing the list view) document in the context of a tenant\n * - undefined means you are not viewing a document at all\n */\n entityType?: 'document' | 'global'\n /**\n * Hoists the forms modified state\n */\n modified?: boolean\n /**\n * Array of options to select from\n */\n options: OptionObject[]\n /**\n * The currently selected tenant ID\n */\n selectedTenantID: number | string | undefined\n /**\n * Sets the entityType when a document is loaded and sets it to undefined when the document unmounts.\n */\n setEntityType: React.Dispatch<React.SetStateAction<'document' | 'global' | undefined>>\n /**\n * Sets the modified state\n */\n setModified: React.Dispatch<React.SetStateAction<boolean>>\n /**\n * Sets the selected tenant ID\n *\n * @param args.id - The ID of the tenant to select\n * @param args.refresh - Whether to refresh the page after changing the tenant\n */\n setTenant: (args: { id: number | string | undefined; refresh?: boolean }) => void\n /**\n * Used to sync tenants displayed in the tenant selector when updates are made to the tenants collection.\n */\n syncTenants: () => Promise<void>\n /**\n *\n */\n updateTenants: (args: { id: number | string; label: string }) => void\n}\n\nconst Context = createContext<ContextType>({\n entityType: undefined,\n options: [],\n selectedTenantID: undefined,\n setEntityType: () => undefined,\n setModified: () => undefined,\n setTenant: () => null,\n syncTenants: () => Promise.resolve(),\n updateTenants: () => null,\n})\n\nconst DEFAULT_COOKIE_NAME = 'payload-tenant'\n\nconst setTenantCookie = (args: { cookieName?: string; value: string }) => {\n const { cookieName = DEFAULT_COOKIE_NAME, value } = args\n document.cookie = generateCookie<string>({\n name: cookieName,\n maxAge: 60 * 60 * 24 * 365, // 1 year in seconds\n path: '/',\n returnCookieAsObject: false,\n value: value || '',\n })\n}\n\nconst deleteTenantCookie = (args: { cookieName?: string } = {}) => {\n const { cookieName = DEFAULT_COOKIE_NAME } = args\n document.cookie = generateCookie<string>({\n name: cookieName,\n maxAge: -1,\n path: '/',\n returnCookieAsObject: false,\n value: '',\n })\n}\n\nconst getTenantCookie = (args: { cookieName?: string } = {}): string | undefined => {\n const { cookieName = DEFAULT_COOKIE_NAME } = args\n const value = `; ${document.cookie}`\n const parts = value.split(`; ${cookieName}=`)\n if (parts.length === 2) {\n return parts.pop()?.split(';').shift()\n }\n return undefined\n}\n\nexport const TenantSelectionProviderClient = ({\n children,\n initialTenantOptions,\n initialValue,\n tenantsCollectionSlug,\n}: {\n children: React.ReactNode\n initialTenantOptions: OptionObject[]\n initialValue?: number | string\n tenantsCollectionSlug: string\n}) => {\n const [selectedTenantID, setSelectedTenantID] = React.useState<number | string | undefined>(\n initialValue,\n )\n const [modified, setModified] = React.useState<boolean>(false)\n const [entityType, setEntityType] = React.useState<'document' | 'global' | undefined>(undefined)\n const { user } = useAuth()\n const { config } = useConfig()\n const router = useRouter()\n const userID = React.useMemo(() => user?.id, [user?.id])\n const prevUserID = React.useRef(userID)\n const userChanged = userID !== prevUserID.current\n const [tenantOptions, setTenantOptions] = React.useState<OptionObject[]>(\n () => initialTenantOptions,\n )\n const selectedTenantLabel = React.useMemo(\n () => tenantOptions.find((option) => option.value === selectedTenantID)?.label,\n [selectedTenantID, tenantOptions],\n )\n\n const setTenantAndCookie = React.useCallback(\n ({ id, refresh }: { id: number | string | undefined; refresh?: boolean }) => {\n setSelectedTenantID(id)\n if (id !== undefined) {\n setTenantCookie({ value: String(id) })\n } else {\n deleteTenantCookie()\n }\n if (refresh) {\n router.refresh()\n }\n },\n [router],\n )\n\n const setTenant = React.useCallback<ContextType['setTenant']>(\n ({ id, refresh }) => {\n if (id === undefined) {\n if (tenantOptions.length > 1 || tenantOptions.length === 0) {\n // users with multiple tenants can clear the tenant selection\n setTenantAndCookie({ id: undefined, refresh })\n } else if (tenantOptions[0]) {\n // if there is only one tenant, auto-select that tenant\n setTenantAndCookie({ id: tenantOptions[0].value, refresh: true })\n }\n } else if (!tenantOptions.find((option) => option.value === id)) {\n // if the tenant is invalid, set the first tenant as selected\n setTenantAndCookie({\n id: tenantOptions[0]?.value,\n refresh,\n })\n } else {\n // if the tenant is in the options, set it as selected\n setTenantAndCookie({ id, refresh })\n }\n },\n [tenantOptions, setTenantAndCookie],\n )\n\n const syncTenants = React.useCallback(async () => {\n try {\n const req = await fetch(\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${tenantsCollectionSlug}/populate-tenant-options`,\n }),\n {\n credentials: 'include',\n method: 'GET',\n },\n )\n\n const result = await req.json()\n\n if (result.tenantOptions && userID) {\n setTenantOptions(result.tenantOptions)\n\n if (result.tenantOptions.length === 1) {\n setSelectedTenantID(result.tenantOptions[0].value)\n setTenantCookie({ value: String(result.tenantOptions[0].value) })\n }\n }\n } catch (e) {\n toast.error(`Error fetching tenants`)\n }\n }, [config.routes.api, tenantsCollectionSlug, userID])\n\n const updateTenants = React.useCallback<ContextType['updateTenants']>(\n ({ id, label }) => {\n setTenantOptions((prev) => {\n return prev.map((currentTenant) => {\n if (id === currentTenant.value) {\n return {\n label,\n value: id,\n }\n }\n return currentTenant\n })\n })\n\n void syncTenants()\n },\n [syncTenants],\n )\n\n /**\n * Sync server-provided tenant options into client state.\n * When the server component re-renders (e.g., after navigation post-login),\n * it provides updated initialTenantOptions. Since useState() ignores new\n * initial values on re-renders, and re-initializes with stale props on\n * remounts, we sync them explicitly via this effect.\n */\n React.useEffect(() => {\n if (initialTenantOptions.length > 0) {\n setTenantOptions((prev) => {\n if (\n prev.length === initialTenantOptions.length &&\n prev.every((opt, i) => opt.value === initialTenantOptions[i]?.value)\n ) {\n return prev\n }\n return initialTenantOptions\n })\n\n if (initialTenantOptions.length === 1 && initialTenantOptions[0]) {\n setSelectedTenantID(initialTenantOptions[0].value)\n setTenantCookie({ value: String(initialTenantOptions[0].value) })\n }\n }\n }, [initialTenantOptions])\n\n React.useEffect(() => {\n if (userChanged || (initialValue && String(initialValue) !== getTenantCookie())) {\n if (userID) {\n // user logging in\n void syncTenants()\n } else {\n // user logging out\n setSelectedTenantID(undefined)\n deleteTenantCookie()\n setTenantOptions((prev) => (prev.length > 0 ? [] : prev))\n router.refresh()\n }\n prevUserID.current = userID\n }\n }, [userID, userChanged, syncTenants, initialValue, router])\n\n /**\n * Populate tenant options when the provider mounts already-authenticated but\n * without server-provided options. This happens when a provider setup remounts\n * this subtree on login (see `config.conditionalProvider`): the remounted\n * instance initializes `prevUserID` to the current user, so `userChanged` above\n * is `false` and never fires `syncTenants`, while the server still rendered the\n * logged-out tree so `initialTenantOptions` is empty — leaving the selector\n * unrendered. Unlike `router.refresh()`, `syncTenants()` is a plain client fetch\n * that cannot trigger a remount loop, so this is safe on every framework adapter.\n */\n const didInitialSync = React.useRef(false)\n React.useEffect(() => {\n if (\n !didInitialSync.current &&\n !userChanged &&\n userID &&\n tenantOptions.length === 0 &&\n initialTenantOptions.length === 0\n ) {\n didInitialSync.current = true\n void syncTenants()\n }\n }, [userChanged, userID, tenantOptions.length, initialTenantOptions, syncTenants])\n\n /**\n * If there is no initial value, clear the tenant and refresh the router.\n * Needed for stale tenantIDs set as a cookie.\n *\n * Only refresh when there is actually a stale cookie to clear: when no cookie\n * is present the server already rendered tenant-less, so a refresh is\n * redundant. This also avoids an infinite loop on framework adapters whose\n * `router.refresh()` remounts this provider (e.g. TanStack Start) — an\n * unconditional refresh would re-fire this mount effect and refresh again,\n * whereas guarding on the (persistent) cookie self-terminates after the one\n * refresh that clears it. The cookie is cleared either way via `setTenant`.\n */\n React.useEffect(() => {\n if (!initialValue) {\n setTenant({ id: undefined, refresh: Boolean(getTenantCookie()) })\n }\n }, [initialValue, setTenant])\n\n /**\n * If there is no selected tenant ID and the entity type is 'global', set the first tenant as selected.\n * This ensures that the global tenant is always set when the component mounts.\n */\n React.useEffect(() => {\n if (!selectedTenantID && tenantOptions.length > 0 && entityType === 'global') {\n setTenant({\n id: tenantOptions[0]?.value,\n refresh: true,\n })\n }\n }, [selectedTenantID, tenantOptions, entityType, setTenant])\n\n return (\n <span\n data-selected-tenant-id={selectedTenantID}\n data-selected-tenant-title={selectedTenantLabel}\n >\n <Context\n value={{\n entityType,\n modified,\n options: tenantOptions,\n selectedTenantID,\n setEntityType,\n setModified,\n setTenant,\n syncTenants,\n updateTenants,\n }}\n >\n {children}\n </Context>\n </span>\n )\n}\n\nexport const useTenantSelection = () => React.use(Context)\n"],"names":["toast","useAuth","useConfig","useRouter","formatAdminURL","React","createContext","generateCookie","Context","entityType","undefined","options","selectedTenantID","setEntityType","setModified","setTenant","syncTenants","Promise","resolve","updateTenants","DEFAULT_COOKIE_NAME","setTenantCookie","args","cookieName","value","document","cookie","name","maxAge","path","returnCookieAsObject","deleteTenantCookie","getTenantCookie","parts","split","length","pop","shift","TenantSelectionProviderClient","children","initialTenantOptions","initialValue","tenantsCollectionSlug","setSelectedTenantID","useState","modified","user","config","router","userID","useMemo","id","prevUserID","useRef","userChanged","current","tenantOptions","setTenantOptions","selectedTenantLabel","find","option","label","setTenantAndCookie","useCallback","refresh","String","req","fetch","apiRoute","routes","api","credentials","method","result","json","e","error","prev","map","currentTenant","useEffect","every","opt","i","didInitialSync","Boolean","span","data-selected-tenant-id","data-selected-tenant-title","useTenantSelection","use"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/providers/TenantSelectionProvider/index.client.tsx"],"sourcesContent":["'use client'\n\nimport type { OptionObject } from 'payload'\n\nimport { toast, useAuth, useConfig, useRouter } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport React, { createContext } from 'react'\n\nimport { generateCookie } from '../../utilities/generateCookie.js'\nimport { shouldRefreshTenantSelection } from '../../utilities/shouldRefreshTenantSelection.js'\n\ntype ContextType = {\n /**\n * What is the context of the selector? It is either 'document' | 'global' | undefined.\n *\n * - 'document' means you are viewing a document in the context of a tenant\n * - 'global' means you are viewing a \"global\" (globals are collection documents but prevent you from viewing the list view) document in the context of a tenant\n * - undefined means you are not viewing a document at all\n */\n entityType?: 'document' | 'global'\n /**\n * Hoists the forms modified state\n */\n modified?: boolean\n /**\n * Array of options to select from\n */\n options: OptionObject[]\n /**\n * The currently selected tenant ID\n */\n selectedTenantID: number | string | undefined\n /**\n * Sets the entityType when a document is loaded and sets it to undefined when the document unmounts.\n */\n setEntityType: React.Dispatch<React.SetStateAction<'document' | 'global' | undefined>>\n /**\n * Sets the modified state\n */\n setModified: React.Dispatch<React.SetStateAction<boolean>>\n /**\n * Sets the selected tenant ID\n *\n * @param args.id - The ID of the tenant to select\n * @param args.refresh - Whether to refresh the page after changing the tenant\n */\n setTenant: (args: { id: number | string | undefined; refresh?: boolean }) => void\n /**\n * Used to sync tenants displayed in the tenant selector when updates are made to the tenants collection.\n */\n syncTenants: () => Promise<void>\n /**\n *\n */\n updateTenants: (args: { id: number | string; label: string }) => void\n}\n\nconst Context = createContext<ContextType>({\n entityType: undefined,\n options: [],\n selectedTenantID: undefined,\n setEntityType: () => undefined,\n setModified: () => undefined,\n setTenant: () => null,\n syncTenants: () => Promise.resolve(),\n updateTenants: () => null,\n})\n\nconst DEFAULT_COOKIE_NAME = 'payload-tenant'\n\nconst setTenantCookie = (args: { cookieName?: string; value: string }) => {\n const { cookieName = DEFAULT_COOKIE_NAME, value } = args\n document.cookie = generateCookie<string>({\n name: cookieName,\n maxAge: 60 * 60 * 24 * 365, // 1 year in seconds\n path: '/',\n returnCookieAsObject: false,\n value: value || '',\n })\n}\n\nconst deleteTenantCookie = (args: { cookieName?: string } = {}) => {\n const { cookieName = DEFAULT_COOKIE_NAME } = args\n document.cookie = generateCookie<string>({\n name: cookieName,\n maxAge: -1,\n path: '/',\n returnCookieAsObject: false,\n value: '',\n })\n}\n\nconst getTenantCookie = (args: { cookieName?: string } = {}): string | undefined => {\n const { cookieName = DEFAULT_COOKIE_NAME } = args\n const value = `; ${document.cookie}`\n const parts = value.split(`; ${cookieName}=`)\n if (parts.length === 2) {\n return parts.pop()?.split(';').shift()\n }\n return undefined\n}\n\nexport const TenantSelectionProviderClient = ({\n children,\n initialTenantOptions,\n initialValue,\n tenantsCollectionSlug,\n}: {\n children: React.ReactNode\n initialTenantOptions: OptionObject[]\n initialValue?: number | string\n tenantsCollectionSlug: string\n}) => {\n const [selectedTenantID, setSelectedTenantID] = React.useState<number | string | undefined>(\n initialValue,\n )\n const [modified, setModified] = React.useState<boolean>(false)\n const [entityType, setEntityType] = React.useState<'document' | 'global' | undefined>(undefined)\n const { user } = useAuth()\n const { config } = useConfig()\n const router = useRouter()\n const userID = React.useMemo(() => user?.id, [user?.id])\n const prevUserID = React.useRef(userID)\n const userChanged = userID !== prevUserID.current\n const [tenantOptions, setTenantOptions] = React.useState<OptionObject[]>(\n () => initialTenantOptions,\n )\n const selectedTenantLabel = React.useMemo(\n () => tenantOptions.find((option) => option.value === selectedTenantID)?.label,\n [selectedTenantID, tenantOptions],\n )\n\n const setTenantAndCookie = React.useCallback(\n ({ id, refresh }: { id: number | string | undefined; refresh?: boolean }) => {\n setSelectedTenantID(id)\n if (id !== undefined) {\n setTenantCookie({ value: String(id) })\n } else {\n deleteTenantCookie()\n }\n if (refresh) {\n router.refresh()\n }\n },\n [router],\n )\n\n const setTenant = React.useCallback<ContextType['setTenant']>(\n ({ id, refresh }) => {\n if (id === undefined) {\n if (tenantOptions.length > 1 || tenantOptions.length === 0) {\n // users with multiple tenants can clear the tenant selection\n setTenantAndCookie({ id: undefined, refresh })\n } else if (tenantOptions[0]) {\n // if there is only one tenant, auto-select that tenant\n setTenantAndCookie({ id: tenantOptions[0].value, refresh: true })\n }\n } else if (!tenantOptions.find((option) => option.value === id)) {\n // if the tenant is invalid, set the first tenant as selected\n setTenantAndCookie({\n id: tenantOptions[0]?.value,\n refresh,\n })\n } else {\n // if the tenant is in the options, set it as selected\n setTenantAndCookie({ id, refresh })\n }\n },\n [tenantOptions, setTenantAndCookie],\n )\n\n const syncTenants = React.useCallback(async () => {\n try {\n const req = await fetch(\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${tenantsCollectionSlug}/populate-tenant-options`,\n }),\n {\n credentials: 'include',\n method: 'GET',\n },\n )\n\n const result = await req.json()\n\n if (result.tenantOptions && userID) {\n setTenantOptions(result.tenantOptions)\n\n if (result.tenantOptions.length === 1) {\n setSelectedTenantID(result.tenantOptions[0].value)\n setTenantCookie({ value: String(result.tenantOptions[0].value) })\n }\n }\n } catch (e) {\n toast.error(`Error fetching tenants`)\n }\n }, [config.routes.api, tenantsCollectionSlug, userID])\n\n const updateTenants = React.useCallback<ContextType['updateTenants']>(\n ({ id, label }) => {\n setTenantOptions((prev) => {\n return prev.map((currentTenant) => {\n if (id === currentTenant.value) {\n return {\n label,\n value: id,\n }\n }\n return currentTenant\n })\n })\n\n void syncTenants()\n },\n [syncTenants],\n )\n\n /**\n * Sync server-provided tenant options into client state.\n * When the server component re-renders (e.g., after navigation post-login),\n * it provides updated initialTenantOptions. Since useState() ignores new\n * initial values on re-renders, and re-initializes with stale props on\n * remounts, we sync them explicitly via this effect.\n */\n React.useEffect(() => {\n if (initialTenantOptions.length > 0) {\n setTenantOptions((prev) => {\n if (\n prev.length === initialTenantOptions.length &&\n prev.every((opt, i) => opt.value === initialTenantOptions[i]?.value)\n ) {\n return prev\n }\n return initialTenantOptions\n })\n\n if (initialTenantOptions.length === 1 && initialTenantOptions[0]) {\n setSelectedTenantID(initialTenantOptions[0].value)\n setTenantCookie({ value: String(initialTenantOptions[0].value) })\n }\n }\n }, [initialTenantOptions])\n\n React.useEffect(() => {\n const hasTenantSelectionMismatch =\n initialValue && String(initialValue) !== getTenantCookie()\n\n if (userID && (userChanged || tenantOptions.length === 0 || hasTenantSelectionMismatch)) {\n // Sync on login, and when the provider remounts after login without tenant options.\n void syncTenants()\n } else if (!userID && (userChanged || hasTenantSelectionMismatch)) {\n // user logging out\n setSelectedTenantID(undefined)\n deleteTenantCookie()\n setTenantOptions((prev) => (prev.length > 0 ? [] : prev))\n router.refresh()\n }\n\n if (userChanged) {\n prevUserID.current = userID\n }\n }, [userID, userChanged, syncTenants, initialValue, router, tenantOptions.length])\n\n /**\n * Populate tenant options when the provider mounts already-authenticated but\n * without server-provided options. This happens when a provider setup remounts\n * this subtree on login (see `config.conditionalProvider`): the remounted\n * instance initializes `prevUserID` to the current user, so `userChanged` above\n * is `false` and never fires `syncTenants`, while the server still rendered the\n * logged-out tree so `initialTenantOptions` is empty — leaving the selector\n * unrendered. Unlike `router.refresh()`, `syncTenants()` is a plain client fetch\n * that cannot trigger a remount loop, so this is safe on every framework adapter.\n */\n const didInitialSync = React.useRef(false)\n React.useEffect(() => {\n if (\n !didInitialSync.current &&\n !userChanged &&\n userID &&\n tenantOptions.length === 0 &&\n initialTenantOptions.length === 0\n ) {\n didInitialSync.current = true\n void syncTenants()\n }\n }, [userChanged, userID, tenantOptions.length, initialTenantOptions, syncTenants])\n\n /**\n * If there is no initial value and a stale tenant cookie exists, clear the\n * tenant and refresh the router.\n */\n React.useEffect(() => {\n if (\n shouldRefreshTenantSelection({\n initialValue,\n tenantCookie: getTenantCookie(),\n })\n ) {\n setTenant({ id: undefined, refresh: true })\n }\n }, [initialValue, setTenant])\n\n /**\n * If there is no selected tenant ID and the entity type is 'global', set the first tenant as selected.\n * This ensures that the global tenant is always set when the component mounts.\n */\n React.useEffect(() => {\n if (!selectedTenantID && tenantOptions.length > 0 && entityType === 'global') {\n setTenant({\n id: tenantOptions[0]?.value,\n refresh: true,\n })\n }\n }, [selectedTenantID, tenantOptions, entityType, setTenant])\n\n return (\n <span\n data-selected-tenant-id={selectedTenantID}\n data-selected-tenant-title={selectedTenantLabel}\n >\n <Context\n value={{\n entityType,\n modified,\n options: tenantOptions,\n selectedTenantID,\n setEntityType,\n setModified,\n setTenant,\n syncTenants,\n updateTenants,\n }}\n >\n {children}\n </Context>\n </span>\n )\n}\n\nexport const useTenantSelection = () => React.use(Context)\n"],"names":["toast","useAuth","useConfig","useRouter","formatAdminURL","React","createContext","generateCookie","shouldRefreshTenantSelection","Context","entityType","undefined","options","selectedTenantID","setEntityType","setModified","setTenant","syncTenants","Promise","resolve","updateTenants","DEFAULT_COOKIE_NAME","setTenantCookie","args","cookieName","value","document","cookie","name","maxAge","path","returnCookieAsObject","deleteTenantCookie","getTenantCookie","parts","split","length","pop","shift","TenantSelectionProviderClient","children","initialTenantOptions","initialValue","tenantsCollectionSlug","setSelectedTenantID","useState","modified","user","config","router","userID","useMemo","id","prevUserID","useRef","userChanged","current","tenantOptions","setTenantOptions","selectedTenantLabel","find","option","label","setTenantAndCookie","useCallback","refresh","String","req","fetch","apiRoute","routes","api","credentials","method","result","json","e","error","prev","map","currentTenant","useEffect","every","opt","i","hasTenantSelectionMismatch","didInitialSync","tenantCookie","span","data-selected-tenant-id","data-selected-tenant-title","useTenantSelection","use"],"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"}
|
|
@@ -12,8 +12,9 @@ import { extractID } from './extractID.js';
|
|
|
12
12
|
const tenantsArrayTenantFieldName = options?.tenantsArrayTenantFieldName || defaults.tenantsArrayTenantFieldName;
|
|
13
13
|
const tenantsArray = user[tenantsArrayFieldName];
|
|
14
14
|
return (Array.isArray(tenantsArray) ? tenantsArray : [])?.reduce((acc, row)=>{
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const tenantRow = row[tenantsArrayTenantFieldName];
|
|
16
|
+
if (tenantRow !== undefined && tenantRow !== null) {
|
|
17
|
+
acc.push(extractID(tenantRow));
|
|
17
18
|
}
|
|
18
19
|
return acc;
|
|
19
20
|
}, []) || [];
|
|
@@ -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 const tenantsArray = (user as Record<string, unknown>)[tenantsArrayFieldName]\n\n return (\n (Array.isArray(tenantsArray) ? tenantsArray : [])?.reduce<IDType[]>((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 const tenantsArray = (user as Record<string, unknown>)[tenantsArrayFieldName]\n\n return (\n (Array.isArray(tenantsArray) ? tenantsArray : [])?.reduce<IDType[]>((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","tenantsArray","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,MAAMC,eAAe,AAACJ,IAAgC,CAACE,sBAAsB;IAE7E,OACE,AAACG,CAAAA,MAAMC,OAAO,CAACF,gBAAgBA,eAAe,EAAE,AAAD,GAAIG,OAAiB,CAACC,KAAKC;QACxE,MAAMC,YAAYD,GAAG,CAACN,4BAA4B;QAClD,IAAIO,cAAcC,aAAaD,cAAc,MAAM;YACjDF,IAAII,IAAI,CAACd,UAAUY;QACrB;QAEA,OAAOF;IACT,GAAG,EAAE,KAAK,EAAE;AAEhB,EAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { MultiTenantPluginConfig } from '../types.js';
|
|
3
|
+
type Args = {
|
|
4
|
+
previousValue?: unknown;
|
|
5
|
+
req: PayloadRequest;
|
|
6
|
+
tenantsArrayFieldName: string;
|
|
7
|
+
tenantsArrayTenantFieldName: string;
|
|
8
|
+
userHasAccessToAllTenants?: MultiTenantPluginConfig['userHasAccessToAllTenants'];
|
|
9
|
+
value: unknown;
|
|
10
|
+
};
|
|
11
|
+
export declare const isValidTenantAssignment: ({ previousValue, req, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, value, }: Args) => boolean;
|
|
12
|
+
export {};
|
|
13
|
+
//# 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,EAAE,MAAM,SAAS,CAAA;AAE7C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAK1D,KAAK,IAAI,GAAG;IACV,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,2BAA2B,CAAC,CAAA;IAChF,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,eAAO,MAAM,uBAAuB,kHAOjC,IAAI,KAAG,OAgCT,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getTenantIDsFromValue } from './getTenantIDsFromValue.js';
|
|
2
|
+
import { getUserTenantIDs } from './getUserTenantIDs.js';
|
|
3
|
+
export const isValidTenantAssignment = ({ previousValue, req, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants, value })=>{
|
|
4
|
+
const { user } = req;
|
|
5
|
+
if (user) {
|
|
6
|
+
if (userHasAccessToAllTenants?.(user)) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
} else if (req.payloadAPI === 'local') {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
const submittedDocumentTenantIDs = getTenantIDsFromValue(value);
|
|
13
|
+
const storedDocumentTenantIDs = getTenantIDsFromValue(previousValue);
|
|
14
|
+
if (!hasTenantValueChanged(submittedDocumentTenantIDs, storedDocumentTenantIDs)) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (submittedDocumentTenantIDs.length === 0) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const userAssignedTenantIDs = getUserTenantIDs(user, {
|
|
21
|
+
tenantsArrayFieldName,
|
|
22
|
+
tenantsArrayTenantFieldName
|
|
23
|
+
});
|
|
24
|
+
return submittedDocumentTenantIDs.every((tenantID)=>userAssignedTenantIDs.some((userAssignedTenantID)=>String(userAssignedTenantID) === String(tenantID)));
|
|
25
|
+
};
|
|
26
|
+
const hasTenantValueChanged = (submittedTenantIDs, storedTenantIDs)=>submittedTenantIDs.length !== storedTenantIDs.length || submittedTenantIDs.some((tenantID)=>!storedTenantIDs.includes(tenantID));
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=isValidTenantAssignment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/isValidTenantAssignment.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport type { MultiTenantPluginConfig } from '../types.js'\n\nimport { getTenantIDsFromValue } from './getTenantIDsFromValue.js'\nimport { getUserTenantIDs } from './getUserTenantIDs.js'\n\ntype Args = {\n previousValue?: unknown\n req: PayloadRequest\n tenantsArrayFieldName: string\n tenantsArrayTenantFieldName: string\n userHasAccessToAllTenants?: MultiTenantPluginConfig['userHasAccessToAllTenants']\n value: unknown\n}\n\nexport const isValidTenantAssignment = ({\n previousValue,\n req,\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n userHasAccessToAllTenants,\n value,\n}: Args): boolean => {\n const { user } = req\n\n if (user) {\n if (userHasAccessToAllTenants?.(user)) {\n return true\n }\n } else if (req.payloadAPI === 'local') {\n return true\n }\n\n const submittedDocumentTenantIDs = getTenantIDsFromValue(value)\n const storedDocumentTenantIDs = getTenantIDsFromValue(previousValue)\n\n if (!hasTenantValueChanged(submittedDocumentTenantIDs, storedDocumentTenantIDs)) {\n return true\n }\n\n if (submittedDocumentTenantIDs.length === 0) {\n return false\n }\n\n const userAssignedTenantIDs = getUserTenantIDs(user, {\n tenantsArrayFieldName,\n tenantsArrayTenantFieldName,\n })\n\n return submittedDocumentTenantIDs.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","submittedDocumentTenantIDs","storedDocumentTenantIDs","hasTenantValueChanged","length","userAssignedTenantIDs","every","tenantID","some","userAssignedTenantID","String","submittedTenantIDs","storedTenantIDs","includes"],"mappings":"AAIA,SAASA,qBAAqB,QAAQ,6BAA4B;AAClE,SAASC,gBAAgB,QAAQ,wBAAuB;AAWxD,OAAO,MAAMC,0BAA0B,CAAC,EACtCC,aAAa,EACbC,GAAG,EACHC,qBAAqB,EACrBC,2BAA2B,EAC3BC,yBAAyB,EACzBC,KAAK,EACA;IACL,MAAM,EAAEC,IAAI,EAAE,GAAGL;IAEjB,IAAIK,MAAM;QACR,IAAIF,4BAA4BE,OAAO;YACrC,OAAO;QACT;IACF,OAAO,IAAIL,IAAIM,UAAU,KAAK,SAAS;QACrC,OAAO;IACT;IAEA,MAAMC,6BAA6BX,sBAAsBQ;IACzD,MAAMI,0BAA0BZ,sBAAsBG;IAEtD,IAAI,CAACU,sBAAsBF,4BAA4BC,0BAA0B;QAC/E,OAAO;IACT;IAEA,IAAID,2BAA2BG,MAAM,KAAK,GAAG;QAC3C,OAAO;IACT;IAEA,MAAMC,wBAAwBd,iBAAiBQ,MAAM;QACnDJ;QACAC;IACF;IAEA,OAAOK,2BAA2BK,KAAK,CAAC,CAACC,WACvCF,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 @@
|
|
|
1
|
+
{"version":3,"file":"shouldRefreshTenantSelection.d.ts","sourceRoot":"","sources":["../../src/utilities/shouldRefreshTenantSelection.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,oCAGtC;IACD,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,KAAG,OAAiD,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/shouldRefreshTenantSelection.ts"],"sourcesContent":["export const shouldRefreshTenantSelection = ({\n initialValue,\n tenantCookie,\n}: {\n initialValue?: number | string\n tenantCookie?: string\n}): boolean => !initialValue && Boolean(tenantCookie)\n"],"names":["shouldRefreshTenantSelection","initialValue","tenantCookie","Boolean"],"mappings":"AAAA,OAAO,MAAMA,+BAA+B,CAAC,EAC3CC,YAAY,EACZC,YAAY,EAIb,GAAc,CAACD,gBAAgBE,QAAQD,cAAa"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/plugin-multi-tenant",
|
|
3
|
-
"version": "4.0.0-internal.
|
|
3
|
+
"version": "4.0.0-internal.b8a92d2",
|
|
4
4
|
"description": "Multi Tenant plugin for Payload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -80,13 +80,13 @@
|
|
|
80
80
|
],
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@payloadcms/eslint-config": "3.28.0",
|
|
83
|
-
"@payloadcms/
|
|
84
|
-
"@payloadcms/
|
|
85
|
-
"payload": "4.0.0-internal.
|
|
83
|
+
"@payloadcms/translations": "4.0.0-internal.b8a92d2",
|
|
84
|
+
"@payloadcms/ui": "4.0.0-internal.b8a92d2",
|
|
85
|
+
"payload": "4.0.0-internal.b8a92d2"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
|
-
"@payloadcms/ui": "4.0.0-internal.
|
|
89
|
-
"payload": "4.0.0-internal.
|
|
88
|
+
"@payloadcms/ui": "4.0.0-internal.b8a92d2",
|
|
89
|
+
"payload": "4.0.0-internal.b8a92d2"
|
|
90
90
|
},
|
|
91
91
|
"homepage:": "https://payloadcms.com",
|
|
92
92
|
"scripts": {
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
96
96
|
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
|
97
97
|
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
98
|
-
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
98
|
+
"copyfiles": "copyfiles -u 1 -e \"**/*.spec.*\" \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
99
99
|
"lint": "eslint .",
|
|
100
100
|
"lint:fix": "eslint . --fix",
|
|
101
101
|
"pack:plugin": "pnpm build && pnpm pack"
|
package/dist/index.spec.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { multiTenantPlugin } from './index.js';
|
|
3
|
-
describe('multiTenantPlugin', ()=>{
|
|
4
|
-
it('applies tenant assignment access defaults', async ()=>{
|
|
5
|
-
let hasAllTenantAccess = false;
|
|
6
|
-
const plugin = multiTenantPlugin({
|
|
7
|
-
collections: {},
|
|
8
|
-
userHasAccessToAllTenants: ()=>hasAllTenantAccess
|
|
9
|
-
});
|
|
10
|
-
const config = await plugin({
|
|
11
|
-
collections: [
|
|
12
|
-
{
|
|
13
|
-
slug: 'users',
|
|
14
|
-
auth: true,
|
|
15
|
-
fields: []
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
slug: 'tenants',
|
|
19
|
-
fields: []
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
});
|
|
23
|
-
const usersCollection = config.collections?.find(({ slug })=>slug === 'users');
|
|
24
|
-
const tenantsField = usersCollection?.fields.find((field)=>'name' in field && field.name === 'tenants');
|
|
25
|
-
const accessArgs = {
|
|
26
|
-
req: {
|
|
27
|
-
user: {
|
|
28
|
-
id: 'test-user'
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
expect(await tenantsField?.access?.create?.(accessArgs)).toBe(false);
|
|
33
|
-
expect(await tenantsField?.access?.update?.(accessArgs)).toBe(false);
|
|
34
|
-
hasAllTenantAccess = true;
|
|
35
|
-
expect(await tenantsField?.access?.create?.(accessArgs)).toBe(true);
|
|
36
|
-
expect(await tenantsField?.access?.update?.(accessArgs)).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
it('retains configured tenant assignment access callbacks', async ()=>{
|
|
39
|
-
const createAccess = ()=>false;
|
|
40
|
-
const updateAccess = ()=>true;
|
|
41
|
-
const plugin = multiTenantPlugin({
|
|
42
|
-
collections: {},
|
|
43
|
-
tenantsArrayField: {
|
|
44
|
-
arrayFieldAccess: {
|
|
45
|
-
create: createAccess,
|
|
46
|
-
update: updateAccess
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
const config = await plugin({
|
|
51
|
-
collections: [
|
|
52
|
-
{
|
|
53
|
-
slug: 'users',
|
|
54
|
-
auth: true,
|
|
55
|
-
fields: []
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
slug: 'tenants',
|
|
59
|
-
fields: []
|
|
60
|
-
}
|
|
61
|
-
]
|
|
62
|
-
});
|
|
63
|
-
const usersCollection = config.collections?.find(({ slug })=>slug === 'users');
|
|
64
|
-
const tenantsField = usersCollection?.fields.find((field)=>'name' in field && field.name === 'tenants');
|
|
65
|
-
expect(tenantsField?.access?.create).toBe(createAccess);
|
|
66
|
-
expect(tenantsField?.access?.update).toBe(updateAccess);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
//# sourceMappingURL=index.spec.js.map
|
package/dist/index.spec.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.spec.ts"],"sourcesContent":["import type { ArrayField, Config } from 'payload'\n\nimport { describe, expect, it } from 'vitest'\n\nimport { multiTenantPlugin } from './index.js'\n\ndescribe('multiTenantPlugin', () => {\n it('applies tenant assignment access defaults', async () => {\n let hasAllTenantAccess = false\n const plugin = multiTenantPlugin({\n collections: {},\n userHasAccessToAllTenants: () => hasAllTenantAccess,\n })\n const config = await plugin({\n collections: [\n { slug: 'users', auth: true, fields: [] },\n { slug: 'tenants', fields: [] },\n ],\n } as Config)\n const usersCollection = config.collections?.find(({ slug }) => slug === 'users')\n const tenantsField = usersCollection?.fields.find(\n (field): field is ArrayField => 'name' in field && field.name === 'tenants',\n )\n const accessArgs = { req: { user: { id: 'test-user' } } } as never\n\n expect(await tenantsField?.access?.create?.(accessArgs)).toBe(false)\n expect(await tenantsField?.access?.update?.(accessArgs)).toBe(false)\n\n hasAllTenantAccess = true\n\n expect(await tenantsField?.access?.create?.(accessArgs)).toBe(true)\n expect(await tenantsField?.access?.update?.(accessArgs)).toBe(true)\n })\n\n it('retains configured tenant assignment access callbacks', async () => {\n const createAccess = () => false\n const updateAccess = () => true\n const plugin = multiTenantPlugin({\n collections: {},\n tenantsArrayField: {\n arrayFieldAccess: {\n create: createAccess,\n update: updateAccess,\n },\n },\n })\n const config = await plugin({\n collections: [\n { slug: 'users', auth: true, fields: [] },\n { slug: 'tenants', fields: [] },\n ],\n } as Config)\n const usersCollection = config.collections?.find(({ slug }) => slug === 'users')\n const tenantsField = usersCollection?.fields.find(\n (field): field is ArrayField => 'name' in field && field.name === 'tenants',\n )\n\n expect(tenantsField?.access?.create).toBe(createAccess)\n expect(tenantsField?.access?.update).toBe(updateAccess)\n })\n})\n"],"names":["describe","expect","it","multiTenantPlugin","hasAllTenantAccess","plugin","collections","userHasAccessToAllTenants","config","slug","auth","fields","usersCollection","find","tenantsField","field","name","accessArgs","req","user","id","access","create","toBe","update","createAccess","updateAccess","tenantsArrayField","arrayFieldAccess"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,iBAAiB,QAAQ,aAAY;AAE9CH,SAAS,qBAAqB;IAC5BE,GAAG,6CAA6C;QAC9C,IAAIE,qBAAqB;QACzB,MAAMC,SAASF,kBAAkB;YAC/BG,aAAa,CAAC;YACdC,2BAA2B,IAAMH;QACnC;QACA,MAAMI,SAAS,MAAMH,OAAO;YAC1BC,aAAa;gBACX;oBAAEG,MAAM;oBAASC,MAAM;oBAAMC,QAAQ,EAAE;gBAAC;gBACxC;oBAAEF,MAAM;oBAAWE,QAAQ,EAAE;gBAAC;aAC/B;QACH;QACA,MAAMC,kBAAkBJ,OAAOF,WAAW,EAAEO,KAAK,CAAC,EAAEJ,IAAI,EAAE,GAAKA,SAAS;QACxE,MAAMK,eAAeF,iBAAiBD,OAAOE,KAC3C,CAACE,QAA+B,UAAUA,SAASA,MAAMC,IAAI,KAAK;QAEpE,MAAMC,aAAa;YAAEC,KAAK;gBAAEC,MAAM;oBAAEC,IAAI;gBAAY;YAAE;QAAE;QAExDnB,OAAO,MAAMa,cAAcO,QAAQC,SAASL,aAAaM,IAAI,CAAC;QAC9DtB,OAAO,MAAMa,cAAcO,QAAQG,SAASP,aAAaM,IAAI,CAAC;QAE9DnB,qBAAqB;QAErBH,OAAO,MAAMa,cAAcO,QAAQC,SAASL,aAAaM,IAAI,CAAC;QAC9DtB,OAAO,MAAMa,cAAcO,QAAQG,SAASP,aAAaM,IAAI,CAAC;IAChE;IAEArB,GAAG,yDAAyD;QAC1D,MAAMuB,eAAe,IAAM;QAC3B,MAAMC,eAAe,IAAM;QAC3B,MAAMrB,SAASF,kBAAkB;YAC/BG,aAAa,CAAC;YACdqB,mBAAmB;gBACjBC,kBAAkB;oBAChBN,QAAQG;oBACRD,QAAQE;gBACV;YACF;QACF;QACA,MAAMlB,SAAS,MAAMH,OAAO;YAC1BC,aAAa;gBACX;oBAAEG,MAAM;oBAASC,MAAM;oBAAMC,QAAQ,EAAE;gBAAC;gBACxC;oBAAEF,MAAM;oBAAWE,QAAQ,EAAE;gBAAC;aAC/B;QACH;QACA,MAAMC,kBAAkBJ,OAAOF,WAAW,EAAEO,KAAK,CAAC,EAAEJ,IAAI,EAAE,GAAKA,SAAS;QACxE,MAAMK,eAAeF,iBAAiBD,OAAOE,KAC3C,CAACE,QAA+B,UAAUA,SAASA,MAAMC,IAAI,KAAK;QAGpEf,OAAOa,cAAcO,QAAQC,QAAQC,IAAI,CAACE;QAC1CxB,OAAOa,cAAcO,QAAQG,QAAQD,IAAI,CAACG;IAC5C;AACF"}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { addCollectionAccess } from './addCollectionAccess.js';
|
|
3
|
-
const createScope = (collection, accessResultCallback)=>({
|
|
4
|
-
accessResultCallback,
|
|
5
|
-
adminUsersSlug: 'users',
|
|
6
|
-
collection,
|
|
7
|
-
fieldName: 'tenant',
|
|
8
|
-
userHasAccessToAllTenants: ()=>false
|
|
9
|
-
});
|
|
10
|
-
const createArgs = (slug = 'posts')=>({
|
|
11
|
-
req: {
|
|
12
|
-
user: {
|
|
13
|
-
collection: 'users',
|
|
14
|
-
id: 'user-1',
|
|
15
|
-
tenants: [
|
|
16
|
-
{
|
|
17
|
-
tenant: 'tenant-1'
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
slug
|
|
23
|
-
});
|
|
24
|
-
describe('addCollectionAccess', ()=>{
|
|
25
|
-
it('adds tenant constraints through base access without replacing collection access', async ()=>{
|
|
26
|
-
const documentRead = vi.fn(()=>({
|
|
27
|
-
published: {
|
|
28
|
-
equals: true
|
|
29
|
-
}
|
|
30
|
-
}));
|
|
31
|
-
const collection = {
|
|
32
|
-
slug: 'posts',
|
|
33
|
-
access: {
|
|
34
|
-
read: documentRead
|
|
35
|
-
},
|
|
36
|
-
fields: []
|
|
37
|
-
};
|
|
38
|
-
const config = {
|
|
39
|
-
baseAccess: {
|
|
40
|
-
collections: {
|
|
41
|
-
read: ()=>({
|
|
42
|
-
archived: {
|
|
43
|
-
not_equals: true
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
addCollectionAccess({
|
|
50
|
-
config,
|
|
51
|
-
scopes: [
|
|
52
|
-
createScope(collection)
|
|
53
|
-
]
|
|
54
|
-
});
|
|
55
|
-
expect(collection.access?.read).toBe(documentRead);
|
|
56
|
-
await expect(config.baseAccess?.collections?.read?.(createArgs())).resolves.toEqual({
|
|
57
|
-
and: [
|
|
58
|
-
{
|
|
59
|
-
archived: {
|
|
60
|
-
not_equals: true
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
tenant: {
|
|
65
|
-
in: [
|
|
66
|
-
'tenant-1'
|
|
67
|
-
]
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
]
|
|
71
|
-
});
|
|
72
|
-
await expect(config.baseAccess?.collections?.read?.(createArgs('pages'))).resolves.toEqual({
|
|
73
|
-
archived: {
|
|
74
|
-
not_equals: true
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
expect(documentRead).not.toHaveBeenCalled();
|
|
78
|
-
});
|
|
79
|
-
it('returns a boolean tenant result for collection create access', async ()=>{
|
|
80
|
-
const collection = {
|
|
81
|
-
slug: 'posts',
|
|
82
|
-
fields: []
|
|
83
|
-
};
|
|
84
|
-
const config = {};
|
|
85
|
-
addCollectionAccess({
|
|
86
|
-
config,
|
|
87
|
-
scopes: [
|
|
88
|
-
createScope(collection)
|
|
89
|
-
]
|
|
90
|
-
});
|
|
91
|
-
await expect(config.baseAccess?.collections?.create?.(createArgs())).resolves.toBe(true);
|
|
92
|
-
});
|
|
93
|
-
it('keeps callback wrapping when an access result override is configured', async ()=>{
|
|
94
|
-
const documentResult = {
|
|
95
|
-
published: {
|
|
96
|
-
equals: true
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const documentRead = vi.fn(()=>documentResult);
|
|
100
|
-
const accessResultCallback = vi.fn(({ accessResult })=>accessResult);
|
|
101
|
-
const collection = {
|
|
102
|
-
slug: 'posts',
|
|
103
|
-
access: {
|
|
104
|
-
read: documentRead
|
|
105
|
-
},
|
|
106
|
-
fields: []
|
|
107
|
-
};
|
|
108
|
-
const config = {};
|
|
109
|
-
addCollectionAccess({
|
|
110
|
-
config,
|
|
111
|
-
scopes: [
|
|
112
|
-
createScope(collection, accessResultCallback)
|
|
113
|
-
]
|
|
114
|
-
});
|
|
115
|
-
expect(config.baseAccess).toBeUndefined();
|
|
116
|
-
await expect(collection.access?.read?.(createArgs())).resolves.toEqual({
|
|
117
|
-
and: [
|
|
118
|
-
documentResult,
|
|
119
|
-
{
|
|
120
|
-
tenant: {
|
|
121
|
-
in: [
|
|
122
|
-
'tenant-1'
|
|
123
|
-
]
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
]
|
|
127
|
-
});
|
|
128
|
-
expect(accessResultCallback).toHaveBeenCalledWith(expect.objectContaining({
|
|
129
|
-
accessKey: 'read'
|
|
130
|
-
}));
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
//# sourceMappingURL=addCollectionAccess.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/addCollectionAccess.spec.ts"],"sourcesContent":["import type { AccessArgs, CollectionConfig, Config } from 'payload'\n\nimport { describe, expect, it, vi } from 'vitest'\n\nimport type { TenantAccessConfig } from './addCollectionAccess.js'\n\nimport { addCollectionAccess } from './addCollectionAccess.js'\n\nconst createScope = (\n collection: CollectionConfig,\n accessResultCallback?: TenantAccessConfig['accessResultCallback'],\n): TenantAccessConfig => ({\n accessResultCallback,\n adminUsersSlug: 'users',\n collection,\n fieldName: 'tenant',\n userHasAccessToAllTenants: () => false,\n})\n\nconst createArgs = (slug = 'posts'): AccessArgs => ({\n req: {\n user: {\n collection: 'users',\n id: 'user-1',\n tenants: [{ tenant: 'tenant-1' }],\n },\n } as unknown as AccessArgs['req'],\n slug,\n})\n\ndescribe('addCollectionAccess', () => {\n it('adds tenant constraints through base access without replacing collection access', async () => {\n const documentRead = vi.fn(() => ({ published: { equals: true } }))\n const collection: CollectionConfig = {\n slug: 'posts',\n access: { read: documentRead },\n fields: [],\n }\n const config = {\n baseAccess: {\n collections: {\n read: () => ({ archived: { not_equals: true } }),\n },\n },\n } as Config\n\n addCollectionAccess({ config, scopes: [createScope(collection)] })\n\n expect(collection.access?.read).toBe(documentRead)\n await expect(config.baseAccess?.collections?.read?.(createArgs())).resolves.toEqual({\n and: [{ archived: { not_equals: true } }, { tenant: { in: ['tenant-1'] } }],\n })\n await expect(config.baseAccess?.collections?.read?.(createArgs('pages'))).resolves.toEqual({\n archived: { not_equals: true },\n })\n expect(documentRead).not.toHaveBeenCalled()\n })\n\n it('returns a boolean tenant result for collection create access', async () => {\n const collection: CollectionConfig = { slug: 'posts', fields: [] }\n const config = {} as Config\n\n addCollectionAccess({ config, scopes: [createScope(collection)] })\n\n await expect(config.baseAccess?.collections?.create?.(createArgs())).resolves.toBe(true)\n })\n\n it('keeps callback wrapping when an access result override is configured', async () => {\n const documentResult = { published: { equals: true } }\n const documentRead = vi.fn(() => documentResult)\n const accessResultCallback = vi.fn(({ accessResult }) => accessResult)\n const collection: CollectionConfig = {\n slug: 'posts',\n access: { read: documentRead },\n fields: [],\n }\n const config = {} as Config\n\n addCollectionAccess({\n config,\n scopes: [createScope(collection, accessResultCallback)],\n })\n\n expect(config.baseAccess).toBeUndefined()\n await expect(collection.access?.read?.(createArgs())).resolves.toEqual({\n and: [documentResult, { tenant: { in: ['tenant-1'] } }],\n })\n expect(accessResultCallback).toHaveBeenCalledWith(\n expect.objectContaining({ accessKey: 'read' }),\n )\n })\n})\n"],"names":["describe","expect","it","vi","addCollectionAccess","createScope","collection","accessResultCallback","adminUsersSlug","fieldName","userHasAccessToAllTenants","createArgs","slug","req","user","id","tenants","tenant","documentRead","fn","published","equals","access","read","fields","config","baseAccess","collections","archived","not_equals","scopes","toBe","resolves","toEqual","and","in","not","toHaveBeenCalled","create","documentResult","accessResult","toBeUndefined","toHaveBeenCalledWith","objectContaining","accessKey"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAIjD,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9D,MAAMC,cAAc,CAClBC,YACAC,uBACwB,CAAA;QACxBA;QACAC,gBAAgB;QAChBF;QACAG,WAAW;QACXC,2BAA2B,IAAM;IACnC,CAAA;AAEA,MAAMC,aAAa,CAACC,OAAO,OAAO,GAAkB,CAAA;QAClDC,KAAK;YACHC,MAAM;gBACJR,YAAY;gBACZS,IAAI;gBACJC,SAAS;oBAAC;wBAAEC,QAAQ;oBAAW;iBAAE;YACnC;QACF;QACAL;IACF,CAAA;AAEAZ,SAAS,uBAAuB;IAC9BE,GAAG,mFAAmF;QACpF,MAAMgB,eAAef,GAAGgB,EAAE,CAAC,IAAO,CAAA;gBAAEC,WAAW;oBAAEC,QAAQ;gBAAK;YAAE,CAAA;QAChE,MAAMf,aAA+B;YACnCM,MAAM;YACNU,QAAQ;gBAAEC,MAAML;YAAa;YAC7BM,QAAQ,EAAE;QACZ;QACA,MAAMC,SAAS;YACbC,YAAY;gBACVC,aAAa;oBACXJ,MAAM,IAAO,CAAA;4BAAEK,UAAU;gCAAEC,YAAY;4BAAK;wBAAE,CAAA;gBAChD;YACF;QACF;QAEAzB,oBAAoB;YAAEqB;YAAQK,QAAQ;gBAACzB,YAAYC;aAAY;QAAC;QAEhEL,OAAOK,WAAWgB,MAAM,EAAEC,MAAMQ,IAAI,CAACb;QACrC,MAAMjB,OAAOwB,OAAOC,UAAU,EAAEC,aAAaJ,OAAOZ,eAAeqB,QAAQ,CAACC,OAAO,CAAC;YAClFC,KAAK;gBAAC;oBAAEN,UAAU;wBAAEC,YAAY;oBAAK;gBAAE;gBAAG;oBAAEZ,QAAQ;wBAAEkB,IAAI;4BAAC;yBAAW;oBAAC;gBAAE;aAAE;QAC7E;QACA,MAAMlC,OAAOwB,OAAOC,UAAU,EAAEC,aAAaJ,OAAOZ,WAAW,WAAWqB,QAAQ,CAACC,OAAO,CAAC;YACzFL,UAAU;gBAAEC,YAAY;YAAK;QAC/B;QACA5B,OAAOiB,cAAckB,GAAG,CAACC,gBAAgB;IAC3C;IAEAnC,GAAG,gEAAgE;QACjE,MAAMI,aAA+B;YAAEM,MAAM;YAASY,QAAQ,EAAE;QAAC;QACjE,MAAMC,SAAS,CAAC;QAEhBrB,oBAAoB;YAAEqB;YAAQK,QAAQ;gBAACzB,YAAYC;aAAY;QAAC;QAEhE,MAAML,OAAOwB,OAAOC,UAAU,EAAEC,aAAaW,SAAS3B,eAAeqB,QAAQ,CAACD,IAAI,CAAC;IACrF;IAEA7B,GAAG,wEAAwE;QACzE,MAAMqC,iBAAiB;YAAEnB,WAAW;gBAAEC,QAAQ;YAAK;QAAE;QACrD,MAAMH,eAAef,GAAGgB,EAAE,CAAC,IAAMoB;QACjC,MAAMhC,uBAAuBJ,GAAGgB,EAAE,CAAC,CAAC,EAAEqB,YAAY,EAAE,GAAKA;QACzD,MAAMlC,aAA+B;YACnCM,MAAM;YACNU,QAAQ;gBAAEC,MAAML;YAAa;YAC7BM,QAAQ,EAAE;QACZ;QACA,MAAMC,SAAS,CAAC;QAEhBrB,oBAAoB;YAClBqB;YACAK,QAAQ;gBAACzB,YAAYC,YAAYC;aAAsB;QACzD;QAEAN,OAAOwB,OAAOC,UAAU,EAAEe,aAAa;QACvC,MAAMxC,OAAOK,WAAWgB,MAAM,EAAEC,OAAOZ,eAAeqB,QAAQ,CAACC,OAAO,CAAC;YACrEC,KAAK;gBAACK;gBAAgB;oBAAEtB,QAAQ;wBAAEkB,IAAI;4BAAC;yBAAW;oBAAC;gBAAE;aAAE;QACzD;QACAlC,OAAOM,sBAAsBmC,oBAAoB,CAC/CzC,OAAO0C,gBAAgB,CAAC;YAAEC,WAAW;QAAO;IAEhD;AACF"}
|