mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-19 22:37:35 +00:00
8a5badddf6
* fix: change oidc config * fix: change oidc config secret * begin models * begin repo * fix: implement grpc app funcs * fix: add application requests * fix: converter * fix: converter * fix: converter and generate clientid * fix: tests * feat: project grant aggregate * feat: project grant * fix: project grant check if role existing * fix: project grant requests * fix: project grant fixes * fix: project grant member model * fix: project grant member aggregate * fix: project grant member eventstore * fix: project grant member requests * feat: user model * begin repo * repo models and more * feat: user command side * lots of functions * user command side * profile requests * commit before rebase on user * save * local config with gopass and more * begin new auth command (user centric) * Update internal/user/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/user_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/eventstore_mock_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * changes from mr review * save files into basedir * changes from mr review * changes from mr review * move to auth request * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * changes requested on mr * fix generate codes * fix return if no events * password code * email verification step * more steps * lot of mfa * begin tests * more next steps * auth api * auth api (user) * auth api (user) * auth api (user) * differ requests * merge * tests * fix compilation error * mock for id generator * Update internal/user/repository/eventsourcing/model/password.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * requests of mr * check email * begin separation of command and query * otp * change packages * some cleanup and fixes * tests for auth request / next steps * add VerificationLifetimes to config and make it run * tests * fix code challenge validation * cleanup * fix merge * begin view * repackaging tests and configs * fix startup config for auth * add migration * add PromptSelectAccount * fix copy / paste * remove user_agent files * fixes * fix sequences in user_session * token commands * token queries and signout * fix * fix set password test * add token handler and table * handle session init * add session state * add user view test cases * change VerifyMyMfaOTP * some fixes * fix user repo in auth api * cleanup * add user session view test * fix merge * begin oidc * user agent and more * config * keys * key command and query * add login statics * key handler * start login * login handlers * lot of fixes * merge oidc * add missing exports * add missing exports * fix some bugs * authrequestid in htmls * getrequest * update auth request * fix userid check * add username to authrequest * fix user session and auth request handling * fix UserSessionsByAgentID * fix auth request tests * fix user session on UserPasswordChanged and MfaOtpRemoved * fix MfaTypesSetupPossible * handle mfa * fill username * auth request query checks new events * fix userSessionByIDs * fix tokens * fix userSessionByIDs test * add user selection * init code * user code creation date * add init user step * add verification failed types * add verification failures * verify init code * user init code handle * user init code handle * fix userSessionByIDs * update logging * user agent cookie * browserinfo from request * add DeleteAuthRequest * add static login files to binary * add login statik to build * move generate to separate file and remove statik.go files * remove static dirs from startup.yaml * generate into separate namespaces * merge master * auth request code * auth request type mapping * fix keys * improve tokens * improve register and basic styling * fix ailerons font * improve password reset * add audience to token * all oidc apps as audience * fix test nextStep * fix email texts * remove "not set" * lot of style changes * improve copy to clipboard * fix footer * add cookie handler * remove placeholders * fix compilation after merge * fix auth config * remove comments * typo * use new secrets store * change default pws to match default policy * fixes * add todo * enable login * fix db name * Auth queries (#179) * my usersession * org structure/ auth handlers * working user grant spooler * auth internal user grants * search my project orgs * remove permissions file * my zitadel permissions * my zitadel permissions * remove unused code * authz * app searches in view * token verification * fix user grant load * fix tests * fix tests * read configs * remove unused const * remove todos * env variables * app_name * working authz * search projects * global resourceowner * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * model2 rename * at least it works * check token expiry * search my user grants * remove token table from authz Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix test * fix ports and enable console Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Silvan <silvan.reusser@gmail.com>
973 lines
32 KiB
Go
973 lines
32 KiB
Go
package eventsourcing
|
|
|
|
import (
|
|
"context"
|
|
"github.com/caos/zitadel/internal/id"
|
|
policy_model "github.com/caos/zitadel/internal/policy/model"
|
|
|
|
"github.com/pquerna/otp/totp"
|
|
|
|
req_model "github.com/caos/zitadel/internal/auth_request/model"
|
|
"github.com/caos/zitadel/internal/cache/config"
|
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
|
"github.com/caos/zitadel/internal/crypto"
|
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
|
es_int "github.com/caos/zitadel/internal/eventstore"
|
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
|
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
|
global_model "github.com/caos/zitadel/internal/model"
|
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
|
)
|
|
|
|
type UserEventstore struct {
|
|
es_int.Eventstore
|
|
userCache *UserCache
|
|
idGenerator id.Generator
|
|
PasswordAlg crypto.HashAlgorithm
|
|
InitializeUserCode crypto.Generator
|
|
EmailVerificationCode crypto.Generator
|
|
PhoneVerificationCode crypto.Generator
|
|
PasswordVerificationCode crypto.Generator
|
|
Multifactors global_model.Multifactors
|
|
validateTOTP func(string, string) bool
|
|
}
|
|
|
|
type UserConfig struct {
|
|
es_int.Eventstore
|
|
Cache *config.CacheConfig
|
|
PasswordSaltCost int
|
|
}
|
|
|
|
func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventstore, error) {
|
|
userCache, err := StartCache(conf.Cache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
initCodeGen := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.InitializeUserCode, aesCrypto)
|
|
emailVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.EmailVerificationCode, aesCrypto)
|
|
phoneVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.PhoneVerificationCode, aesCrypto)
|
|
passwordVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.PasswordVerificationCode, aesCrypto)
|
|
aesOtpCrypto, err := crypto.NewAESCrypto(systemDefaults.Multifactors.OTP.VerificationKey)
|
|
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost)
|
|
|
|
return &UserEventstore{
|
|
Eventstore: conf.Eventstore,
|
|
userCache: userCache,
|
|
idGenerator: id.SonyFlakeGenerator,
|
|
InitializeUserCode: initCodeGen,
|
|
EmailVerificationCode: emailVerificationCode,
|
|
PhoneVerificationCode: phoneVerificationCode,
|
|
PasswordVerificationCode: passwordVerificationCode,
|
|
Multifactors: global_model.Multifactors{
|
|
OTP: global_model.OTP{
|
|
CryptoMFA: aesOtpCrypto,
|
|
Issuer: systemDefaults.Multifactors.OTP.Issuer,
|
|
},
|
|
},
|
|
PasswordAlg: passwordAlg,
|
|
validateTOTP: totp.Validate,
|
|
}, nil
|
|
}
|
|
|
|
func (es *UserEventstore) UserByID(ctx context.Context, id string) (*usr_model.User, error) {
|
|
user := es.userCache.getUser(id)
|
|
|
|
query, err := UserByIDQuery(user.AggregateID, user.Sequence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = es_sdk.Filter(ctx, es.FilterEvents, user.AppendEvents, query)
|
|
if err != nil && caos_errs.IsNotFound(err) && user.Sequence == 0 {
|
|
return nil, err
|
|
}
|
|
es.userCache.cacheUser(user)
|
|
return model.UserToModel(user), nil
|
|
}
|
|
|
|
func (es *UserEventstore) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
|
|
query, err := UserByIDQuery(id, sequence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return es.FilterEvents(ctx, query)
|
|
}
|
|
|
|
func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
|
|
user.SetEmailAsUsername()
|
|
if !user.IsValid() {
|
|
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "User is invalid")
|
|
}
|
|
|
|
id, err := es.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
user.AggregateID = id
|
|
|
|
err = user.HashPasswordIfExisting(policy, es.PasswordAlg, true)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
err = user.GenerateInitCodeIfNeeded(es.InitializeUserCode)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
err = user.GeneratePhoneCodeIfNeeded(es.PhoneVerificationCode)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
repoInitCode := model.InitCodeFromModel(user.InitCode)
|
|
repoPhoneCode := model.PhoneCodeFromModel(user.PhoneCode)
|
|
|
|
createAggregates, err := UserCreateAggregate(ctx, es.AggregateCreator(), repoUser, repoInitCode, repoPhoneCode, resourceOwner)
|
|
|
|
return repoUser, createAggregates, err
|
|
}
|
|
|
|
func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy) (*usr_model.User, error) {
|
|
repoUser, aggregates, err := es.PrepareCreateUser(ctx, user, policy, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoUser.AppendEvents, aggregates...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoUser)
|
|
return model.UserToModel(repoUser), nil
|
|
}
|
|
|
|
func (es *UserEventstore) PrepareRegisterUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
|
|
user.SetEmailAsUsername()
|
|
if !user.IsValid() || user.Password == nil || user.SecretString == "" {
|
|
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "user is invalid")
|
|
}
|
|
id, err := es.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
user.AggregateID = id
|
|
|
|
err = user.HashPasswordIfExisting(policy, es.PasswordAlg, false)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
err = user.GenerateEmailCodeIfNeeded(es.EmailVerificationCode)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
repoEmailCode := model.EmailCodeFromModel(user.EmailCode)
|
|
|
|
aggregates, err := UserRegisterAggregate(ctx, es.AggregateCreator(), repoUser, resourceOwner, repoEmailCode)
|
|
return repoUser, aggregates, err
|
|
}
|
|
|
|
func (es *UserEventstore) RegisterUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, resourceOwner string) (*usr_model.User, error) {
|
|
repoUser, createAggregates, err := es.PrepareRegisterUser(ctx, user, policy, resourceOwner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoUser.AppendEvents, createAggregates...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoUser)
|
|
return model.UserToModel(repoUser), nil
|
|
}
|
|
|
|
func (es *UserEventstore) DeactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
|
|
existing, err := es.UserByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existing.IsInactive() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "cant deactivate inactive user")
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
aggregate := UserDeactivateAggregate(es.AggregateCreator(), repoExisting)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.UserToModel(repoExisting), nil
|
|
}
|
|
|
|
func (es *UserEventstore) ReactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
|
|
existing, err := es.UserByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !existing.IsInactive() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do94s", "user must be inactive")
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
aggregate := UserReactivateAggregate(es.AggregateCreator(), repoExisting)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.UserToModel(repoExisting), nil
|
|
}
|
|
|
|
func (es *UserEventstore) LockUser(ctx context.Context, id string) (*usr_model.User, error) {
|
|
existing, err := es.UserByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !existing.IsActive() && !existing.IsInitial() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di83s", "user must be active or initial")
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
aggregate := UserLockAggregate(es.AggregateCreator(), repoExisting)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.UserToModel(repoExisting), nil
|
|
}
|
|
|
|
func (es *UserEventstore) UnlockUser(ctx context.Context, id string) (*usr_model.User, error) {
|
|
existing, err := es.UserByID(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !existing.IsLocked() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dks83", "user must be locked")
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
aggregate := UserUnlockAggregate(es.AggregateCreator(), repoExisting)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.UserToModel(repoExisting), nil
|
|
}
|
|
|
|
func (es *UserEventstore) InitializeUserCodeByID(ctx context.Context, userID string) (*usr_model.InitUserCode, error) {
|
|
if userID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-d8diw", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.InitCode != nil {
|
|
return user.InitCode, nil
|
|
}
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-d8e2", "init code not found")
|
|
}
|
|
|
|
func (es *UserEventstore) CreateInitializeUserCodeByID(ctx context.Context, userID string) (*usr_model.InitUserCode, error) {
|
|
if userID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
initCode := new(usr_model.InitUserCode)
|
|
err = initCode.GenerateInitUserCode(es.InitializeUserCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
repoInitCode := model.InitCodeFromModel(initCode)
|
|
|
|
agg := UserInitCodeAggregate(es.AggregateCreator(), repoUser, repoInitCode)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return model.InitCodeToModel(repoUser.InitCode), nil
|
|
}
|
|
|
|
func (es *UserEventstore) InitCodeSent(ctx context.Context, userID string) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-0posw", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
agg := UserInitCodeSentAggregate(es.AggregateCreator(), repoUser)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) VerifyInitCode(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, userID, verificationCode, password string) error {
|
|
if userID == "" || verificationCode == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "userId or Code empty")
|
|
}
|
|
pw := &usr_model.Password{SecretString: password}
|
|
err := pw.HashPasswordIfExisting(policy, es.PasswordAlg, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing.InitCode == nil {
|
|
return caos_errs.ThrowNotFound(nil, "EVENT-spo9W", "code not found")
|
|
}
|
|
repoPassword := model.PasswordFromModel(pw)
|
|
repoExisting := model.UserFromModel(existing)
|
|
var updateAggregate func(ctx context.Context) (*es_models.Aggregate, error)
|
|
if err := crypto.VerifyCode(existing.InitCode.CreationDate, existing.InitCode.Expiry, existing.InitCode.Code, verificationCode, es.InitializeUserCode); err != nil {
|
|
updateAggregate = InitCodeCheckFailedAggregate(es.AggregateCreator(), repoExisting)
|
|
} else {
|
|
updateAggregate = InitCodeVerifiedAggregate(es.AggregateCreator(), repoExisting, repoPassword)
|
|
}
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) SkipMfaInit(ctx context.Context, userID string) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
agg := SkipMfaAggregate(es.AggregateCreator(), repoUser)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) UserPasswordByID(ctx context.Context, userID string) (*usr_model.Password, error) {
|
|
if userID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.Password != nil {
|
|
return user.Password, nil
|
|
}
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-d8e2", "password not found")
|
|
}
|
|
|
|
func (es *UserEventstore) CheckPassword(ctx context.Context, userID, password string, authRequest *req_model.AuthRequest) error {
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing.Password == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s35Fa", "no password set")
|
|
}
|
|
if err := crypto.CompareHash(existing.Password.SecretCrypto, []byte(password), es.PasswordAlg); err == nil {
|
|
return es.setPasswordCheckResult(ctx, existing, authRequest, PasswordCheckSucceededAggregate)
|
|
}
|
|
if err := es.setPasswordCheckResult(ctx, existing, authRequest, PasswordCheckFailedAggregate); err != nil {
|
|
return err
|
|
}
|
|
return caos_errs.ThrowInvalidArgument(nil, "EVENT-452ad", "invalid password")
|
|
}
|
|
|
|
func (es *UserEventstore) setPasswordCheckResult(ctx context.Context, user *usr_model.User, authRequest *req_model.AuthRequest, check func(*es_models.AggregateCreator, *model.User, *model.AuthRequest) es_sdk.AggregateFunc) error {
|
|
repoUser := model.UserFromModel(user)
|
|
repoAuthRequest := model.AuthRequestFromModel(authRequest)
|
|
agg := check(es.AggregateCreator(), repoUser, repoAuthRequest)
|
|
err := es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) SetOneTimePassword(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, password *usr_model.Password) (*usr_model.Password, error) {
|
|
user, err := es.UserByID(ctx, password.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return es.changedPassword(ctx, user, policy, password.SecretString, true)
|
|
}
|
|
|
|
func (es *UserEventstore) SetPassword(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, userID, code, password string) error {
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if user.PasswordCode == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-65sdr", "reset code not found")
|
|
}
|
|
if err := crypto.VerifyCode(user.PasswordCode.CreationDate, user.PasswordCode.Expiry, user.PasswordCode.Code, code, es.PasswordVerificationCode); err != nil {
|
|
return caos_errs.ThrowPreconditionFailed(err, "EVENT-sd6DF", "code invalid")
|
|
}
|
|
_, err = es.changedPassword(ctx, user, policy, password, false)
|
|
return err
|
|
}
|
|
|
|
func (es *UserEventstore) ChangePassword(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, userID, old, new string) (*usr_model.Password, error) {
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if user.Password == nil {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Fds3s", "user has no password")
|
|
}
|
|
if err := crypto.CompareHash(user.Password.SecretCrypto, []byte(old), es.PasswordAlg); err != nil {
|
|
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-s56a3", "invalid password")
|
|
}
|
|
return es.changedPassword(ctx, user, policy, new, false)
|
|
}
|
|
|
|
func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, password string, onetime bool) (*usr_model.Password, error) {
|
|
pw := &usr_model.Password{SecretString: password}
|
|
err := pw.HashPasswordIfExisting(policy, es.PasswordAlg, onetime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repoPassword := model.PasswordFromModel(pw)
|
|
repoUser := model.UserFromModel(user)
|
|
agg := PasswordChangeAggregate(es.AggregateCreator(), repoUser, repoPassword)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
|
|
return model.PasswordToModel(repoUser.Password), nil
|
|
}
|
|
|
|
func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string, notifyType usr_model.NotificationType) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
passwordCode := new(model.PasswordCode)
|
|
err = es.generatePasswordCode(passwordCode, notifyType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
agg := RequestSetPassword(es.AggregateCreator(), repoUser, passwordCode)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) PasswordCodeSent(ctx context.Context, userID string) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
agg := PasswordCodeSentAggregate(es.AggregateCreator(), repoUser)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) {
|
|
if userID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.Profile != nil {
|
|
return user.Profile, nil
|
|
}
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dk23f", "profile not found")
|
|
}
|
|
|
|
func (es *UserEventstore) ChangeProfile(ctx context.Context, profile *usr_model.Profile) (*usr_model.Profile, error) {
|
|
if !profile.IsValid() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-d82i3", "profile is invalid")
|
|
}
|
|
existing, err := es.UserByID(ctx, profile.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repoExisting := model.UserFromModel(existing)
|
|
repoNew := model.ProfileFromModel(profile)
|
|
|
|
updateAggregate := ProfileChangeAggregate(es.AggregateCreator(), repoExisting, repoNew)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.ProfileToModel(repoExisting.Profile), nil
|
|
}
|
|
|
|
func (es *UserEventstore) EmailByID(ctx context.Context, userID string) (*usr_model.Email, error) {
|
|
if userID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.Email != nil {
|
|
return user.Email, nil
|
|
}
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dki89", "email not found")
|
|
}
|
|
|
|
func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Email) (*usr_model.Email, error) {
|
|
if !email.IsValid() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lco09", "email is invalid")
|
|
}
|
|
existing, err := es.UserByID(ctx, email.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
emailCode, err := email.GenerateEmailCodeIfNeeded(es.EmailVerificationCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
repoNew := model.EmailFromModel(email)
|
|
repoEmailCode := model.EmailCodeFromModel(emailCode)
|
|
|
|
updateAggregates, err := EmailChangeAggregate(ctx, es.AggregateCreator(), repoExisting, repoNew, repoEmailCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregates...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.EmailToModel(repoExisting.Email), nil
|
|
}
|
|
|
|
func (es *UserEventstore) VerifyEmail(ctx context.Context, userID, verificationCode string) error {
|
|
if userID == "" || verificationCode == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "userId or Code empty")
|
|
}
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing.EmailCode == nil {
|
|
return caos_errs.ThrowNotFound(nil, "EVENT-lso9w", "code not found")
|
|
}
|
|
|
|
err = crypto.VerifyCode(existing.EmailCode.CreationDate, existing.EmailCode.Expiry, existing.EmailCode.Code, verificationCode, es.EmailVerificationCode)
|
|
if err == nil {
|
|
return es.setEmailVerifyResult(ctx, existing, EmailVerifiedAggregate)
|
|
}
|
|
if err := es.setEmailVerifyResult(ctx, existing, EmailVerificationFailedAggregate); err != nil {
|
|
return err
|
|
}
|
|
return caos_errs.ThrowInvalidArgument(err, "EVENT-dtGaa", "invalid code")
|
|
}
|
|
|
|
func (es *UserEventstore) setEmailVerifyResult(ctx context.Context, existing *usr_model.User, check func(aggCreator *es_models.AggregateCreator, existing *model.User) es_sdk.AggregateFunc) error {
|
|
repoExisting := model.UserFromModel(existing)
|
|
err := es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, check(es.AggregateCreator(), repoExisting))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoExisting)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userID string) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lco09", "userID missing")
|
|
}
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing.Email == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "no email existing")
|
|
}
|
|
if existing.IsEmailVerified {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "email already verified")
|
|
}
|
|
|
|
emailCode := new(usr_model.EmailCode)
|
|
err = emailCode.GenerateEmailCode(es.EmailVerificationCode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
repoEmailCode := model.EmailCodeFromModel(emailCode)
|
|
updateAggregate := EmailVerificationCodeAggregate(es.AggregateCreator(), repoExisting, repoEmailCode)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) EmailVerificationCodeSent(ctx context.Context, userID string) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-spo0w", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
agg := EmailCodeSentAggregate(es.AggregateCreator(), repoUser)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) PhoneByID(ctx context.Context, userID string) (*usr_model.Phone, error) {
|
|
if userID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9se", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.Phone != nil {
|
|
return user.Phone, nil
|
|
}
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-pos9e", "phone not found")
|
|
}
|
|
|
|
func (es *UserEventstore) ChangePhone(ctx context.Context, phone *usr_model.Phone) (*usr_model.Phone, error) {
|
|
if !phone.IsValid() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9s4", "phone is invalid")
|
|
}
|
|
existing, err := es.UserByID(ctx, phone.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
phoneCode, err := phone.GeneratePhoneCodeIfNeeded(es.PhoneVerificationCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
repoNew := model.PhoneFromModel(phone)
|
|
repoPhoneCode := model.PhoneCodeFromModel(phoneCode)
|
|
|
|
updateAggregate := PhoneChangeAggregate(es.AggregateCreator(), repoExisting, repoNew, repoPhoneCode)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.PhoneToModel(repoExisting.Phone), nil
|
|
}
|
|
|
|
func (es *UserEventstore) VerifyPhone(ctx context.Context, userID, verificationCode string) error {
|
|
if userID == "" || verificationCode == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dsi8s", "userId or Code empty")
|
|
}
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing.PhoneCode == nil {
|
|
return caos_errs.ThrowNotFound(nil, "EVENT-slp0s", "code not found")
|
|
}
|
|
|
|
err = crypto.VerifyCode(existing.PhoneCode.CreationDate, existing.PhoneCode.Expiry, existing.PhoneCode.Code, verificationCode, es.PhoneVerificationCode)
|
|
if err == nil {
|
|
return es.setPhoneVerifyResult(ctx, existing, PhoneVerifiedAggregate)
|
|
}
|
|
if err := es.setPhoneVerifyResult(ctx, existing, PhoneVerificationFailedAggregate); err != nil {
|
|
return err
|
|
}
|
|
return caos_errs.ThrowInvalidArgument(err, "EVENT-dsf4G", "invalid code")
|
|
}
|
|
|
|
func (es *UserEventstore) setPhoneVerifyResult(ctx context.Context, existing *usr_model.User, check func(aggCreator *es_models.AggregateCreator, existing *model.User) es_sdk.AggregateFunc) error {
|
|
repoExisting := model.UserFromModel(existing)
|
|
err := es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, check(es.AggregateCreator(), repoExisting))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoExisting)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) CreatePhoneVerificationCode(ctx context.Context, userID string) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9sw", "userID missing")
|
|
}
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing.Phone == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp9fs", "no phone existing")
|
|
}
|
|
if existing.IsPhoneVerified {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sleis", "phone already verified")
|
|
}
|
|
|
|
phoneCode := new(usr_model.PhoneCode)
|
|
err = phoneCode.GeneratePhoneCode(es.PhoneVerificationCode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoExisting := model.UserFromModel(existing)
|
|
repoPhoneCode := model.PhoneCodeFromModel(phoneCode)
|
|
updateAggregate := PhoneVerificationCodeAggregate(es.AggregateCreator(), repoExisting, repoPhoneCode)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) PhoneVerificationCodeSent(ctx context.Context, userID string) error {
|
|
if userID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0wa", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
agg := PhoneCodeSentAggregate(es.AggregateCreator(), repoUser)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) AddressByID(ctx context.Context, userID string) (*usr_model.Address, error) {
|
|
if userID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di8ws", "userID missing")
|
|
}
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if user.Address != nil {
|
|
return user.Address, nil
|
|
}
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-so9wa", "address not found")
|
|
}
|
|
|
|
func (es *UserEventstore) ChangeAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) {
|
|
existing, err := es.UserByID(ctx, address.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repoExisting := model.UserFromModel(existing)
|
|
repoNew := model.AddressFromModel(address)
|
|
|
|
updateAggregate := AddressChangeAggregate(es.AggregateCreator(), repoExisting, repoNew)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return model.AddressToModel(repoExisting.Address), nil
|
|
}
|
|
|
|
func (es *UserEventstore) AddOTP(ctx context.Context, userID string) (*usr_model.OTP, error) {
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existing.IsOTPReady() {
|
|
return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-do9se", "user has already configured otp")
|
|
}
|
|
key, err := totp.Generate(totp.GenerateOpts{Issuer: es.Multifactors.OTP.Issuer, AccountName: userID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
encryptedSecret, err := crypto.Encrypt([]byte(key.Secret()), es.Multifactors.OTP.CryptoMFA)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
repoOtp := &model.OTP{Secret: encryptedSecret}
|
|
repoExisting := model.UserFromModel(existing)
|
|
updateAggregate := MfaOTPAddAggregate(es.AggregateCreator(), repoExisting, repoOtp)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
otp := model.OTPToModel(repoExisting.OTP)
|
|
otp.Url = key.URL()
|
|
otp.SecretString = key.Secret()
|
|
return otp, nil
|
|
}
|
|
|
|
func (es *UserEventstore) RemoveOTP(ctx context.Context, userID string) error {
|
|
existing, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing.OTP == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0de", "no otp existing")
|
|
}
|
|
repoExisting := model.UserFromModel(existing)
|
|
updateAggregate := MfaOTPRemoveAggregate(es.AggregateCreator(), repoExisting)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoExisting)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) CheckMfaOTPSetup(ctx context.Context, userID, code string) error {
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if user.OTP == nil || user.IsOTPReady() {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "otp not existing or already set up")
|
|
}
|
|
if err := es.verifyMfaOTP(user.OTP, code); err != nil {
|
|
return err
|
|
}
|
|
repoUser := model.UserFromModel(user)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MfaOTPVerifyAggregate(es.AggregateCreator(), repoUser))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) CheckMfaOTP(ctx context.Context, userID, code string, authRequest *req_model.AuthRequest) error {
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !user.IsOTPReady() {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "opt not ready")
|
|
}
|
|
|
|
repoUser := model.UserFromModel(user)
|
|
repoAuthReq := model.AuthRequestFromModel(authRequest)
|
|
var aggregate func(*es_models.AggregateCreator, *model.User, *model.AuthRequest) es_sdk.AggregateFunc
|
|
if err := es.verifyMfaOTP(user.OTP, code); err != nil {
|
|
aggregate = MfaOTPCheckFailedAggregate
|
|
} else {
|
|
aggregate = MfaOTPCheckSucceededAggregate
|
|
}
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, aggregate(es.AggregateCreator(), repoUser, repoAuthReq))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) verifyMfaOTP(otp *usr_model.OTP, code string) error {
|
|
decrypt, err := crypto.DecryptString(otp.Secret, es.Multifactors.OTP.CryptoMFA)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
valid := es.validateTOTP(code, decrypt)
|
|
if !valid {
|
|
return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Invalid code")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (es *UserEventstore) SignOut(ctx context.Context, agentID, userID string) error {
|
|
user, err := es.UserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
repoUser := model.UserFromModel(user)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, SignOutAggregate(es.AggregateCreator(), repoUser, agentID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
es.userCache.cacheUser(repoUser)
|
|
return nil
|
|
}
|