teqo-backup/src/globals/SiteSettings.ts
Cursor Agent 6705db80eb security: lock public globals update to payloadAdminOnly (Pass 4 P0)
The four public globals (site-settings, home, metadata, privacy-policy)
spelled update access as "any authenticated user" — and campaign JWTs
authenticate against /api/*, so any campaign session (leader included)
could PATCH the public site. payloadAdminOnly matches the collection
lockdown precedent; editors lose config-global writes deliberately
(content editing stays on canManagePublishedContent collections).

Pins: identity + deny (coordinator/editor) + allow (admin fn) + anonymous
read in collectionAccessLockdown.int.spec.ts. Recurrence guard:
globalAccessConventions.unit.spec.ts bans the Boolean(user) shape in
src/globals.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-07-31 20:57:18 -03:00

95 lines
2.4 KiB
TypeScript

import { payloadAdminOnly } from '@/utilities/campaignAccess'
import { revalidateGlobal } from '@/utilities/globals'
import type { GlobalConfig } from 'payload'
const slug = 'site-settings'
const revalidate = async () => revalidateGlobal(slug)
export const SiteSettings: GlobalConfig = {
slug,
label: 'Configurações do site',
admin: {
group: 'Configurações',
},
access: {
read: () => true,
// Site-wide config: campaign JWTs authenticate against /api/*, so "any
// authenticated user" here was a write path into the public site (Pass 4).
update: payloadAdminOnly,
},
hooks: {
afterChange: [revalidate],
},
fields: [
{
name: 'headerTitle',
type: 'text',
label: 'Título do cabeçalho',
},
{
name: 'socialLinks',
type: 'array',
label: 'Redes sociais',
labels: {
singular: 'Rede social',
plural: 'Redes sociais',
},
fields: [
{
name: 'platform',
type: 'select',
label: 'Plataforma',
required: true,
options: [
{ label: 'Instagram', value: 'instagram' },
{ label: 'Facebook', value: 'facebook' },
{ label: 'YouTube', value: 'youtube' },
{ label: 'WhatsApp', value: 'whatsapp' },
],
},
{
name: 'url',
type: 'text',
label: 'URL',
required: true,
validate: (value: string | null | undefined) => {
if (!value) return 'A URL é obrigatória'
try {
const parsed = new URL(value)
if (!['http:', 'https:'].includes(parsed.protocol)) {
return 'A URL deve começar com http:// ou https://'
}
return true
} catch {
return 'Informe uma URL válida'
}
},
},
{
name: 'label',
type: 'text',
label: 'Rótulo de acessibilidade',
admin: {
description:
'Rótulo opcional para acessibilidade. Se estiver vazio, usamos um rótulo padrão da plataforma.',
},
},
{
name: 'enabled',
type: 'checkbox',
label: 'Ativo',
},
],
},
],
versions: {
drafts: {
autosave: {
interval: 1000,
},
schedulePublish: true,
},
max: 10,
},
}