You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Apr 10, 2026. It is now read-only.
Creating or revoking an API key (see #56) must require a current TOTP code when 2FA is enabled. API keys are persistent credentials that survive session expiry — minting one without re-verifying 2FA would let an attacker with a live session cookie create permanent access. The /api/user/api-keys POST and DELETE endpoints should accept an optional totpCode field and enforce it when totp_enabled = 1.
Password reset interaction
In the existing reset-password handler, after rotating key_salt and bumping token_version, also:
Clear totp_secret, totp_enabled = 0, totp_backup_codes = null — password reset is the recovery path for a lost authenticator device
Revoke all API keys (UPDATE api_keys SET revoked = 1 WHERE user_id = ?) — API keys wrap the pre-reset encryption key (v2 envelope encryption) or otherwise represent credentials that should not survive a full credential reset. See feat: Public REST API with API key authentication #56.
The full password reset sequence becomes: rotate key_salt → bump token_version → revoke refresh tokens → clear 2FA → revoke API keys.
Rate limiting
Add a totpLimiter: 5 attempts per 10 min, keyed by user ID (not IP), applied to /confirm and the login TOTP step.
Backup code verification helper
asyncfunctionverifyBackupCode(rawCode: string,storedHashes: string[]): Promise<number|null>// returns index of matched hash (for removal), or null if no match
After a backup code is used, splice it from the array and update totp_backup_codes in the DB.
Part of #50.
Database changes
Add 3 columns to
usersvia migration indb.ts:New endpoints (all require
jwtAuth)POST /api/user/totp/setupGenerates a new TOTP secret and returns setup info. Does not enable 2FA yet — user must confirm first.
totp_secret(overwrite any previous pending secret;totp_enabledstays 0){ "otpauthUrl": "otpauth://totp/TaskDial:user@example.com?secret=BASE32&issuer=TaskDial", "secret": "BASE32" }otpauthUrlas a QR codePOST /api/user/totp/confirmUser submits the first TOTP code to prove they've set up their authenticator correctly.
{ "code": "123456" }totp_secret(window ±1 step for clock drift)totp_enabled = 1totp_backup_codestotp_setupDELETE /api/user/totpDisable 2FA. Requires verification to prevent CSRF.
{ "code": "123456" }— either a current TOTP code or a backup codetotp_secret,totp_enabled = 0,totp_backup_codes = nulltotp_disabledAPI key interaction
Creating or revoking an API key (see #56) must require a current TOTP code when 2FA is enabled. API keys are persistent credentials that survive session expiry — minting one without re-verifying 2FA would let an attacker with a live session cookie create permanent access. The
/api/user/api-keysPOST and DELETE endpoints should accept an optionaltotpCodefield and enforce it whentotp_enabled = 1.Password reset interaction
In the existing
reset-passwordhandler, after rotatingkey_saltand bumpingtoken_version, also:totp_secret,totp_enabled = 0,totp_backup_codes = null— password reset is the recovery path for a lost authenticator deviceUPDATE api_keys SET revoked = 1 WHERE user_id = ?) — API keys wrap the pre-reset encryption key (v2 envelope encryption) or otherwise represent credentials that should not survive a full credential reset. See feat: Public REST API with API key authentication #56.The full password reset sequence becomes: rotate
key_salt→ bumptoken_version→ revoke refresh tokens → clear 2FA → revoke API keys.Rate limiting
Add a
totpLimiter: 5 attempts per 10 min, keyed by user ID (not IP), applied to/confirmand the login TOTP step.Backup code verification helper
After a backup code is used, splice it from the array and update
totp_backup_codesin the DB.