- Add ApiKey Prisma model (keyHash, expiresAt, revokedAt) - Protect /api/v1/* routes with Authorization: Bearer header check in hooks - create rotateApiKey() utility: normal (48h grace) and emergency (immediate) - Add Super-only /admin/api-keys page for key rotation with one-time reveal - Fix roleId check by using parent layout's DB-fetched adminUser - Add API Keys link to admin sidebar
75 lines
2.6 KiB
Svelte
75 lines
2.6 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/state';
|
|
import { canAccessAdmin } from '$lib/svelte-admin/roles';
|
|
import { getAllModelConfigs } from '$lib/svelte-admin/registry';
|
|
import type { LayoutData } from './$types';
|
|
|
|
let { children, data } = $props<{ children: any; data: LayoutData }>();
|
|
|
|
const isAdmin = $derived(canAccessAdmin(data.userRoleId));
|
|
const adminModels = getAllModelConfigs();
|
|
|
|
const navLinks = [
|
|
{ href: '/dashboard', label: 'Dashboard' },
|
|
{ href: '/account', label: 'Account' },
|
|
{ href: '/proclamations', label: 'Proclamations' },
|
|
{ href: '/license-keys', label: 'License Keys' }
|
|
];
|
|
|
|
function isActive(href: string): boolean {
|
|
return page.url.pathname === href || page.url.pathname.startsWith(href + '/');
|
|
}
|
|
</script>
|
|
|
|
<div class="flex min-h-0 flex-1">
|
|
<!-- Sidebar -->
|
|
<nav class="mt-12 mr-12 flex flex-col gap-1">
|
|
{#each navLinks as link}
|
|
<a
|
|
href={link.href}
|
|
class="rounded px-3 py-1.5 text-sm font-medium transition-colors {isActive(link.href)
|
|
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
|
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
|
|
>
|
|
{link.label}
|
|
</a>
|
|
{/each}
|
|
{#if isAdmin}
|
|
<div class="mt-4 flex flex-col gap-1">
|
|
<a
|
|
href="/admin"
|
|
class="rounded px-3 py-1.5 text-sm font-medium transition-colors {isActive('/admin')
|
|
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
|
|
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
|
|
>
|
|
Admin Panel
|
|
</a>
|
|
<div class="ml-3 flex flex-col gap-0.5">
|
|
{#each adminModels as model}
|
|
<a
|
|
href="/admin/{model.slug}"
|
|
class="rounded px-3 py-1 text-sm transition-colors {isActive('/admin/' + model.slug)
|
|
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
|
|
: 'text-blue-600 hover:bg-gray-50 hover:text-blue-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
|
|
>
|
|
{model.displayName}
|
|
</a>
|
|
{/each}
|
|
<a
|
|
href="/admin/api-keys"
|
|
class="rounded px-3 py-1 text-sm transition-colors {isActive('/admin/api-keys')
|
|
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
|
|
: 'text-blue-600 hover:bg-gray-50 hover:text-blue-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
|
|
>
|
|
API Keys
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</nav>
|
|
<!-- Page content -->
|
|
<div class="min-h-0 flex-1 overflow-auto">
|
|
{@render children()}
|
|
</div>
|
|
</div>
|