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"
|
feat: trusted (instance) domains (#8369)
# Which Problems Are Solved
ZITADEL currently selects the instance context based on a HTTP header
(see https://github.com/zitadel/zitadel/issues/8279#issue-2399959845 and
checks it against the list of instance domains. Let's call it instance
or API domain.
For any context based URL (e.g. OAuth, OIDC, SAML endpoints, links in
emails, ...) the requested domain (instance domain) will be used. Let's
call it the public domain.
In cases of proxied setups, all exposed domains (public domains) require
the domain to be managed as instance domain.
This can either be done using the "ExternalDomain" in the runtime config
or via system API, which requires a validation through CustomerPortal on
zitadel.cloud.
# How the Problems Are Solved
- Two new headers / header list are added:
- `InstanceHostHeaders`: an ordered list (first sent wins), which will
be used to match the instance.
(For backward compatibility: the `HTTP1HostHeader`, `HTTP2HostHeader`
and `forwarded`, `x-forwarded-for`, `x-forwarded-host` are checked
afterwards as well)
- `PublicHostHeaders`: an ordered list (first sent wins), which will be
used as public host / domain. This will be checked against a list of
trusted domains on the instance.
- The middleware intercepts all requests to the API and passes a
`DomainCtx` object with the hosts and protocol into the context
(previously only a computed `origin` was passed)
- HTTP / GRPC server do not longer try to match the headers to instances
themself, but use the passed `http.DomainContext` in their interceptors.
- The `RequestedHost` and `RequestedDomain` from authz.Instance are
removed in favor of the `http.DomainContext`
- When authenticating to or signing out from Console UI, the current
`http.DomainContext(ctx).Origin` (already checked by instance
interceptor for validity) is used to compute and dynamically add a
`redirect_uri` and `post_logout_redirect_uri`.
- Gateway passes all configured host headers (previously only did
`x-zitadel-*`)
- Admin API allows to manage trusted domain
# Additional Changes
None
# Additional Context
- part of #8279
- open topics:
- "single-instance" mode
- Console UI
2024-07-31 15:00:38 +00:00
|
|
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
2024-09-26 07:14:33 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
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"
|
2023-06-20 10:36:21 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
2024-09-26 07:14:33 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/notification/senders"
|
2022-04-26 23:01:45 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
2023-12-08 14:30:55 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
2021-01-07 15:06:45 +00:00
|
|
|
)
|
|
|
|
|
2024-06-19 10:56:33 +00:00
|
|
|
func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, resourceOwner string, key string) (err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
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 {
|
2023-12-08 14:30:55 +00:00
|
|
|
return zerrors.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
|
2022-07-28 13:42:35 +00:00
|
|
|
}
|
|
|
|
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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.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-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-SqyJz", "Errors.User.NotFound")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2024-05-07 05:38:26 +00:00
|
|
|
if authz.GetCtxData(ctx).UserID != userID {
|
|
|
|
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, human.ResourceOwner, userID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
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")
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-55M9f", "Errors.Org.NotFound")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
2023-12-21 09:03:37 +00:00
|
|
|
orgPolicy, err := c.domainPolicyWriteModel(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")
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.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 {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.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 == "" {
|
feat: trusted (instance) domains (#8369)
# Which Problems Are Solved
ZITADEL currently selects the instance context based on a HTTP header
(see https://github.com/zitadel/zitadel/issues/8279#issue-2399959845 and
checks it against the list of instance domains. Let's call it instance
or API domain.
For any context based URL (e.g. OAuth, OIDC, SAML endpoints, links in
emails, ...) the requested domain (instance domain) will be used. Let's
call it the public domain.
In cases of proxied setups, all exposed domains (public domains) require
the domain to be managed as instance domain.
This can either be done using the "ExternalDomain" in the runtime config
or via system API, which requires a validation through CustomerPortal on
zitadel.cloud.
# How the Problems Are Solved
- Two new headers / header list are added:
- `InstanceHostHeaders`: an ordered list (first sent wins), which will
be used to match the instance.
(For backward compatibility: the `HTTP1HostHeader`, `HTTP2HostHeader`
and `forwarded`, `x-forwarded-for`, `x-forwarded-host` are checked
afterwards as well)
- `PublicHostHeaders`: an ordered list (first sent wins), which will be
used as public host / domain. This will be checked against a list of
trusted domains on the instance.
- The middleware intercepts all requests to the API and passes a
`DomainCtx` object with the hosts and protocol into the context
(previously only a computed `origin` was passed)
- HTTP / GRPC server do not longer try to match the headers to instances
themself, but use the passed `http.DomainContext` in their interceptors.
- The `RequestedHost` and `RequestedDomain` from authz.Instance are
removed in favor of the `http.DomainContext`
- When authenticating to or signing out from Console UI, the current
`http.DomainContext(ctx).Origin` (already checked by instance
interceptor for validity) is used to compute and dynamically add a
`redirect_uri` and `post_logout_redirect_uri`.
- Gateway passes all configured host headers (previously only did
`x-zitadel-*`)
- Admin API allows to manage trusted domain
# Additional Changes
None
# Additional Context
- part of #8279
- open topics:
- "single-instance" mode
- Console UI
2024-07-31 15:00:38 +00:00
|
|
|
issuer = http_util.DomainContext(ctx).RequestedDomain()
|
2023-04-26 05:17:23 +00:00
|
|
|
}
|
2024-05-14 07:20:31 +00:00
|
|
|
key, err := domain.NewTOTPKey(issuer, accountName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
encryptedSecret, err := crypto.Encrypt([]byte(key.Secret()), 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{
|
2024-05-14 07:20:31 +00:00
|
|
|
user.NewHumanOTPAddedEvent(ctx, userAgg, encryptedSecret),
|
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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.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
|
|
|
}
|
2024-05-07 05:38:26 +00:00
|
|
|
if authz.GetCtxData(ctx).UserID != userID {
|
|
|
|
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, existingOTP.ResourceOwner, userID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2021-01-15 08:32:59 +00:00
|
|
|
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotExisting")
|
2021-01-15 08:32:59 +00:00
|
|
|
}
|
|
|
|
if existingOTP.State == domain.MFAStateReady {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.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 {
|
2024-05-30 22:08:48 +00:00
|
|
|
commands, err := checkTOTP(
|
|
|
|
ctx,
|
|
|
|
userID,
|
|
|
|
resourceOwner,
|
|
|
|
code,
|
|
|
|
c.eventstore.FilterToQueryReducer,
|
|
|
|
c.multifactors.OTP.CryptoMFA,
|
|
|
|
authRequestDomainToAuthRequestInfo(authRequest),
|
|
|
|
)
|
|
|
|
|
|
|
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
|
|
|
logging.OnError(pushErr).Error("error create password check failed event")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkTOTP(
|
|
|
|
ctx context.Context,
|
|
|
|
userID, resourceOwner, code string,
|
|
|
|
queryReducer func(ctx context.Context, r eventstore.QueryReducer) error,
|
|
|
|
alg crypto.EncryptionAlgorithm,
|
|
|
|
optionalAuthRequestInfo *user.AuthRequestInfo,
|
|
|
|
) ([]eventstore.Command, error) {
|
2021-02-08 10:30:30 +00:00
|
|
|
if userID == "" {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
2021-02-08 10:30:30 +00:00
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
existingOTP := NewHumanTOTPWriteModel(userID, resourceOwner)
|
|
|
|
err := queryReducer(ctx, existingOTP)
|
2021-02-08 10:30:30 +00:00
|
|
|
if err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, err
|
2021-02-08 10:30:30 +00:00
|
|
|
}
|
|
|
|
if existingOTP.State != domain.MFAStateReady {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady")
|
2021-02-08 10:30:30 +00:00
|
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
2024-05-30 22:08:48 +00:00
|
|
|
verifyErr := domain.VerifyTOTP(code, existingOTP.Secret, alg)
|
2024-04-10 09:14:55 +00:00
|
|
|
|
|
|
|
// recheck for additional events (failed OTP checks or locks)
|
2024-05-30 22:08:48 +00:00
|
|
|
recheckErr := queryReducer(ctx, existingOTP)
|
2024-04-10 09:14:55 +00:00
|
|
|
if recheckErr != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, recheckErr
|
2024-04-10 09:14:55 +00:00
|
|
|
}
|
|
|
|
if existingOTP.UserLocked {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3fg", "Errors.User.Locked")
|
2024-04-10 09:14:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// the OTP check succeeded and the user was not locked in the meantime
|
|
|
|
if verifyErr == nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return []eventstore.Command{user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, optionalAuthRequestInfo)}, nil
|
2021-02-08 10:30:30 +00:00
|
|
|
}
|
2024-04-10 09:14:55 +00:00
|
|
|
|
|
|
|
// the OTP check failed, therefore check if the limit was reached and the user must additionally be locked
|
|
|
|
commands := make([]eventstore.Command, 0, 2)
|
2024-05-30 22:08:48 +00:00
|
|
|
commands = append(commands, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, optionalAuthRequestInfo))
|
|
|
|
lockoutPolicy, err := getLockoutPolicy(ctx, existingOTP.ResourceOwner, queryReducer)
|
2024-04-10 09:14:55 +00:00
|
|
|
if err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, err
|
2024-04-10 09:14:55 +00:00
|
|
|
}
|
|
|
|
if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount+1 >= lockoutPolicy.MaxOTPAttempts {
|
|
|
|
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
return commands, verifyErr
|
2021-02-08 10:30:30 +00:00
|
|
|
}
|
|
|
|
|
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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.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 {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting")
|
2021-01-07 15:06:45 +00:00
|
|
|
}
|
2024-07-10 12:31:28 +00:00
|
|
|
if userID != authz.GetCtxData(ctx).UserID {
|
|
|
|
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.ResourceOwner, userID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-QSF2s", "Errors.User.UserIDMissing")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
otpWriteModel, err := c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-05-07 05:38:26 +00:00
|
|
|
if authz.GetCtxData(ctx).UserID != userID {
|
|
|
|
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, otpWriteModel.ResourceOwner(), userID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-08-02 16:57:53 +00:00
|
|
|
if otpWriteModel.otpAdded {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-Ad3g2", "Errors.User.MFA.OTP.AlreadyReady")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
if !otpWriteModel.phoneVerified {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Q54j2", "Errors.User.MFA.OTP.NotReady")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-S3br2", "Errors.User.UserIDMissing")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Sr3h3", "Errors.User.MFA.OTP.NotExisting")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
2024-09-26 07:14:33 +00:00
|
|
|
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo, generatorID string) eventstore.Command {
|
|
|
|
return user.NewHumanOTPSMSCodeAddedEvent(ctx, aggregate, code, expiry, info, generatorID)
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
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,
|
2024-09-26 07:14:33 +00:00
|
|
|
c.newPhoneCode,
|
2023-08-15 12:47:05 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-09-26 07:14:33 +00:00
|
|
|
func (c *Commands) HumanOTPSMSCodeSent(ctx context.Context, userID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) (err error) {
|
2023-08-15 12:47:05 +00:00
|
|
|
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 {
|
2024-09-26 07:14:33 +00:00
|
|
|
return user.NewHumanOTPSMSCodeSentEvent(ctx, aggregate, generatorInfo)
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
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))
|
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
commands, err := checkOTP(
|
2023-08-15 12:47:05 +00:00
|
|
|
ctx,
|
|
|
|
userID,
|
|
|
|
code,
|
|
|
|
resourceOwner,
|
|
|
|
authRequest,
|
|
|
|
writeModel,
|
2024-05-30 22:08:48 +00:00
|
|
|
c.eventstore.FilterToQueryReducer,
|
|
|
|
c.userEncryption,
|
2024-09-26 07:14:33 +00:00
|
|
|
c.phoneCodeVerifier,
|
2023-08-15 12:47:05 +00:00
|
|
|
succeededEvent,
|
|
|
|
failedEvent,
|
|
|
|
)
|
2024-05-30 22:08:48 +00:00
|
|
|
if len(commands) > 0 {
|
|
|
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
|
|
|
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
|
|
|
|
}
|
|
|
|
return err
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Sg1hz", "Errors.User.UserIDMissing")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
otpWriteModel, err := c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-05-07 05:38:26 +00:00
|
|
|
if authz.GetCtxData(ctx).UserID != userID {
|
|
|
|
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, otpWriteModel.ResourceOwner(), userID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-08-02 16:57:53 +00:00
|
|
|
if otpWriteModel.otpAdded {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-MKL2s", "Errors.User.MFA.OTP.AlreadyReady")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
if !otpWriteModel.emailVerified {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-KLJ2d", "Errors.User.MFA.OTP.NotReady")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-S2h11", "Errors.User.UserIDMissing")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-b312D", "Errors.User.MFA.OTP.NotExisting")
|
2023-08-02 16:57:53 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
2024-09-26 07:14:33 +00:00
|
|
|
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo, _ string) eventstore.Command {
|
2023-08-15 12:47:05 +00:00
|
|
|
return user.NewHumanOTPEmailCodeAddedEvent(ctx, aggregate, code, expiry, info)
|
|
|
|
}
|
2024-09-26 07:14:33 +00:00
|
|
|
generateCode := func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, string, error) {
|
|
|
|
code, err := c.newEncryptedCodeWithDefault(ctx, filter, typ, alg, defaultConfig)
|
|
|
|
return code, "", err
|
|
|
|
}
|
2023-08-15 12:47:05 +00:00
|
|
|
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,
|
2024-09-26 07:14:33 +00:00
|
|
|
generateCode,
|
2023-08-15 12:47:05 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
commands, err := checkOTP(
|
2023-08-15 12:47:05 +00:00
|
|
|
ctx,
|
|
|
|
userID,
|
|
|
|
code,
|
|
|
|
resourceOwner,
|
|
|
|
authRequest,
|
|
|
|
writeModel,
|
2024-05-30 22:08:48 +00:00
|
|
|
c.eventstore.FilterToQueryReducer,
|
|
|
|
c.userEncryption,
|
2024-09-26 07:14:33 +00:00
|
|
|
nil, // email currently always uses local code checks
|
2023-08-15 12:47:05 +00:00
|
|
|
succeededEvent,
|
|
|
|
failedEvent,
|
|
|
|
)
|
2024-05-30 22:08:48 +00:00
|
|
|
if len(commands) > 0 {
|
|
|
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
|
|
|
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
|
|
|
|
}
|
|
|
|
return err
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
2024-09-26 07:14:33 +00:00
|
|
|
codeAddedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo, generatorID string) eventstore.Command,
|
|
|
|
generateCode func(ctx context.Context, filter preparation.FilterToQueryReducer, secretGeneratorType domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, string, error),
|
2023-08-15 12:47:05 +00:00
|
|
|
) (err error) {
|
|
|
|
if userID == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-S3SF1", "Errors.User.UserIDMissing")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !existingOTP.OTPAdded() {
|
2023-12-08 14:30:55 +00:00
|
|
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
2024-09-26 07:14:33 +00:00
|
|
|
code, generatorID, err := generateCode(ctx, c.eventstore.Filter, secretGeneratorType, c.userEncryption, defaultSecretGenerator) //nolint:staticcheck
|
2023-08-15 12:47:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
|
2024-09-26 07:14:33 +00:00
|
|
|
_, err = c.eventstore.Push(ctx, codeAddedEvent(ctx, userAgg, code.CryptedCode(), code.CodeExpiry(), authRequestDomainToAuthRequestInfo(authRequest), generatorID))
|
2023-08-15 12:47:05 +00:00
|
|
|
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 == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-AE2h2", "Errors.User.UserIDMissing")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !existingOTP.OTPAdded() {
|
2023-12-08 14:30:55 +00:00
|
|
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SD3gh", "Errors.User.MFA.OTP.NotReady")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
|
|
|
|
_, err = c.eventstore.Push(ctx, codeSentEvent(ctx, userAgg))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-05-30 22:08:48 +00:00
|
|
|
func checkOTP(
|
2023-08-15 12:47:05 +00:00
|
|
|
ctx context.Context,
|
|
|
|
userID, code, resourceOwner string,
|
|
|
|
authRequest *domain.AuthRequest,
|
|
|
|
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error),
|
2024-05-30 22:08:48 +00:00
|
|
|
queryReducer func(ctx context.Context, r eventstore.QueryReducer) error,
|
|
|
|
alg crypto.EncryptionAlgorithm,
|
2024-09-26 07:14:33 +00:00
|
|
|
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error),
|
2024-05-30 22:08:48 +00:00
|
|
|
checkSucceededEvent, checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
|
|
|
) ([]eventstore.Command, error) {
|
2023-08-15 12:47:05 +00:00
|
|
|
if userID == "" {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-S453v", "Errors.User.UserIDMissing")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
if code == "" {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-SJl2g", "Errors.User.Code.Empty")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
|
|
|
if err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, err
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
if !existingOTP.OTPAdded() {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-d2r52", "Errors.User.MFA.OTP.NotReady")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
2024-09-26 07:14:33 +00:00
|
|
|
if existingOTP.Code() == nil && existingOTP.GeneratorID() == "" {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
|
2024-09-26 07:14:33 +00:00
|
|
|
verifyErr := verifyCode(
|
|
|
|
ctx,
|
|
|
|
existingOTP.CodeCreationDate(),
|
|
|
|
existingOTP.CodeExpiry(),
|
|
|
|
existingOTP.Code(),
|
|
|
|
existingOTP.GeneratorID(),
|
|
|
|
existingOTP.ProviderVerificationID(),
|
|
|
|
code,
|
|
|
|
alg,
|
|
|
|
getCodeVerifier,
|
|
|
|
)
|
2024-04-10 09:14:55 +00:00
|
|
|
// recheck for additional events (failed OTP checks or locks)
|
2024-05-30 22:08:48 +00:00
|
|
|
recheckErr := queryReducer(ctx, existingOTP)
|
2024-04-10 09:14:55 +00:00
|
|
|
if recheckErr != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, recheckErr
|
2024-04-10 09:14:55 +00:00
|
|
|
}
|
|
|
|
if existingOTP.UserLocked() {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked")
|
2024-04-10 09:14:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// the OTP check succeeded and the user was not locked in the meantime
|
|
|
|
if verifyErr == nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return []eventstore.Command{checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))}, nil
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
2024-04-10 09:14:55 +00:00
|
|
|
|
|
|
|
// the OTP check failed, therefore check if the limit was reached and the user must additionally be locked
|
|
|
|
commands := make([]eventstore.Command, 0, 2)
|
|
|
|
commands = append(commands, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
2024-05-30 22:08:48 +00:00
|
|
|
lockoutPolicy, lockoutErr := getLockoutPolicy(ctx, existingOTP.ResourceOwner(), queryReducer)
|
|
|
|
logging.OnError(lockoutErr).Error("unable to get lockout policy")
|
|
|
|
if lockoutPolicy != nil && lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount()+1 >= lockoutPolicy.MaxOTPAttempts {
|
2024-04-10 09:14:55 +00:00
|
|
|
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
return commands, verifyErr
|
2023-08-15 12:47:05 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|