mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-01 20:32:19 +00:00
move SetEmail buisiness logic into command
This commit is contained in:
@@ -2,27 +2,36 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||||
grpcContext "github.com/zitadel/zitadel/pkg/grpc/context/v2alpha"
|
grpcContext "github.com/zitadel/zitadel/pkg/grpc/context/v2alpha"
|
||||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
|
user "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) prepareSetEmail(ctx context.Context, req *user.SetEmailRequest) (cmd *command.UserEmail, err error) {
|
func (s *Server) SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp *user.SetEmailResponse, err error) {
|
||||||
|
resourceOwner := authz.GetCtxData(ctx).ResourceOwner
|
||||||
|
|
||||||
|
var email *domain.Email
|
||||||
|
|
||||||
switch v := req.GetVerification().(type) {
|
switch v := req.GetVerification().(type) {
|
||||||
case nil, *user.SetEmailRequest_ReturnCode, *user.SetEmailRequest_IsVerified:
|
|
||||||
break
|
|
||||||
case *user.SetEmailRequest_SendCode:
|
case *user.SetEmailRequest_SendCode:
|
||||||
// test execute the template to ensure it's valid
|
email, err = s.command.ChangeUserEmailURLTemplate(ctx, req.GetUserId(), resourceOwner, req.GetEmail(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||||
err = domain.RenderConfirmURLTemplate(io.Discard, v.SendCode.GetUrlTemplate(), req.UserId, "code", "orgID")
|
case *user.SetEmailRequest_ReturnCode:
|
||||||
|
email, err = s.command.ChangeUserEmailReturnCode(ctx, req.GetUserId(), resourceOwner, req.GetEmail(), s.userCodeAlg)
|
||||||
|
case *user.SetEmailRequest_IsVerified:
|
||||||
|
if v.IsVerified {
|
||||||
|
email, err = s.command.ChangeUserEmailVerified(ctx, req.GetUserId(), resourceOwner, req.GetEmail())
|
||||||
|
} else {
|
||||||
|
email, err = s.command.ChangeUserEmail(ctx, req.GetUserId(), resourceOwner, req.GetEmail(), s.userCodeAlg)
|
||||||
|
}
|
||||||
|
case nil:
|
||||||
|
email, err = s.command.ChangeUserEmail(ctx, req.GetUserId(), resourceOwner, req.GetEmail(), s.userCodeAlg)
|
||||||
default:
|
default:
|
||||||
err = caos_errs.ThrowUnimplementedf(nil, "USERv2-Ahng0", "verification oneOf %T in method SetEmail not implemented", v)
|
err = caos_errs.ThrowUnimplementedf(nil, "USERv2-Ahng0", "verification oneOf %T in method SetEmail not implemented", v)
|
||||||
}
|
}
|
||||||
@@ -30,14 +39,6 @@ func (s *Server) prepareSetEmail(ctx context.Context, req *user.SetEmailRequest)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.command.UserEmail(ctx, req.UserId, authz.GetCtxData(ctx).ResourceOwner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func finalizeSetEmail(ctx context.Context, cmd *command.UserEmail, verificationCode *string) (resp *user.SetEmailResponse, err error) {
|
|
||||||
email, err := cmd.Push(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &user.SetEmailResponse{
|
return &user.SetEmailResponse{
|
||||||
Details: &grpcContext.ObjectDetails{
|
Details: &grpcContext.ObjectDetails{
|
||||||
Sequence: email.Sequence,
|
Sequence: email.Sequence,
|
||||||
@@ -45,39 +46,10 @@ func finalizeSetEmail(ctx context.Context, cmd *command.UserEmail, verificationC
|
|||||||
ChangeDate: timestamppb.New(email.ChangeDate),
|
ChangeDate: timestamppb.New(email.ChangeDate),
|
||||||
ResourceOwner: email.ResourceOwner,
|
ResourceOwner: email.ResourceOwner,
|
||||||
},
|
},
|
||||||
VerificationCode: verificationCode,
|
VerificationCode: email.PlainCode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp *user.SetEmailResponse, err error) {
|
|
||||||
cmd, err := s.prepareSetEmail(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = cmd.Change(ctx, domain.EmailAddress(req.Email)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if req.GetIsVerified() {
|
|
||||||
cmd.SetVerified(ctx)
|
|
||||||
return finalizeSetEmail(ctx, cmd, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
generator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.userCodeAlg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
code, plainTextCode, err := domain.NewEmailCode(generator)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd.AddCode(ctx, code, req.GetSendCode().UrlTemplate)
|
|
||||||
|
|
||||||
if req.GetReturnCode() != nil {
|
|
||||||
return finalizeSetEmail(ctx, cmd, &plainTextCode)
|
|
||||||
}
|
|
||||||
return finalizeSetEmail(ctx, cmd, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) VerifyEmail(context.Context, *user.VerifyEmailRequest) (*user.VerifyEmailResponse, error) {
|
func (s *Server) VerifyEmail(context.Context, *user.VerifyEmailRequest) (*user.VerifyEmailResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method VerifyEmail not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method VerifyEmail not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,33 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newCryptoCodeWithExpiry(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (value *crypto.CryptoValue, expiry time.Duration, err error) {
|
type cryptoCode struct {
|
||||||
|
value *crypto.CryptoValue
|
||||||
|
plain string
|
||||||
|
expiry time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCryptoCodeWithExpiry(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*cryptoCode, error) {
|
||||||
config, err := secretGeneratorConfig(ctx, filter, typ)
|
config, err := secretGeneratorConfig(ctx, filter, typ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, err
|
||||||
|
}
|
||||||
|
code := &cryptoCode{
|
||||||
|
expiry: config.Expiry,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch a := alg.(type) {
|
switch a := alg.(type) {
|
||||||
case crypto.HashAlgorithm:
|
case crypto.HashAlgorithm:
|
||||||
value, _, err = crypto.NewCode(crypto.NewHashGenerator(*config, a))
|
code.value, code.plain, err = crypto.NewCode(crypto.NewHashGenerator(*config, a))
|
||||||
case crypto.EncryptionAlgorithm:
|
case crypto.EncryptionAlgorithm:
|
||||||
value, _, err = crypto.NewCode(crypto.NewEncryptionGenerator(*config, a))
|
code.value, code.plain, err = crypto.NewCode(crypto.NewEncryptionGenerator(*config, a))
|
||||||
default:
|
default:
|
||||||
return nil, -1, errors.ThrowInternal(nil, "COMMA-RreV6", "Errors.Internal")
|
return nil, errors.ThrowInternal(nil, "COMMA-RreV6", "Errors.Internal")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return value, config.Expiry, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCryptoCodeWithPlain(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (value *crypto.CryptoValue, plain string, err error) {
|
func newCryptoCodeWithPlain(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (value *crypto.CryptoValue, plain string, err error) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
@@ -18,6 +17,6 @@ func (e *Email) Validate() error {
|
|||||||
return e.Address.Validate()
|
return e.Address.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
|
func newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*cryptoCode, error) {
|
||||||
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg)
|
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
@@ -14,6 +13,6 @@ type Phone struct {
|
|||||||
Verified bool
|
Verified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
|
func newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*cryptoCode, error) {
|
||||||
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg)
|
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id
|
|||||||
return exists, nil
|
return exists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
|
func newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*cryptoCode, error) {
|
||||||
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeInitCode, alg)
|
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeInitCode, alg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,29 +202,29 @@ func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.Hash
|
|||||||
// email not verified or
|
// email not verified or
|
||||||
// user not registered and password set
|
// user not registered and password set
|
||||||
if human.shouldAddInitCode() {
|
if human.shouldAddInitCode() {
|
||||||
value, expiry, err := newUserInitCode(ctx, filter, codeAlg)
|
userCode, err := newUserInitCode(ctx, filter, codeAlg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cmds = append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
|
cmds = append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, userCode.value, userCode.expiry))
|
||||||
} else {
|
} else {
|
||||||
if !human.Email.Verified {
|
if !human.Email.Verified {
|
||||||
value, expiry, err := newEmailCode(ctx, filter, codeAlg)
|
emailCode, err := newEmailCode(ctx, filter, codeAlg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cmds = append(cmds, user.NewHumanEmailCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
|
cmds = append(cmds, user.NewHumanEmailCodeAddedEvent(ctx, &a.Aggregate, emailCode.value, emailCode.expiry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if human.Phone.Verified {
|
if human.Phone.Verified {
|
||||||
cmds = append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &a.Aggregate))
|
cmds = append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &a.Aggregate))
|
||||||
} else if human.Phone.Number != "" {
|
} else if human.Phone.Number != "" {
|
||||||
value, expiry, err := newPhoneCode(ctx, filter, codeAlg)
|
phoneCode, err := newPhoneCode(ctx, filter, codeAlg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cmds = append(cmds, user.NewHumanPhoneCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
|
cmds = append(cmds, user.NewHumanPhoneCodeAddedEvent(ctx, &a.Aggregate, phoneCode.value, phoneCode.expiry))
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmds, nil
|
return cmds, nil
|
||||||
|
|||||||
@@ -2,21 +2,82 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserEmail struct {
|
// ChangeUserEmail sets a user's email address, generates a code
|
||||||
|
// and triggers a notification e-mail with the default confirmation URL format.
|
||||||
|
func (c *Commands) ChangeUserEmail(ctx context.Context, userID, resourceOwner, email string, alg crypto.EncryptionAlgorithm) (*domain.Email, error) {
|
||||||
|
return c.changeUserEmail(ctx, userID, resourceOwner, email, alg, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeUserEmailURLTemplate sets a user's email address, generates a code
|
||||||
|
// and triggers a notification e-mail with the confirmation URL rendered from the passed urlTmpl.
|
||||||
|
// urlTmpl must be a valid [tmpl.Template].
|
||||||
|
func (c *Commands) ChangeUserEmailURLTemplate(ctx context.Context, userID, resourceOwner, email string, alg crypto.EncryptionAlgorithm, urlTmpl string) (*domain.Email, error) {
|
||||||
|
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTmpl, userID, "code", "orgID"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.changeUserEmail(ctx, userID, resourceOwner, email, alg, false, &urlTmpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeUserEmailReturnCode sets a user's email address, generates a code and does not send a notification email.
|
||||||
|
// The generated plain text code will be set in the returned Email object.
|
||||||
|
func (c *Commands) ChangeUserEmailReturnCode(ctx context.Context, userID, resourceOwner, email string, alg crypto.EncryptionAlgorithm) (*domain.Email, error) {
|
||||||
|
return c.changeUserEmail(ctx, userID, resourceOwner, email, alg, true, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeUserEmailVerified sets a user's email address and marks it is verified.
|
||||||
|
// No code is generated and no confirmation e-mail is send.
|
||||||
|
func (c *Commands) ChangeUserEmailVerified(ctx context.Context, userID, resourceOwner, email string) (*domain.Email, error) {
|
||||||
|
return c.changeUserEmail(ctx, userID, resourceOwner, email, nil, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// changeUserEmail set a user's email address.
|
||||||
|
// When alg is nil, the email address is set as verified and the remainder of options are ignored.
|
||||||
|
// returnCode controls if the plain text version of the code will be set in the return object.
|
||||||
|
// When the plain text code is returned, no notification e-mail will be send to the user.
|
||||||
|
// urlTmpl allows changing the target URL that is used by the e-mail annd should be a valid Go template.
|
||||||
|
func (c *Commands) changeUserEmail(ctx context.Context, userID, resourceOwner, email string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl *string) (*domain.Email, error) {
|
||||||
|
cmd, err := c.NewUserEmailEvents(ctx, userID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = cmd.Change(ctx, domain.EmailAddress(email)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alg == nil {
|
||||||
|
cmd.SetVerified(ctx)
|
||||||
|
return cmd.Push(ctx)
|
||||||
|
}
|
||||||
|
if err = cmd.AddGeneratedCode(ctx, alg, urlTmpl, returnCode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Push(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserEmailEvents allows step-by-step additions of events,
|
||||||
|
// operating on the Human Email Model.
|
||||||
|
type UserEmailEvents struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
aggregate *eventstore.Aggregate
|
aggregate *eventstore.Aggregate
|
||||||
events []eventstore.Command
|
events []eventstore.Command
|
||||||
model *HumanEmailWriteModel
|
model *HumanEmailWriteModel
|
||||||
|
|
||||||
|
plainCode *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) UserEmail(ctx context.Context, userID, resourceOwner string) (*UserEmail, error) {
|
// NewUserEmailEvents constructs a UserEmailEvents with a Human Email Write Model,
|
||||||
|
// filtered by userID and resourceOwner.
|
||||||
|
// If a model cannot be found, or it's state is invalid and error is returned.
|
||||||
|
func (c *Commands) NewUserEmailEvents(ctx context.Context, userID, resourceOwner string) (*UserEmailEvents, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-0Gzs3", "Errors.User.Email.IDMissing")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-0Gzs3", "Errors.User.Email.IDMissing")
|
||||||
}
|
}
|
||||||
@@ -31,14 +92,16 @@ func (c *Commands) UserEmail(ctx context.Context, userID, resourceOwner string)
|
|||||||
if model.UserState == domain.UserStateInitial {
|
if model.UserState == domain.UserStateInitial {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J8dsk", "Errors.User.NotInitialised")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J8dsk", "Errors.User.NotInitialised")
|
||||||
}
|
}
|
||||||
return &UserEmail{
|
return &UserEmailEvents{
|
||||||
eventstore: c.eventstore,
|
eventstore: c.eventstore,
|
||||||
aggregate: UserAggregateFromWriteModel(&model.WriteModel),
|
aggregate: UserAggregateFromWriteModel(&model.WriteModel),
|
||||||
model: model,
|
model: model,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserEmail) Change(ctx context.Context, email domain.EmailAddress) error {
|
// Change sets a new email address.
|
||||||
|
// The generated event unsets any previously generated code and verified flag.
|
||||||
|
func (c *UserEmailEvents) Change(ctx context.Context, email domain.EmailAddress) error {
|
||||||
if err := email.Validate(); err != nil {
|
if err := email.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -50,15 +113,27 @@ func (c *UserEmail) Change(ctx context.Context, email domain.EmailAddress) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserEmail) SetVerified(ctx context.Context) {
|
// SetVerified sets the email address to verified.
|
||||||
|
func (c *UserEmailEvents) SetVerified(ctx context.Context) {
|
||||||
c.events = append(c.events, user.NewHumanEmailVerifiedEvent(ctx, c.aggregate))
|
c.events = append(c.events, user.NewHumanEmailVerifiedEvent(ctx, c.aggregate))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserEmail) AddCode(ctx context.Context, code *domain.EmailCode, urlTmpl *string) {
|
// AddGeneratedCode generates a new encrypted code and sets it to the email address.
|
||||||
c.events = append(c.events, user.NewHumanEmailCodeAddedEventV2(ctx, c.aggregate, code.Code, code.Expiry, urlTmpl))
|
// When returnCode a plain text of the code will be returned from Push.
|
||||||
|
func (c *UserEmailEvents) AddGeneratedCode(ctx context.Context, alg crypto.EncryptionAlgorithm, urlTmpl *string, returnCode bool) error {
|
||||||
|
code, err := newEmailCode(ctx, c.eventstore.Filter, alg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.events = append(c.events, user.NewHumanEmailCodeAddedEventV2(ctx, c.aggregate, code.value, code.expiry, urlTmpl, returnCode))
|
||||||
|
if returnCode {
|
||||||
|
c.plainCode = &code.plain
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserEmail) Push(ctx context.Context) (*domain.Email, error) {
|
// Push all events to the eventstore and Reduce them into the Model.
|
||||||
|
func (c *UserEmailEvents) Push(ctx context.Context) (*domain.Email, error) {
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, c.events...)
|
pushedEvents, err := c.eventstore.Push(ctx, c.events...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -67,5 +142,8 @@ func (c *UserEmail) Push(ctx context.Context) (*domain.Email, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return writeModelToEmail(c.model), nil
|
email := writeModelToEmail(c.model)
|
||||||
|
email.PlainCode = c.plainCode
|
||||||
|
|
||||||
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type Email struct {
|
|||||||
|
|
||||||
EmailAddress EmailAddress
|
EmailAddress EmailAddress
|
||||||
IsEmailVerified bool
|
IsEmailVerified bool
|
||||||
|
PlainCode *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailCode struct {
|
type EmailCode struct {
|
||||||
|
|||||||
@@ -182,6 +182,10 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-SWf3g", "reduce.wrong.event.type %s", user.HumanEmailCodeAddedType)
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-SWf3g", "reduce.wrong.event.type %s", user.HumanEmailCodeAddedType)
|
||||||
}
|
}
|
||||||
|
if e.CodeReturned {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
ctx := HandlerContext(event.Aggregate())
|
ctx := HandlerContext(event.Aggregate())
|
||||||
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
||||||
user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType,
|
user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType,
|
||||||
|
|||||||
@@ -122,9 +122,10 @@ func HumanEmailVerificationFailedEventMapper(event *repository.Event) (eventstor
|
|||||||
type HumanEmailCodeAddedEvent struct {
|
type HumanEmailCodeAddedEvent struct {
|
||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
Code *crypto.CryptoValue `json:"code,omitempty"`
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
Expiry time.Duration `json:"expiry,omitempty"`
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
URLTemplate *string `json:"url_template,omitempty"`
|
URLTemplate *string `json:"url_template,omitempty"`
|
||||||
|
CodeReturned bool `json:"code_returned,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *HumanEmailCodeAddedEvent) Data() interface{} {
|
func (e *HumanEmailCodeAddedEvent) Data() interface{} {
|
||||||
@@ -141,7 +142,7 @@ func NewHumanEmailCodeAddedEvent(
|
|||||||
code *crypto.CryptoValue,
|
code *crypto.CryptoValue,
|
||||||
expiry time.Duration,
|
expiry time.Duration,
|
||||||
) *HumanEmailCodeAddedEvent {
|
) *HumanEmailCodeAddedEvent {
|
||||||
return NewHumanEmailCodeAddedEventV2(ctx, aggregate, code, expiry, nil)
|
return NewHumanEmailCodeAddedEventV2(ctx, aggregate, code, expiry, nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHumanEmailCodeAddedEventV2(
|
func NewHumanEmailCodeAddedEventV2(
|
||||||
@@ -149,7 +150,9 @@ func NewHumanEmailCodeAddedEventV2(
|
|||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
code *crypto.CryptoValue,
|
code *crypto.CryptoValue,
|
||||||
expiry time.Duration,
|
expiry time.Duration,
|
||||||
urlTemplate *string) *HumanEmailCodeAddedEvent {
|
urlTemplate *string,
|
||||||
|
codeReturned bool,
|
||||||
|
) *HumanEmailCodeAddedEvent {
|
||||||
return &HumanEmailCodeAddedEvent{
|
return &HumanEmailCodeAddedEvent{
|
||||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
Reference in New Issue
Block a user