feat: pass and handle auth request context for email links (#7815)

* pass and handle auth request context

* tests and cleanup

* cleanup
This commit is contained in:
Livio Spring 2024-04-24 17:50:58 +02:00 committed by GitHub
parent ac985e2dfb
commit d016379e2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 851 additions and 2018 deletions

View File

@ -18,6 +18,7 @@ import (
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/hook"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
@ -69,6 +70,7 @@ func MustNewConfig(v *viper.Viper) *Config {
hook.EnumHookFunc(authz.MemberTypeString),
actions.HTTPConfigDecodeHook,
hooks.MapTypeStringDecode[string, *authz.SystemAPIUser],
hooks.MapTypeStringDecode[string, crypto.HasherConfig],
hooks.SliceTypeStringDecode[authz.RoleMapping],
)),
)

View File

@ -65,7 +65,7 @@ func (s *Server) ResendMyEmailVerification(ctx context.Context, _ *auth_pb.Resen
if err != nil {
return nil, err
}
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner, emailCodeGenerator)
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner, emailCodeGenerator, "")
if err != nil {
return nil, err
}

View File

@ -473,7 +473,7 @@ func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.Res
if err != nil {
return nil, err
}
details, err := s.command.ResendInitialMail(ctx, req.UserId, domain.EmailAddress(req.Email), authz.GetCtxData(ctx).OrgID, initCodeGenerator)
details, err := s.command.ResendInitialMail(ctx, req.UserId, domain.EmailAddress(req.Email), authz.GetCtxData(ctx).OrgID, initCodeGenerator, "")
if err != nil {
return nil, err
}
@ -487,7 +487,7 @@ func (s *Server) ResendHumanEmailVerification(ctx context.Context, req *mgmt_pb.
if err != nil {
return nil, err
}
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, emailCodeGenerator)
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, emailCodeGenerator, "")
if err != nil {
return nil, err
}
@ -590,7 +590,7 @@ func (s *Server) SendHumanResetPasswordNotification(ctx context.Context, req *mg
if err != nil {
return nil, err
}
objectDetails, err := s.command.RequestSetPassword(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, notifyTypeToDomain(req.Type), passwordCodeGenerator)
objectDetails, err := s.command.RequestSetPassword(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, notifyTypeToDomain(req.Type), passwordCodeGenerator, "")
if err != nil {
return nil, err
}

View File

@ -3,6 +3,8 @@ package login
import (
"net/http"
"github.com/zitadel/logging"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
)
@ -33,3 +35,23 @@ func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*
func (l *Login) getParseData(r *http.Request, data interface{}) error {
return l.parser.Parse(r, data)
}
// checkOptionalAuthRequestOfEmailLinks tries to get the [domain.AuthRequest] from the request.
// In case any error occurs, e.g. if the user agent does not correspond, the `authRequestID` query parameter will be
// removed from the request URL and form to ensure subsequent functions and pages do not use it.
// This function is used for handling links in emails, which could possibly be opened on another device than the
// auth request was initiated.
func (l *Login) checkOptionalAuthRequestOfEmailLinks(r *http.Request) *domain.AuthRequest {
authReq, err := l.getAuthRequest(r)
if err == nil {
return authReq
}
logging.WithError(err).Infof("authrequest could not be found for email link on path %s", r.URL.RequestURI())
queries := r.URL.Query()
queries.Del(QueryAuthRequestID)
r.URL.RawQuery = queries.Encode()
r.RequestURI = r.URL.RequestURI()
r.Form.Del(QueryAuthRequestID)
r.PostForm.Del(QueryAuthRequestID)
return nil
}

View File

@ -1,8 +1,8 @@
package login
import (
"fmt"
"net/http"
"net/url"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
@ -38,14 +38,20 @@ type initPasswordData struct {
HasSymbol string
}
func InitPasswordLink(origin, userID, code, orgID string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointInitPassword, userID, code, orgID)
func InitPasswordLink(origin, userID, code, orgID, authRequestID string) string {
v := url.Values{}
v.Set(queryInitPWUserID, userID)
v.Set(queryInitPWCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInitPassword + "?" + v.Encode()
}
func (l *Login) handleInitPassword(w http.ResponseWriter, r *http.Request) {
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
userID := r.FormValue(queryInitPWUserID)
code := r.FormValue(queryInitPWCode)
l.renderInitPassword(w, r, nil, userID, code, nil)
l.renderInitPassword(w, r, authReq, userID, code, nil)
}
func (l *Login) handleInitPasswordCheck(w http.ResponseWriter, r *http.Request) {
@ -94,7 +100,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
l.renderInitPassword(w, r, authReq, userID, "", err)
return
}
_, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), userID, userOrg, domain.NotificationTypeEmail, passwordCodeGenerator)
_, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), userID, userOrg, domain.NotificationTypeEmail, passwordCodeGenerator, authReq.ID)
l.renderInitPassword(w, r, authReq, userID, "", err)
}

View File

@ -1,8 +1,8 @@
package login
import (
"fmt"
"net/http"
"net/url"
"strconv"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
@ -44,16 +44,24 @@ type initUserData struct {
HasSymbol string
}
func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet bool) string {
return fmt.Sprintf("%s%s?userID=%s&loginname=%s&code=%s&orgID=%s&passwordset=%t", externalLink(origin), EndpointInitUser, userID, loginName, code, orgID, passwordSet)
func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet bool, authRequestID string) string {
v := url.Values{}
v.Set(queryInitUserUserID, userID)
v.Set(queryInitUserLoginName, loginName)
v.Set(queryInitUserCode, code)
v.Set(queryOrgID, orgID)
v.Set(queryInitUserPassword, strconv.FormatBool(passwordSet))
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInitUser + "?" + v.Encode()
}
func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) {
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
userID := r.FormValue(queryInitUserUserID)
code := r.FormValue(queryInitUserCode)
loginName := r.FormValue(queryInitUserLoginName)
passwordSet, _ := strconv.ParseBool(r.FormValue(queryInitUserPassword))
l.renderInitUser(w, r, nil, userID, loginName, code, passwordSet, nil)
l.renderInitUser(w, r, authReq, userID, loginName, code, passwordSet, nil)
}
func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) {
@ -105,7 +113,7 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *
l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err)
return
}
_, err = l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID, initCodeGenerator)
_, err = l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID, initCodeGenerator, authReq.ID)
l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err)
}

