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,72 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { load } from './+layout.server';
vi.mock('$lib/api.server', () => ({ createApiClient: vi.fn() }));
import { createApiClient } from '$lib/api.server';
function mockApi(users: unknown[], groups: unknown[], tags: unknown[]) {
vi.mocked(createApiClient).mockReturnValue({
GET: vi
.fn()
.mockResolvedValueOnce({ response: { ok: true }, data: users })
.mockResolvedValueOnce({ response: { ok: true }, data: groups })
.mockResolvedValueOnce({ response: { ok: true }, data: tags })
} as ReturnType<typeof createApiClient>);
}
const adminUser = {
groups: [{ permissions: ['ADMIN', 'ADMIN_USER', 'ADMIN_TAG', 'ADMIN_PERMISSION'] }]
};
const tagAdminUser = { groups: [{ permissions: ['ADMIN_TAG'] }] };
const noPermUser = { groups: [{ permissions: ['READ_ALL'] }] };
beforeEach(() => vi.clearAllMocks());
describe('admin layout load — permission check', () => {
it('throws 403 when user has no admin permission', async () => {
await expect(
load({ fetch: vi.fn() as unknown as typeof fetch, locals: { user: noPermUser } })
).rejects.toMatchObject({ status: 403 });
});
it('throws 403 when user is undefined', async () => {
await expect(
load({ fetch: vi.fn() as unknown as typeof fetch, locals: { user: undefined } })
).rejects.toMatchObject({ status: 403 });
});
it('throws 403 when user has no groups', async () => {
await expect(
load({ fetch: vi.fn() as unknown as typeof fetch, locals: { user: { groups: [] } } })
).rejects.toMatchObject({ status: 403 });
});
it('allows access for a user with ADMIN_TAG only', async () => {
mockApi([], [], []);
await expect(
load({ fetch: vi.fn() as unknown as typeof fetch, locals: { user: tagAdminUser } })
).resolves.toBeDefined();
});
it('returns entity counts and permission flags for a full admin', async () => {
mockApi(
[{ id: 'u1' }, { id: 'u2' }],
[{ id: 'g1' }],
[{ id: 't1' }, { id: 't2' }, { id: 't3' }]
);
const result = await load({
fetch: vi.fn() as unknown as typeof fetch,
locals: { user: adminUser }
});
expect(result.userCount).toBe(2);
expect(result.groupCount).toBe(1);
expect(result.tagCount).toBe(3);
expect(result.canManageUsers).toBe(true);
expect(result.canManageTags).toBe(true);
expect(result.canManageGroups).toBe(true);
expect(result.canRunMaintenance).toBe(true);
});
});