mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-16 12:58:00 +00:00
16171ce3b9
# Which Problems Are Solved OTP Email links currently could not use / include the sessionID they belong to. This prevents an easy use for redirecting and handling OTP via email through the session API. # How the Problems Are Solved Added the sessionID as placeholder for the OTP Email link template. # Additional Changes List all available placeholders in the url_templates of V2 endpoints. # Additional Context - discussed in a customer meeting
195 lines
7.2 KiB
Go
195 lines
7.2 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/notification/senders"
|
|
"github.com/zitadel/zitadel/internal/repository/session"
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func (c *Commands) CreateOTPSMSChallengeReturnCode(dst *string) SessionCommand {
|
|
return c.createOTPSMSChallenge(true, dst)
|
|
}
|
|
|
|
func (c *Commands) CreateOTPSMSChallenge() SessionCommand {
|
|
return c.createOTPSMSChallenge(false, nil)
|
|
}
|
|
|
|
func (c *Commands) createOTPSMSChallenge(returnCode bool, dst *string) SessionCommand {
|
|
return func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error) {
|
|
if cmd.sessionWriteModel.UserID == "" {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-JKL3g", "Errors.User.UserIDMissing")
|
|
}
|
|
writeModel := NewHumanOTPSMSWriteModel(cmd.sessionWriteModel.UserID, "")
|
|
if err := cmd.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
|
return nil, err
|
|
}
|
|
if !writeModel.OTPAdded() {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-BJ2g3", "Errors.User.MFA.OTP.NotReady")
|
|
}
|
|
code, generatorID, err := cmd.createPhoneCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPSMS, cmd.otpAlg, c.defaultSecretGenerators.OTPSMS) //nolint:staticcheck
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if returnCode {
|
|
*dst = code.Plain
|
|
}
|
|
cmd.OTPSMSChallenged(ctx, code.CryptedCode(), code.CodeExpiry(), returnCode, generatorID)
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func (c *Commands) OTPSMSSent(ctx context.Context, sessionID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) error {
|
|
sessionWriteModel := NewSessionWriteModel(sessionID, resourceOwner)
|
|
err := c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sessionWriteModel.OTPSMSCodeChallenge == nil {
|
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-G3t31", "Errors.User.Code.NotFound")
|
|
}
|
|
return c.pushAppendAndReduce(ctx, sessionWriteModel,
|
|
session.NewOTPSMSSentEvent(ctx, &session.NewAggregate(sessionID, sessionWriteModel.ResourceOwner).Aggregate, generatorInfo),
|
|
)
|
|
}
|
|
|
|
func (c *Commands) CreateOTPEmailChallengeURLTemplate(urlTmpl string) (SessionCommand, error) {
|
|
if err := domain.RenderOTPEmailURLTemplate(io.Discard, urlTmpl, "code", "userID", "loginName", "displayName", "sessionID", language.English); err != nil {
|
|
return nil, err
|
|
}
|
|
return c.createOTPEmailChallenge(false, urlTmpl, nil), nil
|
|
}
|
|
|
|
func (c *Commands) CreateOTPEmailChallengeReturnCode(dst *string) SessionCommand {
|
|
return c.createOTPEmailChallenge(true, "", dst)
|
|
}
|
|
|
|
func (c *Commands) CreateOTPEmailChallenge() SessionCommand {
|
|
return c.createOTPEmailChallenge(false, "", nil)
|
|
}
|
|
|
|
func (c *Commands) createOTPEmailChallenge(returnCode bool, urlTmpl string, dst *string) SessionCommand {
|
|
return func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error) {
|
|
if cmd.sessionWriteModel.UserID == "" {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-JK3gp", "Errors.User.UserIDMissing")
|
|
}
|
|
writeModel := NewHumanOTPEmailWriteModel(cmd.sessionWriteModel.UserID, "")
|
|
if err := cmd.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
|
return nil, err
|
|
}
|
|
if !writeModel.OTPAdded() {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-JKLJ3", "Errors.User.MFA.OTP.NotReady")
|
|
}
|
|
code, err := cmd.createCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPEmail, cmd.otpAlg, c.defaultSecretGenerators.OTPEmail) //nolint:staticcheck
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if returnCode {
|
|
*dst = code.Plain
|
|
}
|
|
cmd.OTPEmailChallenged(ctx, code.Crypted, code.Expiry, returnCode, urlTmpl)
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func (c *Commands) OTPEmailSent(ctx context.Context, sessionID, resourceOwner string) error {
|
|
sessionWriteModel := NewSessionWriteModel(sessionID, resourceOwner)
|
|
err := c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sessionWriteModel.OTPEmailCodeChallenge == nil {
|
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SLr02", "Errors.User.Code.NotFound")
|
|
}
|
|
return c.pushAppendAndReduce(ctx, sessionWriteModel,
|
|
session.NewOTPEmailSentEvent(ctx, &session.NewAggregate(sessionID, sessionWriteModel.ResourceOwner).Aggregate),
|
|
)
|
|
}
|
|
|
|
func CheckOTPSMS(code string) SessionCommand {
|
|
return func(ctx context.Context, cmd *SessionCommands) (_ []eventstore.Command, err error) {
|
|
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
|
|
otpWriteModel := NewHumanOTPSMSCodeWriteModel(cmd.sessionWriteModel.UserID, "")
|
|
err := cmd.eventstore.FilterToQueryReducer(ctx, otpWriteModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// explicitly set the challenge from the session write model since the code write model will only check user events
|
|
otpWriteModel.otpCode = cmd.sessionWriteModel.OTPSMSCodeChallenge
|
|
return otpWriteModel, nil
|
|
}
|
|
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
return user.NewHumanOTPSMSCheckSucceededEvent(ctx, aggregate, nil)
|
|
}
|
|
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
return user.NewHumanOTPSMSCheckFailedEvent(ctx, aggregate, nil)
|
|
}
|
|
commands, err := checkOTP(
|
|
ctx,
|
|
cmd.sessionWriteModel.UserID,
|
|
code,
|
|
"",
|
|
nil,
|
|
writeModel,
|
|
cmd.eventstore.FilterToQueryReducer,
|
|
cmd.otpAlg,
|
|
cmd.getCodeVerifier,
|
|
succeededEvent,
|
|
failedEvent,
|
|
)
|
|
if err != nil {
|
|
return commands, err
|
|
}
|
|
cmd.eventCommands = append(cmd.eventCommands, commands...)
|
|
cmd.OTPSMSChecked(ctx, cmd.now())
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func CheckOTPEmail(code string) SessionCommand {
|
|
return func(ctx context.Context, cmd *SessionCommands) (_ []eventstore.Command, err error) {
|
|
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
|
|
otpWriteModel := NewHumanOTPEmailCodeWriteModel(cmd.sessionWriteModel.UserID, "")
|
|
err := cmd.eventstore.FilterToQueryReducer(ctx, otpWriteModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// explicitly set the challenge from the session write model since the code write model will only check user events
|
|
otpWriteModel.otpCode = cmd.sessionWriteModel.OTPEmailCodeChallenge
|
|
return otpWriteModel, nil
|
|
}
|
|
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
return user.NewHumanOTPEmailCheckSucceededEvent(ctx, aggregate, nil)
|
|
}
|
|
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
return user.NewHumanOTPEmailCheckFailedEvent(ctx, aggregate, nil)
|
|
}
|
|
commands, err := checkOTP(
|
|
ctx,
|
|
cmd.sessionWriteModel.UserID,
|
|
code,
|
|
"",
|
|
nil,
|
|
writeModel,
|
|
cmd.eventstore.FilterToQueryReducer,
|
|
cmd.otpAlg,
|
|
nil, // email currently always uses local code checks
|
|
succeededEvent,
|
|
failedEvent,
|
|
)
|
|
if err != nil {
|
|
return commands, err
|
|
}
|
|
cmd.eventCommands = append(cmd.eventCommands, commands...)
|
|
cmd.OTPEmailChecked(ctx, cmd.now())
|
|
return nil, nil
|
|
}
|
|
}
|