teqo-backup/src/collections/Users.ts
Francisco Ignácio Fontoura Solla bb5ac362ed Add users.roles so Payload /admin can separate editors from admins.
Editors publish post/tag/media without PII or campaign access; existing users backfill as admin.
2026-07-27 01:54:29 -03:00

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”.',
},
},
],
}