View File

@ -1,8 +1,8 @@
package login
import (
"fmt"
"net/http"
"net/url"
"github.com/zitadel/zitadel/internal/domain"
)
@ -27,18 +27,24 @@ type mailVerificationData struct {
UserID string
}
func MailVerificationLink(origin, userID, code, orgID string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointMailVerification, userID, code, orgID)
func MailVerificationLink(origin, userID, code, orgID, authRequestID string) string {
v := url.Values{}
v.Set(queryUserID, userID)
v.Set(queryCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointMailVerification + "?" + v.Encode()
}
func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) {
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
userID := r.FormValue(queryUserID)
code := r.FormValue(queryCode)
if code != "" {
l.checkMailCode(w, r, nil, userID, code)
l.checkMailCode(w, r, authReq, userID, code)
return
}
l.renderMailVerification(w, r, nil, userID, nil)
l.renderMailVerification(w, r, authReq, userID, nil)
}
func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Request) {
@ -61,7 +67,7 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque
l.checkMailCode(w, r, authReq, data.UserID, data.Code)
return
}
_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator)
_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator, authReq.ID)
l.renderMailVerification(w, r, authReq, data.UserID, err)
}

View File

@ -33,7 +33,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
l.renderPasswordResetDone(w, r, authReq, err)
return
}
_, err = l.command.RequestSetPassword(setContext(r.Context(), authReq.UserOrgID), user.ID, authReq.UserOrgID, domain.NotificationTypeEmail, passwordCodeGenerator)
_, err = l.command.RequestSetPassword(setContext(r.Context(), authReq.UserOrgID), user.ID, authReq.UserOrgID, domain.NotificationTypeEmail, passwordCodeGenerator, authReq.ID)
l.renderPasswordResetDone(w, r, authReq, err)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -67,22 +68,6 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
if authRequest != nil && authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != resourceOwner {
resourceOwner = authRequest.RequestedOrgID
}
initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.userCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.userCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.userCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
// For consistency with the external authentication flow,
// the setMetadata() function is provided on the pre creation hook, for now,
// like for the ExternalAuthentication flow.
@ -96,22 +81,14 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
l.renderRegister(w, r, authRequest, data, err)
return
}
user, err = l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, nil, nil, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
human := command.AddHumanFromDomain(user, metadatas, authRequest, nil)
err = l.command.AddUserHuman(setContext(r.Context(), resourceOwner), resourceOwner, human, true, l.userCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
if len(metadatas) > 0 {
_, err = l.command.BulkSetUserMetadata(r.Context(), user.AggregateID, resourceOwner, metadatas...)
if err != nil {
// TODO: What if action is configured to be allowed to fail? Same question for external registration.
l.renderRegister(w, r, authRequest, data, err)
return
}
}
userGrants, err := l.runPostCreationActions(user.AggregateID, authRequest, r, resourceOwner, domain.FlowTypeInternalAuthentication)
userGrants, err := l.runPostCreationActions(human.ID, authRequest, r, resourceOwner, domain.FlowTypeInternalAuthentication)
if err != nil {
l.renderError(w, r, authRequest, err)
return
@ -128,7 +105,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.SelectUser(r.Context(), authRequest.ID, user.AggregateID, userAgentID)
err = l.authRepo.SelectUser(r.Context(), authRequest.ID, human.ID, userAgentID)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return

View File

@ -543,23 +543,19 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
if err != nil {
return err
}
initCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, repo.UserCodeAlg)
addMetadata := make([]*command.AddMetadataEntry, len(metadatas))
for i, metadata := range metadatas {
addMetadata[i] = &command.AddMetadataEntry{
Key: metadata.Key,
Value: metadata.Value,
}
}
human := command.AddHumanFromDomain(registerUser, metadatas, request, externalIDP)
err = repo.Command.AddUserHuman(ctx, resourceOwner, human, true, repo.UserCodeAlg)
if err != nil {
return err
}
emailCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, repo.UserCodeAlg)
if err != nil {
return err
}
phoneCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, repo.UserCodeAlg)
if err != nil {
return err
}
human, err := repo.Command.RegisterHuman(ctx, resourceOwner, registerUser, externalIDP, orgMemberRoles, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil {
return err
}
request.SetUserInfo(human.AggregateID, human.Username, human.PreferredLoginName, human.DisplayName, "", human.ResourceOwner)
request.SetUserInfo(human.ID, human.Username, human.Username, human.DisplayName, "", resourceOwner)
request.SelectedIDPConfigID = externalIDP.IDPConfigID
request.LinkingUsers = nil
err = repo.Command.UserIDPLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))

View File

