@@ -7,8 +7,8 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'
77import type { NextRESTClient } from '../__helpers/shared/NextRESTClient.js'
88import type { Relationship } from './payload-types.js'
99
10- import { devUser } from '../credentials.js'
1110import { initPayloadInt } from '../__helpers/shared/initPayloadInt.js'
11+ import { devUser } from '../credentials.js'
1212import { relationshipsSlug , tenantsSlug , usersSlug } from './shared.js'
1313
1414let payload : Payload
@@ -136,6 +136,110 @@ describe('@payloadcms/plugin-multi-tenant', () => {
136136 } )
137137 } )
138138
139+ describe ( 'access control for users with no tenant memberships' , ( ) => {
140+ it ( 'should return Forbidden error (not 500) for user with no tenants' , async ( ) => {
141+ // Create a user with no tenant memberships
142+ const noTenantUser = await payload . create ( {
143+ collection : usersSlug ,
144+ data : {
145+ email : 'no-tenants@test.com' ,
146+ password : 'test' ,
147+ tenants : [ ] ,
148+ } ,
149+ } )
150+
151+ // Create a tenant and document for testing
152+ const tenant = await payload . create ( {
153+ collection : tenantsSlug ,
154+ data : { name : 'Test Tenant' , domain : 'test-tenant.test' } ,
155+ } )
156+ const doc = await payload . create ( {
157+ collection : relationshipsSlug ,
158+ data : { tenant : tenant . id , title : 'Test Doc' } ,
159+ } )
160+
161+ // User with no tenants should get a Forbidden error (clean rejection)
162+ // not a 500 server error (which happens with { $in: [] } on CosmosDB)
163+ await expect (
164+ payload . find ( {
165+ collection : relationshipsSlug ,
166+ overrideAccess : false ,
167+ user : noTenantUser ,
168+ where : { id : { equals : doc . id } } ,
169+ } ) ,
170+ ) . rejects . toThrow ( 'You are not allowed to perform this action.' )
171+
172+ // Cleanup
173+ await payload . delete ( { id : doc . id , collection : relationshipsSlug } )
174+ await payload . delete ( { id : tenant . id , collection : tenantsSlug } )
175+ await payload . delete ( { id : noTenantUser . id , collection : usersSlug } )
176+ } )
177+
178+ it ( 'should allow user with no tenants to access their own user document' , async ( ) => {
179+ // Create a user with no tenant memberships
180+ const noTenantUser = await payload . create ( {
181+ collection : usersSlug ,
182+ data : {
183+ email : 'no-tenants-self@test.com' ,
184+ password : 'test' ,
185+ tenants : [ ] ,
186+ } ,
187+ } )
188+
189+ // User should be able to find themselves
190+ const result = await payload . find ( {
191+ collection : usersSlug ,
192+ overrideAccess : false ,
193+ user : noTenantUser ,
194+ where : { id : { equals : noTenantUser . id } } ,
195+ } )
196+
197+ expect ( result . docs ) . toHaveLength ( 1 )
198+ expect ( result . docs [ 0 ] . id ) . toBe ( noTenantUser . id )
199+
200+ // Cleanup
201+ await payload . delete ( { id : noTenantUser . id , collection : usersSlug } )
202+ } )
203+
204+ it ( 'should allow admin with empty tenants array to access all documents' , async ( ) => {
205+ // Create an admin user with empty tenants array
206+ const adminUser = await payload . create ( {
207+ collection : usersSlug ,
208+ data : {
209+ email : 'admin-empty-tenants@test.com' ,
210+ password : 'test' ,
211+ tenants : [ ] ,
212+ roles : [ 'admin' ] ,
213+ } ,
214+ } )
215+
216+ // Create a tenant and document
217+ const tenant = await payload . create ( {
218+ collection : tenantsSlug ,
219+ data : { name : 'Admin Test Tenant' , domain : 'admin-test.test' } ,
220+ } )
221+ const doc = await payload . create ( {
222+ collection : relationshipsSlug ,
223+ data : { tenant : tenant . id , title : 'Admin Test Doc' } ,
224+ } )
225+
226+ // Admin should have access (userHasAccessToAllTenants returns true for super-admin)
227+ const result = await payload . find ( {
228+ collection : relationshipsSlug ,
229+ overrideAccess : false ,
230+ user : adminUser ,
231+ where : { id : { equals : doc . id } } ,
232+ } )
233+
234+ expect ( result . docs ) . toHaveLength ( 1 )
235+
236+ // Cleanup
237+ await payload . delete ( { id : doc . id , collection : relationshipsSlug } )
238+ await payload . delete ( { id : tenant . id , collection : tenantsSlug } )
239+ await payload . delete ( { id : adminUser . id , collection : usersSlug } )
240+ } )
241+ } )
242+
139243 describe ( 'access control with user object passed directly' , ( ) => {
140244 it ( 'should enforce tenant access when user object is fetched from database' , async ( ) => {
141245 // Create two tenants
0 commit comments