Skip to content

Commit 9c8f320

Browse files
authored
fix(richtext-lexical, plugin-multi-tenant): text editor exposes documents from other tenants (#13229)
## What Before this PR, an internal link in the Lexical editor could reference a document from a different tenant than the active one. Reproduction: 1. `pnpm dev plugin-multi-tenant` 2. Log in with `[email protected]` and password `test` 3. Go to `http://localhost:3000/admin/collections/food-items` and switch between the `Blue Dog` and `Steel Cat` tenants to see which food items each tenant has. 4. Go to http://localhost:3000/admin/collections/food-items/create, and in the new richtext field enter an internal link 5. In the relationship select menu, you will see the 6 food items at once (3 of each of those tenants). In the relationship select menu, you would previously see all 6 food items at once (3 from each of those tenants). Now, you'll only see the 3 from the active tenant. The new test verifies that this is fixed. ## How `baseListFilter` is used, but now it's called `baseFilter` for obvious reasons: it doesn't just filter the List View. Having two different properties where the same function was supposed to be placed wasn't feasible. `baseListFilter` is still supported for backwards compatibility. It's used as a fallback if `baseFilter` isn't defined, and it's documented as deprecated. `baseFilter` is injected into `filterOptions` of the internal link field in the Lexical Editor.
1 parent 161769e commit 9c8f320

File tree

17 files changed

+167
-58
lines changed

17 files changed

+167
-58
lines changed

docs/configuration/collections.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ The following options are available:
142142
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
143143
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
144144
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
145-
| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
145+
| `baseFilter` | Defines a default base filter which will be applied to the List View (along with any other filters applied by the user) and internal links in Lexical Editor, |
146146

147147
<Banner type="warning">
148148
**Note:** If you set `useAsTitle` to a relationship or join field, it will use

docs/plugins/multi-tenant.mdx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,19 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
7676
isGlobal?: boolean
7777
/**
7878
* Set to `false` if you want to manually apply
79-
* the baseListFilter
79+
* the baseFilter
80+
*
81+
* @default true
82+
*/
83+
useBaseFilter?: boolean
84+
/**
85+
* @deprecated Use `useBaseFilter` instead. If both are defined,
86+
* `useBaseFilter` will take precedence. This property remains only
87+
* for backward compatibility and may be removed in a future version.
88+
*
89+
* Originally, `baseListFilter` was intended to filter only the List View
90+
* in the admin panel. However, base filtering is often required in other areas
91+
* such as internal link relationships in the Lexical editor.
8092
*
8193
* @default true
8294
*/
@@ -203,12 +215,12 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
203215
*/
204216
useTenantsCollectionAccess?: boolean
205217
/**
206-
* Opt out including the baseListFilter to filter
218+
* Opt out including the baseFilter to filter
207219
* tenants by selected tenant
208220
*/
209221
useTenantsListFilter?: boolean
210222
/**
211-
* Opt out including the baseListFilter to filter
223+
* Opt out including the baseFilter to filter
212224
* users by selected tenant
213225
*/
214226
useUsersTenantFilter?: boolean

packages/next/src/views/List/index.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,24 +138,22 @@ export const renderListView = async (
138138
throw new Error('not-found')
139139
}
140140

141-
let baseListFilter = undefined
142-
143-
if (typeof collectionConfig.admin?.baseListFilter === 'function') {
144-
baseListFilter = await collectionConfig.admin.baseListFilter({
145-
limit: query.limit,
146-
page: query.page,
147-
req,
148-
sort: query.sort,
149-
})
150-
}
141+
const baseFilterConstraint = await (
142+
collectionConfig.admin?.baseFilter ?? collectionConfig.admin?.baseListFilter
143+
)?.({
144+
limit: query.limit,
145+
page: query.page,
146+
req,
147+
sort: query.sort,
148+
})
151149

152150
let queryPreset: QueryPreset | undefined
153151
let queryPresetPermissions: SanitizedCollectionPermission | undefined
154152

155153
let whereWithMergedSearch = mergeListSearchAndWhere({
156154
collectionConfig,
157155
search: typeof query?.search === 'string' ? query.search : undefined,
158-
where: combineWhereConstraints([query?.where, baseListFilter]),
156+
where: combineWhereConstraints([query?.where, baseFilterConstraint]),
159157
})
160158

161159
if (trash === true) {

packages/payload/src/collections/config/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export type ServerOnlyCollectionProperties = keyof Pick<
2929

3030
export type ServerOnlyCollectionAdminProperties = keyof Pick<
3131
SanitizedCollectionConfig['admin'],
32-
'baseListFilter' | 'components' | 'hidden'
32+
'baseFilter' | 'baseListFilter' | 'components' | 'hidden'
3333
>
3434

3535
export type ServerOnlyUploadProperties = keyof Pick<
@@ -94,6 +94,7 @@ const serverOnlyUploadProperties: Partial<ServerOnlyUploadProperties>[] = [
9494

9595
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
9696
'hidden',
97+
'baseFilter',
9798
'baseListFilter',
9899
'components',
99100
// 'preview' is handled separately

packages/payload/src/collections/config/types.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,39 @@ export type EnableFoldersOptions = {
270270
debug?: boolean
271271
}
272272

273-
export type BaseListFilter = (args: {
273+
export type BaseFilter = (args: {
274274
limit: number
275275
locale?: TypedLocale
276276
page: number
277277
req: PayloadRequest
278278
sort: string
279279
}) => null | Promise<null | Where> | Where
280280

281+
/**
282+
* @deprecated Use `BaseFilter` instead.
283+
*/
284+
export type BaseListFilter = BaseFilter
285+
281286
export type CollectionAdminOptions = {
287+
/**
288+
* Defines a default base filter which will be applied in the following parts of the admin panel:
289+
* - List View
290+
* - Relationship fields for internal links within the Lexical editor
291+
*
292+
* This is especially useful for plugins like multi-tenant. For example,
293+
* a user may have access to multiple tenants, but should only see content
294+
* related to the currently active or selected tenant in those places.
295+
*/
296+
baseFilter?: BaseFilter
297+
/**
298+
* @deprecated Use `baseFilter` instead. If both are defined,
299+
* `baseFilter` will take precedence. This property remains only
300+
* for backward compatibility and may be removed in a future version.
301+
*
302+
* Originally, `baseListFilter` was intended to filter only the List View
303+
* in the admin panel. However, base filtering is often required in other areas
304+
* such as internal link relationships in the Lexical editor.
305+
*/
282306
baseListFilter?: BaseListFilter
283307
/**
284308
* Custom admin components

packages/payload/src/folders/utils/buildFolderWhereConstraints.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ export async function buildFolderWhereConstraints({
2828
}),
2929
]
3030

31-
if (typeof collectionConfig.admin?.baseListFilter === 'function') {
32-
const baseListFilterConstraint = await collectionConfig.admin.baseListFilter({
33-
limit: 0,
34-
locale: localeCode,
35-
page: 1,
36-
req,
37-
sort:
38-
sort ||
39-
(typeof collectionConfig.defaultSort === 'string' ? collectionConfig.defaultSort : 'id'),
40-
})
31+
const baseFilterConstraint = await (
32+
collectionConfig.admin?.baseFilter ?? collectionConfig.admin?.baseListFilter
33+
)?.({
34+
limit: 0,
35+
locale: localeCode,
36+
page: 1,
37+
req,
38+
sort:
39+
sort ||
40+
(typeof collectionConfig.defaultSort === 'string' ? collectionConfig.defaultSort : 'id'),
41+
})
4142

42-
if (baseListFilterConstraint) {
43-
constraints.push(baseListFilterConstraint)
44-
}
43+
if (baseFilterConstraint) {
44+
constraints.push(baseFilterConstraint)
4545
}
4646

4747
if (folderID) {

packages/payload/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,7 @@ export type {
11681168
AfterRefreshHook as CollectionAfterRefreshHook,
11691169
AuthCollection,
11701170
AuthOperationsFromCollectionSlug,
1171+
BaseFilter,
11711172
BaseListFilter,
11721173
BeforeChangeHook as CollectionBeforeChangeHook,
11731174
BeforeDeleteHook as CollectionBeforeDeleteHook,

packages/plugin-multi-tenant/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
3636
*/
3737
isGlobal?: boolean
3838
/**
39-
* Set to `false` if you want to manually apply the baseListFilter
39+
* Set to `false` if you want to manually apply the baseFilter
4040
*
4141
* @default true
4242
*/
43-
useBaseListFilter?: boolean
43+
useBaseFilter?: boolean
4444
/**
4545
* Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied
4646
*

packages/plugin-multi-tenant/src/index.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { addTenantCleanup } from './hooks/afterTenantDelete.js'
1515
import { translations } from './translations/index.js'
1616
import { addCollectionAccess } from './utilities/addCollectionAccess.js'
1717
import { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js'
18-
import { combineListFilters } from './utilities/combineListFilters.js'
18+
import { combineFilters } from './utilities/combineFilters.js'
1919

2020
export const multiTenantPlugin =
2121
<ConfigType>(pluginConfig: MultiTenantPluginConfig<ConfigType>) =>
@@ -143,8 +143,10 @@ export const multiTenantPlugin =
143143
adminUsersCollection.admin = {}
144144
}
145145

146-
adminUsersCollection.admin.baseListFilter = combineListFilters({
147-
baseListFilter: adminUsersCollection.admin?.baseListFilter,
146+
const baseFilter =
147+
adminUsersCollection.admin?.baseFilter ?? adminUsersCollection.admin?.baseListFilter
148+
adminUsersCollection.admin.baseFilter = combineFilters({
149+
baseFilter,
148150
customFilter: (args) =>
149151
filterDocumentsByTenants({
150152
filterFieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,
@@ -207,8 +209,9 @@ export const multiTenantPlugin =
207209
collection.admin = {}
208210
}
209211

210-
collection.admin.baseListFilter = combineListFilters({
211-
baseListFilter: collection.admin?.baseListFilter,
212+
const baseFilter = collection.admin?.baseFilter ?? collection.admin?.baseListFilter
213+
collection.admin.baseFilter = combineFilters({
214+
baseFilter,
212215
customFilter: (args) =>
213216
filterDocumentsByTenants({
214217
filterFieldName: 'id',
@@ -296,7 +299,9 @@ export const multiTenantPlugin =
296299
}),
297300
)
298301

299-
if (pluginConfig.collections[collection.slug]?.useBaseListFilter !== false) {
302+
const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {}
303+
304+
if (useBaseFilter ?? useBaseListFilter ?? true) {
300305
/**
301306
* Add list filter to enabled collections
302307
* - filters results by selected tenant
@@ -305,8 +310,9 @@ export const multiTenantPlugin =
305310
collection.admin = {}
306311
}
307312

308-
collection.admin.baseListFilter = combineListFilters({
309-
baseListFilter: collection.admin?.baseListFilter,
313+
const baseFilter = collection.admin?.baseFilter ?? collection.admin?.baseListFilter
314+
collection.admin.baseFilter = combineFilters({
315+
baseFilter,
310316
customFilter: (args) =>
311317
filterDocumentsByTenants({
312318
filterFieldName: tenantFieldName,

packages/plugin-multi-tenant/src/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,19 @@ export type MultiTenantPluginConfig<ConfigTypes = unknown> = {
3030
*/
3131
isGlobal?: boolean
3232
/**
33-
* Set to `false` if you want to manually apply the baseListFilter
33+
* Set to `false` if you want to manually apply the baseFilter
34+
*
35+
* @default true
36+
*/
37+
useBaseFilter?: boolean
38+
/**
39+
* @deprecated Use `useBaseFilter` instead. If both are defined,
40+
* `useBaseFilter` will take precedence. This property remains only
41+
* for backward compatibility and may be removed in a future version.
42+
*
43+
* Originally, `baseListFilter` was intended to filter only the List View
44+
* in the admin panel. However, base filtering is often required in other areas
45+
* such as internal link relationships in the Lexical editor.
3446
*
3547
* @default true
3648
*/

0 commit comments

Comments
 (0)