@ -160,6 +160,10 @@ func (s *UserSession) Reducers() []handler.AggregateReducer {
Event: user.UserRemovedType,
Reduce: s.Reduce,
},
{
Event: user.HumanRegisteredType,
Reduce: s.Reduce,
},
},
},
{
@ -234,6 +238,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
handler.NewCol("multi_factor_verification_type", domain.MFALevelNotSetUp),
handler.NewCol("external_login_verification", time.Time{}),
handler.NewCol("state", domain.UserSessionStateTerminated),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -247,16 +253,30 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil {
return nil, err
}
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol("password_verification", time.Time{}),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID),
handler.Not(handler.NewCond("user_agent_id", userAgent)),
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)),
},
return handler.NewMultiStatement(event,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol("password_verification", event.CreatedAt()),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID),
handler.NewCond("user_agent_id", userAgent),
}),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol("password_verification", time.Time{}),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID),
handler.Not(handler.NewCond("user_agent_id", userAgent)),
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)),
}),
), nil
case user.UserV1MFAOTPRemovedType,
user.HumanMFAOTPRemovedType,
@ -264,6 +284,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewUpdateStatement(event,
[]handler.Column{
handler.NewCol("second_factor_verification", time.Time{}),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -277,6 +299,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
[]handler.Column{
handler.NewCol("external_login_verification", time.Time{}),
handler.NewCol("selected_idp_config_id", ""),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -289,6 +313,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
[]handler.Column{
handler.NewCol("passwordless_verification", time.Time{}),
handler.NewCol("multi_factor_verification", time.Time{}),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -300,6 +326,23 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
return u.view.DeleteUserSessions(event.Aggregate().ID, event.Aggregate().InstanceID)
}), nil
case user.HumanRegisteredType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
eventData, err := view_model.UserSessionFromEvent(event)
if err != nil {
return err
}
session := &view_model.UserSessionView{
CreationDate: event.CreatedAt(),
ResourceOwner: event.Aggregate().ResourceOwner,
UserAgentID: eventData.UserAgentID,
UserID: event.Aggregate().ID,
State: int32(domain.UserSessionStateActive),
InstanceID: event.Aggregate().InstanceID,
PasswordVerification: event.CreatedAt(),
}
return u.updateSession(session, event)
}), nil
case instance.InstanceRemovedEventType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
return u.view.DeleteInstanceUserSessions(event.Aggregate().InstanceID)

View File

@ -1427,6 +1427,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
Crypted: []byte("userinit"),
},
1*time.Hour,
"",
),
),
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),

View File

@ -10,7 +10,6 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
@ -57,7 +56,13 @@ type AddHuman struct {
Passwordless bool
ExternalIDP bool
Register bool
Metadata []*AddMetadataEntry
// UserAgentID is optional and can be passed in case the user registered themselves.
// This will be used in the login UI to handle authentication automatically.
UserAgentID string
// AuthRequestID is optional and can be passed in case the user registered themselves.
// This will be used to pass the information in notifications for links to the login UI.
AuthRequestID string
Metadata []*AddMetadataEntry
// Links are optional
Links []*AddLink
@ -200,6 +205,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
human.Gender,
human.Email.Address,
domainPolicy.UserLoginMustBeDomain,
"", // no user agent id available
)
} else {
createCmd = user.NewHumanAddedEvent(
@ -272,7 +278,7 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation.
if err != nil {
return nil, err
}
return append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, initCode.Crypted, initCode.Expiry)), nil
return append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, initCode.Crypted, initCode.Expiry, human.AuthRequestID)), nil
}
if !human.Email.Verified {
emailCode, err := c.newEmailCode(ctx, filter, codeAlg)
@ -460,61 +466,6 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
return writeModelToHuman(addedHuman), passwordlessCode, nil
}
// Deprecated: use commands.AddUserHuman
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
if orgID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
}
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
loginPolicy, err := c.getOrgLoginPolicy(ctx, orgID)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-Dfg3g", "Errors.Org.LoginPolicy.NotFound")
}
// check only if local registration is allowed, the idp will be checked separately
if !loginPolicy.AllowRegister && link == nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
}
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, err
}
orgMemberWriteModel := NewOrgMemberWriteModel(orgID, registeredHuman.AggregateID)
orgAgg := OrgAggregateFromWriteModel(&orgMemberWriteModel.WriteModel)
if len(orgMemberRoles) > 0 {
orgMember := &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: orgID,
},
UserID: human.AggregateID,
Roles: orgMemberRoles,
}
memberEvent, err := c.addOrgMember(ctx, orgAgg, orgMemberWriteModel, orgMember)
if err != nil {
return nil, err
}
userEvents = append(userEvents, memberEvent)
}
pushedEvents, err := c.eventstore.Push(ctx, userEvents...)
if err != nil {
return nil, err
}
err = AppendAndReduce(registeredHuman, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToHuman(registeredHuman), nil
}
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
if orgID == "" {
return nil, nil, nil, "", zerrors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.Org.Empty")
@ -522,7 +473,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
if err := human.Normalize(); err != nil {
return nil, nil, nil, "", err
}
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, nil, nil, "", err
}
@ -537,33 +488,8 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
}
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if human == nil {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-JKefw", "Errors.User.Invalid")
}
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
human.Username = string(human.EmailAddress)
}
if orgID == "" {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hYsVH", "Errors.Org.Empty")
}
if err := human.Normalize(); err != nil {
return nil, nil, err
}
if link == nil && (human.Password == nil || human.Password.SecretString == "") {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-X23na", "Errors.User.Password.Empty")
}
if human.Password != nil && human.Password.SecretString != "" {
human.Password.ChangeRequired = false
}
var links []*domain.UserIDPLink
if link != nil {
links = append(links, link)
}
return c.createHuman(ctx, orgID, human, links, true, false, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
//nolint:gocognit
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
return nil, nil, err
}
@ -601,11 +527,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
//TODO: adlerhurst maybe we could simplify the code below
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
if selfregister {
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
} else {
events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
}
events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
for _, link := range links {
event, err := c.addUserIDPLink(ctx, userAgg, link, false)
@ -620,7 +542,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
if err != nil {
return nil, nil, err
}
events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry))
events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry, ""))
} else {
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified {
events = append(events, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
@ -629,7 +551,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
if err != nil {
return nil, nil, err
}
events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry))
events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, ""))
}
}
@ -699,40 +621,6 @@ func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, h
return addEvent
}
func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, human *domain.Human, userLoginMustBeDomain bool) *user.HumanRegisteredEvent {
addEvent := user.NewHumanRegisteredEvent(
ctx,
aggregate,
human.Username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLanguage,
human.Gender,
human.EmailAddress,
userLoginMustBeDomain,
)
if human.Phone != nil {
addEvent.AddPhoneData(human.PhoneNumber)
}
if human.Address != nil {
addEvent.AddAddressData(
human.Country,
human.Locality,
human.PostalCode,
human.Region,
human.StreetAddress)
}
if human.Password != nil {
addEvent.AddPasswordData(human.Password.EncodedSecret, human.Password.ChangeRequired)
}
if human.HashedPassword != "" {
addEvent.AddPasswordData(human.HashedPassword, false)
}
return addEvent
}
func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error {
if agentID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
@ -783,3 +671,53 @@ func humanWriteModelByID(ctx context.Context, filter preparation.FilterToQueryRe
err = humanWriteModel.Reduce()
return humanWriteModel, err
}
func AddHumanFromDomain(user *domain.Human, metadataList []*domain.Metadata, authRequest *domain.AuthRequest, idp *domain.UserIDPLink) *AddHuman {
addMetadata := make([]*AddMetadataEntry, len(metadataList))
for i, metadata := range metadataList {
addMetadata[i] = &AddMetadataEntry{
Key: metadata.Key,
Value: metadata.Value,
}
}
human := new(AddHuman)
if user.Profile != nil {
human.Username = user.Username
human.FirstName = user.FirstName
human.LastName = user.LastName
human.NickName = user.NickName
human.DisplayName = user.DisplayName
human.PreferredLanguage = user.PreferredLanguage
human.Gender = user.Gender
human.Password = user.Password.SecretString
human.Register = true
human.Metadata = addMetadata
human.UserAgentID = authRequest.AgentID
human.AuthRequestID = authRequest.ID
}
if user.Email != nil {
human.Email = Email{
Address: user.EmailAddress,
Verified: user.IsEmailVerified,
}
}
if user.Phone != nil {
human.Phone = Phone{
Number: user.Phone.PhoneNumber,
Verified: user.Phone.IsPhoneVerified,
}
}
if idp != nil {
human.Links = []*AddLink{
{
IDPID: idp.IDPConfigID,
DisplayName: idp.DisplayName,
IDPExternalID: idp.ExternalUserID,
},
}
}
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
human.Username = string(human.Email.Address)
}
return human
}

