feat(09-04): rewire /admin nested route + guard + Tailwind safelist
- Replace flat /admin route with nested AdminLayout subtree (5 children) - Fix beforeEach guard to use to.matched.some(r => r.meta.requiresAdmin) - Add D-09 admin-on-user-route redirect and D-10a non-admin-on-admin redirect - Add Tailwind safelist covering sky+amber color families (formatters + AuditLogTab)
This commit is contained in:
@@ -39,7 +39,22 @@ const routes = [
|
|||||||
|
|
||||||
// Phase 2 — authenticated routes
|
// Phase 2 — authenticated routes
|
||||||
{ path: '/account', redirect: '/settings' },
|
{ path: '/account', redirect: '/settings' },
|
||||||
{ path: '/admin', component: () => import('../views/AdminView.vue'), meta: { requiresAdmin: true } },
|
|
||||||
|
// Admin panel — standalone route subtree with AdminLayout as the route component.
|
||||||
|
// Children do NOT re-declare requiresAdmin — the guard uses to.matched.some() to
|
||||||
|
// inherit from the parent (Vue Router 4 does not propagate meta to children).
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
component: () => import('../layouts/AdminLayout.vue'),
|
||||||
|
meta: { requiresAdmin: true },
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('../views/admin/AdminOverviewView.vue') },
|
||||||
|
{ path: 'users', component: () => import('../views/admin/AdminUsersView.vue') },
|
||||||
|
{ path: 'quotas', component: () => import('../views/admin/AdminQuotasView.vue') },
|
||||||
|
{ path: 'ai', component: () => import('../views/admin/AdminAiView.vue') },
|
||||||
|
{ path: 'audit', component: () => import('../views/admin/AdminAuditView.vue') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
// Cloud storage overview and folder browser
|
// Cloud storage overview and folder browser
|
||||||
{
|
{
|
||||||
@@ -75,11 +90,13 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Navigation guard (D-10): redirect unauthenticated users to /login.
|
// Navigation guard — enforces auth and admin role separation (D-09, D-10).
|
||||||
// On page reload the access token is gone (memory-only per CLAUDE.md), so we attempt
|
// On page reload the access token is gone (memory-only per CLAUDE.md), so we attempt
|
||||||
// a silent refresh via the httpOnly cookie before concluding the session is gone.
|
// a silent refresh via the httpOnly cookie before any role checks fire.
|
||||||
router.beforeEach(async (to) => {
|
router.beforeEach(async (to) => {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
// Step 1: ensure access token is present for non-public routes.
|
||||||
if (!to.meta.public && !authStore.accessToken) {
|
if (!to.meta.public && !authStore.accessToken) {
|
||||||
try {
|
try {
|
||||||
await authStore.refresh()
|
await authStore.refresh()
|
||||||
@@ -88,9 +105,18 @@ router.beforeEach(async (to) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to.meta.requiresAdmin && authStore.user?.role !== 'admin') {
|
const isAdminRoute = to.matched.some(r => r.meta.requiresAdmin)
|
||||||
|
const isAdmin = authStore.user?.role === 'admin'
|
||||||
|
|
||||||
|
// D-10a: non-admin attempting an admin route → redirect to /.
|
||||||
|
if (isAdminRoute && !isAdmin) {
|
||||||
return { path: '/' }
|
return { path: '/' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// D-09: admin attempting a non-admin, non-public route → redirect to /admin.
|
||||||
|
if (!isAdminRoute && !to.meta.public && isAdmin) {
|
||||||
|
return { path: '/admin' }
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
import forms from '@tailwindcss/forms'
|
import forms from '@tailwindcss/forms'
|
||||||
export default {
|
export default {
|
||||||
content: ['./index.html', './src/**/*.{vue,js}'],
|
content: ['./index.html', './src/**/*.{vue,js}'],
|
||||||
|
safelist: [
|
||||||
|
{ pattern: /bg-(blue|sky|green|purple|orange|amber|gray|indigo|red)-(50|100|500|600)/ },
|
||||||
|
{ pattern: /text-(blue|sky|green|purple|orange|amber|gray|indigo|red)-(400|500|600|700)/ },
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user