mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 08:37:32 +00:00
feat: login errors (#204)
* feat: nice error messages * feat: nice error messages * fix: add project type * fix: message ids * handle error messages in login * add some better error messages * fix: better error messages on login * fix: better error messages on login * fix: add internal errors * fix: tests
This commit is contained in:
@@ -149,7 +149,7 @@ func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User,
|
||||
func (es *UserEventstore) PrepareRegisterUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
|
||||
user.SetEmailAsUsername()
|
||||
if !user.IsValid() || user.Password == nil || user.SecretString == "" {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "user is invalid")
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Errors.User.InvalidData")
|
||||
}
|
||||
id, err := es.idGenerator.Next()
|
||||
if err != nil {
|
||||
@@ -326,8 +326,11 @@ func (es *UserEventstore) InitCodeSent(ctx context.Context, userID string) error
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyInitCode(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, userID, verificationCode, password string) error {
|
||||
if userID == "" || verificationCode == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "userId or Code empty")
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if verificationCode == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "Errors.User.Code.Empty")
|
||||
}
|
||||
pw := &usr_model.Password{SecretString: password}
|
||||
err := pw.HashPasswordIfExisting(policy, es.PasswordAlg, false)
|
||||
@@ -339,13 +342,15 @@ func (es *UserEventstore) VerifyInitCode(ctx context.Context, policy *policy_mod
|
||||
return err
|
||||
}
|
||||
if existing.InitCode == nil {
|
||||
return caos_errs.ThrowNotFound(nil, "EVENT-spo9W", "code not found")
|
||||
return caos_errs.ThrowNotFound(nil, "EVENT-spo9W", "Errors.User.Code.NotFound")
|
||||
}
|
||||
repoPassword := model.PasswordFromModel(pw)
|
||||
repoExisting := model.UserFromModel(existing)
|
||||
var updateAggregate func(ctx context.Context) (*es_models.Aggregate, error)
|
||||
if err := crypto.VerifyCode(existing.InitCode.CreationDate, existing.InitCode.Expiry, existing.InitCode.Code, verificationCode, es.InitializeUserCode); err != nil {
|
||||
updateAggregate = InitCodeCheckFailedAggregate(es.AggregateCreator(), repoExisting)
|
||||
es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||
return err
|
||||
} else {
|
||||
updateAggregate = InitCodeVerifiedAggregate(es.AggregateCreator(), repoExisting, repoPassword)
|
||||
}
|
||||
@@ -360,7 +365,7 @@ func (es *UserEventstore) VerifyInitCode(ctx context.Context, policy *policy_mod
|
||||
|
||||
func (es *UserEventstore) SkipMfaInit(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "Errors.User.UserIDMissing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
@@ -398,7 +403,7 @@ func (es *UserEventstore) CheckPassword(ctx context.Context, userID, password st
|
||||
return err
|
||||
}
|
||||
if existing.Password == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s35Fa", "no password set")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s35Fa", "Errors.User.Password.Empty")
|
||||
}
|
||||
if err := crypto.CompareHash(existing.Password.SecretCrypto, []byte(password), es.PasswordAlg); err == nil {
|
||||
return es.setPasswordCheckResult(ctx, existing, authRequest, PasswordCheckSucceededAggregate)
|
||||
@@ -406,7 +411,7 @@ func (es *UserEventstore) CheckPassword(ctx context.Context, userID, password st
|
||||
if err := es.setPasswordCheckResult(ctx, existing, authRequest, PasswordCheckFailedAggregate); err != nil {
|
||||
return err
|
||||
}
|
||||
return caos_errs.ThrowInvalidArgument(nil, "EVENT-452ad", "invalid password")
|
||||
return caos_errs.ThrowInvalidArgument(nil, "EVENT-452ad", "Errors.User.Password.Invalid")
|
||||
}
|
||||
|
||||
func (es *UserEventstore) setPasswordCheckResult(ctx context.Context, user *usr_model.User, authRequest *req_model.AuthRequest, check func(*es_models.AggregateCreator, *model.User, *model.AuthRequest) es_sdk.AggregateFunc) error {
|
||||
@@ -435,10 +440,10 @@ func (es *UserEventstore) SetPassword(ctx context.Context, policy *policy_model.
|
||||
return err
|
||||
}
|
||||
if user.PasswordCode == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-65sdr", "reset code not found")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-65sdr", "Errors.User.Code.NotFound")
|
||||
}
|
||||
if err := crypto.VerifyCode(user.PasswordCode.CreationDate, user.PasswordCode.Expiry, user.PasswordCode.Code, code, es.PasswordVerificationCode); err != nil {
|
||||
return caos_errs.ThrowPreconditionFailed(err, "EVENT-sd6DF", "code invalid")
|
||||
return err
|
||||
}
|
||||
_, err = es.changedPassword(ctx, user, policy, password, false)
|
||||
return err
|
||||
@@ -450,10 +455,10 @@ func (es *UserEventstore) ChangePassword(ctx context.Context, policy *policy_mod
|
||||
return nil, err
|
||||
}
|
||||
if user.Password == nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Fds3s", "user has no password")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Fds3s", "Errors.User.Password.Empty")
|
||||
}
|
||||
if err := crypto.CompareHash(user.Password.SecretCrypto, []byte(old), es.PasswordAlg); err != nil {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-s56a3", "invalid password")
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-s56a3", "Errors.User.Password.Invalid")
|
||||
}
|
||||
return es.changedPassword(ctx, user, policy, new, false)
|
||||
}
|
||||
@@ -478,7 +483,7 @@ func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.U
|
||||
|
||||
func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string, notifyType usr_model.NotificationType) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "Errors.User.UserIDMissing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
@@ -503,7 +508,7 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
||||
|
||||
func (es *UserEventstore) PasswordCodeSent(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "userID missing")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "Errors.User.UserIDMissing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
@@ -603,15 +608,18 @@ func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Emai
|
||||
}
|
||||
|
||||
func (es *UserEventstore) VerifyEmail(ctx context.Context, userID, verificationCode string) error {
|
||||
if userID == "" || verificationCode == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "userId or Code empty")
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if verificationCode == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-skDws", "Errors.User.Code.Empty")
|
||||
}
|
||||
existing, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing.EmailCode == nil {
|
||||
return caos_errs.ThrowNotFound(nil, "EVENT-lso9w", "code not found")
|
||||
return caos_errs.ThrowNotFound(nil, "EVENT-lso9w", "Errors.User.Code.NotFound")
|
||||
}
|
||||
|
||||
err = crypto.VerifyCode(existing.EmailCode.CreationDate, existing.EmailCode.Expiry, existing.EmailCode.Code, verificationCode, es.EmailVerificationCode)
|
||||
@@ -621,7 +629,7 @@ func (es *UserEventstore) VerifyEmail(ctx context.Context, userID, verificationC
|
||||
if err := es.setEmailVerifyResult(ctx, existing, EmailVerificationFailedAggregate); err != nil {
|
||||
return err
|
||||
}
|
||||
return caos_errs.ThrowInvalidArgument(err, "EVENT-dtGaa", "invalid code")
|
||||
return caos_errs.ThrowInvalidArgument(err, "EVENT-dtGaa", "Errors.User.Code.Invalid")
|
||||
}
|
||||
|
||||
func (es *UserEventstore) setEmailVerifyResult(ctx context.Context, existing *usr_model.User, check func(aggCreator *es_models.AggregateCreator, existing *model.User) es_sdk.AggregateFunc) error {
|
||||
@@ -852,7 +860,7 @@ func (es *UserEventstore) AddOTP(ctx context.Context, userID string) (*usr_model
|
||||
return nil, err
|
||||
}
|
||||
if existing.IsOTPReady() {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-do9se", "user has already configured otp")
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-do9se", "Errors.User.Mfa.Otp.AlreadyReady")
|
||||
}
|
||||
key, err := totp.Generate(totp.GenerateOpts{Issuer: es.Multifactors.OTP.Issuer, AccountName: userID})
|
||||
if err != nil {
|
||||
@@ -901,8 +909,11 @@ func (es *UserEventstore) CheckMfaOTPSetup(ctx context.Context, userID, code str
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.OTP == nil || user.IsOTPReady() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "otp not existing or already set up")
|
||||
if user.OTP == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "Errors.Users.Mfa.Otp.NotExisting")
|
||||
}
|
||||
if user.IsOTPReady() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "Errors.Users.Mfa.Otp.AlreadyReady")
|
||||
}
|
||||
if err := es.verifyMfaOTP(user.OTP, code); err != nil {
|
||||
return err
|
||||
@@ -923,18 +934,22 @@ func (es *UserEventstore) CheckMfaOTP(ctx context.Context, userID, code string,
|
||||
return err
|
||||
}
|
||||
if !user.IsOTPReady() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "opt not ready")
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "Errors.User.Mfa.Otp.NotReady")
|
||||
}
|
||||
|
||||
repoUser := model.UserFromModel(user)
|
||||
repoAuthReq := model.AuthRequestFromModel(authRequest)
|
||||
var aggregate func(*es_models.AggregateCreator, *model.User, *model.AuthRequest) es_sdk.AggregateFunc
|
||||
if err := es.verifyMfaOTP(user.OTP, code); err != nil {
|
||||
var checkErr error
|
||||
if checkErr = es.verifyMfaOTP(user.OTP, code); checkErr != nil {
|
||||
aggregate = MfaOTPCheckFailedAggregate
|
||||
} else {
|
||||
aggregate = MfaOTPCheckSucceededAggregate
|
||||
}
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, aggregate(es.AggregateCreator(), repoUser, repoAuthReq))
|
||||
if checkErr != nil {
|
||||
return checkErr
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -951,7 +966,7 @@ func (es *UserEventstore) verifyMfaOTP(otp *usr_model.OTP, code string) error {
|
||||
|
||||
valid := es.validateTOTP(code, decrypt)
|
||||
if !valid {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Invalid code")
|
||||
return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Errors.User.Mfa.Otp.InvalidCode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -1487,7 +1487,7 @@ func TestSetPassword(t *testing.T) {
|
||||
password: "password",
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
errFunc: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1511,7 +1511,7 @@ func TestSetPassword(t *testing.T) {
|
||||
password: "password",
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
errFunc: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -3076,7 +3076,9 @@ func TestCheckMfaOTP(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
|
@@ -12,6 +12,9 @@ func UserByID(db *gorm.DB, table, userID string) (*model.UserView, error) {
|
||||
user := new(model.UserView)
|
||||
query := view.PrepareGetByKey(table, model.UserSearchKey(usr_model.USERSEARCHKEY_USER_ID), userID)
|
||||
err := query(db, user)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-sj8Sw", "Errors.User.NotFound")
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
@@ -19,6 +22,9 @@ func UserByUserName(db *gorm.DB, table, userName string) (*model.UserView, error
|
||||
user := new(model.UserView)
|
||||
query := view.PrepareGetByKey(table, model.UserSearchKey(usr_model.USERSEARCHKEY_USER_NAME), userName)
|
||||
err := query(db, user)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso9s", "Errors.User.NotFound")
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
@@ -36,6 +42,9 @@ func GetGlobalUserByEmail(db *gorm.DB, table, email string) (*model.UserView, er
|
||||
user := new(model.UserView)
|
||||
query := view.PrepareGetByKey(table, model.UserSearchKey(usr_model.USERSEARCHKEY_EMAIL), email)
|
||||
err := query(db, user)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-8uWer", "Errors.User.NotFound")
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user