slides: grantable slides.manage permission for non-admin curators

This commit is contained in:
2026-07-29 08:41:53 -04:00
parent 02147827f0
commit eeae042aa8
8 changed files with 106 additions and 10 deletions

View File

@@ -167,6 +167,11 @@ router.beforeEach(async (to, from, next) => {
if (to.meta.requiresAdmin && !authStore.isAdmin) {
return next('/')
}
// A route may gate on a named permission instead of full admin (e.g. the
// slide manager -> slides.manage). Admins pass via hasPermission.
if (to.meta.requiresPermission && !authStore.hasPermission(to.meta.requiresPermission)) {
return next('/')
}
if (to.meta.guest && authStore.isAuthenticated) {
return next('/')
}

View File

@@ -13,6 +13,13 @@ export const useAuthStore = defineStore('auth', {
roles: (state) => state.user?.roles || [],
hasRole: (state) => (role) => state.user?.roles?.includes(role) || false,
isAdmin: (state) => state.user?.roles?.includes('admin') || false,
// True if the user holds a named permission. Admins hold every permission
// (mirrors the backend require_permission admin bypass), so they pass
// regardless of the permissions list. Non-admins check their granted list.
hasPermission: (state) => (name) =>
state.user?.roles?.includes('admin')
|| state.user?.permissions?.includes(name)
|| false,
// True when an admin-set temporary password must be changed before use.
mustChangePassword: (state) => !!state.user?.mustchangepassword,
// Full name from the employee directory (falls back to username/SSO).

View File

@@ -185,8 +185,14 @@ const defaultNav = [
]
function buildNavItems(items) {
// Drop items the user cannot reach: a nav item may carry a `permission`
// (e.g. Slides -> slides.manage); hide it from anyone who lacks it so the
// link does not dead-end at the router guard. Admins hold every permission.
const visible = items.filter(item =>
!item.permission || authStore.hasPermission(item.permission))
// Sort by position
const sorted = [...items].sort((a, b) => (a.position || 99) - (b.position || 99))
const sorted = [...visible].sort((a, b) => (a.position || 99) - (b.position || 99))
// Assign section headers based on position ranges
const result = []