Make CMS pages permissions more granular.

This commit is contained in:
corsacca 2026-04-27 09:54:44 +01:00
parent b738a35a4a
commit 4cc516b854
14 changed files with 22 additions and 17 deletions

View file

@ -35,7 +35,7 @@ async function flushCache() {
<div>
<h1 class="text-3xl font-bold mb-6">Dashboard</h1>
<section v-if="hasPermission('pages.manage')" class="border border-(--ui-border) rounded-md p-4 max-w-xl">
<section v-if="hasPermission('pages.write')" class="border border-(--ui-border) rounded-md p-4 max-w-xl">
<h2 class="text-lg font-semibold mb-1">CMS cache</h2>
<p class="text-sm text-(--ui-text-muted) mb-3">
Every edit auto-purges the pages it touches. Use this when you've

View file

@ -10,7 +10,8 @@ export const PERMISSIONS = [
'roles.view',
'roles.manage',
'pages.view',
'pages.manage'
'pages.write',
'pages.publish'
] as const
export type Permission = typeof PERMISSIONS[number]
@ -43,9 +44,13 @@ export const PERMISSION_META: Record<string, { title: string; description: strin
title: 'View CMS pages',
description: 'See the CMS pages list and read existing translations.'
},
'pages.manage': {
title: 'Manage CMS pages',
description: 'Create, edit, publish, translate, and delete CMS pages.'
'pages.write': {
title: 'Edit CMS pages',
description: 'Create, edit, translate, and delete CMS pages, categories, and assets.'
},
'pages.publish': {
title: 'Publish CMS pages',
description: 'Publish and unpublish CMS pages.'
}
}

View file

@ -11,7 +11,7 @@ import { logEvent } from '../../../utils/activity-logger'
import { getAuthUser } from '../../../utils/auth'
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const user = await getAuthUser(event)
const count = await purgeAllCmsCache()
logEvent({

View file

@ -8,7 +8,7 @@ import { deleteCategory } from '../../../database/categories'
import { logDelete } from '../../../utils/activity-logger'
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const id = getRouterParam(event, 'id')
if (!id) throw createError({ statusCode: 400, statusMessage: 'id is required' })

View file

@ -32,7 +32,7 @@ function normalizeTranslations(
}
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const id = getRouterParam(event, 'id')
if (!id) throw createError({ statusCode: 400, statusMessage: 'id is required' })

View file

@ -10,7 +10,7 @@ import { reorderCategoryPages, getCategoryPages, purgeSlugs } from '../../../../
import { logUpdate } from '../../../../utils/activity-logger'
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const id = getRouterParam(event, 'id')
if (!id) throw createError({ statusCode: 400, statusMessage: 'id is required' })

View file

@ -37,7 +37,7 @@ function normalizeTranslations(
}
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const body = await readBody<Body>(event)
const slug = (body?.slug ?? '').trim().replace(/^\/+|\/+$/g, '')

View file

@ -8,7 +8,7 @@ import { logDelete } from '../../../utils/activity-logger'
import { purgeCmsPage, purgeCmsCategory } from '../../../utils/cmsCache'
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const id = getRouterParam(event, 'id')
if (!id) throw createError({ statusCode: 400, statusMessage: 'id is required' })

View file

@ -11,7 +11,7 @@ import { logUpdate } from '../../../utils/activity-logger'
import { purgeCmsPage } from '../../../utils/cmsCache'
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const id = getRouterParam(event, 'id')
if (!id) throw createError({ statusCode: 400, statusMessage: 'id is required' })

View file

@ -13,7 +13,7 @@ interface Body {
}
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.publish')
const id = getRouterParam(event, 'id')
if (!id) throw createError({ statusCode: 400, statusMessage: 'id is required' })

View file

@ -20,7 +20,7 @@ interface Body {
}
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
if (!isDeepLConfigured()) {
throw createError({ statusCode: 500, statusMessage: 'DEEPL_API_KEY is not configured' })
}

View file

@ -22,7 +22,7 @@ interface Body {
}
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const id = getRouterParam(event, 'id')
const locale = getRouterParam(event, 'locale')
if (!id) throw createError({ statusCode: 400, statusMessage: 'id is required' })

View file

@ -9,7 +9,7 @@ import { logCreate } from '../../../utils/activity-logger'
import { purgeCmsCategory } from '../../../utils/cmsCache'
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const body = await readBody<{ slug?: string; category_id?: string | null; menu_order?: number }>(event)
const slug = (body?.slug ?? '').trim().replace(/^\/+|\/+$/g, '')

View file

@ -11,7 +11,7 @@ import { uploadToS3, validateImageType, validateFileSize } from '../../../utils/
const MAX_IMAGE_MB = 10
export default defineEventHandler(async (event) => {
await requirePermission(event, 'pages.manage')
await requirePermission(event, 'pages.write')
const form = await readMultipartFormData(event)
const file = form?.find(p => p.name === 'file')