feat(auth): v2.1.0 - MFA TOTP avec QR code, recovery codes
CI / lint (push) Failing after 16s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 21s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

- backend/auth/mfa.py: TOTP (pyotp), recovery codes SHA-256
- Login flow: mfa_required → totp/verify → token (ou recovery)
- 6 nouveaux endpoints /api/auth/mfa/*
- Frontend: QR code setup, 6-digit auto-submit, recovery codes
- Settings: section Sécurité avec enable/disable MFA
- CSS: mfa-challenge, setup-card, recovery-list, badges
- i18n: 36 nouvelles clés EN/FR
- pyotp ajouté aux dépendances
- 30 tests (TOTP, recovery, API endpoints, login flow)
- 439 tests passent au total
This commit is contained in:
2026-09-06 18:42:32 -04:00
parent 7b0dacabdd
commit 83063d3a18
11 changed files with 1569 additions and 67 deletions
+128
View File
@@ -2398,6 +2398,34 @@ select {
background: var(--accent);
color: #fff;
}
/* Theme import/export actions */
.theme-actions {
display: flex;
gap: 8px;
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.theme-action-btn {
padding: 5px 14px;
font-size: 0.7rem;
font-weight: 600;
border: 1px solid var(--border);
background: var(--bg-secondary);
color: var(--text-secondary);
border-radius: 6px;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.theme-action-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.theme-custom-badge {
font-size: 0.6rem;
color: var(--accent);
margin-left: 2px;
}
/* Profile section */
.profile-form { max-width: 420px; }
.profile-field { margin-bottom: 14px; }
@@ -8878,3 +8906,103 @@ body.popup-mode .content-area {
right: 4px;
}
}
/* ── MFA (Multi-Factor Authentication) ────────────────────────────────── */
.mfa-challenge {
margin-top: 20px;
padding: 24px;
border-radius: 12px;
background: var(--surface, #1a1a2e);
border: 1px solid var(--border, #333);
text-align: center;
}
.mfa-challenge .mfa-icon { font-size: 48px; margin-bottom: 12px; }
.mfa-challenge h3 { margin: 0 0 8px; color: var(--text, #fff); }
.mfa-challenge p { color: var(--muted, #999); margin: 0 0 16px; }
.mfa-code-input {
width: 180px;
font-size: 28px;
text-align: center;
letter-spacing: 12px;
padding: 12px;
border: 2px solid var(--border, #444);
border-radius: 8px;
background: var(--bg, #0d0d1a);
color: var(--text, #fff);
font-family: monospace;
outline: none;
}
.mfa-code-input:focus { border-color: var(--accent, #7C3AED); }
.mfa-code-input-group { display: flex; gap: 8px; justify-content: center; align-items: center; margin-bottom: 12px; }
.mfa-code-input-group span { color: var(--muted, #999); font-size: 24px; }
.mfa-link-btn {
background: none; border: none; color: var(--accent, #7C3AED);
cursor: pointer; font-size: 13px; text-decoration: underline;
}
.mfa-link-btn:hover { opacity: 0.8; }
.mfa-error { color: #e74c3c; font-size: 13px; margin-top: 8px; }
.mfa-recovery-input { width: 200px; font-size: 20px; letter-spacing: 4px; }
/* MFA Settings (Security tab) */
.mfa-status-section { padding: 16px 0; }
.mfa-status-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.mfa-badge {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: 600;
}
.mfa-badge-on { background: #1a3a2a; color: #4ade80; }
.mfa-badge-off { background: #3a2a1a; color: #f59e0b; }
.mfa-status-label { color: var(--text, #fff); font-weight: 500; }
/* MFA Setup card */
.mfa-setup-card {
padding: 20px; border-radius: 10px;
background: var(--surface2, #1e1e3a); border: 1px solid var(--border, #333);
}
.mfa-setup-card h4 { margin: 0 0 12px; color: var(--text, #fff); }
.mfa-qr-container { text-align: center; margin: 16px 0; }
.mfa-qr-code img, .mfa-qr-code canvas { max-width: 200px; border-radius: 8px; }
.mfa-secret-details { margin-top: 12px; }
.mfa-secret-code {
font-family: monospace; font-size: 14px; padding: 8px 12px;
background: var(--bg, #0d0d1a); border-radius: 6px;
color: var(--accent, #7C3AED); cursor: pointer; user-select: all;
display: inline-block;
}
.mfa-info-text { color: var(--muted, #999); font-size: 13px; margin: 8px 0; }
.mfa-subtitle { color: var(--muted, #999); font-size: 13px; margin: 0 0 16px; }
.mfa-verify-section { display: flex; gap: 8px; align-items: center; margin-top: 12px; }
.mfa-actions { margin-top: 16px; }
/* MFA Recovery codes display */
.mfa-recovery-card {
padding: 20px; border-radius: 10px;
background: var(--surface2, #1e1e3a); border: 1px solid var(--border, #333);
margin-top: 16px;
}
.mfa-recovery-card h4 { margin: 0 0 8px; color: var(--text, #fff); }
.mfa-recovery-list {
display: grid; grid-template-columns: 1fr 1fr; gap: 6px 24px;
margin: 12px 0; list-style: none; padding: 0;
}
.mfa-recovery-code {
font-family: monospace; font-size: 14px; padding: 4px 8px;
background: var(--bg, #0d0d1a); border-radius: 4px;
color: var(--text, #fff); letter-spacing: 1px;
}
.mfa-recovery-actions { display: flex; gap: 8px; margin-top: 12px; }
/* MFA Disable card */
.mfa-disable-card {
padding: 16px; border-radius: 10px;
background: var(--surface2, #1e1e3a); border: 1px solid #e74c3c33;
margin-top: 16px;
}
.mfa-disable-card h4 { margin: 0 0 8px; color: #e74c3c; }
.mfa-disable-actions { display: flex; gap: 8px; margin-top: 12px; }
.mfa-warning {
color: #f59e0b; font-size: 13px; padding: 8px 12px;
background: #3a2a1a; border-radius: 6px; margin-top: 8px;
}