mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 01:37:31 +00:00
fix: improvements for login flow (incl. webauthn) (#1026)
* fix: typo ZITADEL uppercase for OTP Issuer * fix: password validation after change in current user agent * fix: otp validation after setup in current user agent * add waiting * add waiting * show u2f state * regenerate css * add useragentID to webauthn verify * return mfa attribute in mgmt * switch between providers * use preferredLoginName for webauthn display * some fixes * correct translations for login * add some missing event translations * fix usersession test * remove unnecessary cancel button on password change done
This commit is contained in:
@@ -578,10 +578,10 @@ func (es *UserEventstore) SetOneTimePassword(ctx context.Context, policy *iam_mo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es.changedPassword(ctx, user, policy, password.SecretString, true)
|
||||
return es.changedPassword(ctx, user, policy, password.SecretString, true, "")
|
||||
}
|
||||
|
||||
func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, code, password string) error {
|
||||
func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, code, password, userAgentID string) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -592,7 +592,7 @@ func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.Pas
|
||||
if err := crypto.VerifyCode(user.PasswordCode.CreationDate, user.PasswordCode.Expiry, user.PasswordCode.Code, code, es.PasswordVerificationCode); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = es.changedPassword(ctx, user, policy, password, false)
|
||||
_, err = es.changedPassword(ctx, user, policy, password, false, userAgentID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -655,7 +655,7 @@ func (es *UserEventstore) ChangeMachine(ctx context.Context, machine *usr_model.
|
||||
return model.MachineToModel(repoUser.Machine), nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) ChangePassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, old, new string) (_ *usr_model.Password, err error) {
|
||||
func (es *UserEventstore) ChangePassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, old, new, userAgentID string) (_ *usr_model.Password, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -672,10 +672,10 @@ func (es *UserEventstore) ChangePassword(ctx context.Context, policy *iam_model.
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-s56a3", "Errors.User.Password.Invalid")
|
||||
}
|
||||
return es.changedPassword(ctx, user, policy, new, false)
|
||||
return es.changedPassword(ctx, user, policy, new, false, userAgentID)
|
||||
}
|
||||
|
||||
func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, policy *iam_model.PasswordComplexityPolicyView, password string, onetime bool) (_ *usr_model.Password, err error) {
|
||||
func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, policy *iam_model.PasswordComplexityPolicyView, password string, onetime bool, userAgentID string) (_ *usr_model.Password, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
pw := &usr_model.Password{SecretString: password}
|
||||
@@ -683,7 +683,7 @@ func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.U
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoPassword := model.PasswordFromModel(pw)
|
||||
repoPassword := model.PasswordChangeFromModel(pw, userAgentID)
|
||||
repoUser := model.UserFromModel(user)
|
||||
agg := PasswordChangeAggregate(es.AggregateCreator(), repoUser, repoPassword)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||
@@ -1235,7 +1235,7 @@ func (es *UserEventstore) RemoveOTP(ctx context.Context, userID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) CheckMFAOTPSetup(ctx context.Context, userID, code string) error {
|
||||
func (es *UserEventstore) CheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1250,7 +1250,7 @@ func (es *UserEventstore) CheckMFAOTPSetup(ctx context.Context, userID, code str
|
||||
return err
|
||||
}
|
||||
repoUser := model.UserFromModel(user)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAOTPVerifyAggregate(es.AggregateCreator(), repoUser))
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAOTPVerifyAggregate(es.AggregateCreator(), repoUser, userAgentID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1326,7 +1326,7 @@ func (es *UserEventstore) AddU2F(ctx context.Context, userID string) (*usr_model
|
||||
return webAuthN, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error {
|
||||
func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1337,7 +1337,7 @@ func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName
|
||||
return err
|
||||
}
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN)
|
||||
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1432,7 +1432,7 @@ func (es *UserEventstore) AddPasswordless(ctx context.Context, userID string) (*
|
||||
return webAuthN, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error {
|
||||
func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
|
||||
user, err := es.HumanByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1443,7 +1443,7 @@ func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, t
|
||||
return err
|
||||
}
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN)
|
||||
repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -1678,7 +1678,7 @@ func TestSetPassword(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.SetPassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.code, tt.args.password)
|
||||
err := tt.args.es.SetPassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.code, tt.args.password, "")
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("result has error: %v", err)
|
||||
@@ -1838,7 +1838,7 @@ func TestChangePassword(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.ChangePassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.old, tt.args.new)
|
||||
result, err := tt.args.es.ChangePassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.old, tt.args.new, "")
|
||||
|
||||
if tt.res.errFunc == nil && result.AggregateID == "" {
|
||||
t.Errorf("result has no id")
|
||||
@@ -3578,7 +3578,7 @@ func TestCheckMFAOTPSetup(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.CheckMFAOTPSetup(tt.args.ctx, tt.args.userID, tt.args.code)
|
||||
err := tt.args.es.CheckMFAOTPSetup(tt.args.ctx, tt.args.userID, tt.args.code, "")
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("result should not get err")
|
||||
|
@@ -3,6 +3,7 @@ package model
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
@@ -16,6 +17,10 @@ type OTP struct {
|
||||
State int32 `json:"-"`
|
||||
}
|
||||
|
||||
type OTPVerified struct {
|
||||
UserAgentID string `json:"userAgentID,omitempty"`
|
||||
}
|
||||
|
||||
func OTPFromModel(otp *model.OTP) *OTP {
|
||||
return &OTP{
|
||||
ObjectRoot: otp.ObjectRoot,
|
||||
@@ -55,3 +60,11 @@ func (o *OTP) setData(event *es_models.Event) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OTPVerified) SetData(event *es_models.Event) error {
|
||||
if err := json.Unmarshal(event.Data, o); err != nil {
|
||||
logging.Log("EVEN-BF421").WithError(err).Error("could not unmarshal event data")
|
||||
return caos_errs.ThrowInternal(err, "MODEL-GB6hj", "could not unmarshal event")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -26,6 +26,11 @@ type PasswordCode struct {
|
||||
NotificationType int32 `json:"notificationType,omitempty"`
|
||||
}
|
||||
|
||||
type PasswordChange struct {
|
||||
Password
|
||||
UserAgentID string `json:"userAgentID,omitempty"`
|
||||
}
|
||||
|
||||
func PasswordFromModel(password *model.Password) *Password {
|
||||
return &Password{
|
||||
ObjectRoot: password.ObjectRoot,
|
||||
@@ -51,6 +56,17 @@ func PasswordCodeToModel(code *PasswordCode) *model.PasswordCode {
|
||||
}
|
||||
}
|
||||
|
||||
func PasswordChangeFromModel(password *model.Password, userAgentID string) *PasswordChange {
|
||||
return &PasswordChange{
|
||||
Password: Password{
|
||||
ObjectRoot: password.ObjectRoot,
|
||||
Secret: password.SecretCrypto,
|
||||
ChangeRequired: password.ChangeRequired,
|
||||
},
|
||||
UserAgentID: userAgentID,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Human) appendUserPasswordChangedEvent(event *es_models.Event) error {
|
||||
u.Password = new(Password)
|
||||
err := u.Password.setData(event)
|
||||
@@ -84,3 +100,12 @@ func (c *PasswordCode) SetData(event *es_models.Event) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pw *PasswordChange) SetData(event *es_models.Event) error {
|
||||
if err := json.Unmarshal(event.Data, pw); err != nil {
|
||||
logging.Log("EVEN-ADs31").WithError(err).Error("could not unmarshal event data")
|
||||
return caos_errs.ThrowInternal(err, "MODEL-BDd32", "could not unmarshal event")
|
||||
}
|
||||
pw.ObjectRoot.AppendEvent(event)
|
||||
return nil
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ type WebAuthNVerify struct {
|
||||
AAGUID []byte `json:"aaguid"`
|
||||
SignCount uint32 `json:"signCount"`
|
||||
WebAuthNTokenName string `json:"webAuthNTokenName"`
|
||||
UserAgentID string `json:"userAgentID,omitempty"`
|
||||
}
|
||||
|
||||
type WebAuthNSignCount struct {
|
||||
@@ -104,7 +105,7 @@ func WebAuthNToModel(webAuthN *WebAuthNToken) *model.WebAuthNToken {
|
||||
}
|
||||
}
|
||||
|
||||
func WebAuthNVerifyFromModel(webAuthN *model.WebAuthNToken) *WebAuthNVerify {
|
||||
func WebAuthNVerifyFromModel(webAuthN *model.WebAuthNToken, userAgentID string) *WebAuthNVerify {
|
||||
return &WebAuthNVerify{
|
||||
WebAuthNTokenID: webAuthN.WebAuthNTokenID,
|
||||
KeyID: webAuthN.KeyID,
|
||||
@@ -113,6 +114,7 @@ func WebAuthNVerifyFromModel(webAuthN *model.WebAuthNToken) *WebAuthNVerify {
|
||||
SignCount: webAuthN.SignCount,
|
||||
AttestationType: webAuthN.AttestationType,
|
||||
WebAuthNTokenName: webAuthN.WebAuthNTokenName,
|
||||
UserAgentID: userAgentID,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +150,14 @@ func WebAuthNLoginToModel(webAuthN *WebAuthNLogin) *model.WebAuthNLogin {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebAuthNVerify) SetData(event *es_models.Event) error {
|
||||
if err := json.Unmarshal(event.Data, w); err != nil {
|
||||
logging.Log("EVEN-G342rf").WithError(err).Error("could not unmarshal event data")
|
||||
return caos_errs.ThrowInternal(err, "MODEL-B6641", "could not unmarshal event")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Human) appendU2FAddedEvent(event *es_models.Event) error {
|
||||
webauthn := new(WebAuthNToken)
|
||||
err := webauthn.setData(event)
|
||||
|
@@ -410,16 +410,16 @@ func SkipMFAAggregate(aggCreator *es_models.AggregateCreator, user *model.User)
|
||||
}
|
||||
}
|
||||
|
||||
func PasswordChangeAggregate(aggCreator *es_models.AggregateCreator, user *model.User, password *model.Password) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
func PasswordChangeAggregate(aggCreator *es_models.AggregateCreator, user *model.User, passwordChange *model.PasswordChange) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if password == nil {
|
||||
if passwordChange == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d9832", "Errors.Internal")
|
||||
}
|
||||
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.HumanPasswordChanged, password)
|
||||
return agg.AppendEvent(model.HumanPasswordChanged, passwordChange)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,13 +737,13 @@ func MFAOTPAddAggregate(aggCreator *es_models.AggregateCreator, user *model.User
|
||||
}
|
||||
}
|
||||
|
||||
func MFAOTPVerifyAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
func MFAOTPVerifyAggregate(aggCreator *es_models.AggregateCreator, user *model.User, userAgentID string) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.HumanMFAOTPVerified, nil)
|
||||
return agg.AppendEvent(model.HumanMFAOTPVerified, &model.OTPVerified{UserAgentID: userAgentID})
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1060,10 +1060,10 @@ func TestSkipMFAAggregate(t *testing.T) {
|
||||
|
||||
func TestChangePasswordAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
user *model.User
|
||||
password *model.Password
|
||||
aggCreator *models.AggregateCreator
|
||||
ctx context.Context
|
||||
user *model.User
|
||||
passwordChange *model.PasswordChange
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
@@ -1086,8 +1086,8 @@ func TestChangePasswordAggregate(t *testing.T) {
|
||||
Profile: &model.Profile{DisplayName: "DisplayName"},
|
||||
},
|
||||
},
|
||||
password: &model.Password{ChangeRequired: true},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
passwordChange: &model.PasswordChange{Password: model.Password{ChangeRequired: true}},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
@@ -1113,7 +1113,7 @@ func TestChangePasswordAggregate(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := PasswordChangeAggregate(tt.args.aggCreator, tt.args.user, tt.args.password)(tt.args.ctx)
|
||||
agg, err := PasswordChangeAggregate(tt.args.aggCreator, tt.args.user, tt.args.passwordChange)(tt.args.ctx)
|
||||
|
||||
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
@@ -2329,7 +2329,7 @@ func TestOTPVerifyAggregate(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := MFAOTPVerifyAggregate(tt.args.aggCreator, tt.args.user)(tt.args.ctx)
|
||||
agg, err := MFAOTPVerifyAggregate(tt.args.aggCreator, tt.args.user, "")(tt.args.ctx)
|
||||
|
||||
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
|
Reference in New Issue
Block a user