mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
# Which Problems Are Solved Add Passkey challenge needed for Create/Set session in the RT model. # How the Problems Are Solved Introduce `PasskeyChallengeCommand` (validate/execute/events) for session creation checks Add a generic `BeginWebAuthNLogin` helper that returns session data + assertion JSON bytes. Add unit tests # Additional Changes N/A # Additional Context - Related to https://github.com/zitadel/zitadel/issues/11035 --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Marco A. <[email protected]>
987 lines
38 KiB
Go
987 lines
38 KiB
Go
package domain_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-webauthn/webauthn/protocol"
|
|
"github.com/go-webauthn/webauthn/webauthn"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
|
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
old_domain "github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/session"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
session_grpc "github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
|
)
|
|
|
|
func TestNewPasskeyChallengeCommand(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
sessionID string
|
|
instanceID string
|
|
challengeTypePasskey *domain.ChallengeTypePasskey
|
|
webAuthNBeginLogin func(
|
|
ctx context.Context,
|
|
user webauthn.User,
|
|
rpID string,
|
|
userVerification protocol.UserVerificationRequirement,
|
|
) (sessionData *webauthn.SessionData,
|
|
cred []byte,
|
|
relyingPartyID string,
|
|
err error)
|
|
wantCmd *domain.PasskeyChallengeCommand
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "missing begin webauthn login function",
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
Domain: "example.com",
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementRequired,
|
|
},
|
|
wantErr: zerrors.ThrowInternal(nil, "DOM-jwk5Pe", "begin webauthn login function not set"),
|
|
},
|
|
{
|
|
name: "valid passkey challenge command",
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
Domain: "example.com",
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementRequired,
|
|
},
|
|
wantCmd: &domain.PasskeyChallengeCommand{
|
|
InstanceID: "instance-1",
|
|
SessionID: "session-1",
|
|
ChallengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
Domain: "example.com",
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementRequired,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd, err := domain.NewPasskeyChallengeCommand(tt.sessionID, tt.instanceID, tt.challengeTypePasskey, tt.webAuthNBeginLogin)
|
|
if tt.wantErr != nil {
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.wantCmd.SessionID, cmd.SessionID)
|
|
assert.Equal(t, tt.wantCmd.InstanceID, cmd.InstanceID)
|
|
assert.Equal(t, tt.wantCmd.ChallengeTypePasskey, cmd.ChallengeTypePasskey)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPasskeyChallengeCommand_Validate(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
sessionID string
|
|
instanceID string
|
|
webAuthNBeginLogin func(
|
|
ctx context.Context,
|
|
user webauthn.User,
|
|
rpID string,
|
|
userVerification protocol.UserVerificationRequirement,
|
|
) (sessionData *webauthn.SessionData,
|
|
cred []byte,
|
|
relyingPartyID string,
|
|
err error)
|
|
challengeTypePasskey *domain.ChallengeTypePasskey
|
|
userRepo func(ctrl *gomock.Controller) domain.UserRepository
|
|
sessionRepo func(ctrl *gomock.Controller) domain.SessionRepository
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "no request passkey challenge",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
},
|
|
{
|
|
name: "no session id",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "DOM-EVo5yE", "Errors.Missing.SessionID"),
|
|
},
|
|
{
|
|
name: "no instance id",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "DOM-sh8xvQ", "Errors.Missing.InstanceID"),
|
|
},
|
|
{
|
|
name: "failed to fetch session",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "session-1"),
|
|
),
|
|
)).
|
|
Times(1).
|
|
Return(nil, assert.AnError)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowInternal(assert.AnError, "DOM-zy4hYC", "failed fetching Session"),
|
|
},
|
|
{
|
|
name: "session not found",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "session-1"),
|
|
),
|
|
)).
|
|
Times(1).
|
|
Return(nil, new(database.NoRowFoundError))
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowNotFound(new(database.NoRowFoundError), "DOM-zy4hYC", "Session not found"),
|
|
},
|
|
{
|
|
name: "session without user id",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "session-1"),
|
|
),
|
|
)).
|
|
Times(1).
|
|
Return(&domain.Session{
|
|
UserID: "",
|
|
}, nil)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "DOM-uVyrt2", "Errors.Missing.Session.UserID"),
|
|
},
|
|
{
|
|
name: "failed to fetch user",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
|
|
humanRepo := domainmock.NewHumanRepo(ctrl)
|
|
repo.EXPECT().Human().Times(1).Return(humanRepo)
|
|
|
|
repo.EXPECT().
|
|
Get(gomock.Any(),
|
|
gomock.Any(),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "user-1"),
|
|
),
|
|
),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
humanRepo.PasskeyConditions().TypeCondition(database.TextOperationEqual, domain.PasskeyTypeU2F),
|
|
),
|
|
),
|
|
).
|
|
Times(1).
|
|
Return(nil, assert.AnError)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowInternal(assert.AnError, "DOM-8cGMtd", "failed fetching User"),
|
|
},
|
|
{
|
|
name: "user not found",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
humanRepo := domainmock.NewHumanRepo(ctrl)
|
|
repo.EXPECT().Human().Times(1).Return(humanRepo)
|
|
|
|
repo.EXPECT().
|
|
Get(gomock.Any(),
|
|
gomock.Any(),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "user-1"),
|
|
),
|
|
),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
humanRepo.PasskeyConditions().TypeCondition(database.TextOperationEqual, domain.PasskeyTypeU2F),
|
|
),
|
|
),
|
|
).
|
|
Times(1).
|
|
Return(nil, new(database.NoRowFoundError))
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowNotFound(new(database.NoRowFoundError), "DOM-8cGMtd", "User not found"),
|
|
},
|
|
{
|
|
name: "user not human",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
humanRepo := domainmock.NewHumanRepo(ctrl)
|
|
repo.EXPECT().Human().Times(1).Return(humanRepo)
|
|
|
|
repo.EXPECT().
|
|
Get(gomock.Any(),
|
|
gomock.Any(),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "user-1"),
|
|
),
|
|
),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
humanRepo.PasskeyConditions().TypeCondition(database.TextOperationEqual, domain.PasskeyTypeU2F),
|
|
),
|
|
),
|
|
).
|
|
Times(1).
|
|
Return(&domain.User{}, nil)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "DOM-nd3f4", "Errors.User.NotHuman"),
|
|
},
|
|
{
|
|
name: "user not active",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
humanRepo := domainmock.NewHumanRepo(ctrl)
|
|
repo.EXPECT().Human().Times(1).Return(humanRepo)
|
|
|
|
repo.EXPECT().
|
|
Get(gomock.Any(),
|
|
gomock.Any(),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "user-1"),
|
|
),
|
|
),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
humanRepo.PasskeyConditions().TypeCondition(database.TextOperationEqual, domain.PasskeyTypeU2F),
|
|
),
|
|
),
|
|
).
|
|
Times(1).
|
|
Return(&domain.User{
|
|
State: domain.UserStateInactive,
|
|
Human: &domain.HumanUser{},
|
|
}, nil)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "DOM-bnxBdS", "Errors.User.NotFound"),
|
|
},
|
|
{
|
|
name: "valid request passkey challenge",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
sessionID: "session-1",
|
|
instanceID: "instance-1",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := authz.NewMockContext("instance-1", "", "")
|
|
ctrl := gomock.NewController(t)
|
|
cmd, err := domain.NewPasskeyChallengeCommand(
|
|
tt.sessionID,
|
|
tt.instanceID,
|
|
tt.challengeTypePasskey,
|
|
tt.webAuthNBeginLogin,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
opts := &domain.InvokeOpts{
|
|
Invoker: domain.NewTransactionInvoker(nil),
|
|
}
|
|
if tt.userRepo != nil {
|
|
domain.WithUserRepo(tt.userRepo(ctrl))(opts)
|
|
}
|
|
if tt.sessionRepo != nil {
|
|
domain.WithSessionRepo(tt.sessionRepo(ctrl))(opts)
|
|
}
|
|
err = cmd.Validate(ctx, opts)
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPasskeyChallengeCommand_Execute(t *testing.T) {
|
|
t.Parallel()
|
|
challengedAt := time.Now()
|
|
credentialAssertionDataRequired := []byte(`{"publicKey":{"challenge":"Y2hhbGxlbmdl","timeout":60000,"rpId":"example.com","allowCredentials":[{"type":"public-key","id":"cGFzc2tleS1pZA"}],"userVerification":"required"}}`)
|
|
credentialAssertionDataPreferred := []byte(`{"publicKey":{"challenge":"Y2hhbGxlbmdl","timeout":60000,"rpId":"example.com","allowCredentials":[{"type":"public-key","id":"cGFzc2tleS1pZA"}],"userVerification":"preferred"}}`)
|
|
|
|
tests := []struct {
|
|
name string
|
|
challengeTypePasskey *domain.ChallengeTypePasskey
|
|
sessionRepo func(ctrl *gomock.Controller) domain.SessionRepository
|
|
userRepo func(ctrl *gomock.Controller) domain.UserRepository
|
|
webAuthNBeginLogin func(
|
|
ctx context.Context,
|
|
user webauthn.User,
|
|
rpID string,
|
|
userVerification protocol.UserVerificationRequirement,
|
|
) (sessionData *webauthn.SessionData,
|
|
cred []byte,
|
|
relyingPartyID string,
|
|
err error)
|
|
wantErr error
|
|
wantWebAuthNChallenge *session_grpc.Challenges_WebAuthN
|
|
}{
|
|
{
|
|
name: "no request passkey challenge",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataPreferred, "example.com", nil
|
|
},
|
|
},
|
|
{
|
|
name: "failed to begin webauthn login",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementPreferred,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return nil, nil, "", assert.AnError
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
wantErr: assert.AnError,
|
|
},
|
|
{
|
|
name: "invalid credential assertion data",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "challenge",
|
|
}, []byte("invalid"), "rpID", nil
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
wantErr: zerrors.ThrowInternal(nil, "DOM-liSCA4", "Errors.Unmarshal"),
|
|
},
|
|
{
|
|
name: "failed to update session",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementPreferred,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataPreferred, "example.com", nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementPreferred,
|
|
LastChallengedAt: time.Now(),
|
|
})
|
|
updateSessionFailedExpectation(repo, challenge, assert.AnError, 0)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowInternal(assert.AnError, "DOM-yd3f4", "failed updating Session"),
|
|
},
|
|
{
|
|
name: "failed to update session - no rows updated",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementPreferred,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataPreferred, "example.com", nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementPreferred,
|
|
LastChallengedAt: challengedAt,
|
|
})
|
|
updateSessionFailedExpectation(repo, challenge, nil, 0)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowNotFound(nil, "DOM-yd3f4", "Session not found"),
|
|
},
|
|
{
|
|
name: "failed to update session - more than 1 row updated",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementPreferred,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataPreferred, "example.com", nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementPreferred,
|
|
LastChallengedAt: challengedAt,
|
|
})
|
|
updateSessionFailedExpectation(repo, challenge, nil, 2)
|
|
return repo
|
|
},
|
|
wantErr: zerrors.ThrowInternal(nil, "DOM-yd3f4", "unexpected number of rows updated"),
|
|
},
|
|
{
|
|
name: "session updated successfully with the passkey challenge - required user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementRequired,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataRequired, "example.com", nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypePasswordless)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementRequired,
|
|
LastChallengedAt: time.Now(),
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantWebAuthNChallenge: getChallengePasskey(t, credentialAssertionDataRequired),
|
|
},
|
|
{
|
|
name: "session updated successfully with the passkey challenge - preferred user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementPreferred,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataPreferred, "example.com", nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementPreferred,
|
|
LastChallengedAt: time.Now(),
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantWebAuthNChallenge: getChallengePasskey(t, credentialAssertionDataPreferred),
|
|
},
|
|
{
|
|
name: "session updated successfully with the passkey challenge - unspecified user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementUnspecified,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataPreferred, "example.com", nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementUnspecified,
|
|
LastChallengedAt: time.Now(),
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantWebAuthNChallenge: getChallengePasskey(t, credentialAssertionDataPreferred),
|
|
},
|
|
{
|
|
name: "session updated successfully with the passkey challenge - discouraged user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementDiscouraged,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, credentialAssertionDataPreferred, "example.com", nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementDiscouraged,
|
|
LastChallengedAt: time.Now(),
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantWebAuthNChallenge: getChallengePasskey(t, credentialAssertionDataPreferred),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := authz.NewMockContext("instance-1", "", "")
|
|
ctrl := gomock.NewController(t)
|
|
cmd, err := domain.NewPasskeyChallengeCommand(
|
|
"session-1",
|
|
"instance-1",
|
|
tt.challengeTypePasskey,
|
|
tt.webAuthNBeginLogin,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
opts := &domain.InvokeOpts{
|
|
Invoker: domain.NewTransactionInvoker(nil),
|
|
}
|
|
if tt.sessionRepo != nil {
|
|
domain.WithSessionRepo(tt.sessionRepo(ctrl))(opts)
|
|
}
|
|
if tt.userRepo != nil {
|
|
domain.WithUserRepo(tt.userRepo(ctrl))(opts)
|
|
}
|
|
|
|
// to fetch/validate session and user before calling Execute
|
|
err = cmd.Validate(ctx, opts)
|
|
require.NoError(t, err)
|
|
|
|
err = cmd.Execute(ctx, opts)
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
assert.Equal(t, tt.wantWebAuthNChallenge, cmd.GetWebAuthNChallenge())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPasskeyChallengeCommand_Events(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := authz.NewMockContext("instance-1", "", "")
|
|
challengedAt := time.Now()
|
|
|
|
tests := []struct {
|
|
name string
|
|
challengeTypePasskey *domain.ChallengeTypePasskey
|
|
sessionRepo func(ctrl *gomock.Controller) domain.SessionRepository
|
|
userRepo func(ctrl *gomock.Controller) domain.UserRepository
|
|
webAuthNBeginLogin func(
|
|
ctx context.Context,
|
|
user webauthn.User,
|
|
rpID string,
|
|
userVerification protocol.UserVerificationRequirement,
|
|
) (sessionData *webauthn.SessionData,
|
|
cred []byte,
|
|
relyingPartyID string,
|
|
err error)
|
|
wantEvent eventstore.Command
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "no request passkey challenge",
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
}, []byte("assertion data"), "example.com", nil
|
|
},
|
|
},
|
|
{
|
|
name: "valid request passkey challenge - preferred user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementPreferred,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{Challenge: "Y2hhbGxlbmdl"},
|
|
[]byte(`{"publicKey":{"challenge":"Y2hhbGxlbmdl","timeout":60000,"rpId":"example.com","allowCredentials":[{"type":"public-key","id":"cGFzc2tleS1pZA"}],"userVerification":"preferred"}}`),
|
|
"example.com",
|
|
nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementPreferred,
|
|
LastChallengedAt: challengedAt,
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantEvent: session.NewWebAuthNChallengedEvent(ctx,
|
|
&session.NewAggregate("session-1", "instance-1").Aggregate,
|
|
"Y2hhbGxlbmdl",
|
|
nil,
|
|
old_domain.UserVerificationRequirementPreferred,
|
|
"example.com"),
|
|
},
|
|
{
|
|
name: "valid request passkey challenge - required user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementRequired,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{Challenge: "Y2hhbGxlbmdl"},
|
|
[]byte(`{"publicKey":{"challenge":"Y2hhbGxlbmdl","timeout":60000,"rpId":"example.com","allowCredentials":[{"type":"public-key","id":"cGFzc2tleS1pZA"}],"userVerification":"required"}}`),
|
|
"example.com",
|
|
nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypePasswordless)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementRequired,
|
|
LastChallengedAt: challengedAt,
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantEvent: session.NewWebAuthNChallengedEvent(ctx,
|
|
&session.NewAggregate("session-1", "instance-1").Aggregate,
|
|
"Y2hhbGxlbmdl",
|
|
nil,
|
|
old_domain.UserVerificationRequirementRequired,
|
|
"example.com"),
|
|
},
|
|
{
|
|
name: "valid request passkey challenge - unspecified user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementUnspecified,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{Challenge: "Y2hhbGxlbmdl"},
|
|
[]byte(`{"publicKey":{"challenge":"Y2hhbGxlbmdl","timeout":60000,"rpId":"example.com","allowCredentials":[{"type":"public-key","id":"cGFzc2tleS1pZA"}],"userVerification":"preferred"}}`),
|
|
"example.com",
|
|
nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementUnspecified,
|
|
LastChallengedAt: challengedAt,
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantEvent: session.NewWebAuthNChallengedEvent(ctx,
|
|
&session.NewAggregate("session-1", "instance-1").Aggregate,
|
|
"Y2hhbGxlbmdl",
|
|
nil,
|
|
old_domain.UserVerificationRequirementUnspecified,
|
|
"example.com"),
|
|
},
|
|
{
|
|
name: "valid request passkey challenge - discouraged user verification",
|
|
challengeTypePasskey: &domain.ChallengeTypePasskey{
|
|
UserVerificationRequirement: old_domain.UserVerificationRequirementDiscouraged,
|
|
},
|
|
webAuthNBeginLogin: func(ctx context.Context, user webauthn.User, rpID string, userVerification protocol.UserVerificationRequirement) (sessionData *webauthn.SessionData, cred []byte, relyingPartyID string, err error) {
|
|
return &webauthn.SessionData{Challenge: "Y2hhbGxlbmdl"},
|
|
[]byte(`{"publicKey":{"challenge":"Y2hhbGxlbmdl","timeout":60000,"rpId":"example.com","allowCredentials":[{"type":"public-key","id":"cGFzc2tleS1pZA"}],"userVerification":"preferred"}}`),
|
|
"example.com",
|
|
nil
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
return getUserByPasskeyType(ctrl, domain.PasskeyTypeU2F)
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
getSessionSucceededExpectation(repo)
|
|
challenge := repo.SetChallenge(&domain.SessionChallengePasskey{
|
|
Challenge: "Y2hhbGxlbmdl",
|
|
RPID: "example.com",
|
|
UserVerification: old_domain.UserVerificationRequirementDiscouraged,
|
|
LastChallengedAt: challengedAt,
|
|
})
|
|
updateSessionSucceededExpectation(repo, challenge)
|
|
return repo
|
|
},
|
|
wantEvent: session.NewWebAuthNChallengedEvent(ctx,
|
|
&session.NewAggregate("session-1", "instance-1").Aggregate,
|
|
"Y2hhbGxlbmdl",
|
|
nil,
|
|
old_domain.UserVerificationRequirementDiscouraged,
|
|
"example.com"),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := authz.NewMockContext("instance-1", "", "")
|
|
ctrl := gomock.NewController(t)
|
|
cmd, err := domain.NewPasskeyChallengeCommand(
|
|
"session-1",
|
|
"instance-1",
|
|
tt.challengeTypePasskey,
|
|
tt.webAuthNBeginLogin,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
opts := &domain.InvokeOpts{
|
|
Invoker: domain.NewTransactionInvoker(nil),
|
|
}
|
|
if tt.sessionRepo != nil {
|
|
domain.WithSessionRepo(tt.sessionRepo(ctrl))(opts)
|
|
}
|
|
if tt.userRepo != nil {
|
|
domain.WithUserRepo(tt.userRepo(ctrl))(opts)
|
|
}
|
|
|
|
// to fetch/validate session and user before calling Execute
|
|
err = cmd.Validate(ctx, opts)
|
|
require.NoError(t, err)
|
|
|
|
// to update session before calling Execute
|
|
err = cmd.Execute(ctx, opts)
|
|
require.NoError(t, err)
|
|
|
|
events, err := cmd.Events(ctx, opts)
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
if tt.wantEvent != nil {
|
|
require.Len(t, events, 1)
|
|
assert.Equal(t, tt.wantEvent, events[0])
|
|
return
|
|
}
|
|
assert.Empty(t, events)
|
|
})
|
|
}
|
|
}
|
|
|
|
func getChallengePasskey(t *testing.T, data []byte) *session_grpc.Challenges_WebAuthN {
|
|
webAuthNChallenge := &session_grpc.Challenges_WebAuthN{
|
|
PublicKeyCredentialRequestOptions: new(structpb.Struct),
|
|
}
|
|
err := json.Unmarshal(data, webAuthNChallenge.PublicKeyCredentialRequestOptions)
|
|
require.NoError(t, err)
|
|
|
|
return webAuthNChallenge
|
|
}
|
|
|
|
func getUserByPasskeyType(ctrl *gomock.Controller, passkeyType domain.PasskeyType) *domainmock.UserRepo {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
humanRepo := domainmock.NewHumanRepo(ctrl)
|
|
repo.EXPECT().Human().Times(1).Return(humanRepo)
|
|
|
|
repo.EXPECT().
|
|
Get(gomock.Any(),
|
|
gomock.Any(),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
repo.PrimaryKeyCondition("instance-1", "user-1"),
|
|
),
|
|
),
|
|
dbmock.QueryOptions(
|
|
database.WithCondition(
|
|
humanRepo.PasskeyConditions().TypeCondition(database.TextOperationEqual, passkeyType),
|
|
),
|
|
),
|
|
).
|
|
Times(1).
|
|
Return(&domain.User{
|
|
ID: "user-id",
|
|
InstanceID: "instance-1",
|
|
OrganizationID: "organization-id",
|
|
Username: "username",
|
|
Human: &domain.HumanUser{
|
|
Passkeys: []*domain.Passkey{
|
|
{
|
|
ID: "passkey-id",
|
|
PublicKey: []byte("public-key"),
|
|
Name: "My Passkey",
|
|
Type: passkeyType,
|
|
KeyID: []byte("key-id"),
|
|
VerifiedAt: time.Now().Add(-2 * time.Hour),
|
|
},
|
|
},
|
|
Email: domain.HumanEmail{
|
|
Address: "[email protected]",
|
|
},
|
|
},
|
|
State: domain.UserStateActive,
|
|
}, nil)
|
|
return repo
|
|
}
|