zitadel/internal/command/user_human_otp.go
Livio Spring bb40e173bd
feat(api): add otp (sms and email) checks in session api (#6422)
* feat: add otp (sms and email) checks in session api

* implement sending

* fix tests

* add tests

* add integration tests

* fix merge main and add tests

* put default OTP Email url into config

---------

Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
2023-08-24 09:41:52 +00:00

587 lines
23 KiB
Go

package command
import (
"context"
"time"
"github.com/pquerna/otp"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"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/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, resourceOwner string, key string) error {
encryptedSecret, err := crypto.Encrypt([]byte(key), c.multifactors.OTP.CryptoMFA)
if err != nil {
return err
}
if err = c.checkUserExists(ctx, userID, resourceOwner); err != nil {
return err
}
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return err
}
if otpWriteModel.State == domain.MFAStateReady {
return caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
}
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
_, err = c.eventstore.Push(ctx,
user.NewHumanOTPAddedEvent(ctx, userAgg, encryptedSecret),
user.NewHumanOTPVerifiedEvent(ctx, userAgg, userAgentID),
)
return err
}
func (c *Commands) AddHumanTOTP(ctx context.Context, userID, resourceOwner string) (*domain.TOTP, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
}
prep, err := c.createHumanTOTP(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
err = c.pushAppendAndReduce(ctx, prep.wm, prep.cmds...)
if err != nil {
return nil, err
}
return &domain.TOTP{
ObjectDetails: writeModelToObjectDetails(&prep.wm.WriteModel),
Secret: prep.key.Secret(),
URI: prep.key.URL(),
}, nil
}
type preparedTOTP struct {
wm *HumanTOTPWriteModel
userAgg *eventstore.Aggregate
key *otp.Key
cmds []eventstore.Command
}
func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner string) (*preparedTOTP, error) {
human, err := c.getHuman(ctx, userID, resourceOwner)
if err != nil {
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-MM9fs", "Errors.User.NotFound")
}
org, err := c.getOrg(ctx, human.ResourceOwner)
if err != nil {
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-55M9f", "Errors.Org.NotFound")
}
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
if err != nil {
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
}
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if otpWriteModel.State == domain.MFAStateReady {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
}
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" {
accountName = string(human.EmailAddress)
}
issuer := c.multifactors.OTP.Issuer
if issuer == "" {
issuer = authz.GetInstance(ctx).RequestedDomain()
}
key, secret, err := domain.NewTOTPKey(issuer, accountName, c.multifactors.OTP.CryptoMFA)
if err != nil {
return nil, err
}
return &preparedTOTP{
wm: otpWriteModel,
userAgg: userAgg,
key: key,
cmds: []eventstore.Command{
user.NewHumanOTPAddedEvent(ctx, userAgg, secret),
},
}, nil
}
func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, userAgentID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
}
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotExisting")
}
if existingOTP.State == domain.MFAStateReady {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-qx4ls", "Errors.Users.MFA.OTP.AlreadyReady")
}
if err := domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA); err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanOTPVerifiedEvent(ctx, userAgg, userAgentID))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingOTP, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
}
func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
}
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return err
}
if existingOTP.State != domain.MFAStateReady {
return caos_errs.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 {
_, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
return err
}
_, pushErr := c.eventstore.Push(ctx, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
logging.OnError(pushErr).Error("error create password check failed event")
return err
}
func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
}
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting")
}
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanOTPRemovedEvent(ctx, userAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingOTP, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
}
// AddHumanOTPSMS adds the OTP SMS factor to a user.
// It can only be added if it not already is and the phone has to be verified.
func (c *Commands) AddHumanOTPSMS(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
return c.addHumanOTPSMS(ctx, userID, resourceOwner)
}
// AddHumanOTPSMSWithCheckSucceeded adds the OTP SMS factor to a user.
// It can only be added if it's not already and the phone has to be verified.
// An OTPSMSCheckSucceededEvent will be added to the passed AuthRequest, if not nil.
func (c *Commands) AddHumanOTPSMSWithCheckSucceeded(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) (*domain.ObjectDetails, error) {
if authRequest == nil {
return c.addHumanOTPSMS(ctx, userID, resourceOwner)
}
event := func(ctx context.Context, userAgg *eventstore.Aggregate) eventstore.Command {
return user.NewHumanOTPSMSCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))
}
return c.addHumanOTPSMS(ctx, userID, resourceOwner, event)
}
func (c *Commands) addHumanOTPSMS(ctx context.Context, userID, resourceOwner string, events ...eventCallback) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-QSF2s", "Errors.User.UserIDMissing")
}
if err := authz.UserIDInCTX(ctx, userID); err != nil {
return nil, err
}
otpWriteModel, err := c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if otpWriteModel.otpAdded {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-Ad3g2", "Errors.User.MFA.OTP.AlreadyReady")
}
if !otpWriteModel.phoneVerified {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Q54j2", "Errors.User.MFA.OTP.NotReady")
}
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
cmds := make([]eventstore.Command, len(events)+1)
cmds[0] = user.NewHumanOTPSMSAddedEvent(ctx, userAgg)
for i, event := range events {
cmds[i+1] = event(ctx, userAgg)
}
if err = c.pushAppendAndReduce(ctx, otpWriteModel, cmds...); err != nil {
return nil, err
}
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
}
func (c *Commands) RemoveHumanOTPSMS(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-S3br2", "Errors.User.UserIDMissing")
}
existingOTP, err := c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if userID != authz.GetCtxData(ctx).UserID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
return nil, err
}
}
if !existingOTP.otpAdded {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Sr3h3", "Errors.User.MFA.OTP.NotExisting")
}
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
if err = c.pushAppendAndReduce(ctx, existingOTP, user.NewHumanOTPSMSRemovedEvent(ctx, userAgg)); err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
}
func (c *Commands) HumanSendOTPSMS(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) error {
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
}
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPSMSCodeAddedEvent(ctx, aggregate, code, expiry, info)
}
return c.sendHumanOTP(
ctx,
userID,
resourceOwner,
authRequest,
smsWriteModel,
domain.SecretGeneratorTypeOTPSMS,
c.defaultSecretGenerators.OTPSMS,
codeAddedEvent,
)
}
func (c *Commands) HumanOTPSMSCodeSent(ctx context.Context, userID, resourceOwner string) (err error) {
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
}
codeSentEvent := func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command {
return user.NewHumanOTPSMSCodeSentEvent(ctx, aggregate)
}
return c.humanOTPSent(ctx, userID, resourceOwner, smsWriteModel, codeSentEvent)
}
func (c *Commands) HumanCheckOTPSMS(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
return c.otpSMSCodeWriteModelByID(ctx, userID, resourceOwner)
}
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPSMSCheckSucceededEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
}
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPSMSCheckFailedEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
}
return c.humanCheckOTP(
ctx,
userID,
code,
resourceOwner,
authRequest,
writeModel,
succeededEvent,
failedEvent,
)
}
// AddHumanOTPEmail adds the OTP Email factor to a user.
// It can only be added if it not already is and the phone has to be verified.
func (c *Commands) AddHumanOTPEmail(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
return c.addHumanOTPEmail(ctx, userID, resourceOwner)
}
// AddHumanOTPEmailWithCheckSucceeded adds the OTP Email factor to a user.
// It can only be added if it's not already and the email has to be verified.
// An OTPEmailCheckSucceededEvent will be added to the passed AuthRequest, if not nil.
func (c *Commands) AddHumanOTPEmailWithCheckSucceeded(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) (*domain.ObjectDetails, error) {
if authRequest == nil {
return c.addHumanOTPEmail(ctx, userID, resourceOwner)
}
event := func(ctx context.Context, userAgg *eventstore.Aggregate) eventstore.Command {
return user.NewHumanOTPEmailCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))
}
return c.addHumanOTPEmail(ctx, userID, resourceOwner, event)
}
func (c *Commands) addHumanOTPEmail(ctx context.Context, userID, resourceOwner string, events ...eventCallback) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Sg1hz", "Errors.User.UserIDMissing")
}
otpWriteModel, err := c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if otpWriteModel.otpAdded {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-MKL2s", "Errors.User.MFA.OTP.AlreadyReady")
}
if !otpWriteModel.emailVerified {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-KLJ2d", "Errors.User.MFA.OTP.NotReady")
}
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
cmds := make([]eventstore.Command, len(events)+1)
cmds[0] = user.NewHumanOTPEmailAddedEvent(ctx, userAgg)
for i, event := range events {
cmds[i+1] = event(ctx, userAgg)
}
if err = c.pushAppendAndReduce(ctx, otpWriteModel, cmds...); err != nil {
return nil, err
}
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
}
func (c *Commands) RemoveHumanOTPEmail(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-S2h11", "Errors.User.UserIDMissing")
}
existingOTP, err := c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if userID != authz.GetCtxData(ctx).UserID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
return nil, err
}
}
if !existingOTP.otpAdded {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-b312D", "Errors.User.MFA.OTP.NotExisting")
}
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
if err = c.pushAppendAndReduce(ctx, existingOTP, user.NewHumanOTPEmailRemovedEvent(ctx, userAgg)); err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
}
func (c *Commands) HumanSendOTPEmail(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) error {
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
return c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
}
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPEmailCodeAddedEvent(ctx, aggregate, code, expiry, info)
}
return c.sendHumanOTP(
ctx,
userID,
resourceOwner,
authRequest,
smsWriteModel,
domain.SecretGeneratorTypeOTPEmail,
c.defaultSecretGenerators.OTPEmail,
codeAddedEvent,
)
}
func (c *Commands) HumanOTPEmailCodeSent(ctx context.Context, userID, resourceOwner string) (err error) {
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
return c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
}
codeSentEvent := func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command {
return user.NewHumanOTPEmailCodeSentEvent(ctx, aggregate)
}
return c.humanOTPSent(ctx, userID, resourceOwner, smsWriteModel, codeSentEvent)
}
func (c *Commands) HumanCheckOTPEmail(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
return c.otpEmailCodeWriteModelByID(ctx, userID, resourceOwner)
}
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPEmailCheckSucceededEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
}
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
return user.NewHumanOTPEmailCheckFailedEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
}
return c.humanCheckOTP(
ctx,
userID,
code,
resourceOwner,
authRequest,
writeModel,
succeededEvent,
failedEvent,
)
}
// sendHumanOTP creates a code for a registered mechanism (sms / email), which is used for a check (during login)
func (c *Commands) sendHumanOTP(
ctx context.Context,
userID, resourceOwner string,
authRequest *domain.AuthRequest,
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error),
secretGeneratorType domain.SecretGeneratorType,
defaultSecretGenerator *crypto.GeneratorConfig,
codeAddedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command,
) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-S3SF1", "Errors.User.UserIDMissing")
}
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
if err != nil {
return err
}
if !existingOTP.OTPAdded() {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady")
}
config, err := secretGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator)
if err != nil {
return err
}
gen := crypto.NewEncryptionGenerator(*config, c.userEncryption)
value, _, err := crypto.NewCode(gen)
if err != nil {
return err
}
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
_, err = c.eventstore.Push(ctx, codeAddedEvent(ctx, userAgg, value, gen.Expiry(), authRequestDomainToAuthRequestInfo(authRequest)))
return err
}
func (c *Commands) humanOTPSent(
ctx context.Context,
userID, resourceOwner string,
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error),
codeSentEvent func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command,
) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-AE2h2", "Errors.User.UserIDMissing")
}
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
if err != nil {
return err
}
if !existingOTP.OTPAdded() {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SD3gh", "Errors.User.MFA.OTP.NotReady")
}
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
_, err = c.eventstore.Push(ctx, codeSentEvent(ctx, userAgg))
return err
}
func (c *Commands) humanCheckOTP(
ctx context.Context,
userID, code, resourceOwner string,
authRequest *domain.AuthRequest,
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error),
checkSucceededEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
) error {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-S453v", "Errors.User.UserIDMissing")
}
if code == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-SJl2g", "Errors.User.Code.Empty")
}
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
if err != nil {
return err
}
if !existingOTP.OTPAdded() {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-d2r52", "Errors.User.MFA.OTP.NotReady")
}
if existingOTP.Code() == nil {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
}
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
err = crypto.VerifyCodeWithAlgorithm(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
if err == nil {
_, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
return err
}
_, pushErr := c.eventstore.Push(ctx, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
return err
}
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewHumanTOTPWriteModel(userID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}
func (c *Commands) otpSMSWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPSMSWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewHumanOTPSMSWriteModel(userID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}
func (c *Commands) otpSMSCodeWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPSMSCodeWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewHumanOTPSMSCodeWriteModel(userID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}
func (c *Commands) otpEmailWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewHumanOTPEmailWriteModel(userID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}
func (c *Commands) otpEmailCodeWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailCodeWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewHumanOTPEmailCodeWriteModel(userID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}