mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:37:34 +00:00
feat: session v2 passkey authentication (#5952)
This commit is contained in:
@@ -16,10 +16,10 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type SessionCheck func(ctx context.Context, cmd *SessionChecks) error
|
||||
type SessionCommand func(ctx context.Context, cmd *SessionCommands) error
|
||||
|
||||
type SessionChecks struct {
|
||||
checks []SessionCheck
|
||||
type SessionCommands struct {
|
||||
cmds []SessionCommand
|
||||
|
||||
sessionWriteModel *SessionWriteModel
|
||||
passwordWriteModel *HumanPasswordWriteModel
|
||||
@@ -29,9 +29,9 @@ type SessionChecks struct {
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func (c *Commands) NewSessionChecks(checks []SessionCheck, session *SessionWriteModel) *SessionChecks {
|
||||
return &SessionChecks{
|
||||
checks: checks,
|
||||
func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWriteModel) *SessionCommands {
|
||||
return &SessionCommands{
|
||||
cmds: cmds,
|
||||
sessionWriteModel: session,
|
||||
eventstore: c.eventstore,
|
||||
userPasswordAlg: c.userPasswordAlg,
|
||||
@@ -41,8 +41,8 @@ func (c *Commands) NewSessionChecks(checks []SessionCheck, session *SessionWrite
|
||||
}
|
||||
|
||||
// CheckUser defines a user check to be executed for a session update
|
||||
func CheckUser(id string) SessionCheck {
|
||||
return func(ctx context.Context, cmd *SessionChecks) error {
|
||||
func CheckUser(id string) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) error {
|
||||
if cmd.sessionWriteModel.UserID != "" && id != "" && cmd.sessionWriteModel.UserID != id {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "", "user change not possible")
|
||||
}
|
||||
@@ -51,8 +51,8 @@ func CheckUser(id string) SessionCheck {
|
||||
}
|
||||
|
||||
// CheckPassword defines a password check to be executed for a session update
|
||||
func CheckPassword(password string) SessionCheck {
|
||||
return func(ctx context.Context, cmd *SessionChecks) error {
|
||||
func CheckPassword(password string) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) error {
|
||||
if cmd.sessionWriteModel.UserID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Sfw3f", "Errors.User.UserIDMissing")
|
||||
}
|
||||
@@ -80,17 +80,32 @@ func CheckPassword(password string) SessionCheck {
|
||||
}
|
||||
}
|
||||
|
||||
// Check will execute the checks specified and return an error on the first occurrence
|
||||
func (s *SessionChecks) Check(ctx context.Context) error {
|
||||
for _, check := range s.checks {
|
||||
if err := check(ctx, s); err != nil {
|
||||
// Exec will execute the commands specified and returns an error on the first occurrence
|
||||
func (s *SessionCommands) Exec(ctx context.Context) error {
|
||||
for _, cmd := range s.cmds {
|
||||
if err := cmd(ctx, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SessionChecks) commands(ctx context.Context) (string, []eventstore.Command, error) {
|
||||
func (s *SessionCommands) gethumanWriteModel(ctx context.Context) (*HumanWriteModel, error) {
|
||||
if s.sessionWriteModel.UserID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-eeR2e", "Errors.User.UserIDMissing")
|
||||
}
|
||||
humanWriteModel := NewHumanWriteModel(s.sessionWriteModel.UserID, s.sessionWriteModel.ResourceOwner)
|
||||
err := s.eventstore.FilterToQueryReducer(ctx, humanWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if humanWriteModel.UserState != domain.UserStateActive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Df4b3", "Errors.ie4Ai.NotFound")
|
||||
}
|
||||
return humanWriteModel, nil
|
||||
}
|
||||
|
||||
func (s *SessionCommands) commands(ctx context.Context) (string, []eventstore.Command, error) {
|
||||
if len(s.sessionWriteModel.commands) == 0 {
|
||||
return "", nil, nil
|
||||
}
|
||||
@@ -103,7 +118,7 @@ func (s *SessionChecks) commands(ctx context.Context) (string, []eventstore.Comm
|
||||
return token, s.sessionWriteModel.commands, nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreateSession(ctx context.Context, checks []SessionCheck, metadata map[string][]byte) (set *SessionChanged, err error) {
|
||||
func (c *Commands) CreateSession(ctx context.Context, cmds []SessionCommand, metadata map[string][]byte) (set *SessionChanged, err error) {
|
||||
sessionID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -113,12 +128,12 @@ func (c *Commands) CreateSession(ctx context.Context, checks []SessionCheck, met
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := c.NewSessionChecks(checks, sessionWriteModel)
|
||||
cmd := c.NewSessionCommands(cmds, sessionWriteModel)
|
||||
cmd.sessionWriteModel.Start(ctx)
|
||||
return c.updateSession(ctx, cmd, metadata)
|
||||
}
|
||||
|
||||
func (c *Commands) UpdateSession(ctx context.Context, sessionID, sessionToken string, checks []SessionCheck, metadata map[string][]byte) (set *SessionChanged, err error) {
|
||||
func (c *Commands) UpdateSession(ctx context.Context, sessionID, sessionToken string, cmds []SessionCommand, metadata map[string][]byte) (set *SessionChanged, err error) {
|
||||
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetCtxData(ctx).OrgID)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
||||
if err != nil {
|
||||
@@ -127,7 +142,7 @@ func (c *Commands) UpdateSession(ctx context.Context, sessionID, sessionToken st
|
||||
if err := c.sessionPermission(ctx, sessionWriteModel, sessionToken, domain.PermissionSessionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := c.NewSessionChecks(checks, sessionWriteModel)
|
||||
cmd := c.NewSessionCommands(cmds, sessionWriteModel)
|
||||
return c.updateSession(ctx, cmd, metadata)
|
||||
}
|
||||
|
||||
@@ -154,12 +169,12 @@ func (c *Commands) TerminateSession(ctx context.Context, sessionID, sessionToken
|
||||
return writeModelToObjectDetails(&sessionWriteModel.WriteModel), nil
|
||||
}
|
||||
|
||||
// updateSession execute the [SessionChecks] where new events will be created and as well as for metadata (changes)
|
||||
func (c *Commands) updateSession(ctx context.Context, checks *SessionChecks, metadata map[string][]byte) (set *SessionChanged, err error) {
|
||||
// updateSession execute the [SessionCommands] where new events will be created and as well as for metadata (changes)
|
||||
func (c *Commands) updateSession(ctx context.Context, checks *SessionCommands, metadata map[string][]byte) (set *SessionChanged, err error) {
|
||||
if checks.sessionWriteModel.State == domain.SessionStateTerminated {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMAND-SAjeh", "Errors.Session.Terminated")
|
||||
}
|
||||
if err := checks.Check(ctx); err != nil {
|
||||
if err := checks.Exec(ctx); err != nil {
|
||||
// TODO: how to handle failed checks (e.g. pw wrong) https://github.com/zitadel/zitadel/issues/5807
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -6,10 +6,31 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
usr_repo "github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type PasskeyChallengeModel struct {
|
||||
Challenge string
|
||||
AllowedCrentialIDs [][]byte
|
||||
UserVerification domain.UserVerificationRequirement
|
||||
}
|
||||
|
||||
func (p *PasskeyChallengeModel) WebAuthNLogin(human *domain.Human, credentialAssertionData []byte) (*domain.WebAuthNLogin, error) {
|
||||
if p == nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Ioqu5", "Errors.Session.Passkey.NoChallenge")
|
||||
}
|
||||
return &domain.WebAuthNLogin{
|
||||
ObjectRoot: human.ObjectRoot,
|
||||
CredentialAssertionData: credentialAssertionData,
|
||||
Challenge: p.Challenge,
|
||||
AllowedCredentialIDs: p.AllowedCrentialIDs,
|
||||
UserVerification: p.UserVerification,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type SessionWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
@@ -17,9 +38,12 @@ type SessionWriteModel struct {
|
||||
UserID string
|
||||
UserCheckedAt time.Time
|
||||
PasswordCheckedAt time.Time
|
||||
PasskeyCheckedAt time.Time
|
||||
Metadata map[string][]byte
|
||||
State domain.SessionState
|
||||
|
||||
PasskeyChallenge *PasskeyChallengeModel
|
||||
|
||||
commands []eventstore.Command
|
||||
aggregate *eventstore.Aggregate
|
||||
}
|
||||
@@ -44,6 +68,10 @@ func (wm *SessionWriteModel) Reduce() error {
|
||||
wm.reduceUserChecked(e)
|
||||
case *session.PasswordCheckedEvent:
|
||||
wm.reducePasswordChecked(e)
|
||||
case *session.PasskeyChallengedEvent:
|
||||
wm.reducePasskeyChallenged(e)
|
||||
case *session.PasskeyCheckedEvent:
|
||||
wm.reducePasskeyChecked(e)
|
||||
case *session.TokenSetEvent:
|
||||
wm.reduceTokenSet(e)
|
||||
case *session.TerminateEvent:
|
||||
@@ -62,6 +90,8 @@ func (wm *SessionWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
session.AddedType,
|
||||
session.UserCheckedType,
|
||||
session.PasswordCheckedType,
|
||||
session.PasskeyChallengedType,
|
||||
session.PasskeyCheckedType,
|
||||
session.TokenSetType,
|
||||
session.MetadataSetType,
|
||||
session.TerminateType,
|
||||
@@ -87,6 +117,19 @@ func (wm *SessionWriteModel) reducePasswordChecked(e *session.PasswordCheckedEve
|
||||
wm.PasswordCheckedAt = e.CheckedAt
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reducePasskeyChallenged(e *session.PasskeyChallengedEvent) {
|
||||
wm.PasskeyChallenge = &PasskeyChallengeModel{
|
||||
Challenge: e.Challenge,
|
||||
AllowedCrentialIDs: e.AllowedCrentialIDs,
|
||||
UserVerification: e.UserVerification,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reducePasskeyChecked(e *session.PasskeyCheckedEvent) {
|
||||
wm.PasskeyChallenge = nil
|
||||
wm.PasskeyCheckedAt = e.CheckedAt
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reduceTokenSet(e *session.TokenSetEvent) {
|
||||
wm.TokenID = e.TokenID
|
||||
}
|
||||
@@ -110,6 +153,17 @@ func (wm *SessionWriteModel) PasswordChecked(ctx context.Context, checkedAt time
|
||||
wm.commands = append(wm.commands, session.NewPasswordCheckedEvent(ctx, wm.aggregate, checkedAt))
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) PasskeyChallenged(ctx context.Context, challenge string, allowedCrentialIDs [][]byte, userVerification domain.UserVerificationRequirement) {
|
||||
wm.commands = append(wm.commands, session.NewPasskeyChallengedEvent(ctx, wm.aggregate, challenge, allowedCrentialIDs, userVerification))
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) PasskeyChecked(ctx context.Context, checkedAt time.Time, tokenID string, signCount uint32) {
|
||||
wm.commands = append(wm.commands,
|
||||
session.NewPasskeyCheckedEvent(ctx, wm.aggregate, checkedAt),
|
||||
usr_repo.NewHumanPasswordlessSignCountChangedEvent(ctx, wm.aggregate, tokenID, signCount),
|
||||
)
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) SetToken(ctx context.Context, tokenID string) {
|
||||
wm.commands = append(wm.commands, session.NewTokenSetEvent(ctx, wm.aggregate, tokenID))
|
||||
}
|
||||
|
84
internal/command/session_passkey.go
Normal file
84
internal/command/session_passkey.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type humanPasskeys struct {
|
||||
human *domain.Human
|
||||
tokens []*domain.WebAuthNToken
|
||||
}
|
||||
|
||||
func (s *SessionCommands) getHumanPasskeys(ctx context.Context) (*humanPasskeys, error) {
|
||||
humanWritemodel, err := s.gethumanWriteModel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenReadModel, err := s.getHumanPasswordlessTokenReadModel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &humanPasskeys{
|
||||
human: writeModelToHuman(humanWritemodel),
|
||||
tokens: readModelToPasswordlessTokens(tokenReadModel),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SessionCommands) getHumanPasswordlessTokenReadModel(ctx context.Context) (*HumanPasswordlessTokensReadModel, error) {
|
||||
tokenReadModel := NewHumanPasswordlessTokensReadModel(s.sessionWriteModel.UserID, s.sessionWriteModel.ResourceOwner)
|
||||
err := s.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tokenReadModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreatePasskeyChallenge(userVerification domain.UserVerificationRequirement, dst json.Unmarshaler) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) error {
|
||||
humanPasskeys, err := cmd.getHumanPasskeys(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
webAuthNLogin, err := c.webauthnConfig.BeginLogin(ctx, humanPasskeys.human, userVerification, humanPasskeys.tokens...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = json.Unmarshal(webAuthNLogin.CredentialAssertionData, dst); err != nil {
|
||||
return caos_errs.ThrowInternal(err, "COMMAND-Yah6A", "Errors.Internal")
|
||||
}
|
||||
|
||||
cmd.sessionWriteModel.PasskeyChallenged(ctx, webAuthNLogin.Challenge, webAuthNLogin.AllowedCredentialIDs, webAuthNLogin.UserVerification)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) CheckPasskey(credentialAssertionData json.Marshaler) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) error {
|
||||
credentialAssertionData, err := json.Marshal(credentialAssertionData)
|
||||
if err != nil {
|
||||
return caos_errs.ThrowInvalidArgument(err, "COMMAND-ohG2o", "todo")
|
||||
}
|
||||
humanPasskeys, err := cmd.getHumanPasskeys(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
webAuthN, err := cmd.sessionWriteModel.PasskeyChallenge.WebAuthNLogin(humanPasskeys.human, credentialAssertionData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyID, signCount, err := c.webauthnConfig.FinishLogin(ctx, humanPasskeys.human, webAuthN, credentialAssertionData, humanPasskeys.tokens...)
|
||||
if err != nil && keyID == nil {
|
||||
return err
|
||||
}
|
||||
_, token := domain.GetTokenByKeyID(humanPasskeys.tokens, keyID)
|
||||
if token == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Aej7i", "Errors.User.WebAuthN.NotFound")
|
||||
}
|
||||
cmd.sessionWriteModel.PasskeyChecked(ctx, cmd.now(), token.WebAuthNTokenID, signCount)
|
||||
return nil
|
||||
}
|
||||
}
|
130
internal/command/session_passkeys_test.go
Normal file
130
internal/command/session_passkeys_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestSessionCommands_getHumanPasskeys(t *testing.T) {
|
||||
userAggr := &user.NewAggregate("user1", "org1").Aggregate
|
||||
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
sessionWriteModel *SessionWriteModel
|
||||
}
|
||||
type res struct {
|
||||
want *humanPasskeys
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "missing UID",
|
||||
fields: fields{
|
||||
eventstore: &eventstore.Eventstore{},
|
||||
sessionWriteModel: &SessionWriteModel{},
|
||||
},
|
||||
res: res{
|
||||
want: nil,
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-eeR2e", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "passwordless filter error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
userAggr,
|
||||
"", "", "", "", "", language.Georgian,
|
||||
domain.GenderDiverse, "", true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterError(io.ErrClosedPipe),
|
||||
),
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: nil,
|
||||
err: io.ErrClosedPipe,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
userAggr,
|
||||
"", "", "", "", "", language.Georgian,
|
||||
domain.GenderDiverse, "", true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(eventFromEventPusher(
|
||||
user.NewHumanWebAuthNAddedEvent(eventstore.NewBaseEventForPush(
|
||||
context.Background(), &org.NewAggregate("org1").Aggregate, user.HumanPasswordlessTokenAddedType,
|
||||
), "111", "challenge"),
|
||||
)),
|
||||
),
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &humanPasskeys{
|
||||
human: &domain.Human{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
State: domain.UserStateActive,
|
||||
Profile: &domain.Profile{
|
||||
PreferredLanguage: language.Georgian,
|
||||
Gender: domain.GenderDiverse,
|
||||
},
|
||||
Email: &domain.Email{},
|
||||
},
|
||||
tokens: []*domain.WebAuthNToken{{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
WebAuthNTokenID: "111",
|
||||
State: domain.MFAStateNotReady,
|
||||
Challenge: "challenge",
|
||||
}},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
s := &SessionCommands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
sessionWriteModel: tt.fields.sessionWriteModel,
|
||||
}
|
||||
got, err := s.getHumanPasskeys(context.Background())
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -21,6 +22,121 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestSessionCommands_getHumanWriteModel(t *testing.T) {
|
||||
userAggr := &user.NewAggregate("user1", "org1").Aggregate
|
||||
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
sessionWriteModel *SessionWriteModel
|
||||
}
|
||||
type res struct {
|
||||
want *HumanWriteModel
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "missing UID",
|
||||
fields: fields{
|
||||
eventstore: &eventstore.Eventstore{},
|
||||
sessionWriteModel: &SessionWriteModel{},
|
||||
},
|
||||
res: res{
|
||||
want: nil,
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-eeR2e", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilterError(io.ErrClosedPipe),
|
||||
),
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: nil,
|
||||
err: io.ErrClosedPipe,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "removed user",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
userAggr,
|
||||
"", "", "", "", "", language.Georgian,
|
||||
domain.GenderDiverse, "", true,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewUserRemovedEvent(context.Background(),
|
||||
userAggr,
|
||||
"", nil, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: nil,
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Df4b3", "Errors.ie4Ai.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
userAggr,
|
||||
"", "", "", "", "", language.Georgian,
|
||||
domain.GenderDiverse, "", true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &HumanWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
PreferredLanguage: language.Georgian,
|
||||
Gender: domain.GenderDiverse,
|
||||
UserState: domain.UserStateActive,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
s := &SessionCommands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
sessionWriteModel: tt.fields.sessionWriteModel,
|
||||
}
|
||||
got, err := s.gethumanWriteModel(context.Background())
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_CreateSession(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
@@ -29,7 +145,7 @@ func TestCommands_CreateSession(t *testing.T) {
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
checks []SessionCheck
|
||||
checks []SessionCommand
|
||||
metadata map[string][]byte
|
||||
}
|
||||
type res struct {
|
||||
@@ -126,7 +242,7 @@ func TestCommands_UpdateSession(t *testing.T) {
|
||||
ctx context.Context
|
||||
sessionID string
|
||||
sessionToken string
|
||||
checks []SessionCheck
|
||||
checks []SessionCommand
|
||||
metadata map[string][]byte
|
||||
}
|
||||
type res struct {
|
||||
@@ -231,7 +347,7 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
checks *SessionChecks
|
||||
checks *SessionCommands
|
||||
metadata map[string][]byte
|
||||
}
|
||||
type res struct {
|
||||
@@ -251,7 +367,7 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
checks: &SessionChecks{
|
||||
checks: &SessionCommands{
|
||||
sessionWriteModel: &SessionWriteModel{State: domain.SessionStateTerminated},
|
||||
},
|
||||
},
|
||||
@@ -266,10 +382,10 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
checks: &SessionChecks{
|
||||
checks: &SessionCommands{
|
||||
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
|
||||
checks: []SessionCheck{
|
||||
func(ctx context.Context, cmd *SessionChecks) error {
|
||||
cmds: []SessionCommand{
|
||||
func(ctx context.Context, cmd *SessionCommands) error {
|
||||
return caos_errs.ThrowInternal(nil, "id", "check failed")
|
||||
},
|
||||
},
|
||||
@@ -286,9 +402,9 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
checks: &SessionChecks{
|
||||
checks: &SessionCommands{
|
||||
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
|
||||
checks: []SessionCheck{},
|
||||
cmds: []SessionCommand{},
|
||||
},
|
||||
},
|
||||
res{
|
||||
@@ -321,9 +437,9 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
checks: &SessionChecks{
|
||||
checks: &SessionCommands{
|
||||
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
|
||||
checks: []SessionCheck{
|
||||
cmds: []SessionCommand{
|
||||
CheckUser("userID"),
|
||||
CheckPassword("password"),
|
||||
},
|
||||
|
Reference in New Issue
Block a user