mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-27 20:29:10 +00:00
7d78c9fe22
* feat(auth): add TOTP two-factor authentication with backup codes Adds RFC 6238 time-based one-time password support to every tier, integrated with the existing password and SSO login paths. Backend: - New MfaService wrapping otplib with a plus or minus 1 step tolerance, base32 secret generation, and hashed single-use backup codes (bcrypt). - user_mfa and mfa_used_tokens tables in DatabaseService. The second table is a DB-backed replay blacklist, purged on a 60s interval. - authMiddleware now recognizes an mfa_pending scope. A token carrying that scope is rejected on every route except the MFA challenge and logout, so no API surface is reachable before the second factor clears. - /api/auth/login issues only a short-lived mfa_pending cookie when the user has MFA enrolled. /api/auth/login/mfa consumes that cookie, verifies the code (or backup code), and swaps in a real session. - /api/auth/mfa/* routes for status, enrol/start, enrol/confirm, disable, backup-code regenerate, and SSO-bypass opt-in. - Admin recovery path: POST /api/users/:id/mfa/reset clears the target's MFA state, bumps token_version, and writes an audit log entry. - CLI emergency fallback: backend/src/cli/resetMfa.ts is wired via `npm run reset-mfa <username>` and also exported for tests. - SSO flows (LDAP and OIDC) gate on user_mfa.sso_enforce_mfa before issuing a session; default behaviour keeps the SSO path frictionless. - Per-user lockout after 5 consecutive failed codes (15 min). Frontend: - AppStatus gains an mfa-challenge branch driven by /api/auth/status. - New MfaChallenge screen, MfaEnrollDialog (QR plus manual secret plus backup codes), MfaDisableDialog, MfaBackupCodesDialog. - Account section shows a Two-factor authentication card with enrol, regenerate, disable, and the SSO-enforce toggle (shown only when SSO providers are configured). - Users section gains a Reset 2FA action for admins. Docs: - New user guide at features/two-factor-authentication.mdx. - New admin guide at operations/two-factor-admin.mdx. - SSO page cross-links to the 2FA doc. * fix(mfa): drop unused TEST_PASSWORD import and stale eslint disable * fix(mfa): simplify e2e openAccountSettings helper to match working pattern * fix(mfa): make e2e suite self-contained and always clean up Test #2 called loginAs() before the MFA challenge step, which waited for the dashboard indicator that never appears once the previous test enrolled the user. That timeout skipped the rest of the serial block, including the disable step, leaving MFA enabled and breaking every later spec. Two fixes: - Tests #2 and #3 now navigate directly to the login page instead of piggybacking on loginAs, which only handles the password-only path. - A new afterAll hook unconditionally disables MFA via the API using two unused backup codes, so the DB is reset even if a test fails midway. * fix(e2e): use backup code for mfa recovery to avoid totp replay race The final recovery step in the backup-code replay test previously generated a fresh TOTP to sign back in. When the timing landed inside the same 30-second window that test #2 consumed, the server's replay blacklist correctly rejected it, producing a ~50% flake rate. Backup codes are single-use and sidestep the replay window, so the recovery becomes deterministic. * fix(e2e): drive mfa disable test through the challenge screen Test #4 called loginAs after test #3 left MFA enabled, but loginAs waits for the dashboard indicator and does not handle the challenge screen, so it timed out. Drive the login manually, satisfy the challenge with a backup code, and use a backup code for the disable step too to avoid any TOTP replay-window race against earlier tests in the serial block.
121 lines
6.1 KiB
Plaintext
121 lines
6.1 KiB
Plaintext
---
|
||
title: Two-Factor Authentication
|
||
description: Protect your Sencho account with a time-based one-time password (TOTP) and single-use backup codes.
|
||
---
|
||
|
||
Two-factor authentication (2FA) adds a second step to sign-in. After your password is accepted, Sencho asks for a six-digit code from your authenticator app. Someone who steals your password still cannot sign in without that code.
|
||
|
||
2FA is available on all Sencho tiers, including Community.
|
||
|
||
## How it works
|
||
|
||
1. You enrol once by scanning a QR code with an authenticator app (or typing a secret by hand)
|
||
2. The app generates a fresh six-digit code every 30 seconds
|
||
3. On every sign-in, Sencho asks for the current code after your password passes
|
||
4. You also save ten single-use backup codes for the days when your phone is not available
|
||
|
||
<Frame>
|
||
<img src="/images/two-factor-auth/challenge.png" alt="Sign-in challenge screen asking for the six-digit code" />
|
||
</Frame>
|
||
|
||
## Enrol
|
||
|
||
Open **Settings → Account & Security** and click **Set up 2FA**. Sencho walks you through three steps.
|
||
|
||
### Step 1 – Scan the QR code
|
||
|
||
Scan the QR code with an authenticator app such as **1Password**, **Bitwarden**, **Google Authenticator**, **Authy**, or **Microsoft Authenticator**.
|
||
|
||
<Frame>
|
||
<img src="/images/two-factor-auth/enroll-qr.png" alt="Enrolment dialog showing the QR code" />
|
||
</Frame>
|
||
|
||
If your app cannot scan or you would rather type the secret into a password manager, click **Can't scan? Show secret key** and copy the base32 string. Paste it into the app's manual-entry field.
|
||
|
||
### Step 2 – Confirm
|
||
|
||
Enter the six-digit code your app is currently showing. This proves the app is paired correctly before Sencho turns 2FA on.
|
||
|
||
<Frame>
|
||
<img src="/images/two-factor-auth/enroll-confirm.png" alt="Enrolment confirmation step asking for the six-digit code" />
|
||
</Frame>
|
||
|
||
### Step 3 – Save your backup codes
|
||
|
||
Sencho shows ten single-use backup codes. Each code can sign you in once without the authenticator app. **Save them somewhere safe before closing this dialog**, they are not shown again.
|
||
|
||
<Frame>
|
||
<img src="/images/two-factor-auth/enroll-backup-codes.png" alt="Ten backup codes shown after enrolment" />
|
||
</Frame>
|
||
|
||
Copy them to a password manager or download them as a text file. Common places users store them:
|
||
|
||
- Their primary password manager as a secure note on the Sencho entry
|
||
- An encrypted document on a separate device
|
||
- Printed and stored with other emergency recovery material
|
||
|
||
## Sign in with 2FA
|
||
|
||
After entering your password, Sencho shows the 2FA challenge screen.
|
||
|
||
1. Open your authenticator app, find the Sencho entry, read the six-digit code
|
||
2. Type it into the **Verification code** field
|
||
3. Click **Verify and sign in**
|
||
|
||
If your phone is unavailable, click **Use a backup code instead**, enter one of the codes you saved during enrolment, and click **Verify and sign in**. That code is now used up.
|
||
|
||
## Regenerate backup codes
|
||
|
||
If you think your backup codes have been exposed, or you have used most of them, regenerate them from **Settings → Account & Security → Regenerate backup codes**. Sencho asks for a current code, then issues a fresh set of ten and invalidates the old set immediately.
|
||
|
||
<Frame>
|
||
<img src="/images/two-factor-auth/account-card-enabled.png" alt="Account card showing 2FA enabled with regenerate and disable actions" />
|
||
</Frame>
|
||
|
||
## Disable 2FA
|
||
|
||
From the same Account & Security section, click **Disable 2FA**. Sencho asks you to confirm with a current authenticator code (or a backup code) so that no one else can disable protection without your phone. Once disabled, only your password protects the account.
|
||
|
||
## Signing in with SSO
|
||
|
||
If Sencho is configured with LDAP or an OIDC provider (Google, GitHub, Okta), the SSO flow signs you in without the second factor by default. Your identity provider has already authenticated you.
|
||
|
||
If you want Sencho to ask for a TOTP code even after a successful SSO sign-in, flip the **Require 2FA even when signing in via SSO** switch on the Account & Security card. The switch only appears when at least one SSO provider is enabled.
|
||
|
||
<Frame>
|
||
<img src="/images/two-factor-auth/sso-enforce.png" alt="Toggle that requires 2FA even when signing in via SSO" />
|
||
</Frame>
|
||
|
||
## Troubleshooting
|
||
|
||
### "Code invalid" even though the code is current
|
||
|
||
The most common cause is clock drift between your phone and the Sencho server. Authenticator apps compute codes from the current Unix time, and even a 30 second skew breaks verification.
|
||
|
||
- On iOS, open **Settings → General → Date & Time** and enable **Set Automatically**
|
||
- On Android, open **Settings → System → Date & time** and enable **Automatic date & time**
|
||
- In Google Authenticator, open the menu → **Settings → Time correction for codes → Sync now**
|
||
- In Authy, open **Settings → My account → Sync device time**
|
||
|
||
If your server runs in a container, make sure its host clock is synced with NTP.
|
||
|
||
### Wrong account selected in the authenticator
|
||
|
||
Authenticator apps let you store many accounts. If you have more than one Sencho instance, or a Sencho entry and a different service that looks similar, pick the correct one. The issuer label Sencho uses is simply `Sencho`.
|
||
|
||
### The QR code will not scan
|
||
|
||
Click **Can't scan? Show secret key** during enrolment, copy the base32 string, and paste it into the authenticator app's manual-entry screen. The secret is identical, the QR code is just a convenience.
|
||
|
||
### Lost phone and no backup codes left
|
||
|
||
Contact an administrator. An admin can reset your 2FA from the Users section in **Settings → Users**. See the [admin guide](/operations/two-factor-admin) for the steps. If you are the only admin and have lost access, the administrator can also reset 2FA from the command line on the host running Sencho.
|
||
|
||
### SSO sign-in is unexpectedly asking for a code
|
||
|
||
The **Require 2FA even when signing in via SSO** toggle is on for your account. Open **Settings → Account & Security** and flip it off if SSO alone is enough for your threat model.
|
||
|
||
### Enrolment did not activate after confirmation
|
||
|
||
The browser may have dropped the JSON response. Retry the confirmation step and watch for an error toast. If confirmation continues to fail, cancel the dialog, ensure your clock is synced, and start enrolment again.
|