mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
Editors publish post/tag/media without PII or campaign access; existing users backfill as admin.
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
import { hasPayloadPanelAccess, isPayloadAdmin, payloadAdminOnly } from '@/utilities/campaignAccess'
|
|
import type { Access, CollectionBeforeChangeHook, CollectionConfig, FieldAccess } from 'payload'
|
|
|
|
const canManageUserRoles: FieldAccess = ({ req }) => isPayloadAdmin(req.user)
|
|
|
|
/** Panel users may read/update themselves; admins may read/update anyone. */
|
|
const canAccessSelfOrAdmin: Access = ({ req, id }) => {
|
|
if (!hasPayloadPanelAccess(req.user)) return false
|
|
if (isPayloadAdmin(req.user)) return true
|
|
return id !== undefined && String(id) === String(req.user.id)
|
|
}
|
|
|
|
/**
|
|
* Defense in depth for Local API self-updates with `overrideAccess: true` —
|
|
* field access already blocks role writes when access is enforced.
|
|
*/
|
|
const preventSelfServiceRoleEscalation: CollectionBeforeChangeHook = ({
|
|
data,
|
|
operation,
|
|
originalDoc,
|
|
req,
|
|
}) => {
|
|
if (operation !== 'update' || !originalDoc || !hasPayloadPanelAccess(req.user)) {
|
|
return data
|
|
}
|
|
|
|
if (isPayloadAdmin(req.user)) return data
|
|
if (String(req.user.id) !== String(originalDoc.id)) return data
|
|
|
|
if ('roles' in data) delete data.roles
|
|
|
|
return data
|
|
}
|
|
|
|
export const Users: CollectionConfig = {
|
|
slug: 'users',
|
|
labels: {
|
|
singular: 'Usuário',
|
|
plural: 'Usuários',
|
|
},
|
|
admin: {
|
|
group: 'Coleções',
|
|
useAsTitle: 'email',
|
|
},
|
|
auth: true,
|
|
// Admin accounts are managed only by admin accounts. Without this, Payload's
|
|
// "any authenticated user" default would let a campaignUser JWT manage admins
|
|
// through /api/users — a privilege-escalation path. Editors may enter the
|
|
// panel and read/update their own account; only admins manage other users.
|
|
access: {
|
|
admin: ({ req }) => hasPayloadPanelAccess(req.user),
|
|
create: payloadAdminOnly,
|
|
read: canAccessSelfOrAdmin,
|
|
update: canAccessSelfOrAdmin,
|
|
delete: payloadAdminOnly,
|
|
unlock: payloadAdminOnly,
|
|
},
|
|
hooks: {
|
|
beforeChange: [preventSelfServiceRoleEscalation],
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'roles',
|
|
type: 'select',
|
|
label: 'Papéis',
|
|
hasMany: true,
|
|
required: true,
|
|
defaultValue: ['admin'],
|
|
saveToJWT: true,
|
|
options: [
|
|
{ label: 'Administrador', value: 'admin' },
|
|
{ label: 'Editor', value: 'editor' },
|
|
],
|
|
access: {
|
|
create: canManageUserRoles,
|
|
update: canManageUserRoles,
|
|
},
|
|
admin: {
|
|
description:
|
|
'Administrador: acesso total ao painel. Editor: publica notícias/tags/mídia — sem PII nem campanha. Contas novas de comunicação devem ser “Editor”.',
|
|
},
|
|
},
|
|
],
|
|
}
|