feat(auth): session management UI and improved account lockout feedback

Session management:
- GET /auth/sessions and DELETE /auth/sessions/:id endpoints
- listSessions() and revokeSession() in shared-auth client
- SessionManager component: active sessions list with device info,
  "Aktuell" badge, revoke individual or all other sessions
- Integrated in ManaCore settings page

Account lockout UX:
- Dedicated amber lockout banner (distinct from generic rate-limit)
- "Konto vorübergehend gesperrt" with MM:SS countdown
- "Passwort zurücksetzen" link as alternative action
- formatCountdown helper for clean time display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-27 11:49:17 +01:00
parent 2624e5a6b7
commit 8f56feb115
8 changed files with 898 additions and 4 deletions

View file

@ -213,6 +213,18 @@ export const authStore = {
return authService.getSecurityEvents();
},
async listSessions() {
const authService = getAuthService();
if (!authService) return [];
return authService.listSessions();
},
async revokeSession(sessionId: string) {
const authService = getAuthService();
if (!authService) return { success: false, error: 'Auth not available' };
return authService.revokeSession(sessionId);
},
/**
* Sign in with email and password
*/

View file

@ -1,7 +1,12 @@
<script lang="ts">
import { onMount } from 'svelte';
import { Button, Input, Card, PageHeader, GlobalSettingsSection } from '@manacore/shared-ui';
import { PasskeyManager, TwoFactorSetup, AuditLog } from '@manacore/shared-auth-ui';
import {
PasskeyManager,
TwoFactorSetup,
AuditLog,
SessionManager,
} from '@manacore/shared-auth-ui';
import { authStore } from '$lib/stores/auth.svelte';
import { creditsService } from '$lib/api/credits';
import type { CreditBalance } from '$lib/api/credits';
@ -27,6 +32,10 @@
let securityEvents = $state<any[]>([]);
let securityEventsLoading = $state(false);
// Sessions
let sessions = $state<any[]>([]);
let sessionsLoading = $state(false);
onMount(async () => {
if (authStore.isAuthenticated) {
try {
@ -38,6 +47,10 @@
securityEventsLoading = true;
securityEvents = await authStore.getSecurityEvents();
securityEventsLoading = false;
// Load sessions
sessionsLoading = true;
sessions = await authStore.listSessions();
sessionsLoading = false;
} catch (e) {
console.error('Failed to load data:', e);
}
@ -302,6 +315,23 @@
</div>
</Card>
<!-- Sessions Section -->
<Card>
<div class="p-6">
<SessionManager
{sessions}
loading={sessionsLoading}
onRevoke={(id) => authStore.revokeSession(id)}
onRefresh={async () => {
sessionsLoading = true;
sessions = await authStore.listSessions();
sessionsLoading = false;
}}
primaryColor="#6366f1"
/>
</div>
</Card>
<!-- Two-Factor Authentication Section -->
<Card>
<div class="p-6">