feat: add domain verification notification (#649)

* fix: dont (re)generate client secret with auth type none

* fix(cors): allow Origin from request

* feat: add origin allow list and fix some core issues

* rename migration

* fix UserIDsByDomain

* feat: send email to users after domain claim

* username

* check origin on userinfo

* update oidc pkg

* fix: add migration 1.6

* change username

* change username

* remove unique email aggregate

* change username in mgmt

* search global user by login name

* fix test

* change user search in angular

* fix tests

* merge

* userview in angular

* fix merge

* Update pkg/grpc/management/proto/management.proto

Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>

* Update internal/notification/static/i18n/de.yaml

Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>

* fix

Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
Livio Amstutz
2020-08-27 17:18:23 +02:00
committed by GitHub
parent 3f714679d1
commit 34ec2508d3
73 changed files with 19105 additions and 17845 deletions

View File

@@ -1,10 +1,12 @@
package model
import (
"github.com/caos/zitadel/internal/eventstore/models"
"golang.org/x/text/language"
"time"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/eventstore/models"
req_model "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/model"
)
@@ -17,6 +19,7 @@ type UserView struct {
ResourceOwner string
PasswordSet bool
PasswordChangeRequired bool
UsernameChangeRequired bool
PasswordChanged time.Time
LastLogin time.Time
UserName string

View File

@@ -670,11 +670,11 @@ func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Emai
repoNew := model.EmailFromModel(email)
repoEmailCode := model.EmailCodeFromModel(emailCode)
updateAggregates, err := EmailChangeAggregate(ctx, es.AggregateCreator(), repoExisting, repoNew, repoEmailCode)
updateAggregate, err := EmailChangeAggregate(ctx, es.AggregateCreator(), repoExisting, repoNew, repoEmailCode)
if err != nil {
return nil, err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregates...)
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
if err != nil {
return nil, err
}
@@ -1112,6 +1112,48 @@ func (es *UserEventstore) PrepareDomainClaimed(ctx context.Context, userIDs []st
return aggregates, nil
}
func (es *UserEventstore) DomainClaimedSent(ctx context.Context, userID string) error {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-0posw", "Errors.User.UserIDMissing")
}
user, err := es.UserByID(ctx, userID)
if err != nil {
return err
}
repoUser := model.UserFromModel(user)
agg := DomainClaimedSentAggregate(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) ChangeUsername(ctx context.Context, userID, username string, orgIamPolicy *org_model.OrgIAMPolicy) error {
user, err := es.UserByID(ctx, userID)
if err != nil {
return err
}
oldUsername := user.UserName
user.UserName = username
if err := user.CheckOrgIAMPolicy(orgIamPolicy); err != nil {
return err
}
repoUser := model.UserFromModel(user)
aggregates, err := UsernameChangedAggregates(ctx, es.AggregateCreator(), repoUser, oldUsername, orgIamPolicy.UserLoginMustBeDomain)
if err != nil {
return err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoUser.AppendEvents, aggregates...)
if err != nil {
return err
}
es.userCache.cacheUser(repoUser)
return nil
}
func (es *UserEventstore) generateTemporaryLoginName() (string, error) {
id, err := es.idGenerator.Next()
if err != nil {

View File

@@ -5,7 +5,6 @@ import "github.com/caos/zitadel/internal/eventstore/models"
const (
UserAggregate models.AggregateType = "user"
UserUserNameAggregate models.AggregateType = "user.username"
UserEmailAggregate models.AggregateType = "user.email"
UserAdded models.EventType = "user.added"
UserRegistered models.EventType = "user.selfregistered"
@@ -16,8 +15,6 @@ const (
UserUserNameReserved models.EventType = "user.username.reserved"
UserUserNameReleased models.EventType = "user.username.released"
UserEmailReserved models.EventType = "user.email.reserved"
UserEmailReleased models.EventType = "user.email.released"
UserLocked models.EventType = "user.locked"
UserUnlocked models.EventType = "user.unlocked"
@@ -44,8 +41,9 @@ const (
UserPhoneCodeAdded models.EventType = "user.phone.code.added"
UserPhoneCodeSent models.EventType = "user.phone.code.sent"
UserProfileChanged models.EventType = "user.profile.changed"
UserAddressChanged models.EventType = "user.address.changed"
UserProfileChanged models.EventType = "user.profile.changed"
UserAddressChanged models.EventType = "user.address.changed"
UserUserNameChanged models.EventType = "user.username.changed"
MfaOtpAdded models.EventType = "user.mfa.otp.added"
MfaOtpVerified models.EventType = "user.mfa.otp.verified"
@@ -56,5 +54,6 @@ const (
SignedOut models.EventType = "user.signed.out"
DomainClaimed models.EventType = "user.domain.claimed"
DomainClaimed models.EventType = "user.domain.claimed"
DomainClaimedSent models.EventType = "user.domain.claimed.sent"
)

View File

@@ -134,7 +134,8 @@ func (u *User) AppendEvent(event *es_models.Event) (err error) {
case UserAdded,
UserRegistered,
UserProfileChanged,
DomainClaimed:
DomainClaimed,
UserUserNameChanged:
u.setData(event)
case UserDeactivated:
u.appendDeactivatedEvent()

View File

@@ -33,14 +33,6 @@ func UserUserNameUniqueQuery(userName string) *es_models.SearchQuery {
SetLimit(1)
}
func UserEmailUniqueQuery(email string) *es_models.SearchQuery {
return es_models.NewSearchQuery().
AggregateTypeFilter(model.UserEmailAggregate).
AggregateIDFilter(email).
OrderDesc().
SetLimit(1)
}
func UserAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, user *model.User) (*es_models.Aggregate, error) {
if user == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dis83", "Errors.Internal")
@@ -111,11 +103,7 @@ func UserCreateAggregate(ctx context.Context, aggCreator *es_models.AggregateCre
if err != nil {
return nil, err
}
return []*es_models.Aggregate{
agg,
uniqueAggregates[0],
uniqueAggregates[1],
}, nil
return append(uniqueAggregates, agg), nil
}
func UserRegisterAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, user *model.User, resourceOwner string, initCode *model.InitUserCode, userLoginMustBeDomain bool) ([]*es_models.Aggregate, error) {
@@ -148,11 +136,7 @@ func UserRegisterAggregate(ctx context.Context, aggCreator *es_models.AggregateC
if err != nil {
return nil, err
}
return []*es_models.Aggregate{
agg,
uniqueAggregates[0],
uniqueAggregates[1],
}, nil
return append(uniqueAggregates, agg), nil
}
func getUniqueUserAggregates(ctx context.Context, aggCreator *es_models.AggregateCreator, user *model.User, resourceOwner string, userLoginMustBeDomain bool) ([]*es_models.Aggregate, error) {
@@ -161,13 +145,8 @@ func getUniqueUserAggregates(ctx context.Context, aggCreator *es_models.Aggregat
return nil, err
}
emailAggregate, err := reservedUniqueEmailAggregate(ctx, aggCreator, resourceOwner, user.EmailAddress)
if err != nil {
return nil, err
}
return []*es_models.Aggregate{
userNameAggregate,
emailAggregate,
}, nil
}
func reservedUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, userName string, userLoginMustBeDomain bool) (*es_models.Aggregate, error) {
@@ -206,36 +185,18 @@ func releasedUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.
return aggregate.SetPrecondition(UserUserNameUniqueQuery(username), isEventValidation(aggregate, model.UserUserNameReleased)), nil
}
func reservedUniqueEmailAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, email string) (aggregate *es_models.Aggregate, err error) {
aggregate, err = aggCreator.NewAggregate(ctx, email, model.UserEmailAggregate, model.UserVersion, 0)
if resourceOwner != "" {
aggregate, err = aggCreator.NewAggregate(ctx, email, model.UserEmailAggregate, model.UserVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
}
func changeUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, oldUsername, username string, userLoginMustBeDomain bool) ([]*es_models.Aggregate, error) {
aggregates := make([]*es_models.Aggregate, 2)
var err error
aggregates[0], err = releasedUniqueUserNameAggregate(ctx, aggCreator, resourceOwner, oldUsername)
if err != nil {
return nil, err
}
aggregate, err = aggregate.AppendEvent(model.UserEmailReserved, nil)
aggregates[1], err = reservedUniqueUserNameAggregate(ctx, aggCreator, resourceOwner, username, userLoginMustBeDomain)
if err != nil {
return nil, err
}
return aggregate.SetPrecondition(UserEmailUniqueQuery(email), isEventValidation(aggregate, model.UserEmailReserved)), nil
}
func releasedUniqueEmailAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, email string) (aggregate *es_models.Aggregate, err error) {
aggregate, err = aggCreator.NewAggregate(ctx, email, model.UserEmailAggregate, model.UserVersion, 0)
if resourceOwner != "" {
aggregate, err = aggCreator.NewAggregate(ctx, email, model.UserEmailAggregate, model.UserVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
}
if err != nil {
return nil, err
}
aggregate, err = aggregate.AppendEvent(model.UserEmailReleased, nil)
if err != nil {
return nil, err
}
return aggregate.SetPrecondition(UserEmailUniqueQuery(email), isEventValidation(aggregate, model.UserEmailReleased)), nil
return aggregates, nil
}
func UserDeactivateAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
@@ -401,7 +362,7 @@ func ProfileChangeAggregate(aggCreator *es_models.AggregateCreator, existing *mo
}
}
func EmailChangeAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.User, email *model.Email, code *model.EmailCode) ([]*es_models.Aggregate, error) {
func EmailChangeAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.User, email *model.Email, code *model.EmailCode) (*es_models.Aggregate, error) {
if email == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dki8s", "Errors.Internal")
}
@@ -412,17 +373,6 @@ func EmailChangeAggregate(ctx context.Context, aggCreator *es_models.AggregateCr
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-s90pw", "Errors.NoChangesFound")
}
aggregates := make([]*es_models.Aggregate, 0, 4)
reserveEmailAggregate, err := reservedUniqueEmailAggregate(ctx, aggCreator, "", email.EmailAddress)
if err != nil {
return nil, err
}
aggregates = append(aggregates, reserveEmailAggregate)
releaseEmailAggregate, err := releasedUniqueEmailAggregate(ctx, aggCreator, "", existing.EmailAddress)
if err != nil {
return nil, err
}
aggregates = append(aggregates, releaseEmailAggregate)
agg, err := UserAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
@@ -446,7 +396,7 @@ func EmailChangeAggregate(ctx context.Context, aggCreator *es_models.AggregateCr
return nil, err
}
}
return append(aggregates, agg), nil
return agg, nil
}
func EmailVerifiedAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) es_sdk.AggregateFunc {
@@ -673,7 +623,10 @@ func SignOutAggregates(aggCreator *es_models.AggregateCreator, existingUsers []*
}
func DomainClaimedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existingUser *model.User, tempName string) ([]*es_models.Aggregate, error) {
aggregates := make([]*es_models.Aggregate, 3)
aggregates, err := changeUniqueUserNameAggregate(ctx, aggCreator, existingUser.ResourceOwner, existingUser.UserName, tempName, false)
if err != nil {
return nil, err
}
userAggregate, err := UserAggregateOverwriteContext(ctx, aggCreator, existingUser, existingUser.ResourceOwner, existingUser.AggregateID)
if err != nil {
return nil, err
@@ -682,18 +635,41 @@ func DomainClaimedAggregate(ctx context.Context, aggCreator *es_models.Aggregate
if err != nil {
return nil, err
}
aggregates[0] = userAggregate
releasedUniqueAggregate, err := releasedUniqueUserNameAggregate(ctx, aggCreator, existingUser.ResourceOwner, existingUser.UserName)
return append(aggregates, userAggregate), nil
}
func DomainClaimedSentAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := UserAggregate(ctx, aggCreator, user)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.DomainClaimedSent, nil)
}
}
func UsernameChangedAggregates(ctx context.Context, aggCreator *es_models.AggregateCreator, user *model.User, oldUsername string, userLoginMustBeDomain bool) ([]*es_models.Aggregate, error) {
aggregates, err := changeUniqueUserNameAggregate(ctx, aggCreator, user.ResourceOwner, oldUsername, user.UserName, userLoginMustBeDomain)
if err != nil {
return nil, err
}
aggregates[1] = releasedUniqueAggregate
reservedUniqueAggregate, err := reservedUniqueUserNameAggregate(ctx, aggCreator, existingUser.ResourceOwner, tempName, false)
userAggregate, err := UserAggregate(ctx, aggCreator, user)
if err != nil {
return nil, err
}
aggregates[2] = reservedUniqueAggregate
return aggregates, nil
userAggregate, err = userAggregate.AppendEvent(model.UserUserNameChanged, map[string]interface{}{"userName": user.UserName})
if err != nil {
return nil, err
}
if !userLoginMustBeDomain {
validationQuery := es_models.NewSearchQuery().
AggregateTypeFilter(org_es_model.OrgAggregate).
AggregateIDsFilter()
validation := addUserNameValidation(user.UserName)
userAggregate.SetPrecondition(validationQuery, validation)
}
return append(aggregates, userAggregate), nil
}
func isEventValidation(aggregate *es_models.Aggregate, eventType es_models.EventType) func(...*es_models.Event) error {

View File

@@ -136,7 +136,7 @@ func TestUserCreateAggregate(t *testing.T) {
eventLen: 1,
eventTypes: []models.EventType{model.UserAdded},
checkData: []bool{true},
aggregatesLen: 3,
aggregatesLen: 2,
},
},
{
@@ -166,7 +166,7 @@ func TestUserCreateAggregate(t *testing.T) {
eventLen: 2,
eventTypes: []models.EventType{model.UserAdded, model.InitializedUserCodeAdded},
checkData: []bool{true, true},
aggregatesLen: 3,
aggregatesLen: 2,
},
},
{
@@ -184,7 +184,7 @@ func TestUserCreateAggregate(t *testing.T) {
eventLen: 2,
eventTypes: []models.EventType{model.UserAdded, model.UserPhoneCodeAdded},
checkData: []bool{true, true},
aggregatesLen: 3,
aggregatesLen: 2,
},
},
{
@@ -201,7 +201,7 @@ func TestUserCreateAggregate(t *testing.T) {
eventLen: 2,
eventTypes: []models.EventType{model.UserAdded, model.UserEmailVerified},
checkData: []bool{true, false},
aggregatesLen: 3,
aggregatesLen: 2,
},
},
{
@@ -219,7 +219,7 @@ func TestUserCreateAggregate(t *testing.T) {
eventLen: 2,
eventTypes: []models.EventType{model.UserAdded, model.UserPhoneVerified},
checkData: []bool{true, false},
aggregatesLen: 3,
aggregatesLen: 2,
},
},
}
@@ -231,17 +231,17 @@ func TestUserCreateAggregate(t *testing.T) {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.aggregatesLen, len(aggregates))
}
if !tt.res.wantErr && len(aggregates[0].Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[0].Events))
if !tt.res.wantErr && len(aggregates[1].Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[1].Events))
}
for i := 0; i < tt.res.eventLen; i++ {
if !tt.res.wantErr && aggregates[0].Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], aggregates[0].Events[i].Type.String())
if !tt.res.wantErr && aggregates[1].Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], aggregates[1].Events[i].Type.String())
}
if !tt.res.wantErr && tt.res.checkData[i] && aggregates[0].Events[i].Data == nil {
if !tt.res.wantErr && tt.res.checkData[i] && aggregates[1].Events[i].Data == nil {
t.Errorf("should have data in event")
}
if !tt.res.wantErr && !tt.res.checkData[i] && aggregates[0].Events[i].Data != nil {
if !tt.res.wantErr && !tt.res.checkData[i] && aggregates[1].Events[i].Data != nil {
t.Errorf("should not have data in event")
}
}
@@ -352,14 +352,14 @@ func TestUserRegisterAggregate(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
aggregates, err := UserRegisterAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.new, tt.args.resourceOwner, tt.args.initCode, false)
if tt.res.errFunc == nil && len(aggregates[0].Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[0].Events))
if tt.res.errFunc == nil && len(aggregates[1].Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[1].Events))
}
for i := 0; i < tt.res.eventLen; i++ {
if tt.res.errFunc == nil && aggregates[0].Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], aggregates[0].Events[i].Type.String())
if tt.res.errFunc == nil && aggregates[1].Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], aggregates[1].Events[i].Type.String())
}
if tt.res.errFunc == nil && aggregates[0].Events[i].Data == nil {
if tt.res.errFunc == nil && aggregates[1].Events[i].Data == nil {
t.Errorf("should have data in event")
}
}
@@ -1226,16 +1226,16 @@ func TestChangeEmailAggregate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregates, err := EmailChangeAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.email, tt.args.code)
aggregate, err := EmailChangeAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.email, tt.args.code)
if tt.res.errFunc == nil && len(aggregates[2].Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[1].Events))
if tt.res.errFunc == nil && len(aggregate.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregate.Events))
}
for i := 0; i < tt.res.eventLen; i++ {
if tt.res.errFunc == nil && aggregates[2].Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], aggregates[1].Events[i].Type.String())
if tt.res.errFunc == nil && aggregate.Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], aggregate.Events[i].Type.String())
}
if tt.res.errFunc == nil && aggregates[2].Events[i].Data == nil {
if tt.res.errFunc == nil && aggregate.Events[i].Data == nil {
t.Errorf("should have data in event")
}
}

