2021-01-07 15:06:45 +00:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-08-15 12:47:05 +00:00
|
|
|
"time"
|
2022-08-29 15:09:07 +00:00
|
|
|
|
2023-06-20 10:36:21 +00:00
|
|
|
"github.com/pquerna/otp"
|
2022-04-26 23:01:45 +00:00
|
|
|
"github.com/zitadel/logging"
|
2023-04-11 15:07:32 +00:00
|
|
|
|
2023-04-26 05:17:23 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
2023-04-11 15:07:32 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
2022-04-26 23:01:45 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
|
|
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
2023-06-20 10:36:21 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
2022-04-26 23:01:45 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
2021-01-07 15:06:45 +00:00
|
|
|
)
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, resourceOwner string, key string) error {
|
2022-07-28 13:42:35 +00:00
|
|
|
encryptedSecret, err := crypto.Encrypt([]byte(key), c.multifactors.OTP.CryptoMFA)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-15 12:47:05 +00:00
|
|
|
if err = c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
2022-08-29 15:09:07 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-07-28 13:42:35 +00:00
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
2022-07-28 13:42:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if otpWriteModel.State == domain.MFAStateReady {
|
|
|
|
return caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
|
|
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
|
|
|
|
|
|
|
_, err = c.eventstore.Push(ctx,
|
|
|
|
user.NewHumanOTPAddedEvent(ctx, userAgg, encryptedSecret),
|
|
|
|
user.NewHumanOTPVerifiedEvent(ctx, userAgg, userAgentID),
|
|
|
|
)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
func (c *Commands) AddHumanTOTP(ctx context.Context, userID, resourceOwner string) (*domain.TOTP, error) {
|
2021-01-15 08:32:59 +00:00
|
|
|
if userID == "" {
|
2021-03-19 10:12:56 +00:00
|
|
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2023-08-15 12:47:05 +00:00
|
|
|
prep, err := c.createHumanTOTP(ctx, userID, resourceOwner)
|
2023-06-20 10:36:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-02 16:57:53 +00:00
|
|
|
err = c.pushAppendAndReduce(ctx, prep.wm, prep.cmds...)
|
2023-06-20 10:36:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-02 16:57:53 +00:00
|
|
|
return &domain.TOTP{
|
|
|
|
ObjectDetails: writeModelToObjectDetails(&prep.wm.WriteModel),
|
|
|
|
Secret: prep.key.Secret(),
|
|
|
|
URI: prep.key.URL(),
|
2023-06-20 10:36:21 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-06-22 10:06:32 +00:00
|
|
|
type preparedTOTP struct {
|
2023-08-02 16:57:53 +00:00
|
|
|
wm *HumanTOTPWriteModel
|
2023-06-20 10:36:21 +00:00
|
|
|
userAgg *eventstore.Aggregate
|
|
|
|
key *otp.Key
|
|
|
|
cmds []eventstore.Command
|
|
|
|
}
|
|
|
|
|
2023-06-22 10:06:32 +00:00
|
|
|
func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner string) (*preparedTOTP, error) {
|
2023-06-20 10:36:21 +00:00
|
|
|
human, err := c.getHuman(ctx, userID, resourceOwner)
|
2021-01-15 08:32:59 +00:00
|
|
|
if err != nil {
|
2023-08-02 16:57:53 +00:00
|
|
|
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname")
|
2023-10-19 10:19:10 +00:00
|
|
|
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SqyJz", "Errors.User.NotFound")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2021-02-24 10:17:39 +00:00
|
|
|
org, err := c.getOrg(ctx, human.ResourceOwner)
|
2021-01-15 08:32:59 +00:00
|
|
|
if err != nil {
|
2023-08-02 16:57:53 +00:00
|
|
|
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
|
2021-03-19 10:12:56 +00:00
|
|
|
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-55M9f", "Errors.Org.NotFound")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2022-03-24 16:21:34 +00:00
|
|
|
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
|
2021-01-15 08:32:59 +00:00
|
|
|
if err != nil {
|
2023-08-02 16:57:53 +00:00
|
|
|
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
|
2022-03-28 08:05:09 +00:00
|
|
|
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2022-07-28 13:42:35 +00:00
|
|
|
|
2023-08-02 16:57:53 +00:00
|
|
|
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
2021-01-15 08:32:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if otpWriteModel.State == domain.MFAStateReady {
|
2021-10-19 07:38:35 +00:00
|
|
|
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
2022-07-28 13:42:35 +00:00
|
|
|
|
2021-01-15 08:32:59 +00:00
|
|
|
accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
|
|
|
|
if accountName == "" {
|
2023-03-14 19:20:38 +00:00
|
|
|
accountName = string(human.EmailAddress)
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2023-04-26 05:17:23 +00:00
|
|
|
issuer := c.multifactors.OTP.Issuer
|
|
|
|
if issuer == "" {
|
|
|
|
issuer = authz.GetInstance(ctx).RequestedDomain()
|
|
|
|
}
|
2023-08-02 16:57:53 +00:00
|
|
|
key, secret, err := domain.NewTOTPKey(issuer, accountName, c.multifactors.OTP.CryptoMFA)
|
2021-01-15 08:32:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-22 10:06:32 +00:00
|
|
|
return &preparedTOTP{
|
2023-06-20 10:36:21 +00:00
|
|
|
wm: otpWriteModel,
|
|
|
|
userAgg: userAgg,
|
|
|
|
key: key,
|
|
|
|
cmds: []eventstore.Command{
|
|
|
|
user.NewHumanOTPAddedEvent(ctx, userAgg, secret),
|
2021-01-15 08:32:59 +00:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, userAgentID, resourceOwner string) (*domain.ObjectDetails, error) {
|
2021-01-15 08:32:59 +00:00
|
|
|
if userID == "" {
|
feat: protos refactoring
* start with user
* user first try done in all services
* user, org, idp for discussion
* remove unused stuff
* bla
* dockerbuild
* rename search, get multiple to list...
* add annotation
* update proto dependencies
* update proto dependencies
* change proto imports
* replace all old imports
* fix go out
* remove unused lines
* correct protoc flags
* grpc and openapi flags
* go out source path relative
* -p
* remove dead code
* sourcepath relative
* ls
* is onenapi the problem?
* hobla
* authoption output
* wrong field name
* gopf
* correct option, add correct flags
* small improvments
* SIMPLYFY
* relative path
* gopf bin ich en tubel
* correct path
* default policies in admin
* grpc generation in one file
* remove non ascii
* metadata on manipulations
* correct auth_option import
* fixes
* larry
* idp provider to idp
* fix generate
* admin and auth nearly done
* admin and auth nearly done
* gen
* healthz
* imports
* deleted too much imports
* fix org
* add import
* imports
* import
* naming
* auth_opt
* gopf
* management
* imports
* _TYPE_UNSPECIFIED
* improts
* auth opts
* management policies
* imports
* passwordlessType to MFAType
* auth_opt
* add user grant calls
* add missing messages
* result
* fix option
* improvements
* ids
* fix http
* imports
* fixes
* fields
* body
* add fields
* remove wrong member query
* fix request response
* fixes
* add copy files
* variable versions
* generate all files
* improvements
* add dependencies
* factors
* user session
* oidc information, iam
* remove unused file
* changes
* enums
* dockerfile
* fix build
* remove unused folder
* update readme for build
* move old server impl
* add event type to change
* some changes
* start admin
* remove wrong field
* admin only list calls missing
* fix proto numbers
* surprisingly it compiles
* service ts changes
* admin mgmt
* mgmt
* auth manipulation and gets done, lists missing
* validations and some field changes
* validations
* enum validations
* remove todo
* move proto files to proto/zitadel
* change proto path in dockerfile
* it compiles!
* add validate import
* remove duplicate import
* fix protos
* fix import
* tests
* cleanup
* remove unimplemented methods
* iam member multiple queries
* all auth and admin calls
* add initial password on crate human
* message names
* management user server
* machine done
* fix: todos (#1346)
* fix: pub sub in new eventstore
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix tests
* fix: search method domain
* admin service, user import type typescript
* admin changes
* admin changes
* fix: search method domain
* more user grpc and begin org, fix configs
* fix: return object details
* org grpc
* remove creation date add details
* app
* fix: return object details
* fix: return object details
* mgmt service, project members
* app
* fix: convert policies
* project, members, granted projects, searches
* fix: convert usergrants
* fix: convert usergrants
* auth user detail, user detail, mfa, second factor, auth
* fix: convert usergrants
* mfa, memberships, password, owned proj detail
* fix: convert usergrants
* project grant
* missing details
* changes, userview
* idp table, keys
* org list and user table filter
* unify rest paths (#1381)
* unify rest paths
* post for all searches,
mfa to multi_factor,
secondfactor to second_factor
* remove v1
* fix tests
* rename api client key to app key
* machine keys, age policy
* user list, machine keys, changes
* fix: org states
* add default flag to policy
* second factor to type
* idp id
* app type
* unify ListQuery, ListDetails, ObjectDetails field names
* user grants, apps, memberships
* fix type params
* metadata to detail, linke idps
* api create, membership, app detail, create
* idp, app, policy
* queries, multi -> auth factors and missing fields
* update converters
* provider to user, remove old mgmt refs
* temp remove authfactor dialog, build finish
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
2021-03-09 09:30:11 +00:00
|
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
2021-01-15 08:32:59 +00:00
|
|
|
if err != nil {
|
feat: protos refactoring
* start with user
* user first try done in all services
* user, org, idp for discussion
* remove unused stuff
* bla
* dockerbuild
* rename search, get multiple to list...
* add annotation
* update proto dependencies
* update proto dependencies
* change proto imports
* replace all old imports
* fix go out
* remove unused lines
* correct protoc flags
* grpc and openapi flags
* go out source path relative
* -p
* remove dead code
* sourcepath relative
* ls
* is onenapi the problem?
* hobla
* authoption output
* wrong field name
* gopf
* correct option, add correct flags
* small improvments
* SIMPLYFY
* relative path
* gopf bin ich en tubel
* correct path
* default policies in admin
* grpc generation in one file
* remove non ascii
* metadata on manipulations
* correct auth_option import
* fixes
* larry
* idp provider to idp
* fix generate
* admin and auth nearly done
* admin and auth nearly done
* gen
* healthz
* imports
* deleted too much imports
* fix org
* add import
* imports
* import
* naming
* auth_opt
* gopf
* management
* imports
* _TYPE_UNSPECIFIED
* improts
* auth opts
* management policies
* imports
* passwordlessType to MFAType
* auth_opt
* add user grant calls
* add missing messages
* result
* fix option
* improvements
* ids
* fix http
* imports
* fixes
* fields
* body
* add fields
* remove wrong member query
* fix request response
* fixes
* add copy files
* variable versions
* generate all files
* improvements
* add dependencies
* factors
* user session
* oidc information, iam
* remove unused file
* changes
* enums
* dockerfile
* fix build
* remove unused folder
* update readme for build
* move old server impl
* add event type to change
* some changes
* start admin
* remove wrong field
* admin only list calls missing
* fix proto numbers
* surprisingly it compiles
* service ts changes
* admin mgmt
* mgmt
* auth manipulation and gets done, lists missing
* validations and some field changes
* validations
* enum validations
* remove todo
* move proto files to proto/zitadel
* change proto path in dockerfile
* it compiles!
* add validate import
* remove duplicate import
* fix protos
* fix import
* tests
* cleanup
* remove unimplemented methods
* iam member multiple queries
* all auth and admin calls
* add initial password on crate human
* message names
* management user server
* machine done
* fix: todos (#1346)
* fix: pub sub in new eventstore
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix tests
* fix: search method domain
* admin service, user import type typescript
* admin changes
* admin changes
* fix: search method domain
* more user grpc and begin org, fix configs
* fix: return object details
* org grpc
* remove creation date add details
* app
* fix: return object details
* fix: return object details
* mgmt service, project members
* app
* fix: convert policies
* project, members, granted projects, searches
* fix: convert usergrants
* fix: convert usergrants
* auth user detail, user detail, mfa, second factor, auth
* fix: convert usergrants
* mfa, memberships, password, owned proj detail
* fix: convert usergrants
* project grant
* missing details
* changes, userview
* idp table, keys
* org list and user table filter
* unify rest paths (#1381)
* unify rest paths
* post for all searches,
mfa to multi_factor,
secondfactor to second_factor
* remove v1
* fix tests
* rename api client key to app key
* machine keys, age policy
* user list, machine keys, changes
* fix: org states
* add default flag to policy
* second factor to type
* idp id
* app type
* unify ListQuery, ListDetails, ObjectDetails field names
* user grants, apps, memberships
* fix type params
* metadata to detail, linke idps
* api create, membership, app detail, create
* idp, app, policy
* queries, multi -> auth factors and missing fields
* update converters
* provider to user, remove old mgmt refs
* temp remove authfactor dialog, build finish
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
2021-03-09 09:30:11 +00:00
|
|
|
return nil, err
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
|
|
|
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
|
2021-10-19 07:38:35 +00:00
|
|
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotExisting")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
|
|
|
if existingOTP.State == domain.MFAStateReady {
|
2021-10-19 07:38:35 +00:00
|
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-qx4ls", "Errors.Users.MFA.OTP.AlreadyReady")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2023-08-02 16:57:53 +00:00
|
|
|
if err := domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA); err != nil {
|
feat: protos refactoring
* start with user
* user first try done in all services
* user, org, idp for discussion
* remove unused stuff
* bla
* dockerbuild
* rename search, get multiple to list...
* add annotation
* update proto dependencies
* update proto dependencies
* change proto imports
* replace all old imports
* fix go out
* remove unused lines
* correct protoc flags
* grpc and openapi flags
* go out source path relative
* -p
* remove dead code
* sourcepath relative
* ls
* is onenapi the problem?
* hobla
* authoption output
* wrong field name
* gopf
* correct option, add correct flags
* small improvments
* SIMPLYFY
* relative path
* gopf bin ich en tubel
* correct path
* default policies in admin
* grpc generation in one file
* remove non ascii
* metadata on manipulations
* correct auth_option import
* fixes
* larry
* idp provider to idp
* fix generate
* admin and auth nearly done
* admin and auth nearly done
* gen
* healthz
* imports
* deleted too much imports
* fix org
* add import
* imports
* import
* naming
* auth_opt
* gopf
* management
* imports
* _TYPE_UNSPECIFIED
* improts
* auth opts
* management policies
* imports
* passwordlessType to MFAType
* auth_opt
* add user grant calls
* add missing messages
* result
* fix option
* improvements
* ids
* fix http
* imports
* fixes
* fields
* body
* add fields
* remove wrong member query
* fix request response
* fixes
* add copy files
* variable versions
* generate all files
* improvements
* add dependencies
* factors
* user session
* oidc information, iam
* remove unused file
* changes
* enums
* dockerfile
* fix build
* remove unused folder
* update readme for build
* move old server impl
* add event type to change
* some changes
* start admin
* remove wrong field
* admin only list calls missing
* fix proto numbers
* surprisingly it compiles
* service ts changes
* admin mgmt
* mgmt
* auth manipulation and gets done, lists missing
* validations and some field changes
* validations
* enum validations
* remove todo
* move proto files to proto/zitadel
* change proto path in dockerfile
* it compiles!
* add validate import
* remove duplicate import
* fix protos
* fix import
* tests
* cleanup
* remove unimplemented methods
* iam member multiple queries
* all auth and admin calls
* add initial password on crate human
* message names
* management user server
* machine done
* fix: todos (#1346)
* fix: pub sub in new eventstore
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix tests
* fix: search method domain
* admin service, user import type typescript
* admin changes
* admin changes
* fix: search method domain
* more user grpc and begin org, fix configs
* fix: return object details
* org grpc
* remove creation date add details
* app
* fix: return object details
* fix: return object details
* mgmt service, project members
* app
* fix: convert policies
* project, members, granted projects, searches
* fix: convert usergrants
* fix: convert usergrants
* auth user detail, user detail, mfa, second factor, auth
* fix: convert usergrants
* mfa, memberships, password, owned proj detail
* fix: convert usergrants
* project grant
* missing details
* changes, userview
* idp table, keys
* org list and user table filter
* unify rest paths (#1381)
* unify rest paths
* post for all searches,
mfa to multi_factor,
secondfactor to second_factor
* remove v1
* fix tests
* rename api client key to app key
* machine keys, age policy
* user list, machine keys, changes
* fix: org states
* add default flag to policy
* second factor to type
* idp id
* app type
* unify ListQuery, ListDetails, ObjectDetails field names
* user grants, apps, memberships
* fix type params
* metadata to detail, linke idps
* api create, membership, app detail, create
* idp, app, policy
* queries, multi -> auth factors and missing fields
* update converters
* provider to user, remove old mgmt refs
* temp remove authfactor dialog, build finish
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
2021-03-09 09:30:11 +00:00
|
|
|
return nil, err
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
|
|
|
|
2022-01-03 08:19:07 +00:00
|
|
|
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanOTPVerifiedEvent(ctx, userAgg, userAgentID))
|
feat: protos refactoring
* start with user
* user first try done in all services
* user, org, idp for discussion
* remove unused stuff
* bla
* dockerbuild
* rename search, get multiple to list...
* add annotation
* update proto dependencies
* update proto dependencies
* change proto imports
* replace all old imports
* fix go out
* remove unused lines
* correct protoc flags
* grpc and openapi flags
* go out source path relative
* -p
* remove dead code
* sourcepath relative
* ls
* is onenapi the problem?
* hobla
* authoption output
* wrong field name
* gopf
* correct option, add correct flags
* small improvments
* SIMPLYFY
* relative path
* gopf bin ich en tubel
* correct path
* default policies in admin
* grpc generation in one file
* remove non ascii
* metadata on manipulations
* correct auth_option import
* fixes
* larry
* idp provider to idp
* fix generate
* admin and auth nearly done
* admin and auth nearly done
* gen
* healthz
* imports
* deleted too much imports
* fix org
* add import
* imports
* import
* naming
* auth_opt
* gopf
* management
* imports
* _TYPE_UNSPECIFIED
* improts
* auth opts
* management policies
* imports
* passwordlessType to MFAType
* auth_opt
* add user grant calls
* add missing messages
* result
* fix option
* improvements
* ids
* fix http
* imports
* fixes
* fields
* body
* add fields
* remove wrong member query
* fix request response
* fixes
* add copy files
* variable versions
* generate all files
* improvements
* add dependencies
* factors
* user session
* oidc information, iam
* remove unused file
* changes
* enums
* dockerfile
* fix build
* remove unused folder
* update readme for build
* move old server impl
* add event type to change
* some changes
* start admin
* remove wrong field
* admin only list calls missing
* fix proto numbers
* surprisingly it compiles
* service ts changes
* admin mgmt
* mgmt
* auth manipulation and gets done, lists missing
* validations and some field changes
* validations
* enum validations
* remove todo
* move proto files to proto/zitadel
* change proto path in dockerfile
* it compiles!
* add validate import
* remove duplicate import
* fix protos
* fix import
* tests
* cleanup
* remove unimplemented methods
* iam member multiple queries
* all auth and admin calls
* add initial password on crate human
* message names
* management user server
* machine done
* fix: todos (#1346)
* fix: pub sub in new eventstore
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix tests
* fix: search method domain
* admin service, user import type typescript
* admin changes
* admin changes
* fix: search method domain
* more user grpc and begin org, fix configs
* fix: return object details
* org grpc
* remove creation date add details
* app
* fix: return object details
* fix: return object details
* mgmt service, project members
* app
* fix: convert policies
* project, members, granted projects, searches
* fix: convert usergrants
* fix: convert usergrants
* auth user detail, user detail, mfa, second factor, auth
* fix: convert usergrants
* mfa, memberships, password, owned proj detail
* fix: convert usergrants
* project grant
* missing details
* changes, userview
* idp table, keys
* org list and user table filter
* unify rest paths (#1381)
* unify rest paths
* post for all searches,
mfa to multi_factor,
secondfactor to second_factor
* remove v1
* fix tests
* rename api client key to app key
* machine keys, age policy
* user list, machine keys, changes
* fix: org states
* add default flag to policy
* second factor to type
* idp id
* app type
* unify ListQuery, ListDetails, ObjectDetails field names
* user grants, apps, memberships
* fix type params
* metadata to detail, linke idps
* api create, membership, app detail, create
* idp, app, policy
* queries, multi -> auth factors and missing fields
* update converters
* provider to user, remove old mgmt refs
* temp remove authfactor dialog, build finish
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
2021-03-09 09:30:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = AppendAndReduce(existingOTP, pushedEvents...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
|
2021-02-08 10:30:30 +00:00
|
|
|
if userID == "" {
|
|
|
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
|
|
|
}
|
2023-08-15 12:47:05 +00:00
|
|
|
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
2021-02-08 10:30:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if existingOTP.State != domain.MFAStateReady {
|
2021-10-19 07:38:35 +00:00
|
|
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady")
|
2021-02-08 10:30:30 +00:00
|
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
2023-08-02 16:57:53 +00:00
|
|
|
err = domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA)
|
2021-02-08 10:30:30 +00:00
|
|
|
if err == nil {
|
2022-01-03 08:19:07 +00:00
|
|
|
_, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
2021-02-18 13:48:27 +00:00
|
|
|
return err
|
2021-02-08 10:30:30 +00:00
|
|
|
}
|
2022-01-03 08:19:07 +00:00
|
|
|
_, pushErr := c.eventstore.Push(ctx, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
2023-08-02 16:57:53 +00:00
|
|
|
logging.OnError(pushErr).Error("error create password check failed event")
|
2021-02-08 10:30:30 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-02 16:57:53 +00:00
|
|
|
func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
2021-01-07 15:06:45 +00:00
|
|
|
if userID == "" {
|
2021-03-19 10:12:56 +00:00
|
|
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
|
2021-01-07 15:06:45 +00:00
|
|
|
}
|
|
|
|
|
2023-08-02 16:57:53 +00:00
|
|
|
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
2021-01-07 15:06:45 +00:00
|
|
|
if err != nil {
|
feat: protos refactoring
* start with user
* user first try done in all services
* user, org, idp for discussion
* remove unused stuff
* bla
* dockerbuild
* rename search, get multiple to list...
* add annotation
* update proto dependencies
* update proto dependencies
* change proto imports
* replace all old imports
* fix go out
* remove unused lines
* correct protoc flags
* grpc and openapi flags
* go out source path relative
* -p
* remove dead code
* sourcepath relative
* ls
* is onenapi the problem?
* hobla
* authoption output
* wrong field name
* gopf
* correct option, add correct flags
* small improvments
* SIMPLYFY
* relative path
* gopf bin ich en tubel
* correct path
* default policies in admin
* grpc generation in one file
* remove non ascii
* metadata on manipulations
* correct auth_option import
* fixes
* larry
* idp provider to idp
* fix generate
* admin and auth nearly done
* admin and auth nearly done
* gen
* healthz
* imports
* deleted too much imports
* fix org
* add import
* imports
* import
* naming
* auth_opt
* gopf
* management
* imports
* _TYPE_UNSPECIFIED
* improts
* auth opts
* management policies
* imports
* passwordlessType to MFAType
* auth_opt
* add user grant calls
* add missing messages
* result
* fix option
* improvements
* ids
* fix http
* imports
* fixes
* fields
* body
* add fields
* remove wrong member query
* fix request response
* fixes
* add copy files
* variable versions
* generate all files
* improvements
* add dependencies
* factors
* user session
* oidc information, iam
* remove unused file
* changes
* enums
* dockerfile
* fix build
* remove unused folder
* update readme for build
* move old server impl
* add event type to change
* some changes
* start admin
* remove wrong field
* admin only list calls missing
* fix proto numbers
* surprisingly it compiles
* service ts changes
* admin mgmt
* mgmt
* auth manipulation and gets done, lists missing
* validations and some field changes
* validations
* enum validations
* remove todo
* move proto files to proto/zitadel
* change proto path in dockerfile
* it compiles!
* add validate import
* remove duplicate import
* fix protos
* fix import
* tests
* cleanup
* remove unimplemented methods
* iam member multiple queries
* all auth and admin calls
* add initial password on crate human
* message names
* management user server
* machine done
* fix: todos (#1346)
* fix: pub sub in new eventstore
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix tests
* fix: search method domain
* admin service, user import type typescript
* admin changes
* admin changes
* fix: search method domain
* more user grpc and begin org, fix configs
* fix: return object details
* org grpc
* remove creation date add details
* app
* fix: return object details
* fix: return object details
* mgmt service, project members
* app
* fix: convert policies
* project, members, granted projects, searches
* fix: convert usergrants
* fix: convert usergrants
* auth user detail, user detail, mfa, second factor, auth
* fix: convert usergrants
* mfa, memberships, password, owned proj detail
* fix: convert usergrants
* project grant
* missing details
* changes, userview
* idp table, keys
* org list and user table filter
* unify rest paths (#1381)
* unify rest paths
* post for all searches,
mfa to multi_factor,
secondfactor to second_factor
* remove v1
* fix tests
* rename api client key to app key
* machine keys, age policy
* user list, machine keys, changes
* fix: org states
* add default flag to policy
* second factor to type
* idp id
* app type
* unify ListQuery, ListDetails, ObjectDetails field names
* user grants, apps, memberships
* fix type params
* metadata to detail, linke idps
* api create, membership, app detail, create
* idp, app, policy
* queries, multi -> auth factors and missing fields
* update converters
* provider to user, remove old mgmt refs
* temp remove authfactor dialog, build finish
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
2021-03-09 09:30:11 +00:00
|
|
|
return nil, err
|
2021-01-07 15:06:45 +00:00
|
|
|
}
|
2021-01-15 08:32:59 +00:00
|
|
|
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
|
2021-10-19 07:38:35 +00:00
|
|
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting")
|
2021-01-07 15:06:45 +00:00
|
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
2022-01-03 08:19:07 +00:00
|
|
|
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanOTPRemovedEvent(ctx, userAgg))
|
feat: protos refactoring
* start with user
* user first try done in all services
* user, org, idp for discussion
* remove unused stuff
* bla
* dockerbuild
* rename search, get multiple to list...
* add annotation
* update proto dependencies
* update proto dependencies
* change proto imports
* replace all old imports
* fix go out
* remove unused lines
* correct protoc flags
* grpc and openapi flags
* go out source path relative
* -p
* remove dead code
* sourcepath relative
* ls
* is onenapi the problem?
* hobla
* authoption output
* wrong field name
* gopf
* correct option, add correct flags
* small improvments
* SIMPLYFY
* relative path
* gopf bin ich en tubel
* correct path
* default policies in admin
* grpc generation in one file
* remove non ascii
* metadata on manipulations
* correct auth_option import
* fixes
* larry
* idp provider to idp
* fix generate
* admin and auth nearly done
* admin and auth nearly done
* gen
* healthz
* imports
* deleted too much imports
* fix org
* add import
* imports
* import
* naming
* auth_opt
* gopf
* management
* imports
* _TYPE_UNSPECIFIED
* improts
* auth opts
* management policies
* imports
* passwordlessType to MFAType
* auth_opt
* add user grant calls
* add missing messages
* result
* fix option
* improvements
* ids
* fix http
* imports
* fixes
* fields
* body
* add fields
* remove wrong member query
* fix request response
* fixes
* add copy files
* variable versions
* generate all files
* improvements
* add dependencies
* factors
* user session
* oidc information, iam
* remove unused file
* changes
* enums
* dockerfile
* fix build
* remove unused folder
* update readme for build
* move old server impl
* add event type to change
* some changes
* start admin
* remove wrong field
* admin only list calls missing
* fix proto numbers
* surprisingly it compiles
* service ts changes
* admin mgmt
* mgmt
* auth manipulation and gets done, lists missing
* validations and some field changes
* validations
* enum validations
* remove todo
* move proto files to proto/zitadel
* change proto path in dockerfile
* it compiles!
* add validate import
* remove duplicate import
* fix protos
* fix import
* tests
* cleanup
* remove unimplemented methods
* iam member multiple queries
* all auth and admin calls
* add initial password on crate human
* message names
* management user server
* machine done
* fix: todos (#1346)
* fix: pub sub in new eventstore
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix: todos
* fix tests
* fix: search method domain
* admin service, user import type typescript
* admin changes
* admin changes
* fix: search method domain
* more user grpc and begin org, fix configs
* fix: return object details
* org grpc
* remove creation date add details
* app
* fix: return object details
* fix: return object details
* mgmt service, project members
* app
* fix: convert policies
* project, members, granted projects, searches
* fix: convert usergrants
* fix: convert usergrants
* auth user detail, user detail, mfa, second factor, auth
* fix: convert usergrants
* mfa, memberships, password, owned proj detail
* fix: convert usergrants
* project grant
* missing details
* changes, userview
* idp table, keys
* org list and user table filter
* unify rest paths (#1381)
* unify rest paths
* post for all searches,
mfa to multi_factor,
secondfactor to second_factor
* remove v1
* fix tests
* rename api client key to app key
* machine keys, age policy
* user list, machine keys, changes
* fix: org states
* add default flag to policy
* second factor to type
* idp id
* app type
* unify ListQuery, ListDetails, ObjectDetails field names
* user grants, apps, memberships
* fix type params
* metadata to detail, linke idps
* api create, membership, app detail, create
* idp, app, policy
* queries, multi -> auth factors and missing fields
* update converters
* provider to user, remove old mgmt refs
* temp remove authfactor dialog, build finish
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
2021-03-09 09:30:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = AppendAndReduce(existingOTP, pushedEvents...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
2021-01-07 15:06:45 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
// AddHumanOTPSMS adds the OTP SMS factor to a user.
|
|
|
|
// It can only be added if it not already is and the phone has to be verified.
|
2023-08-02 16:57:53 +00:00
|
|
|
func (c *Commands) AddHumanOTPSMS(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
2023-08-15 12:47:05 +00:00
|
|
|
return c.addHumanOTPSMS(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddHumanOTPSMSWithCheckSucceeded adds the OTP SMS factor to a user.
|
|
|
|
// It can only be added if it's not already and the phone has to be verified.
|
|
|
|
// An OTPSMSCheckSucceededEvent will be added to the passed AuthRequest, if not nil.
|
|
|
|
func (c *Commands) AddHumanOTPSMSWithCheckSucceeded(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) (*domain.ObjectDetails, error) {
|
|
|
|
if authRequest == nil {
|
|
|
|
return c.addHumanOTPSMS(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
event := func(ctx context.Context, userAgg *eventstore.Aggregate) eventstore.Command {
|
|
|
|
return user.NewHumanOTPSMSCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))
|
|
|
|
}
|
|
|
|
return c.addHumanOTPSMS(ctx, userID, resourceOwner, event)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) addHumanOTPSMS(ctx context.Context, userID, resourceOwner string, events ...eventCallback) (*domain.ObjectDetails, error) {
|
2023-08-02 16:57:53 +00:00
|
|
|
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)
|
2023-08-15 12:47:05 +00:00
|
|
|
cmds := make([]eventstore.Command, len(events)+1)
|
|
|
|
cmds[0] = user.NewHumanOTPSMSAddedEvent(ctx, userAgg)
|
|
|
|
for i, event := range events {
|
|
|
|
cmds[i+1] = event(ctx, userAgg)
|
|
|
|
}
|
|
|
|
if err = c.pushAppendAndReduce(ctx, otpWriteModel, cmds...); err != nil {
|
2023-08-02 16:57:53 +00:00
|
|
|
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 {
|
2023-08-15 12:47:05 +00:00
|
|
|
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
|
2023-08-02 16:57:53 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
func (c *Commands) HumanSendOTPSMS(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) error {
|
|
|
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
|
|
|
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
|
|
|
|
return user.NewHumanOTPSMSCodeAddedEvent(ctx, aggregate, code, expiry, info)
|
|
|
|
}
|
|
|
|
return c.sendHumanOTP(
|
|
|
|
ctx,
|
|
|
|
userID,
|
|
|
|
resourceOwner,
|
|
|
|
authRequest,
|
|
|
|
smsWriteModel,
|
|
|
|
domain.SecretGeneratorTypeOTPSMS,
|
2023-08-23 08:04:29 +00:00
|
|
|
c.defaultSecretGenerators.OTPSMS,
|
2023-08-15 12:47:05 +00:00
|
|
|
codeAddedEvent,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) HumanOTPSMSCodeSent(ctx context.Context, userID, resourceOwner string) (err error) {
|
|
|
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
|
|
|
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
codeSentEvent := func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command {
|
|
|
|
return user.NewHumanOTPSMSCodeSentEvent(ctx, aggregate)
|
|
|
|
}
|
|
|
|
return c.humanOTPSent(ctx, userID, resourceOwner, smsWriteModel, codeSentEvent)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) HumanCheckOTPSMS(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
|
|
|
|
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
|
|
|
|
return c.otpSMSCodeWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
|
|
return user.NewHumanOTPSMSCheckSucceededEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
|
|
|
}
|
|
|
|
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
|
|
return user.NewHumanOTPSMSCheckFailedEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
|
|
|
}
|
|
|
|
return c.humanCheckOTP(
|
|
|
|
ctx,
|
|
|
|
userID,
|
|
|
|
code,
|
|
|
|
resourceOwner,
|
|
|
|
authRequest,
|
|
|
|
writeModel,
|
|
|
|
succeededEvent,
|
|
|
|
failedEvent,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddHumanOTPEmail adds the OTP Email factor to a user.
|
|
|
|
// It can only be added if it not already is and the phone has to be verified.
|
2023-08-02 16:57:53 +00:00
|
|
|
func (c *Commands) AddHumanOTPEmail(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
2023-08-15 12:47:05 +00:00
|
|
|
return c.addHumanOTPEmail(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddHumanOTPEmailWithCheckSucceeded adds the OTP Email factor to a user.
|
|
|
|
// It can only be added if it's not already and the email has to be verified.
|
|
|
|
// An OTPEmailCheckSucceededEvent will be added to the passed AuthRequest, if not nil.
|
|
|
|
func (c *Commands) AddHumanOTPEmailWithCheckSucceeded(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) (*domain.ObjectDetails, error) {
|
|
|
|
if authRequest == nil {
|
|
|
|
return c.addHumanOTPEmail(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
event := func(ctx context.Context, userAgg *eventstore.Aggregate) eventstore.Command {
|
|
|
|
return user.NewHumanOTPEmailCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))
|
|
|
|
}
|
|
|
|
return c.addHumanOTPEmail(ctx, userID, resourceOwner, event)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) addHumanOTPEmail(ctx context.Context, userID, resourceOwner string, events ...eventCallback) (*domain.ObjectDetails, error) {
|
2023-08-02 16:57:53 +00:00
|
|
|
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)
|
2023-08-15 12:47:05 +00:00
|
|
|
cmds := make([]eventstore.Command, len(events)+1)
|
|
|
|
cmds[0] = user.NewHumanOTPEmailAddedEvent(ctx, userAgg)
|
|
|
|
for i, event := range events {
|
|
|
|
cmds[i+1] = event(ctx, userAgg)
|
|
|
|
}
|
|
|
|
if err = c.pushAppendAndReduce(ctx, otpWriteModel, cmds...); err != nil {
|
2023-08-02 16:57:53 +00:00
|
|
|
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 {
|
2023-08-15 12:47:05 +00:00
|
|
|
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
|
2023-08-02 16:57:53 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
func (c *Commands) HumanSendOTPEmail(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) error {
|
|
|
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
|
|
|
return c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
|
|
|
|
return user.NewHumanOTPEmailCodeAddedEvent(ctx, aggregate, code, expiry, info)
|
|
|
|
}
|
|
|
|
return c.sendHumanOTP(
|
|
|
|
ctx,
|
|
|
|
userID,
|
|
|
|
resourceOwner,
|
|
|
|
authRequest,
|
|
|
|
smsWriteModel,
|
|
|
|
domain.SecretGeneratorTypeOTPEmail,
|
2023-08-23 08:04:29 +00:00
|
|
|
c.defaultSecretGenerators.OTPEmail,
|
2023-08-15 12:47:05 +00:00
|
|
|
codeAddedEvent,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) HumanOTPEmailCodeSent(ctx context.Context, userID, resourceOwner string) (err error) {
|
|
|
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
|
|
|
return c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
codeSentEvent := func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command {
|
|
|
|
return user.NewHumanOTPEmailCodeSentEvent(ctx, aggregate)
|
|
|
|
}
|
|
|
|
return c.humanOTPSent(ctx, userID, resourceOwner, smsWriteModel, codeSentEvent)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) HumanCheckOTPEmail(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
|
|
|
|
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
|
|
|
|
return c.otpEmailCodeWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
}
|
|
|
|
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
|
|
return user.NewHumanOTPEmailCheckSucceededEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
|
|
|
}
|
|
|
|
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
|
|
|
return user.NewHumanOTPEmailCheckFailedEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
|
|
|
}
|
|
|
|
return c.humanCheckOTP(
|
|
|
|
ctx,
|
|
|
|
userID,
|
|
|
|
code,
|
|
|
|
resourceOwner,
|
|
|
|
authRequest,
|
|
|
|
writeModel,
|
|
|
|
succeededEvent,
|
|
|
|
failedEvent,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// sendHumanOTP creates a code for a registered mechanism (sms / email), which is used for a check (during login)
|
|
|
|
func (c *Commands) sendHumanOTP(
|
|
|
|
ctx context.Context,
|
|
|
|
userID, resourceOwner string,
|
|
|
|
authRequest *domain.AuthRequest,
|
|
|
|
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error),
|
|
|
|
secretGeneratorType domain.SecretGeneratorType,
|
2023-08-23 08:04:29 +00:00
|
|
|
defaultSecretGenerator *crypto.GeneratorConfig,
|
2023-08-15 12:47:05 +00:00
|
|
|
codeAddedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command,
|
|
|
|
) (err error) {
|
|
|
|
if userID == "" {
|
|
|
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-S3SF1", "Errors.User.UserIDMissing")
|
|
|
|
}
|
|
|
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !existingOTP.OTPAdded() {
|
|
|
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady")
|
|
|
|
}
|
2023-08-23 08:04:29 +00:00
|
|
|
config, err := secretGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator)
|
2023-08-15 12:47:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
gen := crypto.NewEncryptionGenerator(*config, c.userEncryption)
|
|
|
|
value, _, err := crypto.NewCode(gen)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
|
|
|
|
_, err = c.eventstore.Push(ctx, codeAddedEvent(ctx, userAgg, value, gen.Expiry(), authRequestDomainToAuthRequestInfo(authRequest)))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) humanOTPSent(
|
|
|
|
ctx context.Context,
|
|
|
|
userID, resourceOwner string,
|
|
|
|
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error),
|
|
|
|
codeSentEvent func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command,
|
|
|
|
) (err error) {
|
|
|
|
if userID == "" {
|
|
|
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-AE2h2", "Errors.User.UserIDMissing")
|
|
|
|
}
|
|
|
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !existingOTP.OTPAdded() {
|
|
|
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SD3gh", "Errors.User.MFA.OTP.NotReady")
|
|
|
|
}
|
|
|
|
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
|
|
|
|
_, err = c.eventstore.Push(ctx, codeSentEvent(ctx, userAgg))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) humanCheckOTP(
|
|
|
|
ctx context.Context,
|
|
|
|
userID, code, resourceOwner string,
|
|
|
|
authRequest *domain.AuthRequest,
|
|
|
|
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error),
|
|
|
|
checkSucceededEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
|
|
|
checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
|
|
|
) error {
|
|
|
|
if userID == "" {
|
|
|
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-S453v", "Errors.User.UserIDMissing")
|
|
|
|
}
|
|
|
|
if code == "" {
|
|
|
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-SJl2g", "Errors.User.Code.Empty")
|
|
|
|
}
|
|
|
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !existingOTP.OTPAdded() {
|
|
|
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-d2r52", "Errors.User.MFA.OTP.NotReady")
|
|
|
|
}
|
|
|
|
if existingOTP.Code() == nil {
|
|
|
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
|
|
|
|
}
|
|
|
|
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
|
|
|
|
err = crypto.VerifyCodeWithAlgorithm(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
|
|
|
|
if err == nil {
|
|
|
|
_, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, pushErr := c.eventstore.Push(ctx, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
|
|
|
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-02 16:57:53 +00:00
|
|
|
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
|
|
|
writeModel = NewHumanTOTPWriteModel(userID, resourceOwner)
|
|
|
|
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModel, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) otpSMSWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPSMSWriteModel, err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
|
|
|
writeModel = NewHumanOTPSMSWriteModel(userID, resourceOwner)
|
|
|
|
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModel, nil
|
|
|
|
}
|
|
|
|
|
2023-08-15 12:47:05 +00:00
|
|
|
func (c *Commands) otpSMSCodeWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPSMSCodeWriteModel, err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
|
|
|
writeModel = NewHumanOTPSMSCodeWriteModel(userID, resourceOwner)
|
|
|
|
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModel, nil
|
|
|
|
}
|
|
|
|
|
2023-08-02 16:57:53 +00:00
|
|
|
func (c *Commands) otpEmailWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailWriteModel, err error) {
|
2021-01-07 15:06:45 +00:00
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
2023-08-02 16:57:53 +00:00
|
|
|
writeModel = NewHumanOTPEmailWriteModel(userID, resourceOwner)
|
2021-02-24 10:17:39 +00:00
|
|
|
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
2021-01-07 15:06:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModel, nil
|
|
|
|
}
|
2023-08-15 12:47:05 +00:00
|
|
|
|
|
|
|
func (c *Commands) otpEmailCodeWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailCodeWriteModel, err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
|
|
|
writeModel = NewHumanOTPEmailCodeWriteModel(userID, resourceOwner)
|
|
|
|
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModel, nil
|
|
|
|
}
|