fix: reducer for PasskeyVerified sets VerifiedAt (#11929)

# Which Problems Are Solved

In https://github.com/zitadel/zitadel/pull/11858#discussion_r2993742914
we discussed on how to translate the ES passkey state to a relational
one. That is used to filter out passkeys during convertion to webauthn
credentials.

It was decided to consider passkeys in ready state those that have a
verified at `time.Time` set.

The change required fixing the reducer for the eventstore event.

# How the Problems Are Solved

  - Set `VerifiedAt` once `HumanWebAuthNVerifiedEvent` is received
  - Also set the verification to `nil`

# Additional Context

- Discussion #11858
- Follow-up for PR #11858
This commit is contained in:
Marco A.
2026-03-31 17:38:47 +02:00
committed by GitHub
parent ec0bd5790f
commit 7a15a06d69
4 changed files with 69 additions and 6 deletions
@@ -185,7 +185,7 @@ CREATE TYPE zitadel.passkey_type AS ENUM (
CREATE TABLE zitadel.user_passkeys(
instance_id TEXT NOT NULL
, token_id TEXT NOT NULL
, key_id BYTEA NOT NULL
, key_id BYTEA
, user_id TEXT NOT NULL
@@ -194,12 +194,12 @@ CREATE TABLE zitadel.user_passkeys(
, verified_at TIMESTAMPTZ
, type zitadel.passkey_type NOT NULL
, name TEXT NOT NULL CHECK (name <> '')
, name TEXT NOT NULL DEFAULT ''
, sign_count INT NOT NULL DEFAULT 0 CHECK (sign_count >= 0)
, challenge BYTEA NOT NULL
, public_key BYTEA NOT NULL
, attestation_type TEXT NOT NULL CHECK (attestation_type <> '')
, authenticator_attestation_guid BYTEA NOT NULL
, public_key BYTEA
, attestation_type TEXT NOT NULL
, authenticator_attestation_guid BYTEA
, relying_party_id TEXT NOT NULL CHECK (relying_party_id <> '')
, PRIMARY KEY (instance_id, token_id)
+3 -1
View File
@@ -1,6 +1,8 @@
package projection
import (
"fmt"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -8,7 +10,7 @@ import (
func assertEvent[T eventstore.Event](event eventstore.Event) (T, error) {
e, ok := event.(T)
if !ok {
return e, zerrors.ThrowInvalidArgumentf(nil, "HANDL-1m9fS", "reduce.wrong.event.type %T", event)
return e, zerrors.CreateZitadelError(zerrors.KindInvalidArgument, nil, "HANDL-1m9fS", fmt.Sprintf("reduce.wrong.event.type %T", event), 1)
}
return e, nil
}
@@ -1124,6 +1124,7 @@ func (p *relationalTablesProjection) reducePasskeyVerified(event eventstore.Even
repo.SetPasskeySignCount(e.SignCount),
repo.SetPasskeyName(e.WebAuthNTokenName),
repo.SetPasskeyUpdatedAt(e.CreatedAt()),
repo.SetPasskeyVerifiedAt(e.CreatedAt()),
),
repo.SetUpdatedAt(e.CreatedAt()),
)
@@ -181,6 +181,66 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
assert.Zero(t, gotUser.Human.RecoveryCodes.LastSuccessfullyCheckedAt)
assert.Equal(t, uint8(1), gotUser.Human.RecoveryCodes.FailedAttempts)
})
t.Run("reduce user.HumanWebAuthNVerifiedEvent event", func(t *testing.T) {
// Given
// create user
existingUserID := gofakeit.UUID()
existingUserAgg := createUser(t, tx, userRepo, instanceID, orgID, existingUserID)
now := time.Now()
// add passkey to the user
pkey := &domain.Passkey{
ID: "pkey-id",
Challenge: []byte("some challenge"),
RelyingPartyID: "rpID",
CreatedAt: now,
UpdatedAt: now,
Type: domain.PasskeyTypePasswordless,
}
_, err = userRepo.Update(t.Context(), tx,
userRepo.PrimaryKeyCondition(instanceID, existingUserID),
userRepo.Human().AddPasskey(pkey),
)
require.NoError(t, err)
pkeyVerifiedEvt := user.NewHumanPasswordlessVerifiedEvent(
t.Context(),
&existingUserAgg.Aggregate,
pkey.ID,
"some-token-name",
"attestation-type",
[]byte("key-id"),
[]byte("public-key-id"),
[]byte("authenticator-attestation-guid"),
33,
"",
)
// Test
eventReduced := callReduce(t, rawTx, handler, pkeyVerifiedEvt)
require.True(t, eventReduced)
// Verify
gotUser, err := userRepo.Get(t.Context(), tx, database.WithCondition(
database.And(
userRepo.IDCondition(existingUserID),
userRepo.InstanceIDCondition(instanceID),
),
))
require.NoError(t, err)
require.Len(t, gotUser.Human.Passkeys, 1)
gotPKey := gotUser.Human.Passkeys[0]
assert.Equal(t, pkeyVerifiedEvt.KeyID, gotPKey.KeyID)
assert.Equal(t, pkeyVerifiedEvt.PublicKey, gotPKey.PublicKey)
assert.Equal(t, pkeyVerifiedEvt.AttestationType, gotPKey.AttestationType)
assert.Equal(t, pkeyVerifiedEvt.AAGUID, gotPKey.AuthenticatorAttestationGUID)
assert.Equal(t, pkeyVerifiedEvt.SignCount, gotPKey.SignCount)
assert.Equal(t, pkeyVerifiedEvt.WebAuthNTokenName, gotPKey.Name)
assert.NotZero(t, gotPKey.VerifiedAt)
})
}
func createUser(t *testing.T,