mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:57:31 +00:00
feat: provide option to limit (T)OTP checks (#7693)
* feat: provide option to limit (T)OTP checks * fix requests in console * update errors pkg * cleanup * cleanup * improve naming of existing config
This commit is contained in:
@@ -158,14 +158,37 @@ func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resource
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||
err = domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA)
|
||||
if err == nil {
|
||||
verifyErr := domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA)
|
||||
|
||||
// recheck for additional events (failed OTP checks or locks)
|
||||
recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP)
|
||||
if recheckErr != nil {
|
||||
return recheckErr
|
||||
}
|
||||
if existingOTP.UserLocked {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3fg", "Errors.User.Locked")
|
||||
}
|
||||
|
||||
// the OTP check succeeded and the user was not locked in the meantime
|
||||
if verifyErr == nil {
|
||||
_, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
return err
|
||||
}
|
||||
_, pushErr := c.eventstore.Push(ctx, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
|
||||
// the OTP check failed, therefore check if the limit was reached and the user must additionally be locked
|
||||
commands := make([]eventstore.Command, 0, 2)
|
||||
commands = append(commands, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount+1 >= lockoutPolicy.MaxOTPAttempts {
|
||||
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
||||
}
|
||||
|
||||
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||
logging.OnError(pushErr).Error("error create password check failed event")
|
||||
return err
|
||||
return verifyErr
|
||||
}
|
||||
|
||||
func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
@@ -515,14 +538,37 @@ func (c *Commands) humanCheckOTP(
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
|
||||
}
|
||||
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
|
||||
err = crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
|
||||
if err == nil {
|
||||
verifyErr := crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
|
||||
|
||||
// recheck for additional events (failed OTP checks or locks)
|
||||
recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP)
|
||||
if recheckErr != nil {
|
||||
return recheckErr
|
||||
}
|
||||
if existingOTP.UserLocked() {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked")
|
||||
}
|
||||
|
||||
// the OTP check succeeded and the user was not locked in the meantime
|
||||
if verifyErr == nil {
|
||||
_, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
return err
|
||||
}
|
||||
_, pushErr := c.eventstore.Push(ctx, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
|
||||
// the OTP check failed, therefore check if the limit was reached and the user must additionally be locked
|
||||
commands := make([]eventstore.Command, 0, 2)
|
||||
commands = append(commands, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount()+1 >= lockoutPolicy.MaxOTPAttempts {
|
||||
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
||||
}
|
||||
|
||||
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
|
||||
return err
|
||||
return verifyErr
|
||||
}
|
||||
|
||||
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
|
||||
|
Reference in New Issue
Block a user