Files
familienarchiv/frontend/src/routes/admin/EntityNav.svelte
Marcel 9cacc6079e fix(admin): guard GET /api/users/{id} with @RequirePermission(ADMIN_USER)
Fixes IDOR: the endpoint was publicly accessible to any authenticated user.
Now requires ADMIN_USER permission, matching all other user management endpoints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:30:23 +02:00

115 lines
3.1 KiB
Svelte

<script lang="ts">
import { page } from '$app/state';
import { m } from '$lib/paraglide/messages.js';
let {
userCount,
groupCount,
tagCount,
canManageUsers,
canManageTags,
canManageGroups,
canRunMaintenance
}: {
userCount: number;
groupCount: number;
tagCount: number;
canManageUsers: boolean;
canManageTags: boolean;
canManageGroups: boolean;
canRunMaintenance: boolean;
} = $props();
const currentPath = $derived(page.url.pathname);
const isActive = (section: string) => currentPath.startsWith(`/admin/${section}`);
</script>
<nav class="flex w-30 flex-shrink-0 flex-col bg-brand-navy" aria-label={m.admin_heading()}>
<div class="px-3 pt-3 pb-1 text-[9px] font-extrabold tracking-widest text-white/30 uppercase">
{m.admin_heading()}
</div>
{#if canManageUsers}
<a
href="/admin/users"
class="flex flex-col gap-0.5 border-l-[3px] px-3.5 py-2.5 transition-colors
{isActive('users')
? 'border-brand-mint bg-white/10'
: 'border-transparent hover:bg-white/5'}"
aria-current={isActive('users') ? 'page' : undefined}
>
<span class="text-[13px] font-black {isActive('users') ? 'text-white/65' : 'text-white/20'}">
{userCount}
</span>
<span
class="text-[9px] font-extrabold tracking-[0.5px] uppercase
{isActive('users') ? 'text-white' : 'text-white/55'}"
>
{m.admin_tab_users()}
</span>
</a>
{/if}
{#if canManageGroups}
<a
href="/admin/groups"
class="flex flex-col gap-0.5 border-l-[3px] px-3.5 py-2.5 transition-colors
{isActive('groups')
? 'border-brand-mint bg-white/10'
: 'border-transparent hover:bg-white/5'}"
aria-current={isActive('groups') ? 'page' : undefined}
>
<span class="text-[13px] font-black {isActive('groups') ? 'text-white/65' : 'text-white/20'}">
{groupCount}
</span>
<span
class="text-[9px] font-extrabold tracking-[0.5px] uppercase
{isActive('groups') ? 'text-white' : 'text-white/55'}"
>
{m.admin_tab_groups()}
</span>
</a>
{/if}
{#if canManageTags}
<a
href="/admin/tags"
class="flex flex-col gap-0.5 border-l-[3px] px-3.5 py-2.5 transition-colors
{isActive('tags')
? 'border-brand-mint bg-white/10'
: 'border-transparent hover:bg-white/5'}"
aria-current={isActive('tags') ? 'page' : undefined}
>
<span class="text-[13px] font-black {isActive('tags') ? 'text-white/65' : 'text-white/20'}">
{tagCount}
</span>
<span
class="text-[9px] font-extrabold tracking-[0.5px] uppercase
{isActive('tags') ? 'text-white' : 'text-white/55'}"
>
{m.admin_tab_tags()}
</span>
</a>
{/if}
<div class="flex-1"></div>
{#if canRunMaintenance}
<a
href="/admin/system"
class="flex flex-col gap-0.5 border-t border-l-[3px] border-white/10 px-3.5 py-2.5 transition-colors
{isActive('system')
? 'border-brand-mint bg-white/10'
: 'border-l-transparent hover:bg-white/5'}"
aria-current={isActive('system') ? 'page' : undefined}
>
<span
class="text-[9px] font-extrabold tracking-[0.5px] uppercase
{isActive('system') ? 'text-white' : 'text-white/55'}"
>
{m.admin_tab_system()}
</span>
</a>
{/if}
</nav>