Files
payload/test/plugin-multi-tenant/collections/MenuItems.ts
German Jablonski 9c8f3202e4 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 `dev@payloadcms.com` 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.
2025-08-07 11:24:15 -04:00

99 lines
2.1 KiB
TypeScript

import type { Access, CollectionConfig, Where } from 'payload'
import { getUserTenantIDs } from '@payloadcms/plugin-multi-tenant/utilities'
import { menuItemsSlug } from '../shared.js'
const collectionTenantReadAccess: Access = ({ req }) => {
// admins can access all tenants
if (req?.user?.roles?.includes('admin')) {
return true
}
if (req.user) {
const assignedTenants = getUserTenantIDs(req.user, {
tenantsArrayFieldName: 'tenants',
tenantsArrayTenantFieldName: 'tenant',
})
// if the user has assigned tenants, add id constraint
if (assignedTenants.length > 0) {
return {
or: [
{
tenant: {
in: assignedTenants,
},
},
{
'tenant.isPublic': {
equals: true,
},
},
],
}
}
}
// if the user has no assigned tenants, return a filter that allows access to public tenants
return {
'tenant.isPublic': {
equals: true,
},
} as Where
}
const collectionTenantUpdateAccess: Access = ({ req }) => {
// admins can update all tenants
if (req?.user?.roles?.includes('admin')) {
return true
}
if (req.user) {
const assignedTenants = getUserTenantIDs(req.user, {
tenantsArrayFieldName: 'tenants',
tenantsArrayTenantFieldName: 'tenant',
})
// if the user has assigned tenants, add id constraint
if (assignedTenants.length > 0) {
return {
tenant: {
in: assignedTenants,
},
}
}
}
return false
}
export const MenuItems: CollectionConfig = {
slug: menuItemsSlug,
access: {
read: collectionTenantReadAccess,
create: ({ req }) => {
return Boolean(req?.user?.roles?.includes('admin'))
},
update: collectionTenantUpdateAccess,
delete: ({ req }) => {
return Boolean(req?.user?.roles?.includes('admin'))
},
},
admin: {
useAsTitle: 'name',
group: 'Tenant Collections',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
},
],
}