mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-09-21 18:39:05 +02:00
fix: login with Yubikey not working in some Safari browsers
This commit is contained in:
@@ -194,6 +194,22 @@ func writeErrorResponse(c *gin.Context, classified classifiedError, requestID st
|
||||
|
||||
func logRequestError(c *gin.Context, err error, classified classifiedError, requestID string) {
|
||||
if classified.status < http.StatusInternalServerError {
|
||||
cause := errors.Unwrap(err)
|
||||
if cause == nil {
|
||||
return
|
||||
}
|
||||
|
||||
slog.DebugContext(c.Request.Context(), "Request rejected",
|
||||
slog.String("error_code", string(classified.code)),
|
||||
slog.String("error_type", errorTypeName(err)),
|
||||
slog.String("cause_type", errorTypeName(cause)),
|
||||
slog.Int("http_status", classified.status),
|
||||
slog.String("request_id", requestID),
|
||||
slog.String("http_method", c.Request.Method),
|
||||
slog.String("http_path", c.Request.URL.Path),
|
||||
slog.Any("error", err),
|
||||
slog.Any("cause", cause),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@ func (s *Service) VerifyLogin(ctx context.Context, dbConfig *appconfig.AppConfig
|
||||
Extensions: storedSession.Extensions,
|
||||
CredParams: storedSession.CredentialParams,
|
||||
}
|
||||
discardUnrequestedFalseAppIDOutput(session.Extensions, credentialAssertionData)
|
||||
|
||||
var user *model.User
|
||||
_, err := s.webAuthn.ValidateDiscoverableLogin(func(_, userHandle []byte) (gowebauthn.User, error) {
|
||||
@@ -535,6 +536,7 @@ func (s *Service) CreateReauthenticationTokenWithWebauthn(ctx context.Context, s
|
||||
Extensions: storedSession.Extensions,
|
||||
CredParams: storedSession.CredentialParams,
|
||||
}
|
||||
discardUnrequestedFalseAppIDOutput(session.Extensions, credentialAssertionData)
|
||||
|
||||
// Validate the credential assertion
|
||||
var user *model.User
|
||||
@@ -589,6 +591,21 @@ func classifyPasskeyError(err error, fallback func(error) *apperror.Error) *appe
|
||||
return fallback(err)
|
||||
}
|
||||
|
||||
func discardUnrequestedFalseAppIDOutput(session protocol.SessionExtensions, credential *protocol.ParsedCredentialAssertionData) {
|
||||
if credential == nil || credential.ClientExtensionResults.AppID == nil || *credential.ClientExtensionResults.AppID {
|
||||
return
|
||||
}
|
||||
|
||||
for _, requested := range session.Requested {
|
||||
if requested == protocol.ExtensionAppID {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Safari reports appid=false for security keys even when the relying party did not request the legacy extension
|
||||
credential.ClientExtensionResults.AppID = nil
|
||||
}
|
||||
|
||||
func (s *Service) ConsumeReauthenticationToken(ctx context.Context, tx *gorm.DB, token string, userID string) (time.Time, error) {
|
||||
hashedToken := utils.CreateSha256Hash(token)
|
||||
var reauthToken ReauthenticationToken
|
||||
|
||||
@@ -263,6 +263,66 @@ func TestClassifyPasskeyErrorPreservesStructuredLookupFailure(t *testing.T) {
|
||||
require.ErrorIs(t, err, cause)
|
||||
}
|
||||
|
||||
func TestDiscardUnrequestedFalseAppIDOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
requested []string
|
||||
appID bool
|
||||
extra map[string]any
|
||||
wantAppID *bool
|
||||
wantError string
|
||||
}{
|
||||
{
|
||||
name: "unrequested false appid is discarded",
|
||||
appID: false,
|
||||
wantAppID: nil,
|
||||
},
|
||||
{
|
||||
name: "requested false appid is preserved",
|
||||
requested: []string{protocol.ExtensionAppID},
|
||||
appID: false,
|
||||
wantAppID: new(false),
|
||||
},
|
||||
{
|
||||
name: "unrequested true appid is rejected",
|
||||
appID: true,
|
||||
wantAppID: new(true),
|
||||
wantError: "appid",
|
||||
},
|
||||
{
|
||||
name: "other unsolicited output is rejected",
|
||||
appID: false,
|
||||
extra: map[string]any{"example": true},
|
||||
wantAppID: nil,
|
||||
wantError: "example",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
credential := &protocol.ParsedCredentialAssertionData{
|
||||
ParsedPublicKeyCredential: protocol.ParsedPublicKeyCredential{
|
||||
ClientExtensionResults: protocol.AuthenticationExtensionsClientOutputs{
|
||||
AppID: new(tc.appID),
|
||||
Extra: tc.extra,
|
||||
},
|
||||
},
|
||||
}
|
||||
session := protocol.SessionExtensions{Requested: tc.requested}
|
||||
|
||||
discardUnrequestedFalseAppIDOutput(session, credential)
|
||||
|
||||
assert.Equal(t, tc.wantAppID, credential.ClientExtensionResults.AppID)
|
||||
err := credential.ClientExtensionResults.Verify(session, protocol.AssertCeremony, protocol.UnsolicitedOutputPolicyReject)
|
||||
if tc.wantError == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tc.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebAuthnManagementOperationsReturnSpecificNotFoundErrors(t *testing.T) {
|
||||
service, err := newService(Dependencies{
|
||||
DB: testutils.NewDatabaseForTest(t),
|
||||
|
||||
Reference in New Issue
Block a user