slides: gate management on slides.manage permission (grantable to non-admin curator)
The lobby-display and screensaver slide manager was admin-only. Add a shared slides.manage permission so a curator can manage both surfaces without full admin. Admins keep access via the require_permission admin bypass. Backend: - plugins/slides/api/routes.py: all 5 management routes require slides.manage - plugins/slides/plugin.py: declare it via get_permissions(); nav item carries the permission so the frontend can gate visibility - shopdb/core/api/auth.py: login response now returns the user's permissions (matches /me) so the frontend authStore has them on fresh login Frontend: - stores/auth.js: hasPermission(name) getter (admin true, else granted list) - router/index.js: guard supports requiresPermission - views/AppLayout.vue: hide nav items whose permission the user lacks - plugins/slides/frontend/routes.js: slide manager gated requiresPermission Tests: no-perm user 403, curator role with the perm 200 (+ login advertises it), admin 200 via bypass. Deploy: run `flask seed permissions` to create the row, then grant it to a role in Settings > Users & Roles.
This commit is contained in:
@@ -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('/')
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
Reference in New Issue
Block a user