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

@ -824,6 +824,52 @@ export function createAuthService(config: AuthServiceConfig) {
}
},
/**
* List active sessions
*/
async listSessions(): Promise<any[]> {
try {
const appToken = await service.getAppToken();
if (!appToken) return [];
const res = await fetch(`${baseUrl}/api/v1/auth/sessions`, {
headers: { Authorization: `Bearer ${appToken}` },
});
if (!res.ok) return [];
return await res.json();
} catch {
return [];
}
},
/**
* Revoke a session
*/
async revokeSession(sessionId: string): Promise<AuthResult> {
try {
const appToken = await service.getAppToken();
if (!appToken) return { success: false, error: 'Not authenticated' };
const res = await fetch(`${baseUrl}/api/v1/auth/sessions/${sessionId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${appToken}` },
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
return { success: false, error: err.message || 'Failed to revoke session' };
}
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to revoke session',
};
}
},
/**
* Get the current app token
*/