Conversation
…ess-control-not-respected-scenario
| globalsSelect: {}; | ||
| locale: null; | ||
| user: | ||
| | (User & { |
There was a problem hiding this comment.
Look at this! So much simpler. So much less confusing. No more "oh I take Config.user, it has collection. I take Config.collections.user, oh it does not have collection. Oh I login, it has collection. Oh I findByID, oh it does not have collection"
| }) | ||
| }) | ||
|
|
||
| describe('access control with user object passed directly', () => { |
There was a problem hiding this comment.
This test was failing before, which was the motivation behind this PR
| import { loginOperation } from '../login.js' | ||
|
|
||
| export type Options<TSlug extends CollectionSlug> = { | ||
| export type Options<TSlug extends AuthCollectionSlug> = { |
There was a problem hiding this comment.
AuthCollectionSlug is more correct than CorrectionSlug. This also matches the types we use in the Payload SDK.
| export const loginOperation = async <TSlug extends CollectionSlug>( | ||
| export const loginOperation = async <TSlug extends AuthCollectionSlug>( | ||
| incomingArgs: Arguments<TSlug>, | ||
| ): Promise<{ user: DataFromCollectionSlug<TSlug> } & Result> => { |
There was a problem hiding this comment.
This type intersection done before to ensure that user has the collection property. Just DataFromCollectionSlug<TSlug> previously did not have a collection property.
Now, we can greatly simplify this
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
…ess-control-not-respected-scenario
|
🚀 This is included in version v3.75.0 |
This PR ensures that all documents returned from auth-enabled collections via local API operations include the
collectionproperty, which is essential for multi-tenant access control to function correctly.Motivation
A common pattern in Payload applications is to fetch a user from the database and pass it to subsequent operations:
This introduces a security vulnerability. The local API expects
user.collectionto be present, but user documents returned byfind/findByIDdid not include this property. Since theuserparameter is loosely typed, TypeScript does not catch this error.In the
@payloadcms/plugin-multi-tenantplugin,useTenantAccesschecksreq.user.collectionto apply tenant-based access control. This is not an issue within plugin-multi-tenant, because the Payload types markreq.user.collectionas required - thus it's reasonable to expect this property to be there. Whencollectionisundefined, the conditionargs.req.user.collection === adminUsersSlugalways evaluates tofalse, bypassing tenant access restrictions.Changes
The
collectionproperty is now injected into auth collection documents immediately after data is fetched from the database in all collection operations (find,findByID,create,update,delete, etc.). This makes it available throughout all operation hooks.Type generation has been updated to include
collectiondirectly in auth collection schemas, producing cleaner types whereUsercontainscollection: 'users'without intersection types. Input types have been updated to makecollectionoptional since it's auto-populated.As a fallback,
createLocalReqnow setsuser.collectionto the default admin user collection if missing - this is an additional safeguard. A TODO has been added to throw an error instead in version 4.0. We cannot throw an error now, as this would be a breaking change.[Link to internal Slack discussion]