feat(admin): add layout server auth guard and Phase 1 hotfixes

- +layout.server.ts: auth guard (throws 403 for non-admin) with granular
  permission flags and entity counts for EntityNav
- GroupsTab: add ⚙ prefix to ADMIN badge (WCAG 1.4.1, non-color indicator)
- TagsTab: remove opacity-0 from action buttons (hidden on touch devices)
- +layout.svelte: remove unused isSystem derived

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-30 01:10:51 +02:00
parent 8fc360a596
commit c8a834b91b
7 changed files with 164 additions and 5 deletions

View File

@@ -0,0 +1,40 @@
import { error } from '@sveltejs/kit';
import { createApiClient } from '$lib/api.server';
import { getErrorMessage } from '$lib/errors';
type UserGroup = { permissions: string[] };
function hasPerm(user: { groups?: UserGroup[] } | undefined, perm: string): boolean {
return user?.groups?.some((g) => g.permissions.includes(perm)) ?? false;
}
function hasAnyAdminPerm(user: { groups?: UserGroup[] } | undefined): boolean {
return (
hasPerm(user, 'ADMIN') ||
hasPerm(user, 'ADMIN_USER') ||
hasPerm(user, 'ADMIN_TAG') ||
hasPerm(user, 'ADMIN_PERMISSION')
);
}
export async function load({ fetch, locals }) {
const user = locals.user;
if (!hasAnyAdminPerm(user)) throw error(403, getErrorMessage('FORBIDDEN'));
const api = createApiClient(fetch);
const [usersResult, groupsResult, tagsResult] = await Promise.all([
api.GET('/api/users'),
api.GET('/api/groups'),
api.GET('/api/tags')
]);
return {
userCount: (usersResult.data ?? []).length,
groupCount: (groupsResult.data ?? []).length,
tagCount: (tagsResult.data ?? []).length,
canManageUsers: hasPerm(user, 'ADMIN_USER'),
canManageTags: hasPerm(user, 'ADMIN_TAG'),
canManageGroups: hasPerm(user, 'ADMIN_PERMISSION'),
canRunMaintenance: hasPerm(user, 'ADMIN')
};
}