diff --git a/actions.go b/actions.go
index 62e2b2013bf929899b4cc383ce2ba448eb14edb0..5277b4a959aa5483a7af4ca0513cf53ee516e4e9 100644
--- a/actions.go
+++ b/actions.go
@@ -177,6 +177,8 @@ type PasswordRecoveryResponse struct {
 // RecoverPassword lets users reset their password by providing
 // secondary credentials, which we authenticate ourselves.
 //
+// Two-factor authentication is disabled on successful recovery.
+//
 // TODO: call out to auth-server for secondary authentication?
 func (s *AccountService) RecoverPassword(ctx context.Context, tx TX, req *PasswordRecoveryRequest) (*PasswordRecoveryResponse, error) {
 	user, err := getUserOrDie(ctx, tx, req.Username)
@@ -208,6 +210,7 @@ func (s *AccountService) RecoverPassword(ctx context.Context, tx TX, req *Passwo
 		if err := s.changeUserPasswordAndUpdateEncryptionKeys(ctx, tx, user, req.RecoveryPassword, req.Password); err != nil {
 			return err
 		}
+
 		// Disable 2FA.
 		return s.disable2FA(ctx, tx, user)
 	})
diff --git a/backend/model.go b/backend/model.go
index d467ae7d4cb1d22e0afa06ce1e462494bda9fc25..31a07bce8765709f36163bd3ef1d5e4f790b3cae 100644
--- a/backend/model.go
+++ b/backend/model.go
@@ -128,12 +128,11 @@ func newUser(entry *ldap.Entry) (*accountserver.User, error) {
 		UID:                  uidNumber,
 		PasswordRecoveryHint: entry.GetAttributeValue(recoveryHintLDAPAttr),
 		U2FRegistrations:     decodeU2FRegistrations(entry.GetAttributeValues(u2fRegistrationsLDAPAttr)),
+		HasOTP:               entry.GetAttributeValue(totpSecretLDAPAttr) != "",
 	}
 
 	// The user has 2FA enabled if it has a TOTP secret or U2F keys.
-	if (entry.GetAttributeValue(totpSecretLDAPAttr) != "") || (len(user.U2FRegistrations) > 0) {
-		user.Has2FA = true
-	}
+	user.Has2FA = (user.HasOTP || (len(user.U2FRegistrations) > 0))
 
 	if user.Lang == "" {
 		user.Lang = "en"
diff --git a/types.go b/types.go
index 91adf6a0c19e11a1ee232f5354970be69dfab7f9..2bb3d7f43837fc2da3e71f035f3436d63393322d 100644
--- a/types.go
+++ b/types.go
@@ -30,13 +30,23 @@ type User struct {
 	// UNIX user id.
 	UID int `json:"uid"`
 
-	Has2FA               bool   `json:"has_2fa"`
-	HasEncryptionKeys    bool   `json:"has_encryption_keys"`
+	// Has2FA is true if the user has a second-factor authentication
+	// mechanism properly set up. In practice, this is the case if either
+	// HasOTP is true, or len(U2FRegistrations) > 0.
+	Has2FA bool `json:"has_2fa"`
+
+	// HasOTP is true if TOTP is set up.
+	HasOTP bool `json:"has_otp"`
+
+	// HasEncryptionKeys is true if encryption keys are properly set up for
+	// this user.  TODO: consider disabling it.
+	HasEncryptionKeys bool `json:"has_encryption_keys"`
+
 	PasswordRecoveryHint string `json:"password_recovery_hint"`
 
 	AppSpecificPasswords []*AppSpecificPasswordInfo `json:"app_specific_passwords,omitempty"`
 
-	U2FRegistrations []*u2f.Registration `json:"u2f_registrations"`
+	U2FRegistrations []*u2f.Registration `json:"u2f_registrations,omitempty"`
 
 	Resources []*Resource `json:"resources,omitempty"`
 }