View File

@ -50,7 +50,7 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email, em
if err != nil {
return nil, err
}
events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry))
events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, ""))
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
@ -99,7 +99,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Gdsgs", "Errors.User.Code.Invalid")
}
func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string, emailCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) {
func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string, emailCodeGenerator crypto.Generator, authRequestID string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
}
@ -122,7 +122,10 @@ func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID,
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry))
if authRequestID == "" {
authRequestID = existingEmail.AuthRequestID
}
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, authRequestID))
if err != nil {
return nil, err
}

View File

@ -19,6 +19,7 @@ type HumanEmailWriteModel struct {
Code *crypto.CryptoValue
CodeCreationDate time.Time
CodeExpiry time.Duration
AuthRequestID string
UserState domain.UserState
}
@ -53,6 +54,7 @@ func (wm *HumanEmailWriteModel) Reduce() error {
wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry
wm.AuthRequestID = e.AuthRequestID
case *user.HumanEmailVerifiedEvent:
wm.IsEmailVerified = true
wm.Code = nil

View File

@ -18,7 +18,7 @@ import (
func TestCommandSide_ChangeHumanEmail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -39,9 +39,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "invalid email, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -59,8 +57,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -81,8 +78,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "user not initialized, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -102,6 +98,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
@ -124,8 +121,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "email not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -161,8 +157,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "verified email changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -215,8 +210,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "email verified, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -265,8 +259,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "email verified, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -315,8 +308,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{
name: "email changed with code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -347,6 +339,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -376,7 +369,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email, tt.args.secretGenerator)
if tt.res.err == nil {
@ -394,7 +387,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
func TestCommandSide_VerifyHumanEmail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -416,9 +409,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -432,9 +423,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{
name: "code missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -448,8 +437,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -466,8 +454,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{
name: "code not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -499,8 +486,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{
name: "invalid code, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -526,6 +512,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -550,8 +537,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{
name: "valid code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -577,6 +563,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -604,7 +591,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
got, err := r.VerifyHumanEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil {
@ -622,13 +609,14 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
userID string
resourceOwner string
secretGenerator crypto.Generator
authRequestID string
}
type res struct {
want *domain.ObjectDetails
@ -643,9 +631,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -658,8 +644,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -675,8 +660,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{
name: "user not initialized, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -696,6 +680,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
@ -713,8 +698,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{
name: "email already verified, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -750,8 +734,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{
name: "new code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -789,6 +772,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -805,13 +789,72 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
},
},
},
{
name: "new code with authRequestID, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"email2@test.ch",
),
),
),
expectPush(
user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator)
got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator, tt.args.authRequestID)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -827,7 +870,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -846,9 +889,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -861,8 +902,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -878,8 +918,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
{
name: "code sent, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -925,7 +964,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
err := r.HumanEmailVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil {

View File

@ -13,7 +13,7 @@ import (
)
// ResendInitialMail resend initial mail and changes email if provided
func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email domain.EmailAddress, resourceOwner string, initCodeGenerator crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email domain.EmailAddress, resourceOwner string, initCodeGenerator crypto.Generator, authRequestID string) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
}
@ -38,7 +38,10 @@ func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email d
if err != nil {
return nil, err
}
events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry))
if authRequestID == "" {
authRequestID = existingCode.AuthRequestID
}
events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry, authRequestID))
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err

