1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-06-27 16:36:00 +00:00

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
This commit is contained in:
Frédéric Guillot 2024-10-26 18:00:41 -07:00
parent ea4d0a4f72
commit 2bcc4b8399

View file

@ -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 { for _, storedCredential := range storedCredentials {
if bytes.Equal(credCredential.ID, credTest.Credential.ID) { if bytes.Equal(credCredential.ID, storedCredential.Credential.ID) {
cred = &credTest 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),