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>
115 lines
3.1 KiB
Svelte
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>
|