chore: allows super-admins to view as tenant in multi-tenant example (#2719)
This commit is contained in:
@@ -53,6 +53,8 @@ This applies to each collection in the following ways:
|
|||||||
- `tenants`: Only super-admins and tenant-admins can read, create, update, or delete tenants. See [Tenants](#tenants) for more details.
|
- `tenants`: Only super-admins and tenant-admins can read, create, update, or delete tenants. See [Tenants](#tenants) for more details.
|
||||||
- `pages`: Everyone can access pages, but only super-admins and tenant-admins can create, update, or delete them.
|
- `pages`: Everyone can access pages, but only super-admins and tenant-admins can create, update, or delete them.
|
||||||
|
|
||||||
|
When a user logs in, a `lastLoggedInTenant` field is saved to their profile. This is done by reading the value of `req.headers.host`, querying for a tenant with a matching `domain`, and verifying that the user is a member of that tenant. This field is then used to automatically assign the tenant to any documents that the user creates, such as pages. Super-admins can also use this field to browse the admin panel as a specific tenant.
|
||||||
|
|
||||||
> If you have versions and drafts enabled on your pages, you will need to add additional read access control condition to check the user's tenants that prevents them from accessing draft documents of other tenants.
|
> If you have versions and drafts enabled on your pages, you will need to add additional read access control condition to check the user's tenants that prevents them from accessing draft documents of other tenants.
|
||||||
|
|
||||||
For more details on how to extend this functionality, see the [Payload Access Control](https://payloadcms.com/docs/access-control/overview#access-control) docs.
|
For more details on how to extend this functionality, see the [Payload Access Control](https://payloadcms.com/docs/access-control/overview#access-control) docs.
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import type { Access } from 'payload/types'
|
import type { Access } from 'payload/types'
|
||||||
|
|
||||||
import { checkUserRoles } from '../../utilities/checkUserRoles'
|
|
||||||
|
|
||||||
export const lastLoggedInTenant: Access = ({ req: { user }, data }) =>
|
export const lastLoggedInTenant: Access = ({ req: { user }, data }) =>
|
||||||
checkUserRoles(['super-admin'], user) || user?.lastLoggedInTenant?.id === data?.id
|
user?.lastLoggedInTenant?.id === data?.id
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import type { Access } from 'payload/types'
|
|||||||
import { isSuperAdmin } from '../../utilities/isSuperAdmin'
|
import { isSuperAdmin } from '../../utilities/isSuperAdmin'
|
||||||
|
|
||||||
export const tenants: Access = ({ req: { user }, data }) =>
|
export const tenants: Access = ({ req: { user }, data }) =>
|
||||||
isSuperAdmin(user) ||
|
|
||||||
// individual documents
|
// individual documents
|
||||||
(data?.tenant?.id && user?.lastLoggedInTenant?.id === data.tenant.id) || {
|
(data?.tenant?.id && user?.lastLoggedInTenant?.id === data.tenant.id) ||
|
||||||
|
(!user?.lastLoggedInTenant?.id && isSuperAdmin(user)) || {
|
||||||
// list of documents
|
// list of documents
|
||||||
tenant: {
|
tenant: {
|
||||||
equals: user?.lastLoggedInTenant?.id,
|
equals: user?.lastLoggedInTenant?.id,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Access } from 'payload/config'
|
import type { Access } from 'payload/config'
|
||||||
|
|
||||||
import { checkUserRoles } from '../../utilities/checkUserRoles'
|
import { isSuperAdmin } from '../../utilities/isSuperAdmin'
|
||||||
|
|
||||||
// the user must be an admin of the tenant being accessed
|
// the user must be an admin of the tenant being accessed
|
||||||
export const tenantAdmins: Access = ({ req: { user } }) => {
|
export const tenantAdmins: Access = ({ req: { user } }) => {
|
||||||
if (checkUserRoles(['super-admin'], user)) {
|
if (isSuperAdmin(user)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { isSuperAdmin } from '../../utilities/isSuperAdmin'
|
|||||||
|
|
||||||
export const adminsAndSelf: Access<any, User> = async ({ req: { user } }) => {
|
export const adminsAndSelf: Access<any, User> = async ({ req: { user } }) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
if (isSuperAdmin(user)) {
|
const isSuper = isSuperAdmin(user)
|
||||||
|
|
||||||
|
// allow super-admins through only if they have not scoped their user via `lastLoggedInTenant`
|
||||||
|
if (isSuper && !user?.lastLoggedInTenant) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,20 +20,34 @@ export const adminsAndSelf: Access<any, User> = async ({ req: { user } }) => {
|
|||||||
equals: user.id,
|
equals: user.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
...(isSuper
|
||||||
'tenants.tenant': {
|
? [
|
||||||
in:
|
{
|
||||||
user?.tenants
|
'tenants.tenant': {
|
||||||
?.map(({ tenant, roles }) =>
|
in: [
|
||||||
roles.includes('admin')
|
typeof user?.lastLoggedInTenant === 'string'
|
||||||
? typeof tenant === 'string'
|
? user?.lastLoggedInTenant
|
||||||
? tenant
|
: user?.lastLoggedInTenant?.id,
|
||||||
: tenant.id
|
].filter(Boolean),
|
||||||
: null,
|
},
|
||||||
) // eslint-disable-line function-paren-newline
|
},
|
||||||
.filter(Boolean) || [],
|
]
|
||||||
},
|
: [
|
||||||
},
|
{
|
||||||
|
'tenants.tenant': {
|
||||||
|
in:
|
||||||
|
user?.tenants
|
||||||
|
?.map(({ tenant, roles }) =>
|
||||||
|
roles.includes('admin')
|
||||||
|
? typeof tenant === 'string'
|
||||||
|
? tenant
|
||||||
|
: tenant.id
|
||||||
|
: null,
|
||||||
|
) // eslint-disable-line function-paren-newline
|
||||||
|
.filter(Boolean) || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
import type { AfterLoginHook } from 'payload/dist/collections/config/types'
|
import type { AfterLoginHook } from 'payload/dist/collections/config/types'
|
||||||
|
|
||||||
import { isSuperAdmin } from '../../utilities/isSuperAdmin'
|
|
||||||
|
|
||||||
export const recordLastLoggedInTenant: AfterLoginHook = async ({ req, user }) => {
|
export const recordLastLoggedInTenant: AfterLoginHook = async ({ req, user }) => {
|
||||||
try {
|
try {
|
||||||
if (!isSuperAdmin(user)) {
|
const relatedOrg = await req.payload.find({
|
||||||
const relatedOrg = await req.payload.find({
|
collection: 'tenants',
|
||||||
collection: 'tenants',
|
where: {
|
||||||
where: {
|
'domains.domain': {
|
||||||
'domains.domain': {
|
in: [req.headers.host],
|
||||||
in: [req.headers.host],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
depth: 0,
|
},
|
||||||
limit: 1,
|
depth: 0,
|
||||||
})
|
limit: 1,
|
||||||
|
})
|
||||||
|
|
||||||
if (relatedOrg.docs.length > 0) {
|
if (relatedOrg.docs.length > 0) {
|
||||||
await req.payload.update({
|
await req.payload.update({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
collection: 'users',
|
collection: 'users',
|
||||||
data: {
|
data: {
|
||||||
lastLoggedInTenant: relatedOrg.docs[0].id,
|
lastLoggedInTenant: relatedOrg.docs[0].id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
req.payload.logger.error(`Error recording last logged in tenant for user ${user.id}: ${err}`)
|
req.payload.logger.error(`Error recording last logged in tenant for user ${user.id}: ${err}`)
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ export const Users: CollectionConfig = {
|
|||||||
access: {
|
access: {
|
||||||
create: () => false,
|
create: () => false,
|
||||||
read: tenantAdmins,
|
read: tenantAdmins,
|
||||||
update: () => false,
|
update: superAdminFieldAccess,
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
position: 'sidebar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,7 +2,4 @@ import type { User } from 'payload/generated-types'
|
|||||||
|
|
||||||
import { checkUserRoles } from './checkUserRoles'
|
import { checkUserRoles } from './checkUserRoles'
|
||||||
|
|
||||||
export const isSuperAdmin = (user: User): boolean => {
|
export const isSuperAdmin = (user: User): boolean => checkUserRoles(['super-admin'], user)
|
||||||
if (user?.email === 'dev@payloadcms.com') return true // for the seed script, remove this in production
|
|
||||||
return checkUserRoles(['super-admin'], user)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user