View File

@@ -36,6 +36,7 @@ type UserView struct {
State int32 `json:"-" gorm:"column:user_state"`
PasswordSet bool `json:"-" gorm:"column:password_set"`
PasswordChangeRequired bool `json:"-" gorm:"column:password_change_required"`
UsernameChangeRequired bool `json:"-" gorm:"column:username_change_required"`
PasswordChanged time.Time `json:"-" gorm:"column:password_change"`
LastLogin time.Time `json:"-" gorm:"column:last_login"`
UserName string `json:"userName" gorm:"column:user_name"`
@@ -72,6 +73,7 @@ func UserFromModel(user *model.UserView) *UserView {
State: int32(user.State),
PasswordSet: user.PasswordSet,
PasswordChangeRequired: user.PasswordChangeRequired,
UsernameChangeRequired: user.UsernameChangeRequired,
PasswordChanged: user.PasswordChanged,
LastLogin: user.LastLogin,
UserName: user.UserName,
@@ -109,6 +111,7 @@ func UserToModel(user *UserView) *model.UserView {
State: model.UserState(user.State),
PasswordSet: user.PasswordSet,
PasswordChangeRequired: user.PasswordChangeRequired,
UsernameChangeRequired: user.UsernameChangeRequired,
PasswordChanged: user.PasswordChanged,
LastLogin: user.LastLogin,
PreferredLoginName: user.PreferredLoginName,
@@ -181,8 +184,13 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) {
case es_model.UserPasswordChanged:
err = u.setPasswordData(event)
case es_model.UserProfileChanged,
es_model.UserAddressChanged,
es_model.DomainClaimed:
es_model.UserAddressChanged:
err = u.setData(event)
case es_model.DomainClaimed:
u.UsernameChangeRequired = true
err = u.setData(event)
case es_model.UserUserNameChanged:
u.UsernameChangeRequired = false
err = u.setData(event)
case es_model.UserEmailChanged:
u.IsEmailVerified = false

View File

@@ -94,9 +94,9 @@ func SearchUsers(db *gorm.DB, table string, req *usr_model.UserSearchRequest) ([
return users, count, nil
}
func GetGlobalUserByEmail(db *gorm.DB, table, email string) (*model.UserView, error) {
func GetGlobalUserByLoginName(db *gorm.DB, table, loginName string) (*model.UserView, error) {
user := new(model.UserView)
query := repository.PrepareGetByKey(table, model.UserSearchKey(usr_model.UserSearchKeyEmail), email)
query := repository.PrepareGetByQuery(table, &model.UserSearchQuery{Key: usr_model.UserSearchKeyLoginNames, Value: loginName, Method: global_model.SearchMethodListContains})
err := query(db, user)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-8uWer", "Errors.User.NotFound")