Fix F-01: TOTP enroll must not set required=1 early
- db/accounts.go: add StorePendingTOTP() which writes totp_secret_enc and totp_secret_nonce but leaves totp_required=0; add comment explaining two-phase flow - server.go (handleTOTPEnroll): switch from SetTOTP() to StorePendingTOTP() so the required flag is only set after the user confirms a valid TOTP code via handleTOTPConfirm, which still calls SetTOTP() - server_test.go: TestTOTPEnrollDoesNotRequireTOTP verifies that after POST /v1/auth/totp/enroll, TOTPRequired is false and the encrypted secret is present; confirms that a subsequent login without a TOTP code still succeeds (no lockout) - AUDIT.md: mark F-01 and F-11 as fixed Security: without this fix an admin who enrolls TOTP but abandons before confirmation is permanently locked out because totp_required=1 but no confirmed secret exists. StorePendingTOTP() keeps the secret pending until the user proves possession by confirming a valid code.
This commit is contained in:
@@ -656,9 +656,11 @@ func (s *Server) handleTOTPEnroll(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store the encrypted pending secret. The totp_required flag is NOT set
|
||||
// yet — it is set only after the user confirms the code.
|
||||
if err := s.db.SetTOTP(acct.ID, secretEnc, secretNonce); err != nil {
|
||||
// Security: use StorePendingTOTP (not SetTOTP) so that totp_required
|
||||
// remains 0 until the user proves possession of the secret by confirming
|
||||
// a valid code. If the user abandons enrollment the flag stays unset and
|
||||
// they can still log in with just their password — no lockout.
|
||||
if err := s.db.StorePendingTOTP(acct.ID, secretEnc, secretNonce); err != nil {
|
||||
middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user