From 1f45d1e1303c5843ceeb473eef343b82491bd706 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Wed, 2 Mar 2022 01:24:31 +0100
Subject: [PATCH] Accounts with WebAuthn only (no TOTP) now exist ... fix code
 to handle that case (#18897)

---
 models/user/list.go        | 26 ++++++++++++++++++++------
 routers/web/admin/users.go | 35 +++++++++++++++++++++++++----------
 2 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/models/user/list.go b/models/user/list.go
index 13138b3e50..06ec511375 100644
--- a/models/user/list.go
+++ b/models/user/list.go
@@ -30,13 +30,19 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
 	for _, user := range users {
 		results[user.ID] = false // Set default to false
 	}
-	tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext))
-	if err == nil {
+
+	if tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)); err == nil {
 		for _, token := range tokenMaps {
 			results[token.UID] = true
 		}
 	}
 
+	if ids, err := users.userIDsWithWebAuthn(db.GetEngine(db.DefaultContext)); err == nil {
+		for _, id := range ids {
+			results[id] = true
+		}
+	}
+
 	return results
 }
 
@@ -47,15 +53,23 @@ func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*auth.TwoFacto
 
 	userIDs := users.GetUserIDs()
 	tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
-	err := e.
-		In("uid", userIDs).
-		Find(&tokenMaps)
-	if err != nil {
+	if err := e.In("uid", userIDs).Find(&tokenMaps); err != nil {
 		return nil, fmt.Errorf("find two factor: %v", err)
 	}
 	return tokenMaps, nil
 }
 
+func (users UserList) userIDsWithWebAuthn(e db.Engine) ([]int64, error) {
+	if len(users) == 0 {
+		return nil, nil
+	}
+	ids := make([]int64, 0, len(users))
+	if err := e.Table(new(auth.WebAuthnCredential)).In("user_id", users.GetUserIDs()).Select("user_id").Distinct("user_id").Find(&ids); err != nil {
+		return nil, fmt.Errorf("find two factor: %v", err)
+	}
+	return ids, nil
+}
+
 // GetUsersByIDs returns all resolved users from a list of Ids.
 func GetUsersByIDs(ids []int64) (UserList, error) {
 	ous := make([]*User, 0, len(ids))
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index 5cb25d8672..4358db89ba 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -217,15 +217,17 @@ func prepareUserInfo(ctx *context.Context) *user_model.User {
 	}
 	ctx.Data["Sources"] = sources
 
-	ctx.Data["TwoFactorEnabled"] = true
-	_, err = auth.GetTwoFactorByUID(u.ID)
+	hasTOTP, err := auth.HasTwoFactorByUID(u.ID)
 	if err != nil {
-		if !auth.IsErrTwoFactorNotEnrolled(err) {
-			ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
-			return nil
-		}
-		ctx.Data["TwoFactorEnabled"] = false
+		ctx.ServerError("auth.HasTwoFactorByUID", err)
+		return nil
 	}
+	hasWebAuthn, err := auth.HasWebAuthnRegistrationsByUID(u.ID)
+	if err != nil {
+		ctx.ServerError("auth.HasWebAuthnRegistrationsByUID", err)
+		return nil
+	}
+	ctx.Data["TwoFactorEnabled"] = hasTOTP || hasWebAuthn
 
 	return u
 }
@@ -327,14 +329,27 @@ func EditUserPost(ctx *context.Context) {
 	if form.Reset2FA {
 		tf, err := auth.GetTwoFactorByUID(u.ID)
 		if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
-			ctx.ServerError("GetTwoFactorByUID", err)
+			ctx.ServerError("auth.GetTwoFactorByUID", err)
 			return
+		} else if tf != nil {
+			if err := auth.DeleteTwoFactorByID(tf.ID, u.ID); err != nil {
+				ctx.ServerError("auth.DeleteTwoFactorByID", err)
+				return
+			}
 		}
 
-		if err = auth.DeleteTwoFactorByID(tf.ID, u.ID); err != nil {
-			ctx.ServerError("DeleteTwoFactorByID", err)
+		wn, err := auth.GetWebAuthnCredentialsByUID(u.ID)
+		if err != nil {
+			ctx.ServerError("auth.GetTwoFactorByUID", err)
 			return
 		}
+		for _, cred := range wn {
+			if _, err := auth.DeleteCredential(cred.ID, u.ID); err != nil {
+				ctx.ServerError("auth.DeleteCredential", err)
+				return
+			}
+		}
+
 	}
 
 	u.LoginName = form.LoginName