mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:47:32 +00:00
feat(api): add and remove OTP (SMS and email) (#6295)
* refactor: rename otp to totp * feat: add otp sms and email * implement tests
This commit is contained in:
@@ -155,6 +155,7 @@ func (wm *SessionWriteModel) AuthenticationTime() time.Time {
|
||||
wm.PasskeyCheckedAt,
|
||||
wm.IntentCheckedAt,
|
||||
// TODO: add U2F and OTP check https://github.com/zitadel/zitadel/issues/5477
|
||||
// TODO: add OTP (sms and email) check https://github.com/zitadel/zitadel/issues/6224
|
||||
} {
|
||||
if check.After(authTime) {
|
||||
authTime = check
|
||||
@@ -178,11 +179,20 @@ func (wm *SessionWriteModel) AuthMethodTypes() []domain.UserAuthMethodType {
|
||||
// TODO: add checks with https://github.com/zitadel/zitadel/issues/5477
|
||||
/*
|
||||
if !wm.TOTPCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeOTP)
|
||||
types = append(types, domain.UserAuthMethodTypeTOTP)
|
||||
}
|
||||
if !wm.U2FCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeU2F)
|
||||
}
|
||||
*/
|
||||
// TODO: add checks with https://github.com/zitadel/zitadel/issues/6224
|
||||
/*
|
||||
if !wm.TOTPFactor.OTPSMSCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeOTPSMS)
|
||||
}
|
||||
if !wm.TOTPFactor.OTPEmailCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeOTPEmail)
|
||||
}
|
||||
*/
|
||||
return types
|
||||
}
|
||||
|
@@ -11,12 +11,11 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *Commands) ImportHumanOTP(ctx context.Context, userID, userAgentID, resourceowner string, key string) error {
|
||||
func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, resourceowner string, key string) error {
|
||||
encryptedSecret, err := crypto.Encrypt([]byte(key), c.multifactors.OTP.CryptoMFA)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -25,7 +24,7 @@ func (c *Commands) ImportHumanOTP(ctx context.Context, userID, userAgentID, reso
|
||||
return err
|
||||
}
|
||||
|
||||
otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner)
|
||||
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -41,7 +40,7 @@ func (c *Commands) ImportHumanOTP(ctx context.Context, userID, userAgentID, reso
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string) (*domain.OTP, error) {
|
||||
func (c *Commands) AddHumanTOTP(ctx context.Context, userID, resourceowner string) (*domain.TOTP, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
|
||||
}
|
||||
@@ -49,21 +48,19 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = c.eventstore.Push(ctx, prep.cmds...)
|
||||
err = c.pushAppendAndReduce(ctx, prep.wm, prep.cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.OTP{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: prep.userAgg.ID,
|
||||
},
|
||||
SecretString: prep.key.Secret(),
|
||||
Url: prep.key.URL(),
|
||||
return &domain.TOTP{
|
||||
ObjectDetails: writeModelToObjectDetails(&prep.wm.WriteModel),
|
||||
Secret: prep.key.Secret(),
|
||||
URI: prep.key.URL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type preparedTOTP struct {
|
||||
wm *HumanOTPWriteModel
|
||||
wm *HumanTOTPWriteModel
|
||||
userAgg *eventstore.Aggregate
|
||||
key *otp.Key
|
||||
cmds []eventstore.Command
|
||||
@@ -72,21 +69,21 @@ type preparedTOTP struct {
|
||||
func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner string) (*preparedTOTP, error) {
|
||||
human, err := c.getHuman(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-DAqe1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname")
|
||||
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-MM9fs", "Errors.User.NotFound")
|
||||
}
|
||||
org, err := c.getOrg(ctx, human.ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-Cm0ds").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
|
||||
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-55M9f", "Errors.Org.NotFound")
|
||||
}
|
||||
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-y5zv9").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
|
||||
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
|
||||
otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceOwner)
|
||||
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,7 +100,7 @@ func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner st
|
||||
if issuer == "" {
|
||||
issuer = authz.GetInstance(ctx).RequestedDomain()
|
||||
}
|
||||
key, secret, err := domain.NewOTPKey(issuer, accountName, c.multifactors.OTP.CryptoMFA)
|
||||
key, secret, err := domain.NewTOTPKey(issuer, accountName, c.multifactors.OTP.CryptoMFA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -117,12 +114,12 @@ func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner st
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) HumanCheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingOTP, err := c.otpWriteModelByID(ctx, userID, resourceowner)
|
||||
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -132,7 +129,7 @@ func (c *Commands) HumanCheckMFAOTPSetup(ctx context.Context, userID, code, user
|
||||
if existingOTP.State == domain.MFAStateReady {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-qx4ls", "Errors.Users.MFA.OTP.AlreadyReady")
|
||||
}
|
||||
if err := domain.VerifyMFAOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA); err != nil {
|
||||
if err := domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||
@@ -148,11 +145,11 @@ func (c *Commands) HumanCheckMFAOTPSetup(ctx context.Context, userID, code, user
|
||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) HumanCheckMFAOTP(ctx context.Context, userID, code, resourceowner string, authRequest *domain.AuthRequest) error {
|
||||
func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resourceowner string, authRequest *domain.AuthRequest) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
||||
}
|
||||
existingOTP, err := c.otpWriteModelByID(ctx, userID, resourceowner)
|
||||
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceowner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -160,22 +157,22 @@ func (c *Commands) HumanCheckMFAOTP(ctx context.Context, userID, code, resourceo
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||
err = domain.VerifyMFAOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA)
|
||||
err = domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA)
|
||||
if err == nil {
|
||||
_, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
return err
|
||||
}
|
||||
_, pushErr := c.eventstore.Push(ctx, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||
logging.Log("COMMAND-9fj7s").OnError(pushErr).Error("error create password check failed event")
|
||||
logging.OnError(pushErr).Error("error create password check failed event")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) HumanRemoveOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingOTP, err := c.otpWriteModelByID(ctx, userID, resourceOwner)
|
||||
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -194,11 +191,128 @@ func (c *Commands) HumanRemoveOTP(ctx context.Context, userID, resourceOwner str
|
||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) otpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPWriteModel, err error) {
|
||||
func (c *Commands) AddHumanOTPSMS(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-QSF2s", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if err := authz.UserIDInCTX(ctx, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
otpWriteModel, err := c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if otpWriteModel.otpAdded {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-Ad3g2", "Errors.User.MFA.OTP.AlreadyReady")
|
||||
}
|
||||
if !otpWriteModel.phoneVerified {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Q54j2", "Errors.User.MFA.OTP.NotReady")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
||||
if err = c.pushAppendAndReduce(ctx, otpWriteModel, user.NewHumanOTPSMSAddedEvent(ctx, userAgg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveHumanOTPSMS(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-S3br2", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingOTP, err := c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userID != authz.GetCtxData(ctx).UserID {
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.ResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !existingOTP.otpAdded {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Sr3h3", "Errors.User.MFA.OTP.NotExisting")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||
if err = c.pushAppendAndReduce(ctx, existingOTP, user.NewHumanOTPSMSRemovedEvent(ctx, userAgg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddHumanOTPEmail(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Sg1hz", "Errors.User.UserIDMissing")
|
||||
}
|
||||
otpWriteModel, err := c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if otpWriteModel.otpAdded {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-MKL2s", "Errors.User.MFA.OTP.AlreadyReady")
|
||||
}
|
||||
if !otpWriteModel.emailVerified {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-KLJ2d", "Errors.User.MFA.OTP.NotReady")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
||||
if err = c.pushAppendAndReduce(ctx, otpWriteModel, user.NewHumanOTPEmailAddedEvent(ctx, userAgg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveHumanOTPEmail(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-S2h11", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingOTP, err := c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userID != authz.GetCtxData(ctx).UserID {
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.ResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !existingOTP.otpAdded {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-b312D", "Errors.User.MFA.OTP.NotExisting")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||
if err = c.pushAppendAndReduce(ctx, existingOTP, user.NewHumanOTPEmailRemovedEvent(ctx, userAgg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewHumanOTPWriteModel(userID, resourceOwner)
|
||||
writeModel = NewHumanTOTPWriteModel(userID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) otpSMSWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPSMSWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewHumanOTPSMSWriteModel(userID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) otpEmailWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewHumanOTPEmailWriteModel(userID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -7,15 +7,15 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type HumanOTPWriteModel struct {
|
||||
type HumanTOTPWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
State domain.MFAState
|
||||
Secret *crypto.CryptoValue
|
||||
}
|
||||
|
||||
func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel {
|
||||
return &HumanOTPWriteModel{
|
||||
func NewHumanTOTPWriteModel(userID, resourceOwner string) *HumanTOTPWriteModel {
|
||||
return &HumanTOTPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
@@ -23,7 +23,7 @@ func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel {
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanOTPWriteModel) Reduce() error {
|
||||
func (wm *HumanTOTPWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanOTPAddedEvent:
|
||||
@@ -40,7 +40,7 @@ func (wm *HumanOTPWriteModel) Reduce() error {
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *HumanOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
func (wm *HumanTOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
@@ -59,3 +59,107 @@ func (wm *HumanOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
type HumanOTPSMSWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
phoneVerified bool
|
||||
otpAdded bool
|
||||
}
|
||||
|
||||
func NewHumanOTPSMSWriteModel(userID, resourceOwner string) *HumanOTPSMSWriteModel {
|
||||
return &HumanOTPSMSWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanOTPSMSWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch event.(type) {
|
||||
case *user.HumanPhoneVerifiedEvent:
|
||||
wm.phoneVerified = true
|
||||
case *user.HumanOTPSMSAddedEvent:
|
||||
wm.otpAdded = true
|
||||
case *user.HumanOTPSMSRemovedEvent:
|
||||
wm.otpAdded = false
|
||||
case *user.HumanPhoneRemovedEvent,
|
||||
*user.UserRemovedEvent:
|
||||
wm.phoneVerified = false
|
||||
wm.otpAdded = false
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *HumanOTPSMSWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(user.HumanPhoneVerifiedType,
|
||||
user.HumanOTPSMSAddedType,
|
||||
user.HumanOTPSMSRemovedType,
|
||||
user.HumanPhoneRemovedType,
|
||||
user.UserRemovedType,
|
||||
).
|
||||
Builder()
|
||||
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
type HumanOTPEmailWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
emailVerified bool
|
||||
otpAdded bool
|
||||
}
|
||||
|
||||
func NewHumanOTPEmailWriteModel(userID, resourceOwner string) *HumanOTPEmailWriteModel {
|
||||
return &HumanOTPEmailWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *HumanOTPEmailWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch event.(type) {
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
wm.emailVerified = true
|
||||
case *user.HumanOTPEmailAddedEvent:
|
||||
wm.otpAdded = true
|
||||
case *user.HumanOTPEmailRemovedEvent:
|
||||
wm.otpAdded = false
|
||||
case *user.UserRemovedEvent:
|
||||
wm.emailVerified = false
|
||||
wm.otpAdded = false
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *HumanOTPEmailWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(user.HumanEmailVerifiedType,
|
||||
user.HumanOTPEmailAddedType,
|
||||
user.HumanOTPEmailRemovedType,
|
||||
user.UserRemovedType,
|
||||
).
|
||||
Builder()
|
||||
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddHumanOTP(t *testing.T) {
|
||||
func TestCommandSide_AddHumanTOTP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
@@ -223,7 +223,7 @@ func TestCommandSide_AddHumanOTP(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.AddHumanOTP(tt.args.ctx, tt.args.userID, tt.args.orgID)
|
||||
got, err := r.AddHumanTOTP(tt.args.ctx, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func TestCommandSide_AddHumanOTP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_createHumanOTP(t *testing.T) {
|
||||
func TestCommands_createHumanTOTP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
@@ -527,11 +527,11 @@ func TestCommands_createHumanOTP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_HumanCheckMFAOTPSetup(t *testing.T) {
|
||||
func TestCommands_HumanCheckMFATOTPSetup(t *testing.T) {
|
||||
ctx := authz.NewMockContext("", "org1", "user1")
|
||||
|
||||
cryptoAlg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
|
||||
key, secret, err := domain.NewOTPKey("example.com", "user1", cryptoAlg)
|
||||
key, secret, err := domain.NewTOTPKey("example.com", "user1", cryptoAlg)
|
||||
require.NoError(t, err)
|
||||
userAgg := &user.NewAggregate("user1", "org1").Aggregate
|
||||
|
||||
@@ -697,7 +697,7 @@ func TestCommands_HumanCheckMFAOTPSetup(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
got, err := c.HumanCheckMFAOTPSetup(ctx, tt.args.userID, tt.args.code, "agent1", tt.args.resourceOwner)
|
||||
got, err := c.HumanCheckMFATOTPSetup(ctx, tt.args.userID, tt.args.code, "agent1", tt.args.resourceOwner)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if tt.want {
|
||||
require.NotNil(t, got)
|
||||
@@ -707,7 +707,7 @@ func TestCommands_HumanCheckMFAOTPSetup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveHumanOTP(t *testing.T) {
|
||||
func TestCommandSide_RemoveHumanTOTP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
@@ -802,7 +802,7 @@ func TestCommandSide_RemoveHumanOTP(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.HumanRemoveOTP(tt.args.ctx, tt.args.userID, tt.args.orgID)
|
||||
got, err := r.HumanRemoveTOTP(tt.args.ctx, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -815,3 +815,540 @@ func TestCommandSide_RemoveHumanOTP(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddHumanOTPSMS(t *testing.T) {
|
||||
ctx := authz.NewMockContext("inst1", "org1", "user1")
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-QSF2s", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong user, permission denied error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "other",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp sms already exists, already exists error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPSMSAddedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowAlreadyExists(nil, "COMMAND-Ad3g2", "Errors.User.MFA.OTP.AlreadyReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone not verified, precondition failed error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Q54j2", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone removed, precondition failed error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanPhoneChangedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"+4179654321",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewHumanPhoneVerifiedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewHumanPhoneRemovedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Q54j2", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful add",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanPhoneChangedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"+4179654321",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewHumanPhoneVerifiedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID("inst1",
|
||||
user.NewHumanOTPSMSAddedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
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(t),
|
||||
}
|
||||
got, err := r.AddHumanOTPSMS(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveHumanOTPSMS(t *testing.T) {
|
||||
ctx := authz.NewMockContext("inst1", "org1", "user1")
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-S3br2", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other user not permission, permission denied error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "other",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp sms not added, not found error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowNotFound(nil, "COMMAND-Sr3h3", "Errors.User.MFA.OTP.NotExisting"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful remove",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPSMSAddedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID("inst1",
|
||||
user.NewHumanOTPSMSRemovedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
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(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveHumanOTPSMS(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddHumanOTPEmail(t *testing.T) {
|
||||
ctx := authz.NewMockContext("inst1", "org1", "user1")
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-Sg1hz", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp email already exists, already exists error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPEmailAddedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowAlreadyExists(nil, "COMMAND-MKL2s", "Errors.User.MFA.OTP.AlreadyReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email not verified, precondition failed error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-KLJ2d", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful add",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanEmailChangedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"email@test.ch",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewHumanEmailVerifiedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID("inst1",
|
||||
user.NewHumanOTPEmailAddedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
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(t),
|
||||
}
|
||||
got, err := r.AddHumanOTPEmail(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveHumanOTPEmail(t *testing.T) {
|
||||
ctx := authz.NewMockContext("inst1", "org1", "user1")
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-S2h11", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other user not permission, permission denied error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "other",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp email not added, not found error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowNotFound(nil, "COMMAND-b312D", "Errors.User.MFA.OTP.NotExisting"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful remove",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPEmailAddedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID("inst1",
|
||||
user.NewHumanOTPEmailRemovedEvent(ctx,
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
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(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveHumanOTPEmail(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -885,70 +885,70 @@ func TestCommandSide_RemoveHumanPhone(t *testing.T) {
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
//{
|
||||
// name: "userid missing, invalid argument error",
|
||||
// fields: fields{
|
||||
// eventstore: eventstoreExpect(
|
||||
// t,
|
||||
// ),
|
||||
// },
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// resourceOwner: "org1",
|
||||
// },
|
||||
// res: res{
|
||||
// err: caos_errs.IsErrorInvalidArgument,
|
||||
// },
|
||||
//},
|
||||
//{
|
||||
// name: "user not existing, precondition error",
|
||||
// fields: fields{
|
||||
// eventstore: eventstoreExpect(
|
||||
// t,
|
||||
// expectFilter(),
|
||||
// ),
|
||||
// },
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// userID: "user1",
|
||||
// resourceOwner: "org1",
|
||||
// },
|
||||
// res: res{
|
||||
// err: caos_errs.IsPreconditionFailed,
|
||||
// },
|
||||
//},
|
||||
//{
|
||||
// name: "phone not existing, precondition error",
|
||||
// fields: fields{
|
||||
// eventstore: eventstoreExpect(
|
||||
// t,
|
||||
// expectFilter(
|
||||
// eventFromEventPusher(
|
||||
// user.NewHumanAddedEvent(context.Background(),
|
||||
// &user.NewAggregate("user1", "org1").Aggregate,
|
||||
// "username",
|
||||
// "firstname",
|
||||
// "lastname",
|
||||
// "nickname",
|
||||
// "displayname",
|
||||
// language.German,
|
||||
// domain.GenderUnspecified,
|
||||
// "email@test.ch",
|
||||
// true,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// },
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// userID: "user1",
|
||||
// resourceOwner: "org1",
|
||||
// },
|
||||
// res: res{
|
||||
// err: caos_errs.IsNotFound,
|
||||
// },
|
||||
//},
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.German,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove phone, ok",
|
||||
fields: fields{
|
||||
|
@@ -57,7 +57,7 @@ func TestCommands_RegisterUserPasskey(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
authenticator: domain.AuthenticatorAttachmentCrossPlattform,
|
||||
},
|
||||
wantErr: caos_errs.ThrowUnauthenticated(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
wantErr: caos_errs.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
},
|
||||
{
|
||||
name: "get human passwordless error",
|
||||
|
@@ -29,5 +29,5 @@ func (c *Commands) CheckUserTOTP(ctx context.Context, userID, code, resourceOwne
|
||||
if err := authz.UserIDInCTX(ctx, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.HumanCheckMFAOTPSetup(ctx, userID, code, "", resourceOwner)
|
||||
return c.HumanCheckMFATOTPSetup(ctx, userID, code, "", resourceOwner)
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ func TestCommands_AddUserTOTP(t *testing.T) {
|
||||
userID: "foo",
|
||||
resourceowner: "org1",
|
||||
},
|
||||
wantErr: caos_errs.ThrowUnauthenticated(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
wantErr: caos_errs.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
},
|
||||
{
|
||||
name: "create otp error",
|
||||
@@ -191,7 +191,7 @@ func TestCommands_CheckUserTOTP(t *testing.T) {
|
||||
ctx := authz.NewMockContext("", "org1", "user1")
|
||||
|
||||
cryptoAlg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
|
||||
key, secret, err := domain.NewOTPKey("example.com", "user1", cryptoAlg)
|
||||
key, secret, err := domain.NewTOTPKey("example.com", "user1", cryptoAlg)
|
||||
require.NoError(t, err)
|
||||
userAgg := &user.NewAggregate("user1", "org1").Aggregate
|
||||
|
||||
@@ -218,7 +218,7 @@ func TestCommands_CheckUserTOTP(t *testing.T) {
|
||||
args: args{
|
||||
userID: "foo",
|
||||
},
|
||||
wantErr: caos_errs.ThrowUnauthenticated(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
wantErr: caos_errs.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
|
@@ -52,7 +52,7 @@ func TestCommands_RegisterUserU2F(t *testing.T) {
|
||||
userID: "foo",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
wantErr: caos_errs.ThrowUnauthenticated(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
wantErr: caos_errs.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
|
||||
},
|
||||
{
|
||||
name: "get human passwordless error",
|
||||
|
Reference in New Issue
Block a user