Files
zitadel/backend/v3/domain/passkey_test.go
T
022bf74060 feat(session): Passkey Check API with relation tables (#11858)
# Which Problems Are Solved

As part of #11035 , this PR implements the Passkey check logic for
session validation

# How the Problems Are Solved

  - Refactor webauth FinishLogin to support new domain model
  - Add webauth config to defaults
  - Implement passkey check logic and tests
- Manual transaction management to avoid stalling the DB while
FinishLogin callback is executed
- Update passkey Type condition to allow passing a text operation
(equal, contains, etc..)

# Additional Context

This is a cherry-picked PR + minor changes, coming from
https://github.com/zitadel/zitadel/pull/11164
- Relates to #11035

---------

Co-authored-by: Fabienne Bühler <[email protected]>
Co-authored-by: Gayathri Vijayan <[email protected]>
2026-03-27 12:38:40 +01:00

159 lines
4.3 KiB
Go

package domain_test
import (
"context"
"testing"
"time"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/internal/api/http"
)
func TestPasskeysToCredentials(t *testing.T) {
verificationDate := time.Now()
t.Parallel()
type args struct {
ctx context.Context
passkeys []*domain.Passkey
rpID string
}
tt := []struct {
name string
args args
want []webauthn.Credential
}{
{
name: "matching rpID",
args: args{
ctx: context.Background(),
passkeys: []*domain.Passkey{
{
KeyID: []byte("key1"),
PublicKey: []byte("publicKey1"),
AttestationType: "attestation1",
AuthenticatorAttestationGUID: []byte("aaguid1"),
SignCount: 1,
RelyingPartyID: "example.com",
VerifiedAt: verificationDate,
},
},
rpID: "example.com",
},
want: []webauthn.Credential{
{
ID: []byte("key1"),
PublicKey: []byte("publicKey1"),
AttestationType: "attestation1",
Authenticator: webauthn.Authenticator{
AAGUID: []byte("aaguid1"),
SignCount: 1,
},
},
},
},
{
name: "not matching rpID",
args: args{
ctx: context.Background(),
passkeys: []*domain.Passkey{
{
KeyID: []byte("key1"),
PublicKey: []byte("publicKey1"),
AttestationType: "attestation1",
AuthenticatorAttestationGUID: []byte("aaguid1"),
SignCount: 1,
RelyingPartyID: "other.com",
VerifiedAt: verificationDate,
},
},
rpID: "example.com",
},
want: []webauthn.Credential{},
},
{
name: "no rpID, same host",
args: args{
ctx: http.WithDomainContext(context.Background(), &http.DomainCtx{
InstanceHost: "example.com:443",
PublicHost: "example.com:443",
Protocol: "https",
}),
passkeys: []*domain.Passkey{
{
KeyID: []byte("key1"),
PublicKey: []byte("publicKey1"),
AttestationType: "attestation1",
AuthenticatorAttestationGUID: []byte("aaguid1"),
SignCount: 1,
RelyingPartyID: "",
VerifiedAt: verificationDate,
},
},
rpID: "example.com",
},
want: []webauthn.Credential{
{
ID: []byte("key1"),
PublicKey: []byte("publicKey1"),
AttestationType: "attestation1",
Authenticator: webauthn.Authenticator{
AAGUID: []byte("aaguid1"),
SignCount: 1,
},
},
},
},
{
name: "no rpID, different host",
args: args{
ctx: http.WithDomainContext(context.Background(), &http.DomainCtx{
InstanceHost: "other.com:443",
PublicHost: "other.com:443",
Protocol: "https",
}),
passkeys: []*domain.Passkey{
{
KeyID: []byte("key1"),
PublicKey: []byte("publicKey1"),
AttestationType: "attestation1",
AuthenticatorAttestationGUID: []byte("aaguid1"),
SignCount: 1,
RelyingPartyID: "",
VerifiedAt: verificationDate,
},
},
rpID: "example.com",
},
want: []webauthn.Credential{},
},
{
name: "pkey not verified should return empty",
args: args{
ctx: context.Background(),
passkeys: []*domain.Passkey{
{
KeyID: []byte("key1"),
PublicKey: []byte("publicKey1"),
AttestationType: "attestation1",
AuthenticatorAttestationGUID: []byte("aaguid1"),
SignCount: 1,
RelyingPartyID: "example.com",
},
},
rpID: "example.com",
},
want: []webauthn.Credential{},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equalf(t, tc.want, domain.PasskeysToCredentials(tc.args.ctx, tc.args.passkeys, tc.args.rpID), "PasskeysToCredentials(%v, %v, %v)", tc.args.ctx, tc.args.passkeys, tc.args.rpID)
})
}
}