View File

@ -19,6 +19,7 @@ type HumanInitCodeWriteModel struct {
Code *crypto.CryptoValue
CodeCreationDate time.Time
CodeExpiry time.Duration
AuthRequestID string
UserState domain.UserState
}
@ -50,6 +51,7 @@ func (wm *HumanInitCodeWriteModel) Reduce() error {
wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry
wm.AuthRequestID = e.AuthRequestID
wm.UserState = domain.UserStateInitial
case *user.HumanInitializedCheckSucceededEvent:
wm.Code = nil

View File

@ -18,7 +18,7 @@ import (
func TestCommandSide_ResendInitialMail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -26,6 +26,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
email string
resourceOwner string
secretGenerator crypto.Generator
authRequestID string
}
type res struct {
want *domain.ObjectDetails
@ -40,9 +41,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -55,8 +54,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -72,8 +70,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{
name: "user not initialized, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -107,8 +104,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{
name: "new code email not changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -128,6 +124,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
@ -141,6 +138,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -159,10 +157,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
},
},
{
name: "new code, ok",
name: "new code email not changed with authRequestID, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -182,6 +179,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"authRequestID",
),
),
),
@ -195,6 +193,63 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
email: "email@test.ch",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "new code, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
expectPush(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -212,10 +267,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
},
},
{
name: "new code with change email, ok",
name: "new code with authRequestID, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -235,6 +289,62 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"authRequestID",
),
),
),
expectPush(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "new code with change email, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
@ -252,6 +362,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -273,9 +384,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, domain.EmailAddress(tt.args.email), tt.args.resourceOwner, tt.args.secretGenerator)
got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, domain.EmailAddress(tt.args.email), tt.args.resourceOwner, tt.args.secretGenerator, tt.args.authRequestID)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -291,7 +402,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
func TestCommandSide_VerifyInitCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher
}
type args struct {
@ -316,9 +427,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -332,9 +441,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "code missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -348,8 +455,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -366,8 +472,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "code not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -399,8 +504,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "invalid code, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -426,6 +530,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -450,8 +555,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "valid code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -477,6 +581,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -506,8 +611,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "valid code with password, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -536,6 +640,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -582,8 +687,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{
name: "valid code with password and userAgentID, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -612,6 +716,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"",
),
),
),
@ -660,7 +765,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
}
err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password, tt.args.userAgentID, tt.args.secretGenerator)
@ -676,7 +781,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
func TestCommandSide_InitCodeSent(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -695,9 +800,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -710,8 +813,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -727,8 +829,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
{
name: "code sent, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -763,7 +864,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
err := r.HumanInitCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil {

View File

@ -165,7 +165,7 @@ func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, re
}
// RequestSetPassword generate and send out new code to change password for a specific user
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator, authRequestID string) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing")
}
@ -185,7 +185,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPasswordCodeAddedEvent(ctx, userAgg, passwordCode.Code, passwordCode.Expiry, notifyType))
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPasswordCodeAddedEvent(ctx, userAgg, passwordCode.Code, passwordCode.Expiry, notifyType, authRequestID))
if err != nil {
return nil, err
}

View File

@ -22,7 +22,7 @@ import (
func TestCommandSide_SetOneTimePassword(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher
checkPermission domain.PermissionCheck
}
@ -46,9 +46,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -61,8 +59,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -78,8 +75,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{
name: "missing permission, error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -121,8 +117,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{
name: "change password onetime, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -184,8 +179,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{
name: "change password no one time, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -248,7 +242,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
checkPermission: tt.fields.checkPermission,
}
@ -268,7 +262,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
userPasswordHasher *crypto.Hasher
}
@ -293,9 +287,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -308,9 +300,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{
name: "password missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -324,8 +314,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -342,8 +331,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{
name: "code not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -376,8 +364,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{
name: "invalid code, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -404,6 +391,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
},
time.Hour*1,
domain.NotificationTypeEmail,
"",
),
),
),
@ -424,8 +412,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{
name: "set password, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -457,6 +444,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
},
time.Hour*1,
domain.NotificationTypeEmail,
"",
),
),
),
@ -500,8 +488,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{
name: "set password with userAgentID, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -533,6 +520,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
},
time.Hour*1,
domain.NotificationTypeEmail,
"",
),
),
),
@ -578,7 +566,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.userEncryption,
}
@ -915,7 +903,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
func TestCommandSide_RequestSetPassword(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -923,6 +911,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
resourceOwner string
notifyType domain.NotificationType
secretGenerator crypto.Generator
authRequestID string
}
type res struct {
want *domain.ObjectDetails
@ -937,9 +926,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -952,8 +939,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -969,8 +955,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{
name: "user initial, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -990,6 +975,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
eventFromEventPusher(
@ -1018,8 +1004,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{
name: "new code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1055,6 +1040,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
},
time.Hour*1,
domain.NotificationTypeEmail,
"",
),
),
),
@ -1071,13 +1057,70 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
},
},
},
{
name: "new code with authRequestID, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanInitializedCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate)),
),
expectPush(
user.NewHumanPasswordCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
domain.NotificationTypeEmail,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
got, err := r.RequestSetPassword(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.notifyType, tt.args.secretGenerator)
got, err := r.RequestSetPassword(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.notifyType, tt.args.secretGenerator, tt.args.authRequestID)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -1093,7 +1136,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
func TestCommandSide_PasswordCodeSent(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -1112,9 +1155,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -1127,8 +1168,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -1144,8 +1184,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
{
name: "code sent, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1186,7 +1225,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
err := r.PasswordCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil {
@ -1201,7 +1240,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
func TestCommandSide_CheckPassword(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher
}
type args struct {
@ -1224,9 +1263,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -1240,9 +1277,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "password missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -1256,8 +1291,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "login policy not found, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
expectFilter(),
),
@ -1275,8 +1309,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "login policy login password not allowed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1316,8 +1349,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1358,8 +1390,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "user locked, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1420,8 +1451,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "existing password empty, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1478,8 +1508,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "password not matching lockout policy not relevant, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1562,8 +1591,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "password not matching, max password attempts reached - user locked, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1653,8 +1681,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "check password, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1734,8 +1761,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "check password, ok, updated hash",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1820,8 +1846,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "check password ok, locked in the mean time",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1900,8 +1925,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{
name: "regression test old version event",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
@ -1996,7 +2020,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
}
err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq, tt.args.lockoutPolicy)

File diff suppressed because it is too large Load Diff

View File

@ -1797,6 +1797,7 @@ func TestExistsUser(t *testing.T) {
domain.GenderFemale,
"support@zitadel.com",
true,
"userAgentID",
),
}, nil
},

View File

@ -1838,7 +1838,7 @@ func TestCommands_verifyUserEmailWithGenerator(t *testing.T) {
func TestCommands_NewUserEmailEvents(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
userID string
@ -1852,7 +1852,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
{
name: "missing userID",
fields: fields{
eventstore: eventstoreExpect(t),
eventstore: expectEventstore(),
},
args: args{
userID: "",
@ -1862,7 +1862,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
{
name: "not found",
fields: fields{
eventstore: eventstoreExpect(t, expectFilter()),
eventstore: expectEventstore(expectFilter()),
},
args: args{
userID: "user1",
@ -1872,8 +1872,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
{
name: "user not initialized",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1893,6 +1892,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
@ -1907,7 +1907,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
_, err := c.NewUserEmailEvents(context.Background(), tt.args.userID)
require.ErrorIs(t, err, tt.wantErr)

View File

@ -131,8 +131,10 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-7yiox1isql", "Errors.User.AlreadyExisting")
}
// check for permission to create user on resourceOwner
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
return err
if !human.Register {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
return err
}
}
// add resourceowner for the events with the aggregate
existingHuman.ResourceOwner = resourceOwner
@ -159,6 +161,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
human.Gender,
human.Email.Address,
domainPolicy.UserLoginMustBeDomain,
human.UserAgentID,
)
} else {
createCmd = user.NewHumanAddedEvent(

View File

@ -232,6 +232,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
domain.GenderUnspecified,
"email@test.ch",
true,
"userAgentID",
),
user.NewHumanInitialCodeAddedEvent(context.Background(),
&userAgg.Aggregate,
@ -242,6 +243,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"),
},
time.Hour*1,
"authRequestID",
),
),
),
@ -261,6 +263,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
},
PreferredLanguage: language.English,
Register: true,
UserAgentID: "userAgentID",
AuthRequestID: "authRequestID",
},
secretGenerator: GetMockSecretGenerator(t),
allowInitMail: true,
@ -344,6 +348,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"),
},
time.Hour*1,
"",
),
),
),
@ -414,6 +419,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"),
},
1*time.Hour,
"",
),
),
),
@ -1031,6 +1037,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"),
},
1*time.Hour,
"",
),
user.NewHumanPhoneVerifiedEvent(
context.Background(),
@ -1174,6 +1181,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"),
},
1*time.Hour,
"",
),
user.NewMetadataSetEvent(
context.Background(),
@ -1993,6 +2001,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&userAgg.Aggregate,
nil, time.Hour*1,
"",
),
),
),

View File

@ -167,6 +167,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
),
@ -225,6 +226,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
eventFromEventPusher(
@ -280,6 +282,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
eventFromEventPusher(

View File

@ -10,6 +10,7 @@ import (
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@ -69,7 +70,7 @@ func TestCommands_RequestPasswordReset(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
),
),
),
@ -167,7 +168,7 @@ func TestCommands_RequestPasswordResetReturnCode(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
),
),
),
@ -279,7 +280,7 @@ func TestCommands_RequestPasswordResetURLTemplate(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
),
),
),
@ -390,7 +391,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
),
),
),

View File

@ -18,7 +18,7 @@ import (
func TestCommandSide_LockUserV2(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type (
@ -40,9 +40,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
@ -58,8 +56,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
@ -77,8 +74,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{
name: "user already locked, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -116,8 +112,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{
name: "user already locked, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -151,8 +146,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{
name: "lock user, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -190,8 +184,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{
name: "lock user, no permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -224,8 +217,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{
name: "lock user machine, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -260,7 +252,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.LockUserV2(tt.args.ctx, tt.args.userID)
@ -279,7 +271,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
func TestCommandSide_UnlockUserV2(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type (
@ -301,9 +293,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
@ -319,8 +309,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
@ -338,8 +327,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{
name: "user already active, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -372,8 +360,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{
name: "user already active, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@ -400,8 +387,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{
name: "unlock user, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -443,8 +429,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{
name: "unlock user, no permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -481,8 +466,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{
name: "unlock user machine, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -521,7 +505,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.UnlockUserV2(tt.args.ctx, tt.args.userID)
@ -540,7 +524,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
func TestCommandSide_DeactivateUserV2(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type (
@ -562,9 +546,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
@ -580,8 +562,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
@ -599,8 +580,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "user initial, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -620,6 +600,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
@ -639,8 +620,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "user already inactive, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -678,8 +658,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "deactivate user, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -722,8 +701,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "deactivate user, no permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -761,8 +739,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "user machine already inactive, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -796,8 +773,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{
name: "deactivate user machine, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -832,7 +808,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.DeactivateUserV2(tt.args.ctx, tt.args.userID)
@ -851,7 +827,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
func TestCommandSide_ReactivateUserV2(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type (
@ -873,9 +849,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
@ -891,8 +865,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
@ -910,8 +883,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{
name: "user already active, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -944,8 +916,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{
name: "user machine already active, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -974,8 +945,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{
name: "reactivate user, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1017,8 +987,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{
name: "reactivate user, no permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1055,8 +1024,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{
name: "reactivate user machine, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -1095,7 +1063,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.ReactivateUserV2(tt.args.ctx, tt.args.userID)
@ -1114,7 +1082,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
func TestCommandSide_RemoveUserV2(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type (
@ -1138,9 +1106,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
@ -1156,8 +1122,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
@ -1175,8 +1140,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{
name: "user removed, notfound error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1217,8 +1181,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{
name: "remove user, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1269,8 +1232,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{
name: "remove user, no permission",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -1308,8 +1270,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{
name: "user machine already removed, notfound error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -1346,8 +1307,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{
name: "remove user machine, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
@ -1395,7 +1355,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.RemoveUserV2(tt.args.ctx, tt.args.userID, tt.args.cascadingMemberships, tt.args.grantIDs...)

View File

@ -169,7 +169,7 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta
return err
}
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendUserInitCode(ctx, notifyUser, code)
SendUserInitCode(ctx, notifyUser, code, e.AuthRequestID)
if err != nil {
return err
}
@ -226,7 +226,7 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
return err
}
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendEmailVerificationCode(ctx, notifyUser, code, e.URLTemplate)
SendEmailVerificationCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID)
if err != nil {
return err
}
@ -285,7 +285,7 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler
if e.NotificationType == domain.NotificationTypeSms {
notify = types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, e)
}
err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate)
err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID)
if err != nil {
return err
}

View File

@ -45,6 +45,7 @@ const (
externalSecure = false
externalProtocol = "http"
defaultOTPEmailTemplate = "/otp/verify?loginName={{.LoginName}}&code={{.Code}}"
authRequestID = "authRequestID"
)
func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
@ -128,7 +129,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s/ui/login/user/init?userID=%s&loginname=%s&code=%s&orgID=%s&passwordset=%t", eventOrigin, userID, preferredLoginName, testCode, orgID, false)
expectContent := fmt.Sprintf("%s/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", eventOrigin, "", testCode, preferredLoginName, orgID, false, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
@ -162,7 +163,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?userID=%s&loginname=%s&code=%s&orgID=%s&passwordset=%t", externalProtocol, instancePrimaryDomain, externalPort, userID, preferredLoginName, testCode, orgID, false)
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, preferredLoginName, orgID, false, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
@ -196,6 +197,46 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
},
}, w
},
}, {
name: "button url without event trigger url with authRequestID",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, preferredLoginName, orgID, false, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, testCode)
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(&eventstore.Config{
Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier,
}),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanInitialCodeAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
Code: code,
Expiry: time.Hour,
AuthRequestID: authRequestID,
},
}, w
},
}}
// TODO: Why don't we have an url template on user.HumanInitialCodeAddedEvent?
for _, tt := range tests {
@ -305,7 +346,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s/ui/login/mail/verification?userID=%s&code=%s&orgID=%s", eventOrigin, userID, testCode, orgID)
expectContent := fmt.Sprintf("%s/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
@ -342,7 +383,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?userID=%s&code=%s&orgID=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, testCode, orgID)
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
@ -378,6 +419,48 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
},
}, w
},
}, {
name: "button url without event trigger url with authRequestID",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, testCode)
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(&eventstore.Config{
Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier,
}),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanEmailCodeAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
AuthRequestID: authRequestID,
},
}, w
},
}, {
name: "button url with url template and event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
@ -524,7 +607,7 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s/ui/login/password/init?userID=%s&code=%s&orgID=%s", eventOrigin, userID, testCode, orgID)
expectContent := fmt.Sprintf("%s/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
@ -561,7 +644,7 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?userID=%s&code=%s&orgID=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, testCode, orgID)
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
@ -597,6 +680,48 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
},
}, w
},
}, {
name: "button url without event trigger url with authRequestID",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, testCode)
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(&eventstore.Config{
Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier,
}),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanPasswordCodeAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
AuthRequestID: authRequestID,
},
}, w
},
}, {
name: "button url with url template and event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {

View File

@ -10,10 +10,10 @@ import (
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendEmailVerificationCode(ctx context.Context, user *query.NotifyUser, code string, urlTmpl string) error {
func (notify Notify) SendEmailVerificationCode(ctx context.Context, user *query.NotifyUser, code string, urlTmpl, authRequestID string) error {
var url string
if urlTmpl == "" {
url = login.MailVerificationLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner)
url = login.MailVerificationLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner, authRequestID)
} else {
var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {

View File

@ -15,10 +15,11 @@ import (
func TestNotify_SendEmailVerificationCode(t *testing.T) {
type args struct {
user *query.NotifyUser
origin string
code string
urlTmpl string
user *query.NotifyUser
origin string
code string
urlTmpl string
authRequestID string
}
tests := []struct {
name string
@ -33,12 +34,13 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
ID: "user1",
ResourceOwner: "org1",
},
origin: "https://example.com",
code: "123",
urlTmpl: "",
origin: "https://example.com",
code: "123",
urlTmpl: "",
authRequestID: "authRequestID",
},
want: &notifyResult{
url: "https://example.com/ui/login/mail/verification?userID=user1&code=123&orgID=org1",
url: "https://example.com/ui/login/mail/verification?authRequestID=authRequestID&code=123&orgID=org1&userID=user1",
args: map[string]interface{}{"Code": "123"},
messageType: domain.VerifyEmailMessageType,
allowUnverifiedNotificationChannel: true,
@ -51,9 +53,10 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
ID: "user1",
ResourceOwner: "org1",
},
origin: "https://example.com",
code: "123",
urlTmpl: "{{",
origin: "https://example.com",
code: "123",
urlTmpl: "{{",
authRequestID: "authRequestID",
},
want: &notifyResult{},
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-oGh5e", "Errors.User.InvalidURLTemplate"),
@ -65,9 +68,10 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
ID: "user1",
ResourceOwner: "org1",
},
origin: "https://example.com",
code: "123",
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
origin: "https://example.com",
code: "123",
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
authRequestID: "authRequestID",
},
want: &notifyResult{
url: "https://example.com/email/verify?userID=user1&code=123&orgID=org1",
@ -80,7 +84,7 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, notify := mockNotify()
err := notify.SendEmailVerificationCode(http_utils.WithComposedOrigin(context.Background(), tt.args.origin), tt.args.user, tt.args.code, tt.args.urlTmpl)
err := notify.SendEmailVerificationCode(http_utils.WithComposedOrigin(context.Background(), tt.args.origin), tt.args.user, tt.args.code, tt.args.urlTmpl, tt.args.authRequestID)
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got)
})

View File

@ -9,8 +9,8 @@ import (
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendUserInitCode(ctx context.Context, user *query.NotifyUser, code string) error {
url := login.InitUserLink(http_utils.ComposedOrigin(ctx), user.ID, user.PreferredLoginName, code, user.ResourceOwner, user.PasswordSet)
func (notify Notify) SendUserInitCode(ctx context.Context, user *query.NotifyUser, code, authRequestID string) error {
url := login.InitUserLink(http_utils.ComposedOrigin(ctx), user.ID, user.PreferredLoginName, code, user.ResourceOwner, user.PasswordSet, authRequestID)
args := make(map[string]interface{})
args["Code"] = code
return notify(url, args, domain.InitCodeMessageType, true)

View File

@ -10,10 +10,10 @@ import (
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendPasswordCode(ctx context.Context, user *query.NotifyUser, code, urlTmpl string) error {
func (notify Notify) SendPasswordCode(ctx context.Context, user *query.NotifyUser, code, urlTmpl, authRequestID string) error {
var url string
if urlTmpl == "" {
url = login.InitPasswordLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner)
url = login.InitPasswordLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner, authRequestID)
} else {
var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {

View File

@ -157,6 +157,8 @@ type HumanRegisteredEvent struct {
Secret *crypto.CryptoValue `json:"secret,omitempty"` // legacy
EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"`
UserAgentID string `json:"userAgentID,omitempty"`
}
func (e *HumanRegisteredEvent) Payload() interface{} {
@ -208,6 +210,7 @@ func NewHumanRegisteredEvent(
gender domain.Gender,
emailAddress domain.EmailAddress,
userLoginMustBeDomain bool,
userAgentID string,
) *HumanRegisteredEvent {
return &HumanRegisteredEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -224,6 +227,7 @@ func NewHumanRegisteredEvent(
Gender: gender,
EmailAddress: emailAddress,
userLoginMustBeDomain: userLoginMustBeDomain,
UserAgentID: userAgentID,
}
}
@ -244,6 +248,7 @@ type HumanInitialCodeAddedEvent struct {
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
AuthRequestID string `json:"authRequestID,omitempty"`
}
func (e *HumanInitialCodeAddedEvent) Payload() interface{} {
@ -263,6 +268,7 @@ func NewHumanInitialCodeAddedEvent(
aggregate *eventstore.Aggregate,
code *crypto.CryptoValue,
expiry time.Duration,
authRequestID string,
) *HumanInitialCodeAddedEvent {
return &HumanInitialCodeAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -273,6 +279,7 @@ func NewHumanInitialCodeAddedEvent(
Code: code,
Expiry: expiry,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
AuthRequestID: authRequestID,
}
}

View File

@ -126,6 +126,8 @@ type HumanEmailCodeAddedEvent struct {
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
// AuthRequest is only used in V1 Login UI
AuthRequestID string `json:"authRequestID,omitempty"`
}
func (e *HumanEmailCodeAddedEvent) Payload() interface{} {
@ -145,8 +147,19 @@ func NewHumanEmailCodeAddedEvent(
aggregate *eventstore.Aggregate,
code *crypto.CryptoValue,
expiry time.Duration,
authRequestID string,
) *HumanEmailCodeAddedEvent {
return NewHumanEmailCodeAddedEventV2(ctx, aggregate, code, expiry, "", false)
return &HumanEmailCodeAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanEmailCodeAddedType,
),
Code: code,
Expiry: expiry,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
AuthRequestID: authRequestID,
}
}
func NewHumanEmailCodeAddedEventV2(

View File

@ -87,6 +87,8 @@ type HumanPasswordCodeAddedEvent struct {
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
// AuthRequest is only used in V1 Login UI
AuthRequestID string `json:"authRequestID,omitempty"`
}
func (e *HumanPasswordCodeAddedEvent) Payload() interface{} {
@ -107,8 +109,20 @@ func NewHumanPasswordCodeAddedEvent(
code *crypto.CryptoValue,
expiry time.Duration,
notificationType domain.NotificationType,
authRequestID string,
) *HumanPasswordCodeAddedEvent {
return NewHumanPasswordCodeAddedEventV2(ctx, aggregate, code, expiry, notificationType, "", false)
return &HumanPasswordCodeAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanPasswordCodeAddedType,
),
Code: code,
Expiry: expiry,
NotificationType: notificationType,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
AuthRequestID: authRequestID,
}
}
func NewHumanPasswordCodeAddedEventV2(