mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 19:46:42 +02:00
feat(auth): rate limit feedback, audit log UI, and E2E tests
Rate-limiting feedback: - LoginPage detects 429/account-locked errors and shows countdown timer - Submit button disabled during cooldown period Audit log: - GET /auth/security-events endpoint (JWT-protected) in auth controller - getSecurityEvents() in BetterAuthService + shared-auth client - AuditLog component with event type labels, relative dates, UA parsing - Integrated in ManaCore settings page E2E tests (passkey-2fa.e2e-spec.ts): - Passkey registration/authentication flow tests - Auth guard enforcement (protected vs public endpoints) - 2FA passthrough route existence tests - Edge cases (cross-user access, missing fields, token shape) CSRF note: Already covered by Better Auth (SameSite + HttpOnly + Trusted Origins). Token refresh already has 4-retry + offline detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
11ab265d55
commit
0dfd603892
9 changed files with 1061 additions and 2 deletions
|
|
@ -145,6 +145,16 @@
|
|||
let twoFactorCode = $state('');
|
||||
let useBackupCode = $state(false);
|
||||
let trustDevice = $state(false);
|
||||
let rateLimitCountdown = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
if (rateLimitCountdown > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
rateLimitCountdown--;
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
});
|
||||
|
||||
// Theme state - can be toggled manually, defaults to system preference
|
||||
let userThemePreference = $state<'light' | 'dark' | null>(null);
|
||||
|
|
@ -252,6 +262,16 @@
|
|||
setError(t.emailNotVerified || 'Email not verified.', 'general');
|
||||
} else {
|
||||
setError(result.error || t.signInFailed, 'general');
|
||||
|
||||
// Detect rate limiting
|
||||
if (result.error?.includes('Too Many') || result.error?.includes('rate limit')) {
|
||||
rateLimitCountdown = 60; // 1 minute cooldown
|
||||
} else if (
|
||||
result.error?.includes('temporarily locked') ||
|
||||
result.error === 'ACCOUNT_LOCKED'
|
||||
) {
|
||||
rateLimitCountdown = (result as any).retryAfter || 300; // 5 min default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -568,6 +588,9 @@
|
|||
{resendingVerification ? t.resendingVerification : t.resendVerification}
|
||||
</button>
|
||||
{/if}
|
||||
{#if rateLimitCountdown > 0}
|
||||
<p class="retry-countdown">Erneut versuchen in {rateLimitCountdown}s</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -652,7 +675,7 @@
|
|||
<!-- Submit -->
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || showSuccess}
|
||||
disabled={loading || showSuccess || rateLimitCountdown > 0}
|
||||
class="submit-button"
|
||||
style:background-color={showSuccess ? '#22c55e' : primaryColor + '60'}
|
||||
style:border-color={showSuccess ? '#22c55e' : primaryColor}
|
||||
|
|
@ -934,6 +957,11 @@
|
|||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.retry-countdown {
|
||||
font-weight: 600;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.resend-link {
|
||||
background: none;
|
||||
border: none;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue