Преглед на файлове

fix(webauthn): add backup eligibility flag workaround to avoid a 401

Since go-webauthn v0.11.0, the backup eligibility flag is strictly validated, but Miniflux does not store this flag.

This workaround to set the flag based on the parsed response, and avoid "BackupEligible flag inconsistency detected during login validation" error.

See https://github.com/go-webauthn/webauthn/pull/240
Frédéric Guillot преди 1 година
родител
ревизия
2bcc4b8399
променени са 1 файла, в които са добавени 46 реда и са изтрити 10 реда
  1. 46 10
      internal/ui/webauthn.go

+ 46 - 10
internal/ui/webauthn.go

@@ -206,6 +206,15 @@ func (h *handler) finishLogin(w http.ResponseWriter, r *http.Request) {
 		json.ServerError(w, r, err)
 		json.ServerError(w, r, err)
 		return
 		return
 	}
 	}
+
+	slog.Debug("WebAuthn: parsed response flags",
+		slog.Bool("user_present", parsedResponse.Response.AuthenticatorData.Flags.HasUserPresent()),
+		slog.Bool("user_verified", parsedResponse.Response.AuthenticatorData.Flags.HasUserPresent()),
+		slog.Bool("has_attested_credential_data", parsedResponse.Response.AuthenticatorData.Flags.HasAttestedCredentialData()),
+		slog.Bool("has_backup_eligible", parsedResponse.Response.AuthenticatorData.Flags.HasBackupEligible()),
+		slog.Bool("has_backup_state", parsedResponse.Response.AuthenticatorData.Flags.HasBackupState()),
+	)
+
 	sessionData := request.WebAuthnSessionData(r)
 	sessionData := request.WebAuthnSessionData(r)
 
 
 	var user *model.User
 	var user *model.User
@@ -218,34 +227,54 @@ func (h *handler) finishLogin(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}
 	}
 
 
-	var cred *model.WebAuthnCredential
+	var matchingCredential *model.WebAuthnCredential
 	if user != nil {
 	if user != nil {
-		creds, err := h.store.WebAuthnCredentialsByUserID(user.ID)
+		storedCredentials, err := h.store.WebAuthnCredentialsByUserID(user.ID)
 		if err != nil {
 		if err != nil {
 			json.ServerError(w, r, err)
 			json.ServerError(w, r, err)
 			return
 			return
 		}
 		}
+
 		sessionData.SessionData.UserID = parsedResponse.Response.UserHandle
 		sessionData.SessionData.UserID = parsedResponse.Response.UserHandle
-		credCredential, err := web.ValidateLogin(WebAuthnUser{user, parsedResponse.Response.UserHandle, creds}, *sessionData.SessionData, parsedResponse)
+		webAuthUser := WebAuthnUser{user, parsedResponse.Response.UserHandle, storedCredentials}
+
+		// Since go-webauthn v0.11.0, the backup eligibility flag is strictly validated, but Miniflux does not store this flag.
+		// This workaround set the flag based on the parsed response, and avoid "BackupEligible flag inconsistency detected during login validation" error.
+		// See https://github.com/go-webauthn/webauthn/pull/240
+		for index := range webAuthUser.Credentials {
+			webAuthUser.Credentials[index].Credential.Flags.BackupEligible = parsedResponse.Response.AuthenticatorData.Flags.HasBackupEligible()
+		}
+
+		for _, webAuthCredential := range webAuthUser.WebAuthnCredentials() {
+			slog.Debug("WebAuthn: stored credential flags",
+				slog.Bool("user_present", webAuthCredential.Flags.UserPresent),
+				slog.Bool("user_verified", webAuthCredential.Flags.UserVerified),
+				slog.Bool("backup_eligible", webAuthCredential.Flags.BackupEligible),
+				slog.Bool("backup_state", webAuthCredential.Flags.BackupState),
+			)
+		}
+
+		credCredential, err := web.ValidateLogin(webAuthUser, *sessionData.SessionData, parsedResponse)
 		if err != nil {
 		if err != nil {
+			slog.Warn("WebAuthn: ValidateLogin failed", slog.Any("error", err))
 			json.Unauthorized(w, r)
 			json.Unauthorized(w, r)
 			return
 			return
 		}
 		}
 
 
-		for _, credTest := range creds {
-			if bytes.Equal(credCredential.ID, credTest.Credential.ID) {
-				cred = &credTest
+		for _, storedCredential := range storedCredentials {
+			if bytes.Equal(credCredential.ID, storedCredential.Credential.ID) {
+				matchingCredential = &storedCredential
 			}
 			}
 		}
 		}
 
 
-		if cred == nil {
+		if matchingCredential == nil {
 			json.ServerError(w, r, fmt.Errorf("no matching credential for %v", credCredential))
 			json.ServerError(w, r, fmt.Errorf("no matching credential for %v", credCredential))
 			return
 			return
 		}
 		}
 	} else {
 	} else {
 		userByHandle := func(rawID, userHandle []byte) (webauthn.User, error) {
 		userByHandle := func(rawID, userHandle []byte) (webauthn.User, error) {
 			var uid int64
 			var uid int64
-			uid, cred, err = h.store.WebAuthnCredentialByHandle(userHandle)
+			uid, matchingCredential, err = h.store.WebAuthnCredentialByHandle(userHandle)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -259,11 +288,18 @@ func (h *handler) finishLogin(w http.ResponseWriter, r *http.Request) {
 			if user == nil {
 			if user == nil {
 				return nil, fmt.Errorf("no user found for handle %x", userHandle)
 				return nil, fmt.Errorf("no user found for handle %x", userHandle)
 			}
 			}
-			return WebAuthnUser{user, userHandle, []model.WebAuthnCredential{*cred}}, nil
+
+			// Since go-webauthn v0.11.0, the backup eligibility flag is strictly validated, but Miniflux does not store this flag.
+			// This workaround set the flag based on the parsed response, and avoid "BackupEligible flag inconsistency detected during login validation" error.
+			// See https://github.com/go-webauthn/webauthn/pull/240
+			matchingCredential.Credential.Flags.BackupEligible = parsedResponse.Response.AuthenticatorData.Flags.HasBackupEligible()
+
+			return WebAuthnUser{user, userHandle, []model.WebAuthnCredential{*matchingCredential}}, nil
 		}
 		}
 
 
 		_, err = web.ValidateDiscoverableLogin(userByHandle, *sessionData.SessionData, parsedResponse)
 		_, err = web.ValidateDiscoverableLogin(userByHandle, *sessionData.SessionData, parsedResponse)
 		if err != nil {
 		if err != nil {
+			slog.Warn("WebAuthn: ValidateDiscoverableLogin failed", slog.Any("error", err))
 			json.Unauthorized(w, r)
 			json.Unauthorized(w, r)
 			return
 			return
 		}
 		}
@@ -275,7 +311,7 @@ func (h *handler) finishLogin(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	h.store.WebAuthnSaveLogin(cred.Handle)
+	h.store.WebAuthnSaveLogin(matchingCredential.Handle)
 
 
 	slog.Info("User authenticated successfully with webauthn",
 	slog.Info("User authenticated successfully with webauthn",
 		slog.Bool("authentication_successful", true),
 		slog.Bool("authentication_successful", true),