feat: add auth command side (#107)

* fix: query tests

* fix: use prepare funcs

* fix: go mod

* fix: generate files

* fix(eventstore): tests

* fix(eventstore): rename modifier to editor

* fix(migrations): add cluster migration,
fix(migrations): fix typo of host in clean clsuter

* fix(eventstore): move health

* fix(eventstore): AggregateTypeFilter aggregateType as param

* code quality

* fix: go tests

* feat: add member funcs

* feat: add member model

* feat: add member events

* feat: add member repo model

* fix: better error func testing

* fix: project member funcs

* fix: add tests

* fix: add tests

* feat: implement member requests

* fix: merge master

* fix: merge master

* fix: read existing in project repo

* fix: fix tests

* feat: add internal cache

* feat: add cache mock

* fix: return values of cache mock

* feat: add project role

* fix: add cache config

* fix: add role to eventstore

* fix: use eventstore sdk

* fix: use eventstore sdk

* fix: add project role grpc requests

* fix: fix getby id

* fix: changes for mr

* fix: change value to interface

* feat: add app event creations

* fix: searchmethods

* Update internal/project/model/project_member.go

Co-Authored-By: Silvan <silvan.reusser@gmail.com>

* fix: use get project func

* fix: append events

* fix: check if value is string on equal ignore case

* fix: add changes test

* fix: add go mod

* fix: add some tests

* fix: return err not nil

* fix: return err not nil

* fix: add aggregate funcs and tests

* fix: add oidc aggregate funcs and tests

* fix: add oidc

* fix: add some tests

* fix: tests

* feat: eventstore repository

* fix: remove gorm

* version

* feat: pkg

* feat: eventstore without eventstore-lib

* rename files

* gnueg

* fix: global model

* feat: add global view functions

* feat(eventstore): sdk

* fix(eventstore): rename app to eventstore

* delete empty test

* fix(models): delete unused struct

* feat(eventstore): overwrite context data

* fix: use global sql config

* fix: oidc validation

* fix: generate client secret

* fix: generate client id

* fix: test change app

* fix: deactivate/reactivate application

* 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

* fixes

* Update internal/auth/repository/eventsourcing/eventstore/auth_request.go

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

* Update internal/auth/repository/eventsourcing/eventstore/auth_request.go

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

* Update internal/auth/repository/eventsourcing/eventstore/auth_request.go

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

* Update internal/auth/repository/eventsourcing/eventstore/auth_request.go

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

* extract method usersForUserSelection

* add todo for policy check

* id on auth req

* fix enum name

Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
Livio Amstutz
2020-05-18 12:06:36 +02:00
committed by GitHub
parent 8203f2dad3
commit effc2c23c2
83 changed files with 5732 additions and 1177 deletions

View File

@@ -50,6 +50,37 @@ Auth:
GatewayPort: 50021
CustomHeaders:
- x-zitadel-
Repository:
SearchLimit: 100
Eventstore:
ServiceName: 'authAPI'
Repository:
SQL:
Host: $ZITADEL_EVENTSTORE_HOST
Port: $ZITADEL_EVENTSTORE_PORT
User: 'auth'
Database: 'eventstore'
SSLmode: disable
Cache:
Type: 'fastcache'
Config:
MaxCacheSizeInByte: 10485760 #10mb
AuthRequest:
Host: $ZITADEL_EVENTSTORE_HOST
Port: $ZITADEL_EVENTSTORE_PORT
User: 'auth'
Database: 'auth'
SSLmode: disable
View:
Host: $ZITADEL_EVENTSTORE_HOST
Port: $ZITADEL_EVENTSTORE_PORT
User: 'auth'
Database: 'auth'
SSLmode: disable
Spooler:
ConcurrentTasks: 4
BulkLimit: 100
FailureCountUntilSkip: 5
Login:
# will get port range 5003x

View File

@@ -42,6 +42,11 @@ SystemDefaults:
Issuer: 'Zitadel'
VerificationKey:
EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY
VerificationLifetimes:
PasswordCheck: 240h #10d
MfaInitSkip: 720h #30d
MfaSoftwareCheck: 18h
MfaHardwareCheck: 12h
DefaultPolicies:
Age:
Description: Standard age policy

2
go.mod
View File

@@ -15,7 +15,7 @@ require (
github.com/aws/aws-sdk-go v1.30.25 // indirect
github.com/caos/logging v0.0.1
github.com/cockroachdb/cockroach-go v0.0.0-20200504194139-73ffeee90b62
github.com/envoyproxy/protoc-gen-validate v0.3.0
github.com/envoyproxy/protoc-gen-validate v0.1.0
github.com/ghodss/yaml v1.0.0
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.4.3

2
go.sum
View File

@@ -84,8 +84,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.3.0 h1:Y2J74o+yAfcD8jpqtkLnUqRo+yshLr4eR1WPYGX0cic=
github.com/envoyproxy/protoc-gen-validate v0.3.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=

View File

@@ -0,0 +1,25 @@
package auth
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
)
type TokenVerifier struct {
}
func Start() (v *TokenVerifier) {
return new(TokenVerifier)
}
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
return "", "", "", nil
}
func (v *TokenVerifier) ResolveGrants(ctx context.Context, userID, orgID string) ([]*auth.Grant, error) {
return nil, nil
}
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return "", nil
}

View File

@@ -0,0 +1,15 @@
package repository
import (
"context"
"github.com/caos/zitadel/internal/auth_request/model"
)
type AuthRequestRepository interface {
CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (*model.AuthRequest, error)
AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error)
CheckUsername(ctx context.Context, id, username string) error
VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error
VerifyMfaOTP(ctx context.Context, agentID, authRequestID string, code string, info *model.BrowserInfo) error
}

View File

@@ -0,0 +1,239 @@
package eventstore
import (
"context"
"time"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/auth_request/repository/cache"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type AuthRequestRepo struct {
UserEvents *user_event.UserEventstore
AuthRequests *cache.AuthRequestCache
View *view.View
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
IdGenerator id.Generator
PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
}
type userSessionViewProvider interface {
UserSessionByIDs(string, string) (*view_model.UserSessionView, error)
UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error)
}
type userViewProvider interface {
UserByID(string) (*view_model.UserView, error)
}
func (repo *AuthRequestRepo) Health(ctx context.Context) error {
if err := repo.UserEvents.Health(ctx); err != nil {
return err
}
return repo.AuthRequests.Health(ctx)
}
func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (*model.AuthRequest, error) {
reqID, err := repo.IdGenerator.Next()
if err != nil {
return nil, err
}
request.ID = reqID
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
if err != nil {
return nil, err
}
return request, nil
}
func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error) {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
return nil, err
}
steps, err := repo.nextSteps(request)
if err != nil {
return nil, err
}
request.PossibleSteps = steps
return request, nil
}
func (repo *AuthRequestRepo) CheckUsername(ctx context.Context, id, username string) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
return err
}
user, err := repo.View.UserByUsername(username)
if err != nil {
return err
}
request.UserID = user.ID
return repo.AuthRequests.SaveAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
return err
}
if request.UserID == userID {
return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "user id does not match request id ")
}
return repo.UserEvents.CheckPassword(ctx, userID, password, request.WithCurrentInfo(info))
}
func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, userID string, code string, info *model.BrowserInfo) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, authRequestID)
if err != nil {
return err
}
if request.UserID != userID {
return errors.ThrowPreconditionFailed(nil, "EVENT-ADJ26", "user id does not match request id")
}
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
}
func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.NextStep, error) {
if request == nil {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "request must not be nil")
}
steps := make([]model.NextStep, 0)
if request.UserID == "" {
if request.Prompt != model.PromptNone {
steps = append(steps, &model.LoginStep{})
}
if request.Prompt == model.PromptSelectAccount {
users, err := repo.usersForUserSelection(request)
if err != nil {
return nil, err
}
steps = append(steps, &model.SelectUserStep{Users: users})
}
return steps, nil
}
userSession, err := userSessionByIDs(repo.UserSessionViewProvider, request.AgentID, request.UserID)
if err != nil {
return nil, err
}
user, err := userByID(repo.UserViewProvider, request.UserID)
if err != nil {
return nil, err
}
if !user.PasswordSet {
return append(steps, &model.InitPasswordStep{}), nil
}
if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
return append(steps, &model.PasswordStep{}), nil
}
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
return append(steps, step), nil
}
if user.PasswordChangeRequired {
steps = append(steps, &model.ChangePasswordStep{})
}
if !user.IsEmailVerified {
steps = append(steps, &model.VerifyEMailStep{})
}
if user.PasswordChangeRequired || !user.IsEmailVerified {
return steps, nil
}
//PLANNED: consent step
return append(steps, &model.RedirectToCallbackStep{}), nil
}
func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) ([]model.UserSelection, error) {
userSessions, err := userSessionsByUserAgentID(repo.UserSessionViewProvider, request.AgentID)
if err != nil {
return nil, err
}
users := make([]model.UserSelection, len(userSessions))
for i, session := range userSessions {
users[i] = model.UserSelection{
UserID: session.UserID,
UserName: session.UserName,
UserSessionState: session.State,
}
}
return users, nil
}
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool) {
mfaLevel := request.MfaLevel()
required := user.MfaMaxSetUp < mfaLevel
if required || !repo.mfaSkippedOrSetUp(user) {
return &model.MfaPromptStep{
Required: required,
MfaProviders: user.MfaTypesSetupPossible(mfaLevel),
}, false
}
switch mfaLevel {
default:
fallthrough
case model.MfaLevelSoftware:
if checkVerificationTime(userSession.MfaSoftwareVerification, repo.MfaSoftwareCheckLifeTime) {
return nil, true
}
fallthrough
case model.MfaLevelHardware:
if checkVerificationTime(userSession.MfaHardwareVerification, repo.MfaHardwareCheckLifeTime) {
return nil, true
}
}
return &model.MfaVerificationStep{
MfaProviders: user.MfaTypesAllowed(mfaLevel),
}, false
}
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool {
if user.MfaMaxSetUp >= 0 {
return true
}
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)
}
func checkVerificationTime(verificationTime time.Time, lifetime time.Duration) bool {
return verificationTime.Add(lifetime).After(time.Now().UTC())
}
func userSessionsByUserAgentID(provider userSessionViewProvider, agentID string) ([]*user_model.UserSessionView, error) {
session, err := provider.UserSessionsByAgentID(agentID)
if err != nil {
return nil, err
}
return view_model.UserSessionsToModel(session), nil
}
func userSessionByIDs(provider userSessionViewProvider, agentID, userID string) (*user_model.UserSessionView, error) {
session, err := provider.UserSessionByIDs(agentID, userID)
if err != nil {
return nil, err
}
return view_model.UserSessionToModel(session), nil
}
func userByID(provider userViewProvider, userID string) (*user_model.UserView, error) {
user, err := provider.UserByID(userID)
if err != nil {
return nil, err
}
return view_model.UserToModel(user), nil
}

View File

@@ -0,0 +1,475 @@
package eventstore
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/auth_request/repository/cache"
"github.com/caos/zitadel/internal/errors"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type mockViewNoUserSession struct{}
func (m *mockViewNoUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) {
return nil, errors.ThrowNotFound(nil, "id", "user session not found")
}
func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
type mockViewUserSession struct {
PasswordVerification time.Time
MfaSoftwareVerification time.Time
Users []mockUser
}
type mockUser struct {
UserID string
UserName string
}
func (m *mockViewUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) {
return &view_model.UserSessionView{
PasswordVerification: m.PasswordVerification,
MfaSoftwareVerification: m.MfaSoftwareVerification,
}, nil
}
func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) {
sessions := make([]*view_model.UserSessionView, len(m.Users))
for i, user := range m.Users {
sessions[i] = &view_model.UserSessionView{
UserID: user.UserID,
UserName: user.UserName,
}
}
return sessions, nil
}
type mockViewNoUser struct{}
func (m *mockViewNoUser) UserByID(string) (*view_model.UserView, error) {
return nil, errors.ThrowNotFound(nil, "id", "user not found")
}
type mockViewUser struct {
PasswordSet bool
PasswordChangeRequired bool
IsEmailVerified bool
OTPState int32
MfaMaxSetUp int32
MfaInitSkipped time.Time
}
func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
return &view_model.UserView{
PasswordSet: m.PasswordSet,
PasswordChangeRequired: m.PasswordChangeRequired,
IsEmailVerified: m.IsEmailVerified,
OTPState: m.OTPState,
MfaMaxSetUp: m.MfaMaxSetUp,
MfaInitSkipped: m.MfaInitSkipped,
}, nil
}
func TestAuthRequestRepo_nextSteps(t *testing.T) {
type fields struct {
UserEvents *user_event.UserEventstore
AuthRequests *cache.AuthRequestCache
View *view.View
userSessionViewProvider userSessionViewProvider
userViewProvider userViewProvider
PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
}
type args struct {
request *model.AuthRequest
}
tests := []struct {
name string
fields fields
args args
want []model.NextStep
wantErr func(error) bool
}{
{
"request nil, error",
fields{},
args{nil},
nil,
errors.IsErrorInvalidArgument,
},
{
"user not set, login step",
fields{},
args{&model.AuthRequest{}},
[]model.NextStep{&model.LoginStep{}},
nil,
},
{
"user not set and prompt none, no step",
fields{},
args{&model.AuthRequest{Prompt: model.PromptNone}},
[]model.NextStep{},
nil,
},
{
"user not set, prompt select account and internal error, internal error",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}},
nil,
errors.IsInternal,
},
{
"user not set, prompt select account, login and select account steps",
fields{
userSessionViewProvider: &mockViewUserSession{
Users: []mockUser{
{
"id1",
"username1",
},
{
"id2",
"username2",
},
},
},
},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}},
[]model.NextStep{
&model.LoginStep{},
&model.SelectUserStep{
Users: []model.UserSelection{
{
UserID: "id1",
UserName: "username1",
},
{
UserID: "id2",
UserName: "username2",
},
},
}},
nil,
},
{
"usersession not found, not found error",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
},
args{&model.AuthRequest{UserID: "UserID"}},
nil,
errors.IsNotFound,
},
{
"user not not found, not found error",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewNoUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
nil,
errors.IsNotFound,
},
{
"password not set, init password step",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
[]model.NextStep{&model.InitPasswordStep{}},
nil,
},
{
"password not verified, password check step",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{
PasswordSet: true,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
[]model.NextStep{&model.PasswordStep{}},
nil,
},
{
"mfa not verified, mfa check step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
OTPState: int32(user_model.MFASTATE_READY),
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
[]model.NextStep{&model.MfaVerificationStep{
MfaProviders: []model.MfaType{model.MfaTypeOTP},
}},
nil,
},
{
"password change required and email verified, password change step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
PasswordChangeRequired: true,
IsEmailVerified: true,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
[]model.NextStep{&model.ChangePasswordStep{}},
nil,
},
{
"email not verified and no password change required, mail verification step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
[]model.NextStep{&model.VerifyEMailStep{}},
nil,
},
{
"email not verified and password change required, mail verification step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
PasswordChangeRequired: true,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
[]model.NextStep{&model.ChangePasswordStep{}, &model.VerifyEMailStep{}},
nil,
},
{
"email verified and no password change required, redirect to callback step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &AuthRequestRepo{
UserEvents: tt.fields.UserEvents,
AuthRequests: tt.fields.AuthRequests,
View: tt.fields.View,
UserSessionViewProvider: tt.fields.userSessionViewProvider,
UserViewProvider: tt.fields.userViewProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
}
got, err := repo.nextSteps(tt.args.request)
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
t.Errorf("nextSteps() wrong error = %v", err)
return
}
assert.ElementsMatch(t, got, tt.want)
})
}
}
func TestAuthRequestRepo_mfaChecked(t *testing.T) {
type fields struct {
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
}
type args struct {
userSession *user_model.UserSessionView
request *model.AuthRequest
user *user_model.UserView
}
tests := []struct {
name string
fields fields
args args
want model.NextStep
wantChecked bool
}{
//{
// "required, prompt and false", //TODO: enable when LevelsOfAssurance is checked
// fields{},
// args{
// request: &model.AuthRequest{PossibleLOAs: []model.LevelOfAssurance{}},
// user: &user_model.UserView{
// OTPState: user_model.MFASTATE_READY,
// },
// },
// false,
//},
{
"not set up, prompt and false",
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
MfaMaxSetUp: -1,
},
},
&model.MfaPromptStep{
MfaProviders: []model.MfaType{},
},
false,
},
{
"checked mfa software, true",
fields{
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
OTPState: user_model.MFASTATE_READY,
},
userSession: &user_model.UserSessionView{MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Hour)},
},
nil,
true,
},
{
"not checked, check and false",
fields{
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
OTPState: user_model.MFASTATE_READY,
},
userSession: &user_model.UserSessionView{},
},
&model.MfaVerificationStep{
MfaProviders: []model.MfaType{model.MfaTypeOTP},
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &AuthRequestRepo{
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
}
got, ok := repo.mfaChecked(tt.args.userSession, tt.args.request, tt.args.user)
if ok != tt.wantChecked {
t.Errorf("mfaChecked() checked = %v, want %v", ok, tt.wantChecked)
}
assert.Equal(t, tt.want, got)
})
}
}
func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
type fields struct {
MfaInitSkippedLifeTime time.Duration
}
type args struct {
user *user_model.UserView
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{
"mfa set up, true",
fields{},
args{&user_model.UserView{
MfaMaxSetUp: model.MfaLevelSoftware,
}},
true,
},
{
"mfa skipped active, true",
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{&user_model.UserView{
MfaMaxSetUp: -1,
MfaInitSkipped: time.Now().UTC().Add(-10 * time.Hour),
}},
true,
},
{
"mfa skipped inactive, false",
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{&user_model.UserView{
MfaMaxSetUp: -1,
MfaInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour),
}},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &AuthRequestRepo{
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
}
if got := repo.mfaSkippedOrSetUp(tt.args.user); got != tt.want {
t.Errorf("mfaSkippedOrSetUp() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,26 @@
package eventstore
import (
"context"
"time"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
token_model "github.com/caos/zitadel/internal/token/model"
token_view_model "github.com/caos/zitadel/internal/token/repository/view/model"
)
type TokenRepo struct {
View *view.View
}
func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID, userID string, lifetime time.Duration) (*token_model.Token, error) {
token, err := repo.View.CreateToken(agentID, applicationID, userID, lifetime)
if err != nil {
return nil, err
}
return token_view_model.TokenToModel(token), nil
}
func (repo *TokenRepo) IsTokenValid(ctx context.Context, tokenID string) (bool, error) {
return repo.View.IsTokenValid(tokenID)
}

View File

@@ -0,0 +1,129 @@
package eventstore
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
type UserRepo struct {
UserEvents *user_event.UserEventstore
View *view.View
}
func (repo *UserRepo) Health(ctx context.Context) error {
return repo.UserEvents.Health(ctx)
}
func (repo *UserRepo) Register(ctx context.Context, user *model.User, resourceOwner string) (*model.User, error) {
return repo.UserEvents.RegisterUser(ctx, user, resourceOwner)
}
func (repo *UserRepo) MyProfile(ctx context.Context) (*model.Profile, error) {
return repo.UserEvents.ProfileByID(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) ChangeMyProfile(ctx context.Context, profile *model.Profile) (*model.Profile, error) {
if err := checkIDs(ctx, profile.ObjectRoot); err != nil {
return nil, err
}
return repo.UserEvents.ChangeProfile(ctx, profile)
}
func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) {
return repo.UserEvents.EmailByID(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) ChangeMyEmail(ctx context.Context, email *model.Email) (*model.Email, error) {
if err := checkIDs(ctx, email.ObjectRoot); err != nil {
return nil, err
}
return repo.UserEvents.ChangeEmail(ctx, email)
}
func (repo *UserRepo) VerifyMyEmail(ctx context.Context, code string) error {
return repo.UserEvents.VerifyEmail(ctx, auth.GetCtxData(ctx).UserID, code)
}
func (repo *UserRepo) ResendMyEmailVerificationMail(ctx context.Context) error {
return repo.UserEvents.CreateEmailVerificationCode(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) MyPhone(ctx context.Context) (*model.Phone, error) {
return repo.UserEvents.PhoneByID(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) ChangeMyPhone(ctx context.Context, phone *model.Phone) (*model.Phone, error) {
if err := checkIDs(ctx, phone.ObjectRoot); err != nil {
return nil, err
}
return repo.UserEvents.ChangePhone(ctx, phone)
}
func (repo *UserRepo) VerifyMyPhone(ctx context.Context, code string) error {
return repo.UserEvents.VerifyPhone(ctx, auth.GetCtxData(ctx).UserID, code)
}
func (repo *UserRepo) ResendMyPhoneVerificationCode(ctx context.Context) error {
return repo.UserEvents.CreatePhoneVerificationCode(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) {
return repo.UserEvents.AddressByID(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) ChangeMyAddress(ctx context.Context, address *model.Address) (*model.Address, error) {
if err := checkIDs(ctx, address.ObjectRoot); err != nil {
return nil, err
}
return repo.UserEvents.ChangeAddress(ctx, address)
}
func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) error {
_, err := repo.UserEvents.ChangePassword(ctx, auth.GetCtxData(ctx).UserID, old, new)
return err
}
func (repo *UserRepo) AddMyMfaOTP(ctx context.Context) (*model.OTP, error) {
return repo.UserEvents.AddOTP(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) VerifyMyMfaOTP(ctx context.Context, code string) error {
return repo.UserEvents.CheckMfaOTPSetup(ctx, auth.GetCtxData(ctx).UserID, code)
}
func (repo *UserRepo) RemoveMyMfaOTP(ctx context.Context) error {
return repo.UserEvents.RemoveOTP(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) SkipMfaInit(ctx context.Context, userID string) error {
return repo.UserEvents.SkipMfaInit(ctx, userID)
}
func (repo *UserRepo) RequestPasswordReset(ctx context.Context, username string) error {
user, err := repo.View.UserByUsername(username)
if err != nil {
return err
}
return repo.UserEvents.RequestSetPassword(ctx, user.ID, model.NOTIFICATIONTYPE_EMAIL)
}
func (repo *UserRepo) SetPassword(ctx context.Context, userID, code, password string) error {
return repo.UserEvents.SetPassword(ctx, userID, code, password)
}
func (repo *UserRepo) SignOut(ctx context.Context, agentID, userID string) error {
return repo.UserEvents.SignOut(ctx, agentID, userID)
}
func checkIDs(ctx context.Context, obj es_models.ObjectRoot) error {
if obj.AggregateID != auth.GetCtxData(ctx).UserID {
return errors.ThrowPermissionDenied(nil, "EVENT-kFi9w", "object does not belong to user")
}
return nil
}

View File

@@ -0,0 +1,42 @@
package handler
import (
"time"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/eventstore/spooler"
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
type Configs map[string]*Config
type Config struct {
MinimumCycleDurationMillisecond int
}
type handler struct {
view *view.View
bulkLimit uint64
cycleDuration time.Duration
errorCountUntilSkip uint64
}
type EventstoreRepos struct {
UserEvents *usr_event.UserEventstore
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, repos EventstoreRepos) []spooler.Handler {
return []spooler.Handler{
&User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}},
&UserSession{handler: handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount}, userEvents: repos.UserEvents},
&Token{handler: handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount}},
}
}
func (configs Configs) cycleDuration(viewModel string) time.Duration {
c, ok := configs[viewModel]
if !ok {
return 1 * time.Second
}
return time.Duration(c.MinimumCycleDurationMillisecond) * time.Millisecond
}

View File

@@ -0,0 +1,69 @@
package handler
import (
"encoding/json"
"time"
caos_errs "github.com/caos/zitadel/internal/errors"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
type Token struct {
handler
}
const (
tokenTable = "auth.tokens"
)
func (u *Token) MinimumCycleDuration() time.Duration { return u.cycleDuration }
func (u *Token) ViewModel() string {
return tokenTable
}
func (u *Token) EventQuery() (*models.SearchQuery, error) {
sequence, err := u.view.GetLatestTokenSequence()
if err != nil {
return nil, err
}
return eventsourcing.UserQuery(sequence), nil
}
func (u *Token) Process(event *models.Event) (err error) {
switch event.Type {
case es_model.SignedOut:
id, err := agentIDFromSession(event)
if err != nil {
return err
}
err = u.view.DeleteSessionTokens(id, event.AggregateID, event.Sequence)
if err != nil {
return err
}
return u.view.ProcessedTokenSequence(event.Sequence)
default:
return u.view.ProcessedTokenSequence(event.Sequence)
}
return nil
}
func (u *Token) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-3jkl4", "id", event.AggregateID).WithError(err).Warn("something went wrong in token handler")
return spooler.HandleError(event, err, u.view.GetLatestTokenFailedEvent, u.view.ProcessedTokenFailedEvent, u.view.ProcessedTokenSequence, u.errorCountUntilSkip)
}
func agentIDFromSession(event *models.Event) (string, error) {
session := make(map[string]interface{})
if err := json.Unmarshal(event.Data, session); err != nil {
logging.Log("EVEN-s3bq9").WithError(err).Error("could not unmarshal event data")
return "", caos_errs.ThrowInternal(nil, "MODEL-sd325", "could not unmarshal data")
}
return session["agentID"].(string), nil
}

View File

@@ -0,0 +1,77 @@
package handler
import (
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/user/repository/eventsourcing"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type User struct {
handler
eventstore eventstore.Eventstore
}
const (
userTable = "auth.users"
)
func (p *User) MinimumCycleDuration() time.Duration { return p.cycleDuration }
func (p *User) ViewModel() string {
return userTable
}
func (p *User) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestUserSequence()
if err != nil {
return nil, err
}
return eventsourcing.UserQuery(sequence), nil
}
func (p *User) Process(event *models.Event) (err error) {
user := new(view_model.UserView)
switch event.Type {
case es_model.UserAdded,
es_model.UserRegistered:
user.AppendEvent(event)
case es_model.UserProfileChanged,
es_model.UserEmailChanged,
es_model.UserEmailVerified,
es_model.UserPhoneChanged,
es_model.UserPhoneVerified,
es_model.UserAddressChanged,
es_model.UserDeactivated,
es_model.UserReactivated,
es_model.UserLocked,
es_model.UserUnlocked,
es_model.MfaOtpAdded,
es_model.MfaOtpVerified,
es_model.MfaOtpRemoved:
user, err = p.view.UserByID(event.AggregateID)
if err != nil {
return err
}
err = user.AppendEvent(event)
case es_model.UserDeleted:
err = p.view.DeleteUser(event.AggregateID, event.Sequence)
default:
return p.view.ProcessedUserSequence(event.Sequence)
}
if err != nil {
return err
}
return p.view.PutUser(user)
}
func (p *User) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-is8wa", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler")
return spooler.HandleError(event, err, p.view.GetLatestUserFailedEvent, p.view.ProcessedUserFailedEvent, p.view.ProcessedUserSequence, p.errorCountUntilSkip)
}

View File

@@ -0,0 +1,90 @@
package handler
import (
"context"
"time"
req_model "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/errors"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/user/repository/eventsourcing"
user_events "github.com/caos/zitadel/internal/user/repository/eventsourcing"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type UserSession struct {
handler
userEvents *user_events.UserEventstore
}
const (
userSessionTable = "auth.user_sessions"
)
func (u *UserSession) MinimumCycleDuration() time.Duration { return u.cycleDuration }
func (u *UserSession) ViewModel() string {
return userSessionTable
}
func (u *UserSession) EventQuery() (*models.SearchQuery, error) {
sequence, err := u.view.GetLatestUserSessionSequence()
if err != nil {
return nil, err
}
return eventsourcing.UserQuery(sequence), nil
}
func (u *UserSession) Process(event *models.Event) (err error) {
eventData, err := view_model.UserSessionFromEvent(event)
if err != nil {
return err
}
session, err := u.view.UserSessionByIDs(eventData.UserAgentID, event.AggregateID)
if err != nil {
if !errors.IsNotFound(err) {
return err
}
session = &view_model.UserSessionView{
CreationDate: event.CreationDate,
ResourceOwner: event.ResourceOwner,
UserAgentID: eventData.UserAgentID,
UserID: event.AggregateID,
State: int32(req_model.UserSessionStateActive),
}
}
switch event.Type {
case es_model.UserPasswordCheckSucceeded,
es_model.UserPasswordCheckFailed,
es_model.UserPasswordChanged,
es_model.MfaOtpCheckSucceeded,
es_model.MfaOtpCheckFailed,
es_model.MfaOtpRemoved:
session.AppendEvent(event)
default:
return u.view.ProcessedUserSessionSequence(event.Sequence)
}
if err := u.FillUserInfo(session, event.AggregateID); err != nil {
return err
}
return u.view.PutUserSession(session)
}
func (u *UserSession) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-sdfw3s", "id", event.AggregateID).WithError(err).Warn("something went wrong in user session handler")
return spooler.HandleError(event, err, u.view.GetLatestUserSessionFailedEvent, u.view.ProcessedUserSessionFailedEvent, u.view.ProcessedUserSessionSequence, u.errorCountUntilSkip)
}
func (u *UserSession) FillUserInfo(session *view_model.UserSessionView, id string) error {
user, err := u.userEvents.UserByID(context.Background(), id)
if err != nil {
return err
}
session.UserName = user.UserName
return nil
}

View File

@@ -0,0 +1,93 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/eventstore"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/spooler"
auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/repository/cache"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
es_int "github.com/caos/zitadel/internal/eventstore"
es_spol "github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/id"
es_user "github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
type Config struct {
Eventstore es_int.Config
AuthRequest cache.Config
View types.SQL
Spooler spooler.SpoolerConfig
}
type EsRepository struct {
spooler *es_spol.Spooler
eventstore.UserRepo
eventstore.AuthRequestRepo
eventstore.TokenRepo
}
func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error) {
es, err := es_int.Start(conf.Eventstore)
if err != nil {
return nil, err
}
sqlClient, err := conf.View.Start()
if err != nil {
return nil, err
}
view, err := auth_view.StartView(sqlClient)
if err != nil {
return nil, err
}
user, err := es_user.StartUser(
es_user.UserConfig{
Eventstore: es,
Cache: conf.Eventstore.Cache,
},
systemDefaults,
)
if err != nil {
return nil, err
}
authReq, err := cache.Start(conf.AuthRequest)
if err != nil {
return nil, err
}
repos := handler.EventstoreRepos{UserEvents: user}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, repos)
return &EsRepository{
spool,
eventstore.UserRepo{
UserEvents: user,
View: view,
},
eventstore.AuthRequestRepo{
UserEvents: user,
AuthRequests: authReq,
View: view,
UserSessionViewProvider: view,
UserViewProvider: view,
IdGenerator: id.SonyFlakeGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,
MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration,
MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration,
},
eventstore.TokenRepo{View: view},
}, nil
}
func (repo *EsRepository) Health(ctx context.Context) error {
if err := repo.UserRepo.Health(ctx); err != nil {
return err
}
return repo.AuthRequestRepo.Health(ctx)
}

View File

@@ -0,0 +1,46 @@
package spooler
import (
"context"
"database/sql"
"fmt"
caos_errs "github.com/caos/zitadel/internal/errors"
"time"
"github.com/cockroachdb/cockroach-go/crdb"
)
const (
lockTable = "auth.locks"
lockedUntilKey = "locked_until"
lockerIDKey = "locker_id"
objectTypeKey = "object_type"
)
type locker struct {
dbClient *sql.DB
}
type lock struct {
LockerID string `gorm:"column:locker_id;primary_key"`
LockedUntil time.Time `gorm:"column:locked_until"`
ViewName string `gorm:"column:object_type;primary_key"`
}
func (l *locker) Renew(lockerID, viewModel string, waitTime time.Duration) error {
return crdb.ExecuteTx(context.Background(), l.dbClient, nil, func(tx *sql.Tx) error {
query := fmt.Sprintf("INSERT INTO %s (%s, %s, %s) VALUES ($1, $2, now()+$3) ON CONFLICT (%s) DO UPDATE SET %s = now()+$4, %s = $5 WHERE (locks.%s < now() OR locks.%s = $6) AND locks.%s = $7",
lockTable, objectTypeKey, lockerIDKey, lockedUntilKey, objectTypeKey, lockedUntilKey, lockerIDKey, lockedUntilKey, lockerIDKey, objectTypeKey)
rs, err := tx.Exec(query, viewModel, lockerID, waitTime.Seconds(), waitTime.Seconds(), lockerID, lockerID, viewModel)
if err != nil {
tx.Rollback()
return err
}
if rows, _ := rs.RowsAffected(); rows == 0 {
tx.Rollback()
return caos_errs.ThrowAlreadyExists(nil, "SPOOL-lso0e", "view already locked")
}
return nil
})
}

View File

@@ -0,0 +1,127 @@
package spooler
import (
"database/sql"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
)
type dbMock struct {
db *sql.DB
mock sqlmock.Sqlmock
}
func mockDB(t *testing.T) *dbMock {
mockDB := dbMock{}
var err error
mockDB.db, mockDB.mock, err = sqlmock.New()
if err != nil {
t.Fatalf("error occured while creating stub db %v", err)
}
mockDB.mock.MatchExpectationsInOrder(true)
return &mockDB
}
func (db *dbMock) expectCommit() *dbMock {
db.mock.ExpectCommit()
return db
}
func (db *dbMock) expectRollback() *dbMock {
db.mock.ExpectRollback()
return db
}
func (db *dbMock) expectBegin() *dbMock {
db.mock.ExpectBegin()
return db
}
func (db *dbMock) expectSavepoint() *dbMock {
db.mock.ExpectExec("SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectReleaseSavepoint() *dbMock {
db.mock.ExpectExec("RELEASE SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRenew(lockerID, view string, affectedRows int64) *dbMock {
query := db.mock.
ExpectExec(`INSERT INTO auth\.locks \(object_type, locker_id, locked_until\) VALUES \(\$1, \$2, now\(\)\+\$3\) ON CONFLICT \(object_type\) DO UPDATE SET locked_until = now\(\)\+\$4, locker_id = \$5 WHERE \(locks\.locked_until < now\(\) OR locks\.locker_id = \$6\) AND locks\.object_type = \$7`).
WithArgs(view, lockerID, sqlmock.AnyArg(), sqlmock.AnyArg(), lockerID, lockerID, view).
WillReturnResult(sqlmock.NewResult(1, 1))
if affectedRows == 0 {
query.WillReturnResult(sqlmock.NewResult(0, 0))
} else {
query.WillReturnResult(sqlmock.NewResult(1, affectedRows))
}
return db
}
func Test_locker_Renew(t *testing.T) {
type fields struct {
db *dbMock
}
type args struct {
lockerID string
viewModel string
waitTime time.Duration
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "renew succeeded",
fields: fields{
db: mockDB(t).
expectBegin().
expectSavepoint().
expectRenew("locker", "view", 1).
expectReleaseSavepoint().
expectCommit(),
},
args: args{lockerID: "locker", viewModel: "view", waitTime: 1 * time.Second},
wantErr: false,
},
{
name: "renew now rows updated",
fields: fields{
db: mockDB(t).
expectBegin().
expectSavepoint().
expectRenew("locker", "view", 0).
expectRollback(),
},
args: args{lockerID: "locker", viewModel: "view", waitTime: 1 * time.Second},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &locker{
dbClient: tt.fields.db.db,
}
if err := l.Renew(tt.args.lockerID, tt.args.viewModel, tt.args.waitTime); (err != nil) != tt.wantErr {
t.Errorf("locker.Renew() error = %v, wantErr %v", err, tt.wantErr)
}
if err := tt.fields.db.mock.ExpectationsWereMet(); err != nil {
t.Errorf("not all database expectations met: %v", err)
}
})
}
}

View File

@@ -0,0 +1,30 @@
package spooler
import (
"database/sql"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/spooler"
)
type SpoolerConfig struct {
BulkLimit uint64
FailureCountUntilSkip uint64
ConcurrentTasks int
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sql *sql.DB, repos handler.EventstoreRepos) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentTasks: c.ConcurrentTasks,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, repos),
}
spool := spoolerConfig.New()
spool.Start()
return spool
}

View File

@@ -0,0 +1,17 @@
package view
import (
"github.com/caos/zitadel/internal/view"
)
const (
errTable = "auth.failed_event"
)
func (v *View) saveFailedEvent(failedEvent *view.FailedEvent) error {
return view.SaveFailedEvent(v.Db, errTable, failedEvent)
}
func (v *View) latestFailedEvent(viewName string, sequence uint64) (*view.FailedEvent, error) {
return view.LatestFailedEvent(v.Db, errTable, viewName, sequence)
}

View File

@@ -0,0 +1,17 @@
package view
import (
"github.com/caos/zitadel/internal/view"
)
const (
sequencesTable = "auth.current_sequences"
)
func (v *View) saveCurrentSequence(viewName string, sequence uint64) error {
return view.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence)
}
func (v *View) latestSequence(viewName string) (uint64, error) {
return view.LatestSequence(v.Db, sequencesTable, viewName)
}

View File

@@ -0,0 +1,77 @@
package view
import (
"time"
"github.com/caos/zitadel/internal/token/repository/view"
"github.com/caos/zitadel/internal/token/repository/view/model"
global_view "github.com/caos/zitadel/internal/view"
)
const (
tokenTable = "auth.tokens"
)
func (v *View) TokenByID(tokenID string) (*model.Token, error) {
return view.TokenByID(v.Db, tokenTable, tokenID)
}
func (v *View) IsTokenValid(tokenID string) (bool, error) {
return view.IsTokenValid(v.Db, tokenTable, tokenID)
}
func (v *View) CreateToken(agentID, applicationID, userID string, lifetime time.Duration) (*model.Token, error) {
now := time.Now().UTC()
token := &model.Token{
CreationDate: now,
UserID: userID,
ApplicationID: applicationID,
UserAgentID: agentID,
Expiration: now.Add(lifetime),
}
err := view.PutToken(v.Db, tokenTable, token)
if err != nil {
return nil, err
}
return token, nil
}
func (v *View) PutToken(token *model.Token) error {
err := view.PutToken(v.Db, tokenTable, token)
if err != nil {
return err
}
return v.ProcessedTokenSequence(token.Sequence)
}
func (v *View) DeleteToken(tokenID string, eventSequence uint64) error {
err := view.DeleteToken(v.Db, tokenTable, tokenID)
if err != nil {
return nil
}
return v.ProcessedTokenSequence(eventSequence)
}
func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64) error {
err := view.DeleteTokens(v.Db, tokenTable, agentID, userID)
if err != nil {
return nil
}
return v.ProcessedTokenSequence(eventSequence)
}
func (v *View) GetLatestTokenSequence() (uint64, error) {
return v.latestSequence(tokenTable)
}
func (v *View) ProcessedTokenSequence(eventSequence uint64) error {
return v.saveCurrentSequence(tokenTable, eventSequence)
}
func (v *View) GetLatestTokenFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(tokenTable, sequence)
}
func (v *View) ProcessedTokenFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@@ -0,0 +1,68 @@
package view
import (
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
global_view "github.com/caos/zitadel/internal/view"
)
const (
userTable = "auth.users"
)
func (v *View) UserByID(userID string) (*model.UserView, error) {
return view.UserByID(v.Db, userTable, userID)
}
func (v *View) UserByUsername(userName string) (*model.UserView, error) {
return view.UserByUserName(v.Db, userTable, userName)
}
func (v *View) SearchUsers(request *usr_model.UserSearchRequest) ([]*model.UserView, int, error) {
return view.SearchUsers(v.Db, userTable, request)
}
func (v *View) GetGlobalUserByEmail(email string) (*model.UserView, error) {
return view.GetGlobalUserByEmail(v.Db, userTable, email)
}
func (v *View) IsUserUnique(userName, email string) (bool, error) {
return view.IsUserUnique(v.Db, userTable, userName, email)
}
func (v *View) UserMfas(userID string) ([]*usr_model.MultiFactor, error) {
return view.UserMfas(v.Db, userTable, userID)
}
func (v *View) PutUser(user *model.UserView) error {
err := view.PutUser(v.Db, userTable, user)
if err != nil {
return err
}
return v.ProcessedUserSequence(user.Sequence)
}
func (v *View) DeleteUser(userID string, eventSequence uint64) error {
err := view.DeleteUser(v.Db, userTable, userID)
if err != nil {
return nil
}
return v.ProcessedUserSequence(eventSequence)
}
func (v *View) GetLatestUserSequence() (uint64, error) {
return v.latestSequence(userTable)
}
func (v *View) ProcessedUserSequence(eventSequence uint64) error {
return v.saveCurrentSequence(userTable, eventSequence)
}
func (v *View) GetLatestUserFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(userTable, sequence)
}
func (v *View) ProcessedUserFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@@ -0,0 +1,55 @@
package view
import (
"github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
global_view "github.com/caos/zitadel/internal/view"
)
const (
userSessionTable = "auth.user_sessions"
)
func (v *View) UserSessionByID(sessionID string) (*model.UserSessionView, error) {
return view.UserSessionByID(v.Db, userSessionTable, sessionID)
}
func (v *View) UserSessionByIDs(agentID, userID string) (*model.UserSessionView, error) {
return view.UserSessionByIDs(v.Db, userSessionTable, agentID, userID)
}
func (v *View) UserSessionsByAgentID(agentID string) ([]*model.UserSessionView, error) {
return view.UserSessionsByAgentID(v.Db, userSessionTable, agentID)
}
func (v *View) PutUserSession(userSession *model.UserSessionView) error {
err := view.PutUserSession(v.Db, userSessionTable, userSession)
if err != nil {
return err
}
return v.ProcessedUserSessionSequence(userSession.Sequence)
}
func (v *View) DeleteUserSession(sessionID string, eventSequence uint64) error {
err := view.DeleteUserSession(v.Db, userSessionTable, sessionID)
if err != nil {
return nil
}
return v.ProcessedUserSessionSequence(eventSequence)
}
func (v *View) GetLatestUserSessionSequence() (uint64, error) {
return v.latestSequence(userSessionTable)
}
func (v *View) ProcessedUserSessionSequence(eventSequence uint64) error {
return v.saveCurrentSequence(userSessionTable, eventSequence)
}
func (v *View) GetLatestUserSessionFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(userSessionTable, sequence)
}
func (v *View) ProcessedUserSessionFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@@ -0,0 +1,25 @@
package view
import (
"database/sql"
"github.com/jinzhu/gorm"
)
type View struct {
Db *gorm.DB
}
func StartView(sqlClient *sql.DB) (*View, error) {
gorm, err := gorm.Open("postgres", sqlClient)
if err != nil {
return nil, err
}
return &View{
Db: gorm,
}, nil
}
func (v *View) Health() (err error) {
return v.Db.DB().Ping()
}

View File

@@ -0,0 +1,12 @@
package repository
import (
"context"
)
type Repository interface {
Health(context.Context) error
UserRepository
AuthRequestRepository
TokenRepository
}

View File

@@ -0,0 +1,13 @@
package repository
import (
"context"
"time"
"github.com/caos/zitadel/internal/token/model"
)
type TokenRepository interface {
CreateToken(ctx context.Context, agentID, applicationID, userID string, lifetime time.Duration) (*model.Token, error)
IsTokenValid(ctx context.Context, tokenID string) (bool, error)
}

View File

@@ -0,0 +1,42 @@
package repository
import (
"context"
"github.com/caos/zitadel/internal/user/model"
)
type UserRepository interface {
Register(ctx context.Context, user *model.User, resourceOwner string) (*model.User, error)
myUserRepo
SkipMfaInit(ctx context.Context, userID string) error
RequestPasswordReset(ctx context.Context, username string) error
SetPassword(ctx context.Context, userID, code, password string) error
SignOut(ctx context.Context, agentID, userID string) error
}
type myUserRepo interface {
MyProfile(ctx context.Context) (*model.Profile, error)
ChangeMyProfile(ctx context.Context, profile *model.Profile) (*model.Profile, error)
MyEmail(ctx context.Context) (*model.Email, error)
ChangeMyEmail(ctx context.Context, email *model.Email) (*model.Email, error)
VerifyMyEmail(ctx context.Context, code string) error
ResendMyEmailVerificationMail(ctx context.Context) error
MyPhone(ctx context.Context) (*model.Phone, error)
ChangeMyPhone(ctx context.Context, phone *model.Phone) (*model.Phone, error)
VerifyMyPhone(ctx context.Context, code string) error
ResendMyPhoneVerificationCode(ctx context.Context) error
MyAddress(ctx context.Context) (*model.Address, error)
ChangeMyAddress(ctx context.Context, address *model.Address) (*model.Address, error)
ChangeMyPassword(ctx context.Context, old, new string) error
AddMyMfaOTP(ctx context.Context) (*model.OTP, error)
VerifyMyMfaOTP(ctx context.Context, code string) error
RemoveMyMfaOTP(ctx context.Context) error
}

View File

@@ -0,0 +1,82 @@
package model
import (
"time"
)
type AuthRequest struct {
ID string
AgentID string
CreationDate time.Time
ChangeDate time.Time
BrowserInfo *BrowserInfo
ApplicationID string
CallbackURI string
TransferState string
Prompt Prompt
PossibleLOAs []LevelOfAssurance
UiLocales []string
LoginHint string
PreselectedUserID string
MaxAuthAge uint32
Request Request
levelOfAssurance LevelOfAssurance
projectApplicationIDs []string
UserID string
PossibleSteps []NextStep
}
type Prompt int32
const (
PromptUnspecified Prompt = iota
PromptNone
PromptLogin
PromptConsent
PromptSelectAccount
)
type LevelOfAssurance int
const (
LevelOfAssuranceNone LevelOfAssurance = iota
)
func NewAuthRequest(id, agentID string, info *BrowserInfo, applicationID, callbackURI, transferState string,
prompt Prompt, possibleLOAs []LevelOfAssurance, uiLocales []string, loginHint, preselectedUserID string, maxAuthAge uint32, request Request) *AuthRequest {
return &AuthRequest{
ID: id,
AgentID: agentID,
BrowserInfo: info,
ApplicationID: applicationID,
CallbackURI: callbackURI,
TransferState: transferState,
Prompt: prompt,
PossibleLOAs: possibleLOAs,
UiLocales: uiLocales,
LoginHint: loginHint,
PreselectedUserID: preselectedUserID,
MaxAuthAge: maxAuthAge,
Request: request,
}
}
func (a *AuthRequest) IsValid() bool {
return a.ID != "" &&
a.AgentID != "" &&
a.BrowserInfo != nil && a.BrowserInfo.IsValid() &&
a.ApplicationID != "" &&
a.CallbackURI != "" &&
a.Request != nil && a.Request.IsValid()
}
func (a *AuthRequest) MfaLevel() MfaLevel {
return -1
//PLANNED: check a.PossibleLOAs (and Prompt Login?)
}
func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
a.BrowserInfo = info
return a
}

View File

@@ -0,0 +1,263 @@
package model
import (
"net"
"reflect"
"testing"
)
func TestAuthRequest_IsValid(t *testing.T) {
type fields struct {
ID string
AgentID string
BrowserInfo *BrowserInfo
ApplicationID string
CallbackURI string
Request Request
}
tests := []struct {
name string
fields fields
want bool
}{
{
"missing id, false",
fields{},
false,
},
{
"missing agent id, false",
fields{
ID: "id",
},
false,
},
{
"missing browser info, false",
fields{
ID: "id",
AgentID: "agentID",
},
false,
},
{
"browser info invalid, false",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{},
},
false,
},
{
"missing application id, false",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept language",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
false,
},
{
"missing callback uri, false",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept language",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
ApplicationID: "appID",
},
false,
},
{
"missing request, false",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept language",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
ApplicationID: "appID",
CallbackURI: "schema://callback",
},
false,
},
{
"request invalid, false",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept language",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
ApplicationID: "appID",
CallbackURI: "schema://callback",
Request: &AuthRequestOIDC{},
},
false,
},
{
"valid auth request, true",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept language",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
ApplicationID: "appID",
CallbackURI: "schema://callback",
Request: &AuthRequestOIDC{
Scopes: []string{"openid"},
CodeChallenge: &OIDCCodeChallenge{
Challenge: "challenge",
Method: CodeChallengeMethodS256,
},
},
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &AuthRequest{
ID: tt.fields.ID,
AgentID: tt.fields.AgentID,
BrowserInfo: tt.fields.BrowserInfo,
ApplicationID: tt.fields.ApplicationID,
CallbackURI: tt.fields.CallbackURI,
Request: tt.fields.Request,
}
if got := a.IsValid(); got != tt.want {
t.Errorf("IsValid() = %v, want %v", got, tt.want)
}
})
}
}
func TestAuthRequest_MfaLevel(t *testing.T) {
type fields struct {
Prompt Prompt
PossibleLOAs []LevelOfAssurance
}
tests := []struct {
name string
fields fields
want MfaLevel
}{
//PLANNED: Add / replace test cases when LOA is set
{"-1",
fields{},
-1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &AuthRequest{
Prompt: tt.fields.Prompt,
PossibleLOAs: tt.fields.PossibleLOAs,
}
if got := a.MfaLevel(); got != tt.want {
t.Errorf("MfaLevel() = %v, want %v", got, tt.want)
}
})
}
}
func TestAuthRequest_WithCurrentInfo(t *testing.T) {
type fields struct {
ID string
AgentID string
BrowserInfo *BrowserInfo
}
type args struct {
info *BrowserInfo
}
tests := []struct {
name string
fields fields
args args
want *AuthRequest
}{
{
"unchanged",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "ua",
AcceptLanguage: "de",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
args{
&BrowserInfo{
UserAgent: "ua",
AcceptLanguage: "de",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
&AuthRequest{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "ua",
AcceptLanguage: "de",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
},
{
"changed",
fields{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "ua",
AcceptLanguage: "de",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
args{
&BrowserInfo{
UserAgent: "ua",
AcceptLanguage: "de",
RemoteIP: net.IPv4(16, 12, 20, 19),
},
},
&AuthRequest{
ID: "id",
AgentID: "agentID",
BrowserInfo: &BrowserInfo{
UserAgent: "ua",
AcceptLanguage: "de",
RemoteIP: net.IPv4(16, 12, 20, 19),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &AuthRequest{
ID: tt.fields.ID,
AgentID: tt.fields.AgentID,
BrowserInfo: tt.fields.BrowserInfo,
}
if got := a.WithCurrentInfo(tt.args.info); !reflect.DeepEqual(got, tt.want) {
t.Errorf("WithCurrentInfo() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,15 @@
package model
import "net"
type BrowserInfo struct {
UserAgent string
AcceptLanguage string
RemoteIP net.IP
}
func (i *BrowserInfo) IsValid() bool {
return i.UserAgent != "" &&
i.AcceptLanguage != "" &&
i.RemoteIP != nil && !i.RemoteIP.IsUnspecified()
}

View File

@@ -0,0 +1,17 @@
package model
type OIDCCodeChallenge struct {
Challenge string
Method OIDCCodeChallengeMethod
}
func (c *OIDCCodeChallenge) IsValid() bool {
return c.Challenge != ""
}
type OIDCCodeChallengeMethod int32
const (
CodeChallengeMethodPlain OIDCCodeChallengeMethod = iota
CodeChallengeMethodS256
)

View File

@@ -0,0 +1,117 @@
package model
type NextStep interface {
Type() NextStepType
}
type NextStepType int32
const (
NextStepUnspecified NextStepType = iota
NextStepLogin
NextStepUserSelection
NextStepPassword
NextStepChangePassword
NextStepInitPassword
NextStepVerifyEmail
NextStepMfaPrompt
NextStepMfaVerify
NextStepRedirectToCallback
)
type UserSessionState int32
const (
UserSessionStateActive UserSessionState = iota
UserSessionStateTerminated
)
type LoginStep struct {
NotFound bool
}
func (s *LoginStep) Type() NextStepType {
return NextStepLogin
}
type SelectUserStep struct {
Users []UserSelection
}
func (s *SelectUserStep) Type() NextStepType {
return NextStepUserSelection
}
type UserSelection struct {
UserID string
UserName string
UserSessionState UserSessionState
}
type PasswordStep struct {
FailureCount uint16
}
func (s *PasswordStep) Type() NextStepType {
return NextStepPassword
}
type ChangePasswordStep struct {
}
func (s *ChangePasswordStep) Type() NextStepType {
return NextStepChangePassword
}
type InitPasswordStep struct {
}
func (s *InitPasswordStep) Type() NextStepType {
return NextStepInitPassword
}
type VerifyEMailStep struct {
}
func (s *VerifyEMailStep) Type() NextStepType {
return NextStepVerifyEmail
}
type MfaPromptStep struct {
Required bool
MfaProviders []MfaType
}
func (s *MfaPromptStep) Type() NextStepType {
return NextStepMfaPrompt
}
type MfaVerificationStep struct {
FailureCount uint16
MfaProviders []MfaType
}
func (s *MfaVerificationStep) Type() NextStepType {
return NextStepMfaVerify
}
type RedirectToCallbackStep struct {
}
func (s *RedirectToCallbackStep) Type() NextStepType {
return NextStepRedirectToCallback
}
type MfaType int
const (
MfaTypeOTP MfaType = iota
)
type MfaLevel int
const (
MfaLevelSoftware MfaLevel = iota
MfaLevelHardware
MfaLevelHardwareCertified
)

View File

@@ -0,0 +1,48 @@
package model
type Request interface {
Type() AuthRequestType
IsValid() bool
}
type AuthRequestType int32
const (
AuthRequestTypeOIDC AuthRequestType = iota
AuthRequestTypeSAML
)
type AuthRequestOIDC struct {
Scopes []string
ResponseType OIDCResponseType
Nonce string
CodeChallenge *OIDCCodeChallenge
}
func (a *AuthRequestOIDC) Type() AuthRequestType {
return AuthRequestTypeOIDC
}
func (a *AuthRequestOIDC) IsValid() bool {
return len(a.Scopes) > 0 &&
a.CodeChallenge == nil || a.CodeChallenge != nil && a.CodeChallenge.IsValid()
}
type AuthRequestSAML struct {
}
func (a *AuthRequestSAML) Type() AuthRequestType {
return AuthRequestTypeSAML
}
func (a *AuthRequestSAML) IsValid() bool {
return true
}
type OIDCResponseType int32
const (
OIDCResponseTypeCode OIDCResponseType = iota
OIDCResponseTypeIdToken
OIDCResponseTypeToken
)

View File

@@ -0,0 +1,67 @@
package cache
import (
"context"
"database/sql"
"encoding/json"
"errors"
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/config/types"
caos_errs "github.com/caos/zitadel/internal/errors"
)
type Config struct {
Connection types.SQL
}
type AuthRequestCache struct {
client *sql.DB
}
func Start(conf Config) (*AuthRequestCache, error) {
client, err := sql.Open("postgres", conf.Connection.ConnectionString())
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "SQL-9qBtr", "unable to open database connection")
}
return &AuthRequestCache{
client: client,
}, nil
}
func (c *AuthRequestCache) Health(ctx context.Context) error {
return c.client.PingContext(ctx)
}
func (c *AuthRequestCache) GetAuthRequestByID(_ context.Context, id string) (*model.AuthRequest, error) {
var b []byte
err := c.client.QueryRow("SELECT request FROM auth.authrequests WHERE id = ?", id).Scan(&b)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, caos_errs.ThrowNotFound(err, "CACHE-d24aD", "auth request not found")
}
return nil, caos_errs.ThrowInternal(err, "CACHE-as3kj", "unable to get auth request from database")
}
request := new(model.AuthRequest)
err = json.Unmarshal(b, &request)
if err != nil {
return nil, caos_errs.ThrowInternal(err, "CACHE-2wshg", "unable to unmarshal auth request")
}
return request, nil
}
func (c *AuthRequestCache) SaveAuthRequest(_ context.Context, request *model.AuthRequest) error {
b, err := json.Marshal(request)
if err != nil {
return caos_errs.ThrowInternal(err, "CACHE-32FH9", "unable to marshal auth request")
}
stmt, err := c.client.Prepare("INSERT INTO auth.authrequests (id, request) VALUES($1, $2)")
if err != nil {
return caos_errs.ThrowInternal(err, "CACHE-dswfF", "sql prepare failed")
}
_, err = stmt.Exec(request.ID, b)
if err != nil {
return caos_errs.ThrowInternal(err, "CACHE-sw4af", "unable to save auth request")
}
return nil
}

View File

@@ -0,0 +1,3 @@
package repository
//go:generate mockgen -package mock -destination ./mock/repository.mock.go github.com/caos/zitadel/internal/auth_request/repository Repository

View File

@@ -0,0 +1,12 @@
package mock
import (
"github.com/golang/mock/gomock"
"github.com/caos/zitadel/internal/auth_request/repository"
)
func NewMockAuthRequestRepository(ctrl *gomock.Controller) repository.Repository {
repo := NewMockRepository(ctrl)
return repo
}

View File

@@ -0,0 +1,79 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/zitadel/internal/auth_request/repository (interfaces: Repository)
// Package mock is a generated GoMock package.
package mock
import (
context "context"
model "github.com/caos/zitadel/internal/auth_request/model"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockRepository is a mock of Repository interface
type MockRepository struct {
ctrl *gomock.Controller
recorder *MockRepositoryMockRecorder
}
// MockRepositoryMockRecorder is the mock recorder for MockRepository
type MockRepositoryMockRecorder struct {
mock *MockRepository
}
// NewMockRepository creates a new mock instance
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
mock := &MockRepository{ctrl: ctrl}
mock.recorder = &MockRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
return m.recorder
}
// GetAuthRequestByID mocks base method
func (m *MockRepository) GetAuthRequestByID(arg0 context.Context, arg1 string) (*model.AuthRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAuthRequestByID", arg0, arg1)
ret0, _ := ret[0].(*model.AuthRequest)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAuthRequestByID indicates an expected call of GetAuthRequestByID
func (mr *MockRepositoryMockRecorder) GetAuthRequestByID(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthRequestByID", reflect.TypeOf((*MockRepository)(nil).GetAuthRequestByID), arg0, arg1)
}
// Health mocks base method
func (m *MockRepository) Health(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Health", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Health indicates an expected call of Health
func (mr *MockRepositoryMockRecorder) Health(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockRepository)(nil).Health), arg0)
}
// SaveAuthRequest mocks base method
func (m *MockRepository) SaveAuthRequest(arg0 context.Context, arg1 string) (*model.AuthRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveAuthRequest", arg0, arg1)
ret0, _ := ret[0].(*model.AuthRequest)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SaveAuthRequest indicates an expected call of SaveAuthRequest
func (mr *MockRepositoryMockRecorder) SaveAuthRequest(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveAuthRequest", reflect.TypeOf((*MockRepository)(nil).SaveAuthRequest), arg0, arg1)
}

View File

@@ -0,0 +1,14 @@
package repository
import (
"context"
"github.com/caos/zitadel/internal/auth_request/model"
)
type Repository interface {
Health(ctx context.Context) error
GetAuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error)
SaveAuthRequest(ctx context.Context, id string) (*model.AuthRequest, error)
}

View File

@@ -10,6 +10,7 @@ type SystemDefaults struct {
SecretGenerators SecretGenerators
UserVerificationKey *crypto.KeyConfig
Multifactors MultifactorConfig
VerificationLifetimes VerificationLifetimes
DefaultPolicies DefaultPolicies
IamID string
SetUp types.IAMSetUp
@@ -33,6 +34,13 @@ type OTPConfig struct {
VerificationKey *crypto.KeyConfig
}
type VerificationLifetimes struct {
PasswordCheck types.Duration
MfaInitSkip types.Duration
MfaSoftwareCheck types.Duration
MfaHardwareCheck types.Duration
}
type DefaultPolicies struct {
Age pol.PasswordAgePolicyDefault
Complexity pol.PasswordComplexityPolicyDefault

View File

@@ -27,8 +27,8 @@ func CreateMockEncryptionAlg(ctrl *gomock.Controller) EncryptionAlgorithm {
return mCrypto
}
func createMockHashAlg(t *testing.T) HashAlgorithm {
mCrypto := NewMockHashAlgorithm(gomock.NewController(t))
func CreateMockHashAlg(ctrl *gomock.Controller) HashAlgorithm {
mCrypto := NewMockHashAlgorithm(ctrl)
mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash")
mCrypto.EXPECT().Hash(gomock.Any()).DoAndReturn(
func(code []byte) ([]byte, error) {

View File

@@ -113,7 +113,7 @@ func TestVerifyCode(t *testing.T) {
Crypted: []byte("code"),
},
verificationCode: "code",
g: createMockGenerator(t, createMockHashAlg(t)),
g: createMockGenerator(t, CreateMockHashAlg(gomock.NewController(t))),
},
false,
},
@@ -240,7 +240,7 @@ func Test_verifyHashedCode(t *testing.T) {
args{
cryptoCode: nil,
verificationCode: "",
alg: createMockHashAlg(t),
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
@@ -252,7 +252,7 @@ func Test_verifyHashedCode(t *testing.T) {
Crypted: nil,
},
verificationCode: "",
alg: createMockHashAlg(t),
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
@@ -265,7 +265,7 @@ func Test_verifyHashedCode(t *testing.T) {
Crypted: nil,
},
verificationCode: "",
alg: createMockHashAlg(t),
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
@@ -278,7 +278,7 @@ func Test_verifyHashedCode(t *testing.T) {
Crypted: []byte("code"),
},
verificationCode: "wrong",
alg: createMockHashAlg(t),
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
@@ -291,7 +291,7 @@ func Test_verifyHashedCode(t *testing.T) {
Crypted: []byte("code"),
},
verificationCode: "code",
alg: createMockHashAlg(t),
alg: CreateMockHashAlg(gomock.NewController(t)),
},
false,
},

View File

@@ -100,7 +100,7 @@ func Hash(value []byte, alg HashAlgorithm) (*CryptoValue, error) {
func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error {
if value.Algorithm != alg.Algorithm() {
return errors.ThrowInvalidArgument(nil, "CRYPT-HF32f", "value was hash with a different algorithm")
return errors.ThrowInvalidArgument(nil, "CRYPT-HF32f", "value was hashed with a different algorithm")
}
return alg.CompareHash(value.Crypted, comparer)
}

View File

@@ -10,7 +10,7 @@ import (
type filterFunc func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error)
type appendFunc func(...*es_models.Event) error
type aggregateFunc func(context.Context) (*es_models.Aggregate, error)
type AggregateFunc func(context.Context) (*es_models.Aggregate, error)
type pushFunc func(context.Context, ...*es_models.Aggregate) error
func Filter(ctx context.Context, filter filterFunc, appender appendFunc, query *es_models.SearchQuery) error {
@@ -32,7 +32,7 @@ func Filter(ctx context.Context, filter filterFunc, appender appendFunc, query *
// Push creates the aggregates from aggregater
// and pushes the aggregates to the given pushFunc
// the given events are appended by the appender
func Push(ctx context.Context, push pushFunc, appender appendFunc, aggregaters ...aggregateFunc) (err error) {
func Push(ctx context.Context, push pushFunc, appender appendFunc, aggregaters ...AggregateFunc) (err error) {
if len(aggregaters) < 1 {
return errors.ThrowPreconditionFailed(nil, "SDK-q9wjp", "no aggregaters passed")
}
@@ -73,7 +73,7 @@ func appendAggregates(appender appendFunc, aggregates []*models.Aggregate) error
return nil
}
func makeAggregates(ctx context.Context, aggregaters []aggregateFunc) (aggregates []*models.Aggregate, err error) {
func makeAggregates(ctx context.Context, aggregaters []AggregateFunc) (aggregates []*models.Aggregate, err error) {
aggregates = make([]*models.Aggregate, len(aggregaters))
for i, aggregater := range aggregaters {
aggregates[i], err = aggregater(ctx)

View File

@@ -80,7 +80,7 @@ func TestPush(t *testing.T) {
type args struct {
push pushFunc
appender appendFunc
aggregaters []aggregateFunc
aggregaters []AggregateFunc
}
tests := []struct {
name string
@@ -101,7 +101,7 @@ func TestPush(t *testing.T) {
args: args{
push: nil,
appender: nil,
aggregaters: []aggregateFunc{
aggregaters: []AggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return nil, errors.ThrowInternal(nil, "SDK-Ec5x2", "test err")
},
@@ -116,7 +116,7 @@ func TestPush(t *testing.T) {
return errors.ThrowInternal(nil, "SDK-0g4gW", "test error")
},
appender: nil,
aggregaters: []aggregateFunc{
aggregaters: []AggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{}, nil
},
@@ -133,7 +133,7 @@ func TestPush(t *testing.T) {
appender: func(...*es_models.Event) error {
return errors.ThrowInvalidArgument(nil, "SDK-BDhcT", "test err")
},
aggregaters: []aggregateFunc{
aggregaters: []AggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
},
@@ -150,7 +150,7 @@ func TestPush(t *testing.T) {
appender: func(...*es_models.Event) error {
return nil
},
aggregaters: []aggregateFunc{
aggregaters: []AggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
},
@@ -167,7 +167,7 @@ func TestPush(t *testing.T) {
appender: func(...*es_models.Event) error {
return nil
},
aggregaters: []aggregateFunc{
aggregaters: []AggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
},

3
internal/id/gen_mock.go Normal file
View File

@@ -0,0 +1,3 @@
package id
//go:generate mockgen -package mock -destination ./mock/generator.mock.go github.com/caos/zitadel/internal/id Generator

View File

@@ -0,0 +1,5 @@
package id
type Generator interface {
Next() (string, error)
}

View File

@@ -0,0 +1,48 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/zitadel/internal/id (interfaces: Generator)
// Package mock is a generated GoMock package.
package mock
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockGenerator is a mock of Generator interface
type MockGenerator struct {
ctrl *gomock.Controller
recorder *MockGeneratorMockRecorder
}
// MockGeneratorMockRecorder is the mock recorder for MockGenerator
type MockGeneratorMockRecorder struct {
mock *MockGenerator
}
// NewMockGenerator creates a new mock instance
func NewMockGenerator(ctrl *gomock.Controller) *MockGenerator {
mock := &MockGenerator{ctrl: ctrl}
mock.recorder = &MockGeneratorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockGenerator) EXPECT() *MockGeneratorMockRecorder {
return m.recorder
}
// Next mocks base method
func (m *MockGenerator) Next() (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Next")
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Next indicates an expected call of Next
func (mr *MockGeneratorMockRecorder) Next() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Next", reflect.TypeOf((*MockGenerator)(nil).Next))
}

28
internal/id/sonyflake.go Normal file
View File

@@ -0,0 +1,28 @@
package id
import (
"strconv"
"time"
"github.com/sony/sonyflake"
)
type sonyflakeGenerator struct {
*sonyflake.Sonyflake
}
func (s *sonyflakeGenerator) Next() (string, error) {
id, err := s.NextID()
if err != nil {
return "", err
}
return strconv.FormatUint(id, 10), nil
}
var (
SonyFlakeGenerator = Generator(&sonyflakeGenerator{
sonyflake.NewSonyflake(sonyflake.Settings{
StartTime: time.Date(2019, 4, 29, 0, 0, 0, 0, time.UTC),
}),
})
)

View File

@@ -0,0 +1,87 @@
package eventsourcing
import (
"context"
usr_model "github.com/caos/zitadel/internal/user/model"
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
type UserRepo struct {
UserEvents *usr_event.UserEventstore
}
func (repo *UserRepo) UserByID(ctx context.Context, id string) (project *usr_model.User, err error) {
return repo.UserEvents.UserByID(ctx, id)
}
func (repo *UserRepo) CreateUser(ctx context.Context, user *usr_model.User) (*usr_model.User, error) {
return repo.UserEvents.CreateUser(ctx, user)
}
func (repo *UserRepo) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) {
return repo.UserEvents.RegisterUser(ctx, user, resourceOwner)
}
func (repo *UserRepo) DeactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
return repo.UserEvents.DeactivateUser(ctx, id)
}
func (repo *UserRepo) ReactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
return repo.UserEvents.ReactivateUser(ctx, id)
}
func (repo *UserRepo) LockUser(ctx context.Context, id string) (*usr_model.User, error) {
return repo.UserEvents.LockUser(ctx, id)
}
func (repo *UserRepo) UnlockUser(ctx context.Context, id string) (*usr_model.User, error) {
return repo.UserEvents.UnlockUser(ctx, id)
}
func (repo *UserRepo) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
return repo.UserEvents.SetOneTimePassword(ctx, password)
}
func (repo *UserRepo) RequestSetPassword(ctx context.Context, id string, notifyType usr_model.NotificationType) error {
return repo.UserEvents.RequestSetPassword(ctx, id, notifyType)
}
func (repo *UserRepo) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) {
return repo.UserEvents.ProfileByID(ctx, userID)
}
func (repo *UserRepo) ChangeProfile(ctx context.Context, profile *usr_model.Profile) (*usr_model.Profile, error) {
return repo.UserEvents.ChangeProfile(ctx, profile)
}
func (repo *UserRepo) EmailByID(ctx context.Context, userID string) (*usr_model.Email, error) {
return repo.UserEvents.EmailByID(ctx, userID)
}
func (repo *UserRepo) ChangeEmail(ctx context.Context, email *usr_model.Email) (*usr_model.Email, error) {
return repo.UserEvents.ChangeEmail(ctx, email)
}
func (repo *UserRepo) CreateEmailVerificationCode(ctx context.Context, userID string) error {
return repo.UserEvents.CreateEmailVerificationCode(ctx, userID)
}
func (repo *UserRepo) PhoneByID(ctx context.Context, userID string) (*usr_model.Phone, error) {
return repo.UserEvents.PhoneByID(ctx, userID)
}
func (repo *UserRepo) ChangePhone(ctx context.Context, email *usr_model.Phone) (*usr_model.Phone, error) {
return repo.UserEvents.ChangePhone(ctx, email)
}
func (repo *UserRepo) CreatePhoneVerificationCode(ctx context.Context, userID string) error {
return repo.UserEvents.CreatePhoneVerificationCode(ctx, userID)
}
func (repo *UserRepo) AddressByID(ctx context.Context, userID string) (*usr_model.Address, error) {
return repo.UserEvents.AddressByID(ctx, userID)
}
func (repo *UserRepo) ChangeAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) {
return repo.UserEvents.ChangeAddress(ctx, address)
}

View File

@@ -0,0 +1,35 @@
package eventsourcing
import (
"context"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
grant_event "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing"
)
type UserGrantRepo struct {
UserGrantEvents *grant_event.UserGrantEventStore
}
func (repo *UserGrantRepo) UserGrantByID(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
return repo.UserGrantEvents.UserGrantByID(ctx, grantID)
}
func (repo *UserGrantRepo) AddUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
return repo.UserGrantEvents.AddUserGrant(ctx, grant)
}
func (repo *UserGrantRepo) ChangeUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
return repo.UserGrantEvents.ChangeUserGrant(ctx, grant)
}
func (repo *UserGrantRepo) DeactivateUserGrant(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
return repo.UserGrantEvents.DeactivateUserGrant(ctx, grantID)
}
func (repo *UserGrantRepo) ReactivateUserGrant(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
return repo.UserGrantEvents.ReactivateUserGrant(ctx, grantID)
}
func (repo *UserGrantRepo) RemoveUserGrant(ctx context.Context, grantID string) error {
return repo.UserGrantEvents.RemoveUserGrant(ctx, grantID)
}

View File

@@ -0,0 +1,58 @@
package model
import (
"time"
"github.com/caos/zitadel/internal/model"
)
type Token struct {
ID string
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
UserID string
ApplicationID string
UserAgentID string
Expiration time.Time
Sequence uint64
}
type TokenSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn TokenSearchKey
Asc bool
Queries []*TokenSearchQuery
}
type TokenSearchKey int32
const (
TOKENSEARCHKEY_UNSPECIFIED TokenSearchKey = iota
TOKENSEARCHKEY_TOKEN_ID
TOKENSEARCHKEY_USER_ID
TOKENSEARCHKEY_APPLICATION_ID
TOKENSEARCHKEY_USER_AGENT_ID
TOKENSEARCHKEY_EXPIRATION
TOKENSEARCHKEY_RESOURCEOWNER
)
type TokenSearchQuery struct {
Key TokenSearchKey
Method model.SearchMethod
Value string
}
type TokenSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*Token
}
func (r *TokenSearchRequest) EnsureLimit(limit uint64) {
if r.Limit == 0 || r.Limit > limit {
r.Limit = limit
}
}

View File

@@ -0,0 +1,56 @@
package model
import (
"time"
"github.com/caos/zitadel/internal/token/model"
)
const (
TokenKeyTokenID = "id"
TokenKeyUserID = "user_id"
TokenKeyApplicationID = "application_id"
TokenKeyUserAgentID = "user_agent_id"
TokenKeyExpiration = "expiration"
TokenKeyResourceOwner = "resource_owner"
)
type Token struct {
ID string `json:"-" gorm:"column:id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
UserID string `json:"-" gorm:"column:user_id"`
ApplicationID string `json:"-" gorm:"column:application_id"`
UserAgentID string `json:"-" gorm:"column:user_agent_id"`
Expiration time.Time `json:"-" gorm:"column:expiration"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func TokenFromModel(token *model.Token) *Token {
return &Token{
ID: token.ID,
CreationDate: token.CreationDate,
ChangeDate: token.ChangeDate,
ResourceOwner: token.ResourceOwner,
UserID: token.UserID,
ApplicationID: token.ApplicationID,
UserAgentID: token.UserAgentID,
Expiration: token.Expiration,
Sequence: token.Sequence,
}
}
func TokenToModel(token *Token) *model.Token {
return &model.Token{
ID: token.ID,
CreationDate: token.CreationDate,
ChangeDate: token.ChangeDate,
ResourceOwner: token.ResourceOwner,
UserID: token.UserID,
ApplicationID: token.ApplicationID,
UserAgentID: token.UserAgentID,
Expiration: token.Expiration,
Sequence: token.Sequence,
}
}

View File

@@ -0,0 +1,69 @@
package model
import (
global_model "github.com/caos/zitadel/internal/model"
token_model "github.com/caos/zitadel/internal/token/model"
"github.com/caos/zitadel/internal/view"
)
type TokenSearchRequest token_model.TokenSearchRequest
type TokenSearchQuery token_model.TokenSearchQuery
type TokenSearchKey token_model.TokenSearchKey
func (req TokenSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req TokenSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req TokenSearchRequest) GetSortingColumn() view.ColumnKey {
if req.SortingColumn == token_model.TOKENSEARCHKEY_UNSPECIFIED {
return nil
}
return TokenSearchKey(req.SortingColumn)
}
func (req TokenSearchRequest) GetAsc() bool {
return req.Asc
}
func (req TokenSearchRequest) GetQueries() []view.SearchQuery {
result := make([]view.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = TokenSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req TokenSearchQuery) GetKey() view.ColumnKey {
return TokenSearchKey(req.Key)
}
func (req TokenSearchQuery) GetMethod() global_model.SearchMethod {
return req.Method
}
func (req TokenSearchQuery) GetValue() interface{} {
return req.Value
}
func (key TokenSearchKey) ToColumnName() string {
switch token_model.TokenSearchKey(key) {
case token_model.TOKENSEARCHKEY_TOKEN_ID:
return TokenKeyTokenID
case token_model.TOKENSEARCHKEY_USER_AGENT_ID:
return TokenKeyUserAgentID
case token_model.TOKENSEARCHKEY_USER_ID:
return TokenKeyUserID
case token_model.TOKENSEARCHKEY_APPLICATION_ID:
return TokenKeyApplicationID
case token_model.TOKENSEARCHKEY_EXPIRATION:
return TokenKeyExpiration
case token_model.TOKENSEARCHKEY_RESOURCEOWNER:
return TokenKeyResourceOwner
default:
return ""
}
}

View File

@@ -0,0 +1,48 @@
package view
import (
"time"
"github.com/jinzhu/gorm"
"github.com/caos/zitadel/internal/errors"
token_model "github.com/caos/zitadel/internal/token/model"
"github.com/caos/zitadel/internal/token/repository/view/model"
"github.com/caos/zitadel/internal/view"
)
func TokenByID(db *gorm.DB, table, tokenID string) (*model.Token, error) {
token := new(model.Token)
query := view.PrepareGetByKey(table, model.TokenSearchKey(token_model.TOKENSEARCHKEY_TOKEN_ID), tokenID)
err := query(db, token)
return token, err
}
func IsTokenValid(db *gorm.DB, table, tokenID string) (bool, error) {
token, err := TokenByID(db, table, tokenID)
if err == nil {
return token.Expiration.After(time.Now().UTC()), nil
}
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}
func PutToken(db *gorm.DB, table string, token *model.Token) error {
save := view.PrepareSave(table)
return save(db, token)
}
func DeleteToken(db *gorm.DB, table, tokenID string) error {
delete := view.PrepareDeleteByKey(table, model.TokenSearchKey(token_model.TOKENSEARCHKEY_TOKEN_ID), tokenID)
return delete(db)
}
func DeleteTokens(db *gorm.DB, table, agentID, userID string) error {
delete := view.PrepareDeleteByKeys(table,
view.Key{Key: model.TokenSearchKey(token_model.TOKENSEARCHKEY_USER_AGENT_ID), Value: agentID},
view.Key{Key: model.TokenSearchKey(token_model.TOKENSEARCHKEY_USER_ID), Value: userID},
)
return delete(db)
}

View File

@@ -0,0 +1,61 @@
package model
import (
"time"
req_model "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/model"
)
type UserSessionView struct {
ID string
CreationDate time.Time
ChangeDate time.Time
State req_model.UserSessionState
ResourceOwner string
UserAgentID string
UserID string
UserName string
PasswordVerification time.Time
MfaSoftwareVerification time.Time
MfaHardwareVerification time.Time
Sequence uint64
}
type UserSessionSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn UserSessionSearchKey
Asc bool
Queries []*UserSessionSearchQuery
}
type UserSessionSearchKey int32
const (
USERSESSIONSEARCHKEY_UNSPECIFIED UserSessionSearchKey = iota
USERSESSIONSEARCHKEY_SESSION_ID
USERSESSIONSEARCHKEY_USER_AGENT_ID
USERSESSIONSEARCHKEY_USER_ID
USERSESSIONSEARCHKEY_STATE
USERSESSIONSEARCHKEY_RESOURCEOWNER
)
type UserSessionSearchQuery struct {
Key UserSessionSearchKey
Method model.SearchMethod
Value string
}
type UserSessionSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*UserSessionView
}
func (r *UserSessionSearchRequest) EnsureLimit(limit uint64) {
if r.Limit == 0 || r.Limit > limit {
r.Limit = limit
}
}

View File

@@ -1,8 +1,10 @@
package model
import (
"github.com/caos/zitadel/internal/model"
"time"
req_model "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/model"
)
type UserView struct {
@@ -11,6 +13,8 @@ type UserView struct {
ChangeDate time.Time
State UserState
ResourceOwner string
PasswordSet bool
PasswordChangeRequired bool
PasswordChanged time.Time
LastLogin time.Time
UserName string
@@ -30,6 +34,8 @@ type UserView struct {
Region string
StreetAddress string
OTPState MfaState
MfaMaxSetUp req_model.MfaLevel
MfaInitSkipped time.Time
Sequence uint64
}
@@ -78,3 +84,35 @@ func (r *UserSearchRequest) EnsureLimit(limit uint64) {
func (r *UserSearchRequest) AppendMyOrgQuery(orgID string) {
r.Queries = append(r.Queries, &UserSearchQuery{Key: USERSEARCHKEY_RESOURCEOWNER, Method: model.SEARCHMETHOD_EQUALS, Value: orgID})
}
func (u *UserView) MfaTypesSetupPossible(level req_model.MfaLevel) []req_model.MfaType {
types := make([]req_model.MfaType, 0)
switch level {
case req_model.MfaLevelSoftware:
if u.OTPState != MFASTATE_READY {
types = append(types, req_model.MfaTypeOTP)
}
//PLANNED: add sms
fallthrough
case req_model.MfaLevelHardware:
//PLANNED: add token
}
return types
}
func (u *UserView) MfaTypesAllowed(level req_model.MfaLevel) []req_model.MfaType {
types := make([]req_model.MfaType, 0)
switch level {
default:
fallthrough
case req_model.MfaLevelSoftware:
if u.OTPState == MFASTATE_READY {
types = append(types, req_model.MfaTypeOTP)
}
//PLANNED: add sms
fallthrough
case req_model.MfaLevelHardware:
//PLANNED: add token
}
return types
}

View File

@@ -4,6 +4,10 @@ import (
"context"
"strconv"
"github.com/pquerna/otp/totp"
"github.com/sony/sonyflake"
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"
@@ -14,8 +18,6 @@ import (
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"
"github.com/pquerna/otp/totp"
"github.com/sony/sonyflake"
)
type UserEventstore struct {
@@ -28,6 +30,7 @@ type UserEventstore struct {
PhoneVerificationCode crypto.Generator
PasswordVerificationCode crypto.Generator
Multifactors global_model.Multifactors
validateTOTP func(string, string) bool
}
type UserConfig struct {
@@ -71,6 +74,7 @@ func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventsto
PasswordVerificationCode: passwordVerificationCode,
Multifactors: mfa,
PasswordAlg: passwordAlg,
validateTOTP: totp.Validate,
}, nil
}
@@ -333,31 +337,80 @@ func (es *UserEventstore) UserPasswordByID(ctx context.Context, userID string) (
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, password *usr_model.Password) (*usr_model.Password, error) {
return es.changedPassword(ctx, password, true)
}
func (es *UserEventstore) SetPassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
return es.changedPassword(ctx, password, false)
}
func (es *UserEventstore) changedPassword(ctx context.Context, password *usr_model.Password, onetime bool) (*usr_model.Password, error) {
if !password.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dosi3", "password invalid")
}
user, err := es.UserByID(ctx, password.AggregateID)
if err != nil {
return nil, err
}
return es.changedPassword(ctx, user, password.SecretString, true)
}
err = password.HashPasswordIfExisting(es.PasswordAlg, onetime)
func (es *UserEventstore) SetPassword(ctx context.Context, 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, password, false)
return err
}
func (es *UserEventstore) ChangePassword(ctx context.Context, 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, new, false)
}
func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, password string, onetime bool) (*usr_model.Password, error) {
//TODO: check password policy
secret, err := crypto.Hash([]byte(password), es.PasswordAlg)
if err != nil {
return nil, err
}
repoPassword := &model.Password{Secret: secret, ChangeRequired: onetime}
repoUser := model.UserFromModel(user)
repoPassword := model.PasswordFromModel(password)
agg := PasswordChangeAggregate(es.AggregateCreator(), repoUser, repoPassword)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
if err != nil {
@@ -666,21 +719,6 @@ func (es *UserEventstore) ChangeAddress(ctx context.Context, address *usr_model.
return model.AddressToModel(repoExisting.Address), nil
}
func (es *UserEventstore) OTPByID(ctx context.Context, userID string) (*usr_model.OTP, 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.OTP != nil {
return user.OTP, nil
}
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dps09", "otp not found")
}
func (es *UserEventstore) AddOTP(ctx context.Context, userID string) (*usr_model.OTP, error) {
existing, err := es.UserByID(ctx, userID)
if err != nil {
@@ -731,22 +769,77 @@ func (es *UserEventstore) RemoveOTP(ctx context.Context, userID string) error {
return nil
}
func (es *UserEventstore) CheckMfaOTP(ctx context.Context, userID, code string) error {
existing, err := es.UserByID(ctx, userID)
func (es *UserEventstore) CheckMfaOTPSetup(ctx context.Context, userID, code string) error {
user, err := es.UserByID(ctx, userID)
if err != nil {
return err
}
if existing.OTP == nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0de", "no otp existing")
if user.OTP == nil || user.IsOTPReady() {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "otp not existing or already set up")
}
decrypt, err := crypto.DecryptString(existing.OTP.Secret, es.Multifactors.OTP.CryptoMFA)
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
}
valid := totp.Validate(code, decrypt)
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
}

View File

@@ -2,15 +2,17 @@ package eventsourcing
import (
"encoding/json"
"time"
"github.com/golang/mock/gomock"
"github.com/sony/sonyflake"
mock_cache "github.com/caos/zitadel/internal/cache/mock"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore/mock"
es_models "github.com/caos/zitadel/internal/eventstore/models"
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/golang/mock/gomock"
"github.com/sony/sonyflake"
"time"
)
func GetMockedEventstore(ctrl *gomock.Controller, mockEs *mock.MockEventstore) *UserEventstore {
@@ -38,10 +40,7 @@ func GetMockedEventstoreWithPw(ctrl *gomock.Controller, mockEs *mock.MockEventst
}
if password {
es.PasswordVerificationCode = GetMockPwGenerator(ctrl)
hash := crypto.NewMockHashAlgorithm(ctrl)
hash.EXPECT().Hash(gomock.Any()).Return(nil, nil)
hash.EXPECT().Algorithm().Return("bcrypt")
es.PasswordAlg = hash
es.PasswordAlg = crypto.CreateMockHashAlg(ctrl)
}
return es
}
@@ -174,8 +173,10 @@ func GetMockManipulateUserWithPhoneCodeGen(ctrl *gomock.Controller, user model.U
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
data, _ := json.Marshal(user)
code, _ := json.Marshal(user.PasswordCode)
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserPasswordCodeAdded, Data: code},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
@@ -394,29 +395,48 @@ func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
return GetMockedEventstore(ctrl, mockEs)
}
func GetMockManipulateUserWithOTP(ctrl *gomock.Controller) *UserEventstore {
func GetMockManipulateUserWithOTP(ctrl *gomock.Controller, decrypt, verified bool) *UserEventstore {
user := model.User{
Profile: &model.Profile{
UserName: "UserName",
},
}
otp := model.OTP{Secret: &crypto.CryptoValue{
otp := model.OTP{
Secret: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code"),
}}
},
}
dataUser, _ := json.Marshal(user)
dataOtp, _ := json.Marshal(otp)
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.MfaOtpAdded, Data: dataOtp},
}
if verified {
events = append(events, &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.MfaOtpVerified})
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
es := GetMockedEventstore(ctrl, mockEs)
if !decrypt {
return es
}
enc := crypto.NewMockEncryptionAlgorithm(ctrl)
enc.EXPECT().Algorithm().Return("enc")
enc.EXPECT().Encrypt(gomock.Any()).Return(nil, nil)
enc.EXPECT().EncryptionKeyID().Return("id")
enc.EXPECT().DecryptionKeyIDs().Return([]string{"id"})
enc.EXPECT().DecryptString(gomock.Any(), gomock.Any()).Return("code", nil)
es.Multifactors = global_model.Multifactors{OTP: global_model.OTP{
Issuer: "Issuer",
CryptoMFA: enc,
}}
return es
}
func GetMockManipulateUserNoEvents(ctrl *gomock.Controller) *UserEventstore {

View File

@@ -2,14 +2,19 @@ package eventsourcing
import (
"context"
"net"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/caos/zitadel/internal/api/auth"
req_model "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/user/model"
repo_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/golang/mock/gomock"
"testing"
"time"
)
func TestUserByID(t *testing.T) {
@@ -1025,15 +1030,16 @@ func TestSetOneTimePassword(t *testing.T) {
}
}
func TestSetPassword(t *testing.T) {
func TestCheckPassword(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
password *model.Password
userID string
password string
authRequest *req_model.AuthRequest
}
type res struct {
password *model.Password
errFunc func(err error) bool
}
tests := []struct {
@@ -1042,22 +1048,40 @@ func TestSetPassword(t *testing.T) {
res res
}{
{
name: "create pw",
name: "check pw ok",
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}}),
es: GetMockManipulateUserWithPasswordAndEmailCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
Password: &repo_model.Password{Secret: &crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
Crypted: []byte("password"),
}},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
userID: "userID",
password: "password",
authRequest: &req_model.AuthRequest{
ID: "id",
AgentID: "agentID",
BrowserInfo: &req_model.BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept langugage",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
res: res{
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, ChangeRequired: false},
},
},
res: res{},
},
{
name: "empty userid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: ""}, SecretString: "Password"},
userID: "",
password: "password",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
@@ -1068,22 +1092,308 @@ func TestSetPassword(t *testing.T) {
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
userID: "userID",
password: "password",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
{
name: "no password",
args: args{
es: GetMockUserByIDOK(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
password: "password",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid password",
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
Password: &repo_model.Password{Secret: &crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
Crypted: []byte("password"),
}},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
password: "wrong password",
authRequest: &req_model.AuthRequest{
ID: "id",
AgentID: "agentID",
BrowserInfo: &req_model.BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept langugage",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
},
res: res{
errFunc: caos_errs.IsErrorInvalidArgument,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.SetPassword(tt.args.ctx, tt.args.password)
err := tt.args.es.CheckPassword(tt.args.ctx, tt.args.userID, tt.args.password, tt.args.authRequest)
if tt.res.errFunc == nil && err != nil {
t.Errorf("result has error: %v", err)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v", err)
}
})
}
}
func TestSetPassword(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
userID string
code string
password string
}
type res struct {
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "create pw",
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
PasswordCode: &repo_model.PasswordCode{Code: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code"),
}},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
password: "password",
},
res: res{},
},
{
name: "empty userid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "",
code: "code",
password: "password",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing user not found",
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
password: "password",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
{
name: "no passcode",
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
password: "password",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid passcode",
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
PasswordCode: &repo_model.PasswordCode{Code: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc2",
KeyID: "id",
Crypted: []byte("code2"),
}},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
password: "password",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.es.SetPassword(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.password)
if tt.res.errFunc == nil && err != nil {
t.Errorf("result has error: %v", err)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v", err)
}
})
}
}
func TestChangePassword(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
userID string
old string
new string
}
type res struct {
password string
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "change pw",
args: args{
es: GetMockManipulateUserWithPasswordAndEmailCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
Password: &repo_model.Password{Secret: &crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
Crypted: []byte("old"),
}},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
old: "old",
new: "new",
},
res: res{
password: "new",
},
},
{
name: "empty userid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "",
old: "old",
new: "new",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing user not found",
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
old: "old",
new: "new",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
{
name: "no password",
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
old: "old",
new: "new",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid password",
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
Password: &repo_model.Password{Secret: &crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
Crypted: []byte("older"),
}},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
old: "old",
new: "new",
},
res: res{
errFunc: caos_errs.IsErrorInvalidArgument,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ChangePassword(tt.args.ctx, tt.args.userID, tt.args.old, tt.args.new)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result.ChangeRequired != false {
t.Errorf("should not be one time")
if tt.res.errFunc == nil && string(result.SecretCrypto.Crypted) != tt.res.password {
t.Errorf("got wrong result crypted: expected: %v, actual: %v ", tt.res.password, result.SecretString)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v", err)
@@ -2036,69 +2346,6 @@ func TestChangeAddress(t *testing.T) {
}
}
func TestOTPByID(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
existing *model.User
}
type res struct {
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "get by id, ok",
args: args{
es: GetMockManipulateUserWithOTP(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
},
},
{
name: "empty userid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing user not found",
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.OTPByID(tt.args.ctx, tt.args.existing.AggregateID)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result == nil {
t.Errorf("got wrong result change required: actual: %v ", result)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestAddOTP(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
@@ -2168,6 +2415,245 @@ func TestAddOTP(t *testing.T) {
}
}
func TestCheckMfaOTPSetup(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
userID string
code string
}
type res struct {
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "setup ok",
args: args{
es: func() *UserEventstore {
es := GetMockManipulateUserWithOTP(ctrl, true, false)
es.validateTOTP = func(string, string) bool {
return true
}
return es
}(),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "id",
code: "code",
},
res: res{},
},
{
name: "wrong code",
args: args{
es: func() *UserEventstore {
es := GetMockManipulateUserWithOTP(ctrl, true, false)
es.validateTOTP = func(string, string) bool {
return false
}
return es
}(),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "id",
code: "code",
},
res: res{
errFunc: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "empty userid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
code: "code",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "empty code",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing user not found",
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
{
name: "user has no otp",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.es.CheckMfaOTPSetup(tt.args.ctx, tt.args.userID, tt.args.code)
if tt.res.errFunc == nil && err != nil {
t.Errorf("result should not get err")
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestCheckMfaOTP(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
userID string
code string
authRequest *req_model.AuthRequest
}
type res struct {
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "check ok",
args: args{
es: func() *UserEventstore {
es := GetMockManipulateUserWithOTP(ctrl, true, true)
es.validateTOTP = func(string, string) bool {
return true
}
return es
}(),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "id",
code: "code",
authRequest: &req_model.AuthRequest{
ID: "id",
AgentID: "agentID",
BrowserInfo: &req_model.BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept langugage",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
},
res: res{},
},
{
name: "wrong code",
args: args{
es: func() *UserEventstore {
es := GetMockManipulateUserWithOTP(ctrl, true, true)
es.validateTOTP = func(string, string) bool {
return false
}
return es
}(),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "id",
code: "code",
authRequest: &req_model.AuthRequest{
ID: "id",
AgentID: "agentID",
BrowserInfo: &req_model.BrowserInfo{
UserAgent: "user agent",
AcceptLanguage: "accept langugage",
RemoteIP: net.IPv4(29, 4, 20, 19),
},
},
},
res: res{},
},
{
name: "empty userid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
code: "code",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "empty code",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing user not found",
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
{
name: "user has no otp",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.es.CheckMfaOTP(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.authRequest)
if tt.res.errFunc == nil && err != nil {
t.Errorf("result should not get err, got : %v", err)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestRemoveOTP(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
@@ -2186,7 +2672,7 @@ func TestRemoveOTP(t *testing.T) {
{
name: "remove ok",
args: args{
es: GetMockManipulateUserWithOTP(ctrl),
es: GetMockManipulateUserWithOTP(ctrl, false, true),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
},
@@ -2238,80 +2724,3 @@ func TestRemoveOTP(t *testing.T) {
})
}
}
func TestCheckOTP(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
userID string
code string
}
type res struct {
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "empty userid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
code: "code",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "empty code",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing user not found",
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
{
name: "user has no otp",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
code: "code",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.es.CheckMfaOTP(tt.args.ctx, tt.args.userID, tt.args.code)
if tt.res.errFunc == nil && err != nil {
t.Errorf("result should not get err")
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -0,0 +1,35 @@
package model
import (
"net"
"github.com/caos/zitadel/internal/auth_request/model"
)
type AuthRequest struct {
ID string `json:"id,omitempty"`
UserAgentID string `json:"userAgentID,omitempty"`
*BrowserInfo
}
func AuthRequestFromModel(request *model.AuthRequest) *AuthRequest {
return &AuthRequest{
ID: request.ID,
UserAgentID: request.AgentID,
BrowserInfo: BrowserInfoFromModel(request.BrowserInfo),
}
}
type BrowserInfo struct {
UserAgent string `json:"userAgent,omitempty"`
AcceptLanguage string `json:"acceptLanguage,omitempty"`
RemoteIP net.IP `json:"remoteIP,omitempty"`
}
func BrowserInfoFromModel(info *model.BrowserInfo) *BrowserInfo {
return &BrowserInfo{
UserAgent: info.UserAgent,
AcceptLanguage: info.AcceptLanguage,
RemoteIP: info.RemoteIP,
}
}

View File

@@ -26,6 +26,8 @@ const (
UserPasswordChanged models.EventType = "user.password.changed"
UserPasswordCodeAdded models.EventType = "user.password.code.added"
UserPasswordCodeSent models.EventType = "user.password.code.sent"
UserPasswordCheckSucceeded models.EventType = "user.password.check.succeeded"
UserPasswordCheckFailed models.EventType = "user.password.check.failed"
UserEmailChanged models.EventType = "user.email.changed"
UserEmailVerified models.EventType = "user.email.verified"
@@ -43,5 +45,9 @@ const (
MfaOtpAdded models.EventType = "user.mfa.otp.added"
MfaOtpVerified models.EventType = "user.mfa.otp.verified"
MfaOtpRemoved models.EventType = "user.mfa.otp.removed"
MfaOtpCheckSucceeded models.EventType = "user.mfa.otp.check.succeeded"
MfaOtpCheckFailed models.EventType = "user.mfa.otp.check.failed"
MfaInitSkipped models.EventType = "user.mfa.init.skipped"
SignedOut models.EventType = "user.signed.out"
)

View File

@@ -5,6 +5,7 @@ import (
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
@@ -167,6 +168,25 @@ func PasswordChangeAggregate(aggCreator *es_models.AggregateCreator, existing *m
}
}
func PasswordCheckSucceededAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, check *model.AuthRequest) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := UserAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.UserPasswordCheckSucceeded, check)
}
}
func PasswordCheckFailedAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, check *model.AuthRequest) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := UserAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.UserPasswordCheckFailed, check)
}
}
func RequestSetPassword(aggCreator *es_models.AggregateCreator, existing *model.User, request *model.PasswordCode) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if request == nil {
@@ -338,6 +358,32 @@ func MfaOTPVerifyAggregate(aggCreator *es_models.AggregateCreator, existing *mod
}
}
func MfaOTPCheckSucceededAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, authReq *model.AuthRequest) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if authReq == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-sd5DA", "authReq must not be nil")
}
agg, err := UserAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.MfaOtpCheckSucceeded, authReq)
}
}
func MfaOTPCheckFailedAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, authReq *model.AuthRequest) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if authReq == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-64sd6", "authReq must not be nil")
}
agg, err := UserAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.MfaOtpCheckFailed, authReq)
}
}
func MfaOTPRemoveAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := UserAggregate(ctx, aggCreator, existing)
@@ -347,3 +393,13 @@ func MfaOTPRemoveAggregate(aggCreator *es_models.AggregateCreator, existing *mod
return agg.AppendEvent(model.MfaOtpRemoved, nil)
}
}
func SignOutAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, agentID string) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := UserAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.SignedOut, map[string]interface{}{"agentID": agentID})
}
}

View File

@@ -2,12 +2,15 @@ package model
import (
"encoding/json"
"time"
"github.com/caos/logging"
req_model "github.com/caos/zitadel/internal/auth_request/model"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/user/model"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"time"
)
const (
@@ -28,6 +31,8 @@ type UserView struct {
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
State int32 `json:"-" gorm:"column:user_state"`
PasswordSet bool `json:"-" gorm:"column:password_set"`
PasswordChangeRequired bool `json:"-" gorm:"column:password_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"`
@@ -47,6 +52,8 @@ type UserView struct {
Region string `json:"region" gorm:"column:region"`
StreetAddress string `json:"streetAddress" gorm:"column:street_address"`
OTPState int32 `json:"-" gorm:"column:otp_state"`
MfaMaxSetUp int32 `json:"-" gorm:"column:mfa_max_set_up"`
MfaInitSkipped time.Time `json:"-" gorm:"column:mfa_init_skipped"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
@@ -57,6 +64,8 @@ func UserFromModel(user *model.UserView) *UserView {
CreationDate: user.CreationDate,
ResourceOwner: user.ResourceOwner,
State: int32(user.State),
PasswordSet: user.PasswordSet,
PasswordChangeRequired: user.PasswordChangeRequired,
PasswordChanged: user.PasswordChanged,
LastLogin: user.LastLogin,
UserName: user.UserName,
@@ -76,6 +85,8 @@ func UserFromModel(user *model.UserView) *UserView {
Region: user.Region,
StreetAddress: user.StreetAddress,
OTPState: int32(user.OTPState),
MfaMaxSetUp: int32(user.MfaMaxSetUp),
MfaInitSkipped: user.MfaInitSkipped,
Sequence: user.Sequence,
}
}
@@ -87,6 +98,8 @@ func UserToModel(user *UserView) *model.UserView {
CreationDate: user.CreationDate,
ResourceOwner: user.ResourceOwner,
State: model.UserState(user.State),
PasswordSet: user.PasswordSet,
PasswordChangeRequired: user.PasswordChangeRequired,
PasswordChanged: user.PasswordChanged,
LastLogin: user.LastLogin,
UserName: user.UserName,
@@ -106,6 +119,8 @@ func UserToModel(user *UserView) *model.UserView {
Region: user.Region,
StreetAddress: user.StreetAddress,
OTPState: model.MfaState(user.OTPState),
MfaMaxSetUp: req_model.MfaLevel(user.MfaMaxSetUp),
MfaInitSkipped: user.MfaInitSkipped,
Sequence: user.Sequence,
}
}
@@ -118,43 +133,52 @@ func UsersToModel(users []*UserView) []*model.UserView {
return result
}
func (p *UserView) AppendEvent(event *models.Event) (err error) {
p.ChangeDate = event.CreationDate
p.Sequence = event.Sequence
func (u *UserView) AppendEvent(event *models.Event) (err error) {
u.ChangeDate = event.CreationDate
u.Sequence = event.Sequence
switch event.Type {
case es_model.UserAdded,
es_model.UserRegistered:
p.CreationDate = event.CreationDate
p.setRootData(event)
err = p.setData(event)
u.CreationDate = event.CreationDate
u.setRootData(event)
err = u.setData(event)
if err != nil {
return err
}
err = u.setPasswordData(event)
case es_model.UserPasswordChanged:
err = u.setPasswordData(event)
case es_model.UserProfileChanged,
es_model.UserAddressChanged:
err = p.setData(event)
err = u.setData(event)
case es_model.UserEmailChanged:
p.IsEmailVerified = false
err = p.setData(event)
u.IsEmailVerified = false
err = u.setData(event)
case es_model.UserEmailVerified:
p.IsEmailVerified = true
u.IsEmailVerified = true
case es_model.UserPhoneChanged:
p.IsPhoneVerified = false
err = p.setData(event)
u.IsPhoneVerified = false
err = u.setData(event)
case es_model.UserPhoneVerified:
p.IsPhoneVerified = true
u.IsPhoneVerified = true
case es_model.UserDeactivated:
p.State = int32(model.USERSTATE_INACTIVE)
u.State = int32(model.USERSTATE_INACTIVE)
case es_model.UserReactivated,
es_model.UserUnlocked:
p.State = int32(model.USERSTATE_ACTIVE)
u.State = int32(model.USERSTATE_ACTIVE)
case es_model.UserLocked:
p.State = int32(model.USERSTATE_LOCKED)
u.State = int32(model.USERSTATE_LOCKED)
case es_model.MfaOtpAdded:
p.OTPState = int32(model.MFASTATE_NOTREADY)
u.OTPState = int32(model.MFASTATE_NOTREADY)
case es_model.MfaOtpVerified:
p.OTPState = int32(model.MFASTATE_READY)
u.OTPState = int32(model.MFASTATE_READY)
u.MfaInitSkipped = time.Time{}
case es_model.MfaOtpRemoved:
p.OTPState = int32(model.MFASTATE_UNSPECIFIED)
u.OTPState = int32(model.MFASTATE_UNSPECIFIED)
case es_model.MfaInitSkipped:
u.MfaInitSkipped = event.CreationDate
}
p.ComputeObject()
u.ComputeObject()
return err
}
@@ -165,12 +189,23 @@ func (u *UserView) setRootData(event *models.Event) {
func (u *UserView) setData(event *models.Event) error {
if err := json.Unmarshal(event.Data, u); err != nil {
logging.Log("EVEN-lso9e").WithError(err).Error("could not unmarshal event data")
logging.Log("MODEL-lso9e").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(nil, "MODEL-8iows", "could not unmarshal data")
}
return nil
}
func (u *UserView) setPasswordData(event *models.Event) error {
password := new(es_model.Password)
if err := json.Unmarshal(event.Data, password); err != nil {
logging.Log("MODEL-sdw4r").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(nil, "MODEL-6jhsw", "could not unmarshal data")
}
u.PasswordSet = password.Secret != nil
u.PasswordChangeRequired = password.ChangeRequired
return nil
}
func (u *UserView) ComputeObject() {
if u.State == int32(model.USERSTATE_UNSPECIFIED) || u.State == int32(model.USERSTATE_INITIAL) {
if u.IsEmailVerified {
@@ -179,4 +214,7 @@ func (u *UserView) ComputeObject() {
u.State = int32(model.USERSTATE_INITIAL)
}
}
if u.OTPState == int32(model.MFASTATE_READY) {
u.MfaMaxSetUp = int32(req_model.MfaLevelSoftware)
}
}

View File

@@ -0,0 +1,91 @@
package model
import (
"encoding/json"
"time"
"github.com/caos/logging"
req_model "github.com/caos/zitadel/internal/auth_request/model"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/user/model"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
const (
UserSessionKeySessionID = "id"
UserSessionKeyUserAgentID = "user_agent_id"
UserSessionKeyUserID = "user_id"
UserSessionKeyState = "state"
UserSessionKeyResourceOwner = "resource_owner"
)
type UserSessionView struct {
ID string `json:"-" gorm:"column:id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
State int32 `json:"-" gorm:"column:state"`
UserAgentID string `json:"userAgentID" gorm:"column:user_agent_id"`
UserID string `json:"userID" gorm:"column:user_id"`
UserName string `json:"userName" gorm:"column:user_name"`
PasswordVerification time.Time `json:"-" gorm:"column:password_verification"`
MfaSoftwareVerification time.Time `json:"-" gorm:"column:mfa_software_verification"`
MfaHardwareVerification time.Time `json:"-" gorm:"column:mfa_hardware_verification"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func UserSessionFromEvent(event *models.Event) (*UserSessionView, error) {
v := new(UserSessionView)
if err := json.Unmarshal(event.Data, v); err != nil {
logging.Log("EVEN-lso9e").WithError(err).Error("could not unmarshal event data")
return nil, caos_errs.ThrowInternal(nil, "MODEL-sd325", "could not unmarshal data")
}
return v, nil
}
func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView {
return &model.UserSessionView{
ID: userSession.ID,
ChangeDate: userSession.ChangeDate,
CreationDate: userSession.CreationDate,
ResourceOwner: userSession.ResourceOwner,
State: req_model.UserSessionState(userSession.State),
UserAgentID: userSession.UserAgentID,
UserID: userSession.UserID,
UserName: userSession.UserName,
PasswordVerification: userSession.PasswordVerification,
MfaSoftwareVerification: userSession.MfaSoftwareVerification,
MfaHardwareVerification: userSession.MfaHardwareVerification,
Sequence: userSession.Sequence,
}
}
func UserSessionsToModel(userSessions []*UserSessionView) []*model.UserSessionView {
result := make([]*model.UserSessionView, len(userSessions))
for i, s := range userSessions {
result[i] = UserSessionToModel(s)
}
return result
}
func (v *UserSessionView) AppendEvent(event *models.Event) {
v.ChangeDate = event.CreationDate
switch event.Type {
case es_model.UserPasswordCheckSucceeded:
v.PasswordVerification = event.CreationDate
case es_model.UserPasswordCheckFailed,
es_model.UserPasswordChanged:
v.PasswordVerification = time.Time{}
case es_model.MfaOtpCheckSucceeded:
v.MfaSoftwareVerification = event.CreationDate
case es_model.MfaOtpCheckFailed,
es_model.MfaOtpRemoved:
v.MfaSoftwareVerification = time.Time{}
case es_model.SignedOut:
v.PasswordVerification = time.Time{}
v.MfaSoftwareVerification = time.Time{}
v.State = int32(req_model.UserSessionStateTerminated)
}
}

View File

@@ -0,0 +1,67 @@
package model
import (
global_model "github.com/caos/zitadel/internal/model"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/view"
)
type UserSessionSearchRequest usr_model.UserSessionSearchRequest
type UserSessionSearchQuery usr_model.UserSessionSearchQuery
type UserSessionSearchKey usr_model.UserSessionSearchKey
func (req UserSessionSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req UserSessionSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req UserSessionSearchRequest) GetSortingColumn() view.ColumnKey {
if req.SortingColumn == usr_model.USERSESSIONSEARCHKEY_UNSPECIFIED {
return nil
}
return UserSessionSearchKey(req.SortingColumn)
}
func (req UserSessionSearchRequest) GetAsc() bool {
return req.Asc
}
func (req UserSessionSearchRequest) GetQueries() []view.SearchQuery {
result := make([]view.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = UserSessionSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req UserSessionSearchQuery) GetKey() view.ColumnKey {
return UserSessionSearchKey(req.Key)
}
func (req UserSessionSearchQuery) GetMethod() global_model.SearchMethod {
return req.Method
}
func (req UserSessionSearchQuery) GetValue() interface{} {
return req.Value
}
func (key UserSessionSearchKey) ToColumnName() string {
switch usr_model.UserSessionSearchKey(key) {
case usr_model.USERSESSIONSEARCHKEY_SESSION_ID:
return UserSessionKeySessionID
case usr_model.USERSESSIONSEARCHKEY_USER_AGENT_ID:
return UserSessionKeyUserAgentID
case usr_model.USERSESSIONSEARCHKEY_USER_ID:
return UserSessionKeyUserID
case usr_model.USERSESSIONSEARCHKEY_STATE:
return UserSessionKeyState
case usr_model.USERSESSIONSEARCHKEY_RESOURCEOWNER:
return UserSessionKeyResourceOwner
default:
return ""
}
}

View File

@@ -0,0 +1,90 @@
package model
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
es_models "github.com/caos/zitadel/internal/eventstore/models"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
func now() time.Time {
return time.Now().UTC().Round(1 * time.Second)
}
func TestAppendEvent(t *testing.T) {
type args struct {
event *es_models.Event
userView *UserSessionView
}
tests := []struct {
name string
args args
result *UserSessionView
}{
{
name: "append password check succeeded event",
args: args{
event: &es_models.Event{CreationDate: now(), Type: es_model.UserPasswordCheckSucceeded},
userView: &UserSessionView{},
},
result: &UserSessionView{ChangeDate: now(), PasswordVerification: now()},
},
{
name: "append password check failed event",
args: args{
event: &es_models.Event{CreationDate: now(), Type: es_model.UserPasswordCheckFailed},
userView: &UserSessionView{PasswordVerification: now()},
},
result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}},
},
{
name: "append password changed event",
args: args{
event: &es_models.Event{CreationDate: now(), Type: es_model.UserPasswordChanged},
userView: &UserSessionView{PasswordVerification: now()},
},
result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}},
},
{
name: "append otp check succeeded event",
args: args{
event: &es_models.Event{CreationDate: now(), Type: es_model.MfaOtpCheckSucceeded},
userView: &UserSessionView{},
},
result: &UserSessionView{ChangeDate: now(), MfaSoftwareVerification: now()},
},
{
name: "append otp check failed event",
args: args{
event: &es_models.Event{CreationDate: now(), Type: es_model.MfaOtpCheckFailed},
userView: &UserSessionView{MfaSoftwareVerification: now()},
},
result: &UserSessionView{ChangeDate: now(), MfaSoftwareVerification: time.Time{}},
},
{
name: "append otp removed event",
args: args{
event: &es_models.Event{CreationDate: now(), Type: es_model.MfaOtpCheckFailed},
userView: &UserSessionView{MfaSoftwareVerification: now()},
},
result: &UserSessionView{ChangeDate: now(), MfaSoftwareVerification: time.Time{}},
},
{
name: "append otp removed event",
args: args{
event: &es_models.Event{CreationDate: now(), Type: es_model.SignedOut},
userView: &UserSessionView{PasswordVerification: now(), MfaSoftwareVerification: now()},
},
result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}, MfaSoftwareVerification: time.Time{}, State: 1},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.userView.AppendEvent(tt.args.event)
assert.Equal(t, tt.result, tt.args.userView)
})
}
}

View File

@@ -2,10 +2,13 @@ package model
import (
"encoding/json"
"testing"
"time"
"github.com/caos/zitadel/internal/crypto"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/user/model"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"testing"
)
func mockUserData(user *es_model.User) []byte {
@@ -13,6 +16,11 @@ func mockUserData(user *es_model.User) []byte {
return data
}
func mockPasswordData(password *es_model.Password) []byte {
data, _ := json.Marshal(password)
return data
}
func mockProfileData(profile *es_model.Profile) []byte {
data, _ := json.Marshal(profile)
return data
@@ -33,7 +41,7 @@ func mockAddressData(address *es_model.Address) []byte {
return data
}
func getFullUser() *es_model.User {
func getFullUser(password *es_model.Password) *es_model.User {
return &es_model.User{
Profile: &es_model.Profile{
UserName: "UserName",
@@ -49,6 +57,7 @@ func getFullUser() *es_model.User {
Address: &es_model.Address{
Country: "Country",
},
Password: password,
}
}
@@ -65,11 +74,43 @@ func TestUserAppendEvent(t *testing.T) {
{
name: "append added user event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserAdded, ResourceOwner: "OrgID", Data: mockUserData(getFullUser())},
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserAdded, ResourceOwner: "OrgID", Data: mockUserData(getFullUser(nil))},
user: &UserView{},
},
result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_INITIAL)},
},
{
name: "append added user with password event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserAdded, ResourceOwner: "OrgID", Data: mockUserData(getFullUser(&es_model.Password{Secret: &crypto.CryptoValue{}}))},
user: &UserView{},
},
result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_INITIAL), PasswordSet: true},
},
{
name: "append added user with password but change required event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserAdded, ResourceOwner: "OrgID", Data: mockUserData(getFullUser(&es_model.Password{ChangeRequired: true, Secret: &crypto.CryptoValue{}}))},
user: &UserView{},
},
result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_INITIAL), PasswordSet: true, PasswordChangeRequired: true},
},
{
name: "append password change event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserPasswordChanged, ResourceOwner: "OrgID", Data: mockPasswordData(&es_model.Password{Secret: &crypto.CryptoValue{}})},
user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", IsEmailVerified: true, Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_ACTIVE)},
},
result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", IsEmailVerified: true, Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_ACTIVE), PasswordSet: true},
},
{
name: "append password change with change required event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserPasswordChanged, ResourceOwner: "OrgID", Data: mockPasswordData(&es_model.Password{ChangeRequired: true, Secret: &crypto.CryptoValue{}})},
user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", IsEmailVerified: true, Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_ACTIVE)},
},
result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", IsEmailVerified: true, Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_ACTIVE), PasswordSet: true, PasswordChangeRequired: true},
},
{
name: "append change user profile event",
args: args{
@@ -174,6 +215,14 @@ func TestUserAppendEvent(t *testing.T) {
},
result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_ACTIVE), OTPState: int32(model.MFASTATE_UNSPECIFIED)},
},
{
name: "append mfa init skipped event",
args: args{
event: &es_models.Event{Sequence: 1, CreationDate: time.Now().UTC(), Type: es_model.MfaInitSkipped, AggregateID: "AggregateID", ResourceOwner: "OrgID"},
user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_ACTIVE)},
},
result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", State: int32(model.USERSTATE_ACTIVE), MfaInitSkipped: time.Now().UTC()},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -211,6 +260,15 @@ func TestUserAppendEvent(t *testing.T) {
if tt.args.user.OTPState != tt.result.OTPState {
t.Errorf("got wrong result OTPState: expected: %v, actual: %v ", tt.result.OTPState, tt.args.user.OTPState)
}
if tt.args.user.MfaInitSkipped.Round(1*time.Second) != tt.result.MfaInitSkipped.Round(1*time.Second) {
t.Errorf("got wrong result MfaInitSkipped: expected: %v, actual: %v ", tt.result.MfaInitSkipped.Round(1*time.Second), tt.args.user.MfaInitSkipped.Round(1*time.Second))
}
if tt.args.user.PasswordSet != tt.result.PasswordSet {
t.Errorf("got wrong result PasswordSet: expected: %v, actual: %v ", tt.result.PasswordSet, tt.args.user.PasswordSet)
}
if tt.args.user.PasswordChangeRequired != tt.result.PasswordChangeRequired {
t.Errorf("got wrong result PasswordChangeRequired: expected: %v, actual: %v ", tt.result.PasswordChangeRequired, tt.args.user.PasswordChangeRequired)
}
})
}
}

View File

@@ -0,0 +1,58 @@
package view
import (
"github.com/jinzhu/gorm"
global_model "github.com/caos/zitadel/internal/model"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/user/repository/view/model"
"github.com/caos/zitadel/internal/view"
)
func UserSessionByID(db *gorm.DB, table, sessionID string) (*model.UserSessionView, error) {
userSession := new(model.UserSessionView)
query := view.PrepareGetByKey(table, model.UserSessionSearchKey(usr_model.USERSESSIONSEARCHKEY_SESSION_ID), sessionID)
err := query(db, userSession)
return userSession, err
}
func UserSessionByIDs(db *gorm.DB, table, agentID, userID string) (*model.UserSessionView, error) {
userSession := new(model.UserSessionView)
userAgentQuery := model.UserSessionSearchQuery{
Key: usr_model.USERSESSIONSEARCHKEY_USER_AGENT_ID,
Method: global_model.SEARCHMETHOD_EQUALS,
Value: agentID,
}
userQuery := model.UserSessionSearchQuery{
Key: usr_model.USERSESSIONSEARCHKEY_USER_ID,
Method: global_model.SEARCHMETHOD_EQUALS,
Value: userID,
}
query := view.PrepareGetByQuery(table, userAgentQuery, userQuery)
err := query(db, userSession)
return userSession, err
}
func UserSessionsByAgentID(db *gorm.DB, table, agentID string) ([]*model.UserSessionView, error) {
userSessions := make([]*model.UserSessionView, 0)
userAgentQuery := &usr_model.UserSessionSearchQuery{
Key: usr_model.USERSESSIONSEARCHKEY_USER_AGENT_ID,
Method: global_model.SEARCHMETHOD_EQUALS,
Value: agentID,
}
query := view.PrepareSearchQuery(table, model.UserSessionSearchRequest{
Queries: []*usr_model.UserSessionSearchQuery{userAgentQuery},
})
_, err := query(db, userSessions)
return userSessions, err
}
func PutUserSession(db *gorm.DB, table string, session *model.UserSessionView) error {
save := view.PrepareSave(table)
return save(db, session)
}
func DeleteUserSession(db *gorm.DB, table, sessionID string) error {
delete := view.PrepareDeleteByKey(table, model.UserSessionSearchKey(usr_model.USERSESSIONSEARCHKEY_USER_ID), sessionID)
return delete(db)
}

View File

@@ -1,11 +1,15 @@
package view
import (
"database/sql/driver"
"fmt"
"github.com/DATA-DOG/go-sqlmock"
"github.com/caos/zitadel/internal/model"
"github.com/jinzhu/gorm"
"strconv"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jinzhu/gorm"
"github.com/caos/zitadel/internal/model"
)
var (
@@ -14,6 +18,14 @@ var (
expectedGetByQueryCaseSensitive = `SELECT \* FROM "%s" WHERE \(%s %s \$1\) LIMIT 1`
expectedSave = `UPDATE "%s" SET "test" = \$1 WHERE "%s"."%s" = \$2`
expectedRemove = `DELETE FROM "%s" WHERE \(%s = \$1\)`
expectedRemoveByKeys = func(i int, table string) string {
sql := fmt.Sprintf(`DELETE FROM "%s"`, table)
sql += ` WHERE \(%s = \$1\)`
for j := 1; j < i; j++ {
sql = sql + ` AND \(%s = \$` + strconv.Itoa(j+1) + `\)`
}
return sql
}
expectedRemoveByObject = `DELETE FROM "%s" WHERE "%s"."%s" = \$1`
expectedRemoveByObjectMultiplePK = `DELETE FROM "%s" WHERE "%s"."%s" = \$1 AND "%s"."%s" = \$2`
expectedSearch = `SELECT \* FROM "%s" OFFSET 0`
@@ -235,6 +247,21 @@ func (db *dbMock) expectRemove(table, key, value string) *dbMock {
return db
}
func (db *dbMock) expectRemoveKeys(table string, keys ...Key) *dbMock {
keynames := make([]interface{}, len(keys))
keyvalues := make([]driver.Value, len(keys))
for i, key := range keys {
keynames[i] = key.Key.ToColumnName()
keyvalues[i] = key.Value
}
query := fmt.Sprintf(expectedRemoveByKeys(len(keys), table), keynames...)
db.mock.ExpectExec(query).
WithArgs(keyvalues...).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveByObject(table string, object Test) *dbMock {
query := fmt.Sprintf(expectedRemoveByObject, table, table, "primary_id")
db.mock.ExpectExec(query).

View File

@@ -3,9 +3,11 @@ package view
import (
"errors"
"fmt"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/jinzhu/gorm"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func PrepareGetByKey(table string, key ColumnKey, id string) func(db *gorm.DB, res interface{}) error {
@@ -71,6 +73,27 @@ func PrepareDeleteByKey(table string, key ColumnKey, id string) func(db *gorm.DB
}
}
type Key struct {
Key ColumnKey
Value string
}
func PrepareDeleteByKeys(table string, keys ...Key) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
for _, key := range keys {
db = db.Table(table).
Where(fmt.Sprintf("%s = ?", key.Key.ToColumnName()), key.Value)
}
err := db.
Delete(nil).
Error
if err != nil {
return caos_errs.ThrowInternal(err, "VIEW-die73", "could not delete object")
}
return nil
}
}
func PrepareDeleteByObject(table string, object interface{}) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
err := db.Table(table).

View File

@@ -391,6 +391,97 @@ func TestPrepareDelete(t *testing.T) {
}
}
func TestPrepareDeleteByKeys(t *testing.T) {
type args struct {
table string
keys []Key
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"delete single key",
mockDB(t).
expectBegin(nil).
expectRemoveKeys("TESTTABLE", Key{Key: TestSearchKey_ID, Value: "VALUE"}).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"delete multiple keys",
mockDB(t).
expectBegin(nil).
expectRemoveKeys("TESTTABLE", Key{Key: TestSearchKey_ID, Value: "VALUE"}, Key{Key: TestSearchKey_TEST, Value: "VALUE2"}).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
{Key: TestSearchKey_TEST, Value: "VALUE2"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: caos_errs.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getDelete := PrepareDeleteByKeys(tt.args.table, tt.args.keys...)
err := getDelete(tt.db.db)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPrepareDeleteByObject(t *testing.T) {
type args struct {
table string

View File

@@ -0,0 +1,121 @@
BEGIN;
CREATE DATABASE auth;
COMMIT;
BEGIN;
CREATE USER auth;
GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE auth TO auth;
GRANT SELECT, INSERT, UPDATE ON DATABASE eventstore TO auth;
GRANT SELECT, INSERT, UPDATE ON TABLE eventstore.* TO auth;
COMMIT;
BEGIN;
CREATE TABLE auth.locks (
locker_id TEXT,
locked_until TIMESTAMPTZ,
object_type TEXT,
PRIMARY KEY (object_type)
);
CREATE TABLE auth.current_sequences (
view_name TEXT,
current_sequence BIGINT,
PRIMARY KEY (view_name)
);
CREATE TABLE auth.failed_event (
view_name TEXT,
failed_sequence BIGINT,
failure_count SMALLINT,
err_msg TEXT,
PRIMARY KEY (view_name, failed_sequence)
);
CREATE TABLE auth.auth_requests (
id TEXT,
request JSONB,
PRIMARY KEY (id)
);
CREATE TABLE auth.users (
id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
resource_owner TEXT,
user_state SMALLINT,
password_set BOOLEAN,
password_change_required BOOLEAN,
password_change TIMESTAMPTZ,
last_login TIMESTAMPTZ,
user_name TEXT,
first_name TEXT,
last_name TEXT,
nick_name TEXT,
display_name TEXT,
preferred_language TEXT,
gender SMALLINT,
email TEXT,
is_email_verified BOOLEAN,
phone TEXT,
is_phone_verified BOOLEAN,
country TEXT,
locality TEXT,
postal_code TEXT,
region TEXT,
street_address TEXT,
otp_state SMALLINT,
mfa_max_set_up SMALLINT,
mfa_init_skipped TIMESTAMPTZ,
sequence BIGINT,
PRIMARY KEY (id)
);
CREATE TABLE auth.user_sessions (
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
resource_owner TEXT,
state SMALLINT,
user_agent_id TEXT,
user_id TEXT,
user_name TEXT,
password_verification TIMESTAMPTZ,
mfa_software_verification TIMESTAMPTZ,
mfa_hardware_verification TIMESTAMPTZ,
sequence BIGINT,
PRIMARY KEY (user_agent_id, user_id)
);
CREATE TABLE auth.tokens (
id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
resource_owner TEXT,
application_id TEXT,
user_agent_id TEXT,
user_id TEXT,
expiration TIMESTAMPTZ,
sequence BIGINT,
PRIMARY KEY (id)
);
COMMIT;

View File

@@ -3,8 +3,10 @@ package api
import (
"context"
auth_util "github.com/caos/zitadel/internal/api/auth"
grpc_util "github.com/caos/zitadel/internal/api/grpc"
"github.com/caos/zitadel/internal/api/grpc/server"
"github.com/caos/zitadel/internal/auth/repository"
"github.com/caos/zitadel/pkg/auth/api/grpc"
)
@@ -12,8 +14,8 @@ type Config struct {
GRPC grpc_util.Config
}
func Start(ctx context.Context, conf Config) {
grpcServer := grpc.StartServer(conf.GRPC.ToServerConfig())
func Start(ctx context.Context, conf Config, authZ auth_util.Config, repo repository.Repository) {
grpcServer := grpc.StartServer(conf.GRPC.ToServerConfig(), authZ, repo)
grpcGateway := grpc.StartGateway(conf.GRPC.ToGatewayConfig())
server.StartServer(ctx, grpcServer)

View File

@@ -85,11 +85,6 @@ var AuthService_AuthMethods = utils_auth.MethodMapping{
CheckParam: "",
},
"/zitadel.auth.api.v1.AuthService/SetMyPassword": utils_auth.Option{
Permission: "authenticated",
CheckParam: "",
},
"/zitadel.auth.api.v1.AuthService/ChangeMyPassword": utils_auth.Option{
Permission: "authenticated",
CheckParam: "",

View File

@@ -628,7 +628,9 @@ type UserProfile struct {
DisplayName string `protobuf:"bytes,6,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`
PreferredLanguage string `protobuf:"bytes,7,opt,name=preferred_language,json=preferredLanguage,proto3" json:"preferred_language,omitempty"`
Gender Gender `protobuf:"varint,8,opt,name=gender,proto3,enum=zitadel.auth.api.v1.Gender" json:"gender,omitempty"`
Sequence uint64 `protobuf:"varint,26,opt,name=sequence,proto3" json:"sequence,omitempty"`
Sequence uint64 `protobuf:"varint,9,opt,name=sequence,proto3" json:"sequence,omitempty"`
CreationDate *timestamp.Timestamp `protobuf:"bytes,10,opt,name=creation_date,json=creationDate,proto3" json:"creation_date,omitempty"`
ChangeDate *timestamp.Timestamp `protobuf:"bytes,11,opt,name=change_date,json=changeDate,proto3" json:"change_date,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -722,6 +724,20 @@ func (m *UserProfile) GetSequence() uint64 {
return 0
}
func (m *UserProfile) GetCreationDate() *timestamp.Timestamp {
if m != nil {
return m.CreationDate
}
return nil
}
func (m *UserProfile) GetChangeDate() *timestamp.Timestamp {
if m != nil {
return m.ChangeDate
}
return nil
}
type UpdateUserProfileRequest struct {
FirstName string `protobuf:"bytes,1,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"`
LastName string `protobuf:"bytes,2,opt,name=last_name,json=lastName,proto3" json:"last_name,omitempty"`
@@ -806,6 +822,8 @@ type UserEmail struct {
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
IsEmailVerified bool `protobuf:"varint,3,opt,name=isEmailVerified,proto3" json:"isEmailVerified,omitempty"`
Sequence uint64 `protobuf:"varint,4,opt,name=sequence,proto3" json:"sequence,omitempty"`
CreationDate *timestamp.Timestamp `protobuf:"bytes,5,opt,name=creation_date,json=creationDate,proto3" json:"creation_date,omitempty"`
ChangeDate *timestamp.Timestamp `protobuf:"bytes,6,opt,name=change_date,json=changeDate,proto3" json:"change_date,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -864,6 +882,20 @@ func (m *UserEmail) GetSequence() uint64 {
return 0
}
func (m *UserEmail) GetCreationDate() *timestamp.Timestamp {
if m != nil {
return m.CreationDate
}
return nil
}
func (m *UserEmail) GetChangeDate() *timestamp.Timestamp {
if m != nil {
return m.ChangeDate
}
return nil
}
type VerifyMyUserEmailRequest struct {
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
@@ -994,6 +1026,8 @@ type UserPhone struct {
Phone string `protobuf:"bytes,2,opt,name=phone,proto3" json:"phone,omitempty"`
IsPhoneVerified bool `protobuf:"varint,3,opt,name=is_phone_verified,json=isPhoneVerified,proto3" json:"is_phone_verified,omitempty"`
Sequence uint64 `protobuf:"varint,4,opt,name=sequence,proto3" json:"sequence,omitempty"`
CreationDate *timestamp.Timestamp `protobuf:"bytes,5,opt,name=creation_date,json=creationDate,proto3" json:"creation_date,omitempty"`
ChangeDate *timestamp.Timestamp `protobuf:"bytes,6,opt,name=change_date,json=changeDate,proto3" json:"change_date,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -1052,6 +1086,20 @@ func (m *UserPhone) GetSequence() uint64 {
return 0
}
func (m *UserPhone) GetCreationDate() *timestamp.Timestamp {
if m != nil {
return m.CreationDate
}
return nil
}
func (m *UserPhone) GetChangeDate() *timestamp.Timestamp {
if m != nil {
return m.ChangeDate
}
return nil
}
type UpdateUserPhoneRequest struct {
Phone string `protobuf:"bytes,1,opt,name=phone,proto3" json:"phone,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
@@ -1138,6 +1186,8 @@ type UserAddress struct {
Region string `protobuf:"bytes,5,opt,name=region,proto3" json:"region,omitempty"`
StreetAddress string `protobuf:"bytes,6,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"`
Sequence uint64 `protobuf:"varint,7,opt,name=sequence,proto3" json:"sequence,omitempty"`
CreationDate *timestamp.Timestamp `protobuf:"bytes,8,opt,name=creation_date,json=creationDate,proto3" json:"creation_date,omitempty"`
ChangeDate *timestamp.Timestamp `protobuf:"bytes,9,opt,name=change_date,json=changeDate,proto3" json:"change_date,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -1217,6 +1267,20 @@ func (m *UserAddress) GetSequence() uint64 {
return 0
}
func (m *UserAddress) GetCreationDate() *timestamp.Timestamp {
if m != nil {
return m.CreationDate
}
return nil
}
func (m *UserAddress) GetChangeDate() *timestamp.Timestamp {
if m != nil {
return m.ChangeDate
}
return nil
}
type UpdateUserAddressRequest struct {
Country string `protobuf:"bytes,1,opt,name=country,proto3" json:"country,omitempty"`
Locality string `protobuf:"bytes,2,opt,name=locality,proto3" json:"locality,omitempty"`
@@ -1996,189 +2060,190 @@ func init() {
func init() { proto.RegisterFile("auth.proto", fileDescriptor_8bbd6f3875b0e874) }
var fileDescriptor_8bbd6f3875b0e874 = []byte{
// 2761 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x5a, 0x4d, 0x6f, 0xdb, 0xc8,
0x19, 0x0e, 0x25, 0x59, 0x96, 0x5f, 0xf9, 0x83, 0x1e, 0x27, 0x36, 0x2d, 0x3b, 0x89, 0xc2, 0x6c,
0xb0, 0x8e, 0x36, 0xb1, 0x36, 0xde, 0x2d, 0xba, 0xc9, 0x1e, 0x0a, 0xc5, 0xa2, 0x13, 0x35, 0xd6,
0xc7, 0x52, 0x4a, 0xda, 0x2c, 0x50, 0xa8, 0x8c, 0x38, 0x92, 0xb9, 0x2b, 0x89, 0x0c, 0x49, 0x39,
0xd0, 0x5e, 0x5a, 0xec, 0x69, 0xdb, 0xcb, 0xa2, 0xdb, 0x43, 0x8f, 0x3d, 0xf4, 0x0f, 0xb4, 0x05,
0xda, 0x02, 0xfd, 0x05, 0xbd, 0xf5, 0xd0, 0xfe, 0x82, 0xa2, 0xbf, 0x22, 0x87, 0xa2, 0x98, 0x0f,
0x4a, 0xfc, 0x10, 0x25, 0x05, 0x45, 0x4f, 0xe1, 0xbc, 0x5f, 0xf3, 0xbc, 0xcf, 0xbc, 0xf3, 0x6a,
0x66, 0x1c, 0x00, 0x6d, 0xe4, 0x5e, 0x1c, 0x5b, 0xb6, 0xe9, 0x9a, 0x68, 0xe7, 0x2b, 0xc3, 0xd5,
0x74, 0xdc, 0x3f, 0xa6, 0x32, 0xcd, 0x32, 0x8e, 0x2f, 0x1f, 0xe4, 0x0e, 0x7b, 0xa6, 0xd9, 0xeb,
0xe3, 0xa2, 0x66, 0x19, 0x45, 0x6d, 0x38, 0x34, 0x5d, 0xcd, 0x35, 0xcc, 0xa1, 0xc3, 0x5c, 0x72,
0x07, 0x5c, 0x4b, 0x47, 0xaf, 0x46, 0xdd, 0x22, 0x1e, 0x58, 0xee, 0x98, 0x2b, 0x0f, 0xc3, 0x4a,
0xc7, 0xb5, 0x47, 0x1d, 0x97, 0x6b, 0x6f, 0x86, 0xb5, 0xae, 0x31, 0xc0, 0x8e, 0xab, 0x0d, 0x2c,
0x6e, 0xb0, 0x77, 0xa9, 0xf5, 0x0d, 0x5d, 0x73, 0x71, 0xd1, 0xfb, 0xe0, 0x8a, 0x7b, 0xf4, 0x9f,
0xce, 0xfd, 0x1e, 0x1e, 0xde, 0x77, 0xde, 0x68, 0xbd, 0x1e, 0xb6, 0x8b, 0xa6, 0x45, 0x61, 0xcd,
0x80, 0x28, 0x91, 0x6c, 0x98, 0xda, 0xb3, 0x62, 0x1a, 0xf9, 0x27, 0x20, 0x3e, 0x77, 0xb0, 0xdd,
0xc4, 0x8e, 0x63, 0x98, 0xc3, 0x17, 0x06, 0x7e, 0xe3, 0xa0, 0x0a, 0x6c, 0x8c, 0x1c, 0x6c, 0xb7,
0x1d, 0x26, 0x74, 0x24, 0x21, 0x9f, 0x3c, 0xca, 0x9e, 0xbc, 0x77, 0x3c, 0x83, 0x9b, 0xe3, 0x90,
0xb7, 0xba, 0x3e, 0x9a, 0x0a, 0x1c, 0xf9, 0x9f, 0x02, 0x6c, 0x85, 0x2c, 0xd0, 0x26, 0x24, 0x0c,
0x5d, 0x12, 0xf2, 0xc2, 0xd1, 0x9a, 0x9a, 0x30, 0x74, 0xb4, 0x0f, 0x19, 0xad, 0x87, 0x87, 0x6e,
0xdb, 0xd0, 0xa5, 0x04, 0x95, 0xae, 0xd2, 0x71, 0x45, 0x47, 0x65, 0xb6, 0x36, 0x6d, 0xc7, 0xd5,
0x5c, 0x2c, 0x25, 0xf3, 0xc2, 0xd1, 0xe6, 0xc9, 0x9d, 0x45, 0x30, 0x9a, 0xc4, 0x58, 0x5d, 0x23,
0x5a, 0xfa, 0x89, 0xf6, 0x60, 0x95, 0xe6, 0x63, 0xe8, 0x52, 0x8a, 0xc6, 0x4f, 0x93, 0x61, 0x45,
0x47, 0x07, 0xb0, 0x46, 0x15, 0x43, 0x6d, 0x80, 0xa5, 0x15, 0xaa, 0xca, 0x10, 0x41, 0x4d, 0x1b,
0x60, 0x94, 0x83, 0x8c, 0x83, 0x5f, 0x8f, 0xf0, 0xb0, 0x83, 0xa5, 0x74, 0x5e, 0x38, 0x4a, 0xa9,
0x93, 0xb1, 0xfc, 0x9f, 0x55, 0x48, 0x91, 0x19, 0x23, 0xb9, 0x7c, 0x0c, 0x2b, 0x0c, 0x6b, 0x82,
0x62, 0xbd, 0x11, 0x8f, 0x95, 0x82, 0x64, 0xc6, 0xe8, 0x07, 0xb0, 0xd1, 0xb1, 0x31, 0x5d, 0xb1,
0xb6, 0xee, 0x65, 0x9a, 0x3d, 0xc9, 0x1d, 0xb3, 0xf2, 0x38, 0xf6, 0xca, 0xe3, 0xb8, 0xe5, 0x95,
0x87, 0xba, 0xee, 0x39, 0x94, 0x49, 0x80, 0x53, 0xd8, 0xd2, 0x3a, 0xae, 0x71, 0xe9, 0x0b, 0x91,
0x5a, 0x18, 0x62, 0x73, 0xea, 0x42, 0x83, 0x7c, 0x0a, 0xd9, 0xce, 0x85, 0x36, 0xec, 0x61, 0x16,
0x60, 0x65, 0x61, 0x00, 0x60, 0xe6, 0xd4, 0xf9, 0x21, 0x40, 0x5f, 0x73, 0xdc, 0x76, 0xdf, 0xec,
0x19, 0x43, 0xca, 0xd7, 0x7c, 0xdf, 0x35, 0x62, 0x7d, 0x4e, 0x8c, 0x91, 0x02, 0xa2, 0xa5, 0x39,
0xce, 0x1b, 0xd3, 0xd6, 0xdb, 0x2c, 0xa2, 0x2e, 0xad, 0x2e, 0x0c, 0xb0, 0xe5, 0xf9, 0x9c, 0x32,
0x97, 0xe0, 0x62, 0x66, 0x42, 0x8b, 0x79, 0x1d, 0xa0, 0x6b, 0xd8, 0x8e, 0xcb, 0xb4, 0x6b, 0x54,
0xbb, 0x46, 0x25, 0x54, 0x7d, 0x00, 0x14, 0x0f, 0xd3, 0x02, 0xf3, 0x25, 0x02, 0x4f, 0x39, 0x34,
0x3a, 0x5f, 0x32, 0x65, 0x96, 0x29, 0x89, 0x80, 0x2a, 0x6f, 0xc1, 0xba, 0x6e, 0x38, 0x56, 0x5f,
0x1b, 0x33, 0xfd, 0x3a, 0xd5, 0x67, 0xb9, 0x8c, 0x9a, 0xdc, 0x07, 0x64, 0xd9, 0xb8, 0x8b, 0x6d,
0x1b, 0xeb, 0xed, 0xbe, 0x36, 0xec, 0x8d, 0xb4, 0x1e, 0x96, 0x36, 0xa8, 0xe1, 0xf6, 0x44, 0x73,
0xce, 0x15, 0xe8, 0x23, 0x48, 0xf7, 0xf0, 0x50, 0xc7, 0xb6, 0xb4, 0x49, 0x6b, 0xe8, 0x60, 0x66,
0x0d, 0x3d, 0xa1, 0x26, 0x2a, 0x37, 0x45, 0x57, 0x61, 0x05, 0x0f, 0x34, 0xa3, 0x2f, 0x6d, 0xd1,
0xb0, 0x6c, 0x80, 0x0a, 0xb0, 0x6d, 0x38, 0x6d, 0xfa, 0xdd, 0xbe, 0xc4, 0xb6, 0xd1, 0x35, 0xb0,
0x2e, 0x89, 0x79, 0xe1, 0x28, 0xa3, 0x6e, 0x19, 0x8e, 0x42, 0xe4, 0x2f, 0xb8, 0x98, 0x44, 0xb0,
0x2e, 0xcc, 0x21, 0x96, 0xb6, 0x59, 0x04, 0x3a, 0xe0, 0x11, 0xe8, 0xf7, 0x34, 0x02, 0xf2, 0x22,
0x34, 0x88, 0x7c, 0x12, 0x41, 0x82, 0xd5, 0x8e, 0x39, 0x1a, 0xba, 0xf6, 0x58, 0xda, 0x61, 0xdb,
0x98, 0x0f, 0xc9, 0x56, 0xea, 0x9b, 0x1d, 0xad, 0x6f, 0xb8, 0x63, 0xe9, 0x2a, 0x67, 0x97, 0x8f,
0xd1, 0x4d, 0xc8, 0x5a, 0xa6, 0xe3, 0x6a, 0xfd, 0x76, 0xc7, 0xd4, 0xb1, 0x74, 0x8d, 0xaa, 0x81,
0x89, 0x4e, 0x4d, 0x1d, 0xa3, 0x5d, 0x48, 0xdb, 0xb8, 0x67, 0x98, 0x43, 0x69, 0x97, 0x6d, 0x5e,
0x36, 0x42, 0x77, 0x60, 0xd3, 0x71, 0x6d, 0x8c, 0xdd, 0xb6, 0xa6, 0xeb, 0x36, 0x76, 0x1c, 0x69,
0x8f, 0xea, 0x37, 0x98, 0xb4, 0xc4, 0x84, 0xe8, 0x13, 0x90, 0x42, 0xd5, 0xd5, 0xb6, 0xf1, 0xeb,
0x91, 0x61, 0x63, 0x5d, 0x92, 0x68, 0x22, 0xbb, 0xc1, 0x4a, 0x52, 0xb9, 0x36, 0xd0, 0x00, 0xf6,
0x43, 0x0d, 0xe0, 0xcf, 0x09, 0xc8, 0x92, 0x6d, 0xdc, 0xb0, 0xcd, 0xae, 0xd1, 0xc7, 0x91, 0x3e,
0x10, 0x28, 0xc6, 0xc4, 0xdc, 0x62, 0x4c, 0xce, 0x2d, 0xc6, 0xd4, 0xbc, 0x62, 0x5c, 0x59, 0x50,
0x8c, 0xe9, 0x65, 0x8b, 0x71, 0x75, 0x71, 0x31, 0x66, 0x96, 0x2f, 0x46, 0x3f, 0x71, 0xb9, 0x28,
0x71, 0xd2, 0x73, 0x8b, 0x34, 0x18, 0x1f, 0x7d, 0x84, 0x72, 0xec, 0xb8, 0xe8, 0x6e, 0x80, 0x18,
0xca, 0xe6, 0x63, 0x78, 0xfb, 0x78, 0xd5, 0x5e, 0x11, 0x05, 0xe9, 0x6f, 0x82, 0x9f, 0xa4, 0xf7,
0xfd, 0x24, 0x25, 0x22, 0x96, 0x53, 0xc2, 0xde, 0xf7, 0x13, 0x96, 0x8c, 0x1a, 0x4e, 0xc8, 0xbb,
0x1f, 0x22, 0x2f, 0x15, 0xb1, 0x0d, 0x10, 0xf9, 0x70, 0x26, 0x91, 0x2b, 0x11, 0xa7, 0xb9, 0xa4,
0xa6, 0x97, 0x26, 0x55, 0x7e, 0x03, 0x6b, 0x84, 0x31, 0xba, 0x69, 0x23, 0xe5, 0x36, 0xd9, 0xfe,
0x09, 0xff, 0xf6, 0x3f, 0x82, 0xf0, 0x2e, 0xa7, 0x04, 0xcc, 0xd8, 0xfc, 0xfe, 0x15, 0x4b, 0x85,
0x56, 0xec, 0x11, 0x48, 0xd4, 0x6e, 0x5c, 0x1d, 0x4f, 0x00, 0x78, 0x0b, 0x76, 0x03, 0x52, 0x74,
0xd7, 0x46, 0x97, 0x8a, 0xca, 0xe5, 0xa7, 0xb0, 0xcb, 0x7c, 0x23, 0x9e, 0xe1, 0x0c, 0xbc, 0x48,
0x89, 0x98, 0x48, 0x8f, 0x60, 0x77, 0x5a, 0x36, 0x81, 0x48, 0x79, 0x2f, 0xf7, 0x28, 0x08, 0xa6,
0x90, 0xc7, 0x8c, 0x3a, 0xda, 0xad, 0x66, 0x51, 0xc7, 0xfa, 0x5e, 0x62, 0x61, 0xdf, 0x4b, 0xce,
0xee, 0x7b, 0xf3, 0xc8, 0x7b, 0xe8, 0x87, 0x4d, 0xdd, 0x3c, 0xd8, 0x37, 0xbd, 0x79, 0x19, 0xec,
0xb5, 0xb7, 0x8f, 0xd3, 0x76, 0x4a, 0x14, 0xa4, 0xab, 0x1c, 0x82, 0xfc, 0x89, 0x9f, 0xbb, 0x80,
0xeb, 0x22, 0xd6, 0xff, 0x2e, 0xb0, 0xe6, 0xe4, 0xb5, 0xc0, 0x70, 0xca, 0xbe, 0x46, 0x9d, 0x88,
0x6f, 0xd4, 0xc9, 0xf9, 0x8d, 0x3a, 0x35, 0xa7, 0x51, 0xaf, 0x2c, 0x68, 0xd4, 0xe9, 0x59, 0x8d,
0xda, 0x4f, 0xe3, 0x6a, 0x88, 0xc6, 0x7f, 0x09, 0xfe, 0xae, 0xc1, 0x3d, 0x3c, 0x3a, 0xe4, 0x69,
0x3a, 0x8c, 0x91, 0xcc, 0xdb, 0xc7, 0x2b, 0x76, 0x92, 0xf0, 0x31, 0x49, 0xec, 0x3d, 0x5f, 0x62,
0x89, 0x90, 0xd1, 0x34, 0xc5, 0xbb, 0xc1, 0x14, 0x93, 0x21, 0x43, 0x7f, 0xb2, 0xf9, 0x49, 0xb2,
0xa9, 0x90, 0x95, 0x97, 0x76, 0x31, 0x92, 0xf6, 0x4a, 0xc8, 0x32, 0x48, 0x80, 0x7c, 0x08, 0xd0,
0xe0, 0xbf, 0x44, 0x95, 0x72, 0x78, 0xd1, 0xe4, 0x4f, 0x60, 0xcb, 0xd3, 0x7a, 0x89, 0xdf, 0x81,
0x8c, 0xf7, 0xd3, 0x15, 0xae, 0xa2, 0xa7, 0xea, 0x44, 0x25, 0xf7, 0x61, 0xb3, 0x11, 0xf8, 0x85,
0x43, 0xf7, 0x60, 0xdd, 0xec, 0xeb, 0xed, 0x78, 0xe7, 0xac, 0xd9, 0xd7, 0x3d, 0x1f, 0x62, 0x3d,
0xc4, 0x6f, 0xa6, 0xd6, 0x89, 0x88, 0xf5, 0x10, 0xbf, 0xf1, 0xac, 0x65, 0x19, 0xd6, 0x79, 0xbb,
0xe8, 0x6a, 0x75, 0xd7, 0x42, 0xc8, 0x5f, 0xac, 0xbc, 0x40, 0xcb, 0xb0, 0x5e, 0x1d, 0xf5, 0x5d,
0xe3, 0x4c, 0xeb, 0xb8, 0xa6, 0xed, 0xa0, 0x8f, 0x21, 0x35, 0xe8, 0x6a, 0xde, 0x3d, 0x23, 0x3f,
0xb3, 0x1d, 0xfa, 0x1c, 0x54, 0x6a, 0x2d, 0xbb, 0x90, 0xf5, 0x09, 0xd1, 0x87, 0x90, 0x72, 0xc7,
0x16, 0x9b, 0x68, 0xf3, 0xe4, 0x70, 0x76, 0x90, 0xae, 0xd6, 0x1a, 0x5b, 0x58, 0xa5, 0x96, 0xe8,
0xa3, 0xe0, 0x61, 0xfd, 0xfa, 0x6c, 0x97, 0xb3, 0x92, 0xff, 0xac, 0x2e, 0x7f, 0x23, 0xc0, 0x26,
0x4b, 0x4d, 0xc5, 0x8e, 0x65, 0x0e, 0x9d, 0xc0, 0xfd, 0x42, 0x08, 0xdc, 0x2f, 0x44, 0x48, 0x8e,
0x6c, 0xaf, 0x29, 0x93, 0x4f, 0xb2, 0x47, 0x1c, 0xdc, 0xb1, 0xb1, 0xcb, 0xb7, 0x17, 0x1f, 0x4d,
0xa1, 0xa4, 0xde, 0x01, 0x8a, 0x0a, 0x9b, 0xf5, 0x4a, 0xf9, 0xf4, 0xb4, 0x6f, 0xe0, 0xa1, 0x5b,
0x1a, 0xb9, 0x17, 0xe4, 0x74, 0xd0, 0xa1, 0xa3, 0x29, 0x96, 0x0c, 0x13, 0x54, 0x74, 0x74, 0x1b,
0x36, 0xb8, 0x92, 0x43, 0x60, 0xb8, 0xd6, 0x99, 0xb0, 0x49, 0x65, 0xf2, 0xef, 0x04, 0xd8, 0xaf,
0x8e, 0x1b, 0xb6, 0xf9, 0x05, 0xee, 0xb8, 0x75, 0xbb, 0xd7, 0xc4, 0x9a, 0xdd, 0xb9, 0xf0, 0x2a,
0x6e, 0x17, 0xd2, 0x66, 0xb7, 0xeb, 0x60, 0x97, 0x06, 0x4f, 0xa9, 0x7c, 0x44, 0x9a, 0x68, 0xdf,
0x18, 0x18, 0x2c, 0x64, 0x4a, 0x65, 0x03, 0x92, 0xbe, 0xe6, 0x74, 0x68, 0x4a, 0x19, 0x95, 0x7c,
0xa2, 0x33, 0x58, 0x7d, 0x3d, 0xc2, 0xb6, 0x81, 0xc9, 0x66, 0x20, 0x6b, 0x7d, 0x6f, 0x76, 0xa2,
0x11, 0x00, 0x9f, 0x8d, 0xb0, 0x3d, 0x56, 0x3d, 0x67, 0xf9, 0x8f, 0x02, 0xec, 0xc5, 0x18, 0xa1,
0x33, 0x48, 0x7e, 0x89, 0xc7, 0xbc, 0x0c, 0x0a, 0x4b, 0xc6, 0x7f, 0x86, 0xc7, 0x74, 0x63, 0x7e,
0x2d, 0x24, 0xf2, 0x57, 0x54, 0x12, 0x00, 0x3d, 0x84, 0xf4, 0x00, 0xbb, 0x17, 0xa6, 0xce, 0xcb,
0xe3, 0xd6, 0xcc, 0x50, 0xcc, 0xbd, 0x4a, 0x0d, 0x55, 0xee, 0x40, 0xe8, 0xb8, 0xd4, 0xfa, 0x23,
0xef, 0x6c, 0xc7, 0x06, 0xf2, 0x6f, 0x05, 0xc8, 0xcd, 0xa2, 0x96, 0x57, 0xd1, 0xbb, 0x71, 0x7b,
0x0b, 0xd6, 0x5d, 0x93, 0x74, 0x2a, 0x1b, 0x3b, 0xa3, 0x3e, 0x2b, 0xa7, 0x94, 0x9a, 0xa5, 0x32,
0x95, 0x8a, 0xd0, 0x87, 0xa4, 0x45, 0x51, 0x65, 0x8a, 0x72, 0x2d, 0xcd, 0x4c, 0xa0, 0x6e, 0xf7,
0x54, 0x6e, 0x27, 0xdf, 0x83, 0xad, 0x8a, 0x53, 0xd2, 0x07, 0xc6, 0x70, 0x82, 0x6a, 0x1f, 0x32,
0x86, 0xd3, 0xd6, 0x88, 0x8c, 0xe2, 0xca, 0xa8, 0xab, 0x06, 0x33, 0x91, 0xef, 0x42, 0xb2, 0x6e,
0xf7, 0x22, 0xbf, 0x2e, 0x08, 0x52, 0xbe, 0x53, 0x2f, 0xfd, 0x96, 0x1f, 0xc0, 0x46, 0x75, 0xdc,
0xc0, 0xf6, 0xc0, 0x60, 0xef, 0x02, 0x28, 0x0f, 0x59, 0x6b, 0x3a, 0xa4, 0x1b, 0x7f, 0x4d, 0xf5,
0x8b, 0x0a, 0x76, 0xe0, 0x61, 0x82, 0x5d, 0xe4, 0xf3, 0x70, 0xf8, 0xbc, 0xa9, 0xa8, 0x4d, 0xa5,
0xd9, 0xac, 0xd4, 0x6b, 0xcd, 0x56, 0xa9, 0xa5, 0xb4, 0x9f, 0xd7, 0x9a, 0x0d, 0xe5, 0xb4, 0x72,
0x56, 0x51, 0xca, 0xe2, 0x15, 0x74, 0x00, 0x7b, 0x11, 0x8b, 0xd2, 0x69, 0xab, 0xf2, 0x42, 0x11,
0x05, 0x74, 0x13, 0x0e, 0x22, 0xca, 0x96, 0xa2, 0x56, 0x2b, 0xb5, 0x52, 0x4b, 0x29, 0x8b, 0x89,
0xc2, 0x6b, 0x10, 0xc9, 0x86, 0xf2, 0x92, 0x27, 0xad, 0x02, 0xed, 0xc3, 0x35, 0x2a, 0x53, 0x9a,
0x8d, 0x7a, 0xad, 0xa9, 0xb4, 0x5e, 0x36, 0x94, 0xf6, 0x69, 0xbd, 0xac, 0x88, 0x57, 0xd0, 0x75,
0xd8, 0x8f, 0xa8, 0x2a, 0xe5, 0x76, 0xab, 0xfe, 0x4c, 0xa9, 0x89, 0x02, 0xba, 0x0d, 0x37, 0x63,
0xd5, 0xdc, 0x28, 0x51, 0xf8, 0xbd, 0xc0, 0x0e, 0x27, 0x2c, 0xc1, 0x1c, 0xec, 0x52, 0x84, 0xfe,
0xcc, 0x14, 0x9e, 0xda, 0x55, 0x10, 0xa7, 0xba, 0x49, 0x4e, 0xbb, 0x80, 0xa6, 0xd2, 0x4a, 0x8d,
0xcb, 0x13, 0xe8, 0x1a, 0x6c, 0x4f, 0xe5, 0x65, 0xe5, 0x5c, 0x21, 0x19, 0x26, 0x83, 0x41, 0xce,
0xeb, 0xa7, 0xcf, 0x94, 0xb2, 0x98, 0x0a, 0x1a, 0x37, 0x9f, 0x37, 0x1b, 0x4a, 0xad, 0x2c, 0xae,
0x04, 0xc5, 0x95, 0x5a, 0xa5, 0x55, 0x29, 0x9d, 0x8b, 0xe9, 0xc2, 0x8f, 0x21, 0xcd, 0xce, 0xa6,
0x64, 0xf2, 0x27, 0x4a, 0xad, 0xac, 0xa8, 0xa1, 0x55, 0xd8, 0x86, 0x0d, 0x2e, 0x3f, 0x53, 0xaa,
0xa5, 0x73, 0x82, 0x73, 0x0b, 0xb2, 0x5c, 0x44, 0x05, 0x09, 0x84, 0x60, 0x93, 0x0b, 0xca, 0x95,
0x17, 0x64, 0x51, 0xc4, 0x64, 0xa1, 0x0c, 0xab, 0xbc, 0x43, 0xa3, 0x3d, 0xd8, 0xa9, 0x9e, 0x95,
0x28, 0x67, 0xc1, 0xd8, 0x5b, 0x90, 0xf5, 0x14, 0xcd, 0x6a, 0x93, 0x45, 0xf6, 0x04, 0xf5, 0x56,
0x43, 0x4c, 0x14, 0xba, 0x90, 0xf1, 0x3a, 0x25, 0x92, 0xe0, 0x2a, 0xf9, 0x9e, 0x51, 0x29, 0xbb,
0x80, 0x26, 0x9a, 0x5a, 0xbd, 0xd5, 0x56, 0x95, 0x52, 0xf9, 0xa5, 0x28, 0x10, 0x5c, 0x13, 0x39,
0x93, 0x25, 0x08, 0x6b, 0x3e, 0x59, 0xb5, 0xfe, 0x82, 0x70, 0x59, 0x78, 0x05, 0xd7, 0x66, 0x36,
0x12, 0x74, 0x07, 0x6e, 0x55, 0x5f, 0x36, 0xd4, 0xfa, 0x0f, 0x95, 0xd3, 0x56, 0x5d, 0x7d, 0xd2,
0x54, 0x4a, 0xea, 0xe9, 0xd3, 0x67, 0xca, 0xcb, 0x10, 0x02, 0x19, 0x6e, 0xcc, 0x36, 0xab, 0xab,
0x4f, 0xda, 0xb5, 0x52, 0x55, 0x11, 0x85, 0xc2, 0x4f, 0x61, 0xdd, 0xdf, 0x61, 0x08, 0x2d, 0xcc,
0xae, 0xaa, 0xb4, 0x9e, 0xd6, 0xcb, 0x6d, 0xe5, 0xb3, 0xe7, 0xa5, 0xf3, 0xa6, 0x78, 0x05, 0x1d,
0x82, 0x14, 0x50, 0x34, 0x5b, 0x25, 0xb5, 0xd5, 0x6c, 0xff, 0xa8, 0xd2, 0x7a, 0x2a, 0x0a, 0xa4,
0x88, 0x03, 0xda, 0xd3, 0x7a, 0xad, 0x55, 0xaa, 0xd4, 0x9a, 0x62, 0xe2, 0xe4, 0xaf, 0xfb, 0x90,
0x25, 0xbf, 0x1d, 0x4d, 0x6c, 0x5f, 0x1a, 0x1d, 0x8c, 0x9e, 0xc1, 0xea, 0x53, 0xac, 0xf5, 0xdd,
0x8b, 0xaf, 0xd0, 0x6e, 0xe4, 0xf9, 0x45, 0x19, 0x58, 0xee, 0x38, 0x17, 0x23, 0x97, 0xc5, 0xaf,
0xff, 0xf1, 0xef, 0x5f, 0x27, 0x00, 0x65, 0x8a, 0x17, 0x3c, 0xc2, 0x13, 0x58, 0x51, 0xb1, 0xa6,
0x8f, 0xdf, 0x39, 0xd4, 0x26, 0x0d, 0x95, 0x41, 0xe9, 0xa2, 0x4d, 0xfd, 0x6b, 0x90, 0x79, 0xc1,
0x1f, 0x40, 0x63, 0x63, 0xed, 0x45, 0xe4, 0x4d, 0xfa, 0xd6, 0x2a, 0x6f, 0xd3, 0x60, 0x59, 0xb4,
0x36, 0x79, 0x44, 0x45, 0x3f, 0x83, 0xed, 0x27, 0xd8, 0x65, 0x37, 0x1a, 0xef, 0xb1, 0x32, 0x36,
0xf0, 0x9d, 0x65, 0x1e, 0x3e, 0x1d, 0xf9, 0x83, 0xaf, 0xff, 0x24, 0x6d, 0xc1, 0x06, 0xb1, 0xc1,
0x43, 0xd7, 0xe8, 0x68, 0x2e, 0xd6, 0xe9, 0xcc, 0x08, 0x89, 0xc5, 0x01, 0x2e, 0x92, 0x43, 0x81,
0xf7, 0xa4, 0x8a, 0xbe, 0x02, 0x71, 0x02, 0xc0, 0x7b, 0x44, 0x88, 0x9b, 0x3f, 0x1f, 0x3b, 0x3f,
0xf7, 0x94, 0xef, 0xc5, 0x4d, 0xbd, 0x83, 0xb6, 0xd9, 0xbc, 0x04, 0x80, 0xc5, 0xe7, 0xf9, 0x8d,
0x00, 0x3b, 0xec, 0x34, 0x1d, 0x9c, 0xff, 0xfe, 0xec, 0x79, 0x62, 0x6e, 0xeb, 0x4b, 0xc0, 0x2a,
0xc6, 0xc1, 0xda, 0xcd, 0x45, 0x61, 0x3d, 0x12, 0x0a, 0xc8, 0x85, 0xcd, 0x09, 0x2b, 0xec, 0xa6,
0x1b, 0xc7, 0x49, 0xfc, 0xcb, 0x2a, 0xf5, 0x93, 0x0b, 0x71, 0x53, 0x6f, 0xa3, 0xad, 0xe9, 0xd4,
0xec, 0x9e, 0xfc, 0xad, 0x00, 0xdb, 0xec, 0x64, 0xec, 0x9f, 0xf9, 0x83, 0x05, 0x6c, 0xf8, 0x2f,
0xa1, 0x0b, 0xe1, 0xdc, 0x8f, 0x83, 0x73, 0x35, 0x17, 0x86, 0x43, 0x78, 0xf8, 0x95, 0x00, 0xdb,
0x91, 0x4b, 0x77, 0xcc, 0xfa, 0xc4, 0x5d, 0xce, 0x63, 0xf7, 0xd6, 0xf7, 0xe2, 0xb0, 0x1c, 0xca,
0x7b, 0x21, 0x2c, 0x45, 0x76, 0xf9, 0x1d, 0x13, 0x4c, 0xdf, 0x09, 0x70, 0x5d, 0xc5, 0x0e, 0x1e,
0xea, 0xd5, 0xb1, 0xef, 0xf5, 0xa0, 0x43, 0x1f, 0x90, 0xab, 0xf3, 0xd6, 0x2a, 0x0e, 0x48, 0x29,
0x0e, 0xc8, 0x91, 0x7c, 0x3b, 0x02, 0xc4, 0xa6, 0x53, 0x5f, 0xfa, 0xe6, 0x0c, 0x17, 0x0c, 0xbb,
0xdf, 0xbf, 0x7b, 0xc1, 0x50, 0xbf, 0x25, 0x0b, 0x86, 0xbd, 0x0e, 0x84, 0x0b, 0x86, 0xcd, 0xbc,
0xa8, 0x60, 0xfc, 0x77, 0xf8, 0x85, 0x70, 0x96, 0x2b, 0x18, 0x0a, 0x87, 0xf0, 0xf0, 0x6d, 0xa8,
0x60, 0xe6, 0x21, 0x9a, 0xfd, 0xaa, 0xf0, 0x3f, 0x96, 0x0b, 0x45, 0x12, 0x57, 0x2e, 0xbe, 0xf7,
0x12, 0xb6, 0x74, 0xec, 0xbd, 0xe0, 0xff, 0x52, 0x2e, 0x1c, 0xc8, 0xec, 0x72, 0xf1, 0x77, 0x5d,
0xef, 0xdd, 0xe1, 0xdd, 0xbb, 0xae, 0x77, 0x61, 0x5f, 0xae, 0xeb, 0xf2, 0xdb, 0x7f, 0xa4, 0xeb,
0x7a, 0xf3, 0x2f, 0xea, 0xba, 0xc1, 0xd7, 0x8e, 0x25, 0x60, 0x2d, 0xd7, 0x75, 0x39, 0x2c, 0xc2,
0xca, 0x6b, 0x58, 0xa3, 0xac, 0x54, 0xbb, 0x5a, 0x3c, 0x1d, 0xb7, 0x16, 0xdd, 0xca, 0x1d, 0xf9,
0x6e, 0xdc, 0xc4, 0x22, 0xda, 0x9c, 0x4e, 0x4c, 0xee, 0xee, 0xe8, 0xe7, 0x02, 0x6c, 0x34, 0xc9,
0x9c, 0x93, 0x57, 0x86, 0xd9, 0x7f, 0x5d, 0x0c, 0x3d, 0x79, 0xc4, 0xd6, 0xc6, 0x83, 0xb8, 0xa9,
0xa5, 0xdc, 0x8e, 0xaf, 0x36, 0x78, 0x34, 0x9a, 0xf5, 0x2f, 0x05, 0x10, 0xbd, 0x4d, 0x3c, 0x41,
0x71, 0x7b, 0x2e, 0x0a, 0x66, 0x1e, 0x0b, 0xe2, 0x61, 0x1c, 0x88, 0x7c, 0xee, 0x60, 0x06, 0x88,
0x22, 0xff, 0x7b, 0x04, 0x01, 0x33, 0x86, 0xb5, 0x92, 0xae, 0x57, 0xbb, 0x5a, 0xbd, 0xd5, 0x88,
0x5d, 0x82, 0xdb, 0x71, 0x6f, 0x1a, 0xbe, 0xc7, 0x88, 0x39, 0xab, 0x2f, 0x6f, 0x07, 0x16, 0xa1,
0x68, 0xba, 0x16, 0x6f, 0x1d, 0xbe, 0x17, 0x9b, 0x56, 0x03, 0xdd, 0x9a, 0xf7, 0x33, 0x43, 0x27,
0x5b, 0x0e, 0xc9, 0xf7, 0xe3, 0x90, 0xdc, 0xc8, 0xed, 0x47, 0x90, 0xf8, 0x5b, 0x47, 0x1f, 0xd6,
0x55, 0x3c, 0x30, 0x2f, 0xf1, 0x02, 0x3e, 0xe2, 0xd6, 0x21, 0x7e, 0x5f, 0x16, 0xa2, 0x14, 0xa0,
0x3f, 0x08, 0xb0, 0xc3, 0xcf, 0xd8, 0xbe, 0xd3, 0xbc, 0x83, 0x8e, 0x97, 0x7c, 0x3a, 0xf0, 0x4a,
0xb3, 0xb8, 0xb4, 0x3d, 0xe7, 0x27, 0xbe, 0x5c, 0xe4, 0x83, 0x62, 0xaf, 0x6f, 0xbe, 0xd2, 0xfa,
0xe4, 0x6c, 0x44, 0x9c, 0x4d, 0xbb, 0xe7, 0x14, 0xdb, 0x0e, 0xf5, 0x67, 0x7d, 0x0c, 0x2a, 0x4e,
0x45, 0x1b, 0xd0, 0x8b, 0x78, 0x2c, 0x3f, 0xb3, 0xb7, 0x54, 0xe8, 0x86, 0x2f, 0x1f, 0xc7, 0xc1,
0xb8, 0x86, 0x76, 0x3c, 0x18, 0x6d, 0xc3, 0x31, 0xb4, 0x01, 0x7d, 0x05, 0x40, 0xbf, 0x10, 0x60,
0x8f, 0xb6, 0x8b, 0xcf, 0x59, 0x70, 0xff, 0xb5, 0x3e, 0x0e, 0x89, 0x1c, 0xc7, 0xcd, 0xd4, 0x57,
0x3e, 0x89, 0xc3, 0xb1, 0x8f, 0xf6, 0x8a, 0xbe, 0x97, 0x81, 0x22, 0x0f, 0x55, 0x1c, 0xe0, 0xc7,
0x7f, 0x11, 0xbe, 0x2b, 0x7d, 0x23, 0xa0, 0x4f, 0x21, 0x43, 0xae, 0x30, 0xf9, 0x52, 0xa3, 0x22,
0x17, 0xd0, 0xd1, 0x85, 0xeb, 0x5a, 0xce, 0xa3, 0x62, 0xb1, 0x67, 0xb8, 0x17, 0xa3, 0x57, 0xc7,
0x1d, 0x73, 0x50, 0xec, 0x68, 0xe6, 0xd4, 0xd1, 0xfa, 0xb2, 0x57, 0x24, 0xd3, 0x9c, 0x24, 0x3f,
0x3c, 0x7e, 0x50, 0x10, 0x12, 0x27, 0xa2, 0x66, 0x59, 0x7d, 0xfe, 0x33, 0x51, 0xfc, 0xc2, 0x31,
0x87, 0x41, 0x49, 0xcf, 0xb6, 0x3a, 0x8f, 0x22, 0x36, 0x8f, 0x22, 0x36, 0x9f, 0xdf, 0x5d, 0x34,
0x23, 0xfd, 0x1f, 0x24, 0xc4, 0xf4, 0x55, 0x9a, 0x32, 0xf4, 0xd1, 0x7f, 0x03, 0x00, 0x00, 0xff,
0xff, 0x66, 0xd3, 0x09, 0x6b, 0x7c, 0x22, 0x00, 0x00,
// 2779 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0x4b, 0x6f, 0x1b, 0xd7,
0x15, 0xf6, 0x90, 0x14, 0x45, 0x1e, 0x4a, 0xd4, 0xe8, 0xca, 0x96, 0x46, 0x0f, 0xdb, 0xf4, 0x38,
0x46, 0x64, 0xc6, 0x16, 0x13, 0x25, 0x45, 0x63, 0x67, 0x51, 0xd0, 0xe2, 0xc8, 0x66, 0x2d, 0x3e,
0x32, 0xa4, 0xdd, 0x3a, 0x40, 0xc1, 0x8e, 0x39, 0x57, 0xd4, 0x24, 0x24, 0x67, 0x3c, 0x33, 0x94,
0xc1, 0x6c, 0x0a, 0x78, 0x95, 0x76, 0x13, 0x34, 0x5d, 0x74, 0xd9, 0x45, 0x77, 0x5d, 0xb5, 0x45,
0x1f, 0x7f, 0xa1, 0x9b, 0xae, 0xda, 0x5f, 0x50, 0xf4, 0x2f, 0x14, 0x05, 0xb2, 0x28, 0x8a, 0xfb,
0x18, 0x72, 0x1e, 0x1c, 0x91, 0x82, 0x51, 0xa0, 0x2b, 0xf3, 0x9e, 0xe7, 0x77, 0xcf, 0x39, 0xf7,
0xcc, 0xbd, 0xc7, 0x02, 0xd0, 0x46, 0xee, 0xd9, 0x81, 0x65, 0x9b, 0xae, 0x89, 0x36, 0xbe, 0x34,
0x5c, 0x4d, 0xc7, 0xfd, 0x03, 0x4a, 0xd3, 0x2c, 0xe3, 0xe0, 0xfc, 0x83, 0x9d, 0xbd, 0x9e, 0x69,
0xf6, 0xfa, 0xb8, 0xa4, 0x59, 0x46, 0x49, 0x1b, 0x0e, 0x4d, 0x57, 0x73, 0x0d, 0x73, 0xe8, 0x30,
0x95, 0x9d, 0x5d, 0xce, 0xa5, 0xab, 0x97, 0xa3, 0xd3, 0x12, 0x1e, 0x58, 0xee, 0x98, 0x33, 0xf7,
0xc2, 0x4c, 0xc7, 0xb5, 0x47, 0x5d, 0x97, 0x73, 0x6f, 0x86, 0xb9, 0xae, 0x31, 0xc0, 0x8e, 0xab,
0x0d, 0x2c, 0x2e, 0xb0, 0x75, 0xae, 0xf5, 0x0d, 0x5d, 0x73, 0x71, 0xc9, 0xfb, 0xc1, 0x19, 0xf7,
0xe8, 0x3f, 0xdd, 0xfb, 0x3d, 0x3c, 0xbc, 0xef, 0xbc, 0xd6, 0x7a, 0x3d, 0x6c, 0x97, 0x4c, 0x8b,
0xc2, 0x9a, 0x01, 0x51, 0x22, 0xbb, 0x61, 0x6c, 0x4f, 0x8a, 0x71, 0xe4, 0x1f, 0x81, 0xf8, 0xcc,
0xc1, 0x76, 0x0b, 0x3b, 0x8e, 0x61, 0x0e, 0x9f, 0x1b, 0xf8, 0xb5, 0x83, 0xaa, 0xb0, 0x3a, 0x72,
0xb0, 0xdd, 0x71, 0x18, 0xd1, 0x91, 0x84, 0x42, 0x72, 0x3f, 0x77, 0xf8, 0xce, 0xc1, 0x8c, 0xd8,
0x1c, 0x84, 0xb4, 0xd5, 0x95, 0xd1, 0x94, 0xe0, 0xc8, 0x7f, 0x17, 0x60, 0x2d, 0x24, 0x81, 0xf2,
0x90, 0x30, 0x74, 0x49, 0x28, 0x08, 0xfb, 0x59, 0x35, 0x61, 0xe8, 0x68, 0x1b, 0x32, 0x5a, 0x0f,
0x0f, 0xdd, 0x8e, 0xa1, 0x4b, 0x09, 0x4a, 0x5d, 0xa6, 0xeb, 0xaa, 0x8e, 0x2a, 0x2c, 0x37, 0x1d,
0xc7, 0xd5, 0x5c, 0x2c, 0x25, 0x0b, 0xc2, 0x7e, 0xfe, 0xf0, 0xce, 0x3c, 0x18, 0x2d, 0x22, 0xac,
0x66, 0x09, 0x97, 0xfe, 0x44, 0x5b, 0xb0, 0x4c, 0xf7, 0x63, 0xe8, 0x52, 0x8a, 0xda, 0x4f, 0x93,
0x65, 0x55, 0x47, 0xbb, 0x90, 0xa5, 0x8c, 0xa1, 0x36, 0xc0, 0xd2, 0x12, 0x65, 0x65, 0x08, 0xa1,
0xae, 0x0d, 0x30, 0xda, 0x81, 0x8c, 0x83, 0x5f, 0x8d, 0xf0, 0xb0, 0x8b, 0xa5, 0x74, 0x41, 0xd8,
0x4f, 0xa9, 0x93, 0xb5, 0xfc, 0x9f, 0x65, 0x48, 0x11, 0x8f, 0x91, 0xbd, 0x7c, 0x04, 0x4b, 0x0c,
0x6b, 0x82, 0x62, 0xbd, 0x11, 0x8f, 0x95, 0x82, 0x64, 0xc2, 0xe8, 0x7b, 0xb0, 0xda, 0xb5, 0x31,
0xcd, 0x58, 0x47, 0xf7, 0x76, 0x9a, 0x3b, 0xdc, 0x39, 0x60, 0xe5, 0x71, 0xe0, 0x95, 0xc7, 0x41,
0xdb, 0x2b, 0x0f, 0x75, 0xc5, 0x53, 0xa8, 0x10, 0x03, 0x47, 0xb0, 0xa6, 0x75, 0x5d, 0xe3, 0xdc,
0x67, 0x22, 0x35, 0xd7, 0x44, 0x7e, 0xaa, 0x42, 0x8d, 0x7c, 0x02, 0xb9, 0xee, 0x99, 0x36, 0xec,
0x61, 0x66, 0x60, 0x69, 0xae, 0x01, 0x60, 0xe2, 0x54, 0xf9, 0x01, 0x40, 0x5f, 0x73, 0xdc, 0x4e,
0xdf, 0xec, 0x19, 0x43, 0x1a, 0xaf, 0x8b, 0x75, 0xb3, 0x44, 0xfa, 0x84, 0x08, 0x23, 0x05, 0x44,
0x4b, 0x73, 0x9c, 0xd7, 0xa6, 0xad, 0x77, 0x98, 0x45, 0x5d, 0x5a, 0x9e, 0x6b, 0x60, 0xcd, 0xd3,
0x39, 0x62, 0x2a, 0xc1, 0x64, 0x66, 0x42, 0xc9, 0xbc, 0x0e, 0x70, 0x6a, 0xd8, 0x8e, 0xcb, 0xb8,
0x59, 0xca, 0xcd, 0x52, 0x0a, 0x65, 0xef, 0x02, 0xc5, 0xc3, 0xb8, 0xc0, 0x74, 0x09, 0xc1, 0x63,
0x0e, 0x8d, 0xee, 0x17, 0x8c, 0x99, 0x63, 0x4c, 0x42, 0xa0, 0xcc, 0x5b, 0xb0, 0xa2, 0x1b, 0x8e,
0xd5, 0xd7, 0xc6, 0x8c, 0xbf, 0x42, 0xf9, 0x39, 0x4e, 0xa3, 0x22, 0xf7, 0x01, 0x59, 0x36, 0x3e,
0xc5, 0xb6, 0x8d, 0xf5, 0x4e, 0x5f, 0x1b, 0xf6, 0x46, 0x5a, 0x0f, 0x4b, 0xab, 0x54, 0x70, 0x7d,
0xc2, 0x39, 0xe1, 0x0c, 0xf4, 0x21, 0xa4, 0x7b, 0x78, 0xa8, 0x63, 0x5b, 0xca, 0xd3, 0x1a, 0xda,
0x9d, 0x59, 0x43, 0x8f, 0xa9, 0x88, 0xca, 0x45, 0xd1, 0x55, 0x58, 0xc2, 0x03, 0xcd, 0xe8, 0x4b,
0x6b, 0xd4, 0x2c, 0x5b, 0xa0, 0x22, 0xac, 0x1b, 0x4e, 0x87, 0xfe, 0xee, 0x9c, 0x63, 0xdb, 0x38,
0x35, 0xb0, 0x2e, 0x89, 0x05, 0x61, 0x3f, 0xa3, 0xae, 0x19, 0x8e, 0x42, 0xe8, 0xcf, 0x39, 0x99,
0x58, 0xb0, 0xce, 0xcc, 0x21, 0x96, 0xd6, 0x99, 0x05, 0xba, 0xe0, 0x16, 0xe8, 0xef, 0xa9, 0x05,
0xe4, 0x59, 0x68, 0x12, 0xfa, 0xc4, 0x82, 0x04, 0xcb, 0x5d, 0x73, 0x34, 0x74, 0xed, 0xb1, 0xb4,
0xc1, 0x8e, 0x31, 0x5f, 0x92, 0xa3, 0xd4, 0x37, 0xbb, 0x5a, 0xdf, 0x70, 0xc7, 0xd2, 0x55, 0x1e,
0x5d, 0xbe, 0x46, 0x37, 0x21, 0x67, 0x99, 0x8e, 0xab, 0xf5, 0x3b, 0x5d, 0x53, 0xc7, 0xd2, 0x35,
0xca, 0x06, 0x46, 0x3a, 0x32, 0x75, 0x8c, 0x36, 0x21, 0x6d, 0xe3, 0x9e, 0x61, 0x0e, 0xa5, 0x4d,
0x76, 0x78, 0xd9, 0x0a, 0xdd, 0x81, 0xbc, 0xe3, 0xda, 0x18, 0xbb, 0x1d, 0x4d, 0xd7, 0x6d, 0xec,
0x38, 0xd2, 0x16, 0xe5, 0xaf, 0x32, 0x6a, 0x99, 0x11, 0xd1, 0xc7, 0x20, 0x85, 0xaa, 0xab, 0x63,
0xe3, 0x57, 0x23, 0xc3, 0xc6, 0xba, 0x24, 0xd1, 0x8d, 0x6c, 0x06, 0x2b, 0x49, 0xe5, 0xdc, 0x40,
0x03, 0xd8, 0x0e, 0x35, 0x80, 0x3f, 0x24, 0x21, 0x47, 0x8e, 0x71, 0xd3, 0x36, 0x4f, 0x8d, 0x3e,
0x8e, 0xf4, 0x81, 0x40, 0x31, 0x26, 0x2e, 0x2c, 0xc6, 0xe4, 0x85, 0xc5, 0x98, 0xba, 0xa8, 0x18,
0x97, 0xe6, 0x14, 0x63, 0x7a, 0xd1, 0x62, 0x5c, 0x9e, 0x5f, 0x8c, 0x99, 0xc5, 0x8b, 0xd1, 0x1f,
0xb8, 0x6c, 0x30, 0x70, 0xd1, 0x56, 0x07, 0x97, 0x6c, 0x75, 0xa1, 0x2e, 0x95, 0xbb, 0x4c, 0x97,
0x92, 0xff, 0x94, 0x00, 0xe9, 0x99, 0x45, 0x14, 0x7d, 0xc9, 0x23, 0x09, 0xc7, 0x8e, 0x8b, 0xee,
0x06, 0xd2, 0x42, 0x73, 0xf9, 0x08, 0xbe, 0x7d, 0xb4, 0x6c, 0x2f, 0x89, 0x82, 0xf4, 0x17, 0xc1,
0x9f, 0xa2, 0x77, 0xfd, 0x29, 0x4a, 0x44, 0x24, 0xa7, 0xe9, 0x7a, 0xd7, 0x9f, 0xae, 0x64, 0x54,
0x70, 0x92, 0xba, 0xfb, 0xa1, 0xd4, 0xa5, 0x22, 0xb2, 0x81, 0x34, 0x3e, 0x98, 0x99, 0xc6, 0xa5,
0x88, 0xd2, 0x85, 0x29, 0x4d, 0x2f, 0x9c, 0x52, 0xf9, 0x5f, 0x02, 0x64, 0x49, 0xc8, 0x68, 0xcf,
0x88, 0x54, 0xfb, 0xa4, 0xfb, 0x24, 0xfc, 0xdd, 0x67, 0x1f, 0xc2, 0x4d, 0x86, 0x46, 0x60, 0x46,
0xef, 0xf1, 0x17, 0x4c, 0x6a, 0x5e, 0xc1, 0x2c, 0xbd, 0x5d, 0xc1, 0xa4, 0x2f, 0x55, 0x30, 0x0f,
0x41, 0xa2, 0x28, 0xc7, 0xb5, 0xf1, 0x64, 0xfb, 0x5e, 0xbd, 0xdc, 0x80, 0x14, 0x6d, 0x59, 0xd1,
0x4a, 0xa1, 0x74, 0xf9, 0x09, 0x6c, 0x32, 0xdd, 0x88, 0x66, 0x38, 0x7e, 0x9e, 0xa5, 0x44, 0x8c,
0xa5, 0x87, 0xb0, 0x39, 0xad, 0xda, 0x80, 0xa5, 0x82, 0x17, 0xf9, 0x28, 0x08, 0xc6, 0x90, 0xff,
0xcd, 0x33, 0x47, 0x7b, 0xf5, 0xac, 0xcc, 0xb1, 0xae, 0x9f, 0x98, 0xdb, 0xf5, 0x93, 0xb3, 0xbb,
0xfe, 0xff, 0x6f, 0xee, 0x1e, 0xf8, 0xa3, 0x46, 0x41, 0x7b, 0x51, 0xbb, 0xe9, 0xed, 0x9a, 0x45,
0x2d, 0xfb, 0xed, 0xa3, 0xb4, 0x9d, 0x12, 0x05, 0xe9, 0x2a, 0x0f, 0x80, 0xfc, 0xb1, 0x3f, 0x75,
0x01, 0xd5, 0x79, 0x49, 0xff, 0x6b, 0x82, 0x7d, 0x18, 0xbc, 0xcf, 0x4f, 0x38, 0xe0, 0xbe, 0x8f,
0x64, 0x22, 0xfe, 0x23, 0x99, 0xbc, 0xf8, 0x23, 0x99, 0xba, 0xe0, 0x23, 0xb9, 0x34, 0xe7, 0x23,
0x99, 0x9e, 0xf5, 0x91, 0xf4, 0x27, 0x71, 0x79, 0x5e, 0x12, 0x33, 0x6f, 0x97, 0xc4, 0xec, 0xa5,
0x92, 0xf8, 0x0f, 0xc1, 0xdf, 0xb1, 0x39, 0x5e, 0x2f, 0x19, 0xf2, 0x34, 0x98, 0x2c, 0x1f, 0x99,
0x6f, 0x1f, 0x2d, 0xd9, 0x49, 0x92, 0x8d, 0x49, 0x58, 0xdf, 0xf1, 0x85, 0x35, 0x11, 0x12, 0x9a,
0x06, 0xf8, 0x6e, 0x30, 0xc0, 0xc9, 0x90, 0xa0, 0x3f, 0xd4, 0x85, 0x49, 0xa8, 0x53, 0x21, 0x29,
0x2f, 0xe8, 0xa5, 0x48, 0xd0, 0x97, 0x42, 0x92, 0xc1, 0xf0, 0xcb, 0x7b, 0x00, 0x4d, 0x7e, 0x07,
0xa9, 0x56, 0xc2, 0x25, 0x23, 0x7f, 0x0c, 0x6b, 0x1e, 0xd7, 0xdb, 0xf8, 0x1d, 0xc8, 0x78, 0x97,
0x96, 0x70, 0x0d, 0x3f, 0x51, 0x27, 0x2c, 0xb9, 0x0f, 0xf9, 0x66, 0xe0, 0x6e, 0x83, 0xee, 0xc1,
0x8a, 0xd9, 0xd7, 0x3b, 0xf1, 0xca, 0x39, 0xb3, 0xaf, 0x7b, 0x3a, 0x44, 0x7a, 0x88, 0x5f, 0x4f,
0xa5, 0x13, 0x11, 0xe9, 0x21, 0x7e, 0xed, 0x49, 0xcb, 0x32, 0xac, 0xf0, 0x5e, 0x79, 0xaa, 0x35,
0x5c, 0x0b, 0x21, 0xff, 0x51, 0xe1, 0xc7, 0xa3, 0x02, 0x2b, 0xb5, 0x51, 0xdf, 0x35, 0x8e, 0xb5,
0xae, 0x6b, 0xda, 0x0e, 0xfa, 0x08, 0x52, 0x83, 0x53, 0xcd, 0x7b, 0x61, 0x16, 0x66, 0x7e, 0x8a,
0x7c, 0x0a, 0x2a, 0x95, 0x96, 0x5d, 0xc8, 0xf9, 0x88, 0xe8, 0x7d, 0x48, 0xb9, 0x63, 0x8b, 0x39,
0xca, 0x1f, 0xee, 0xcd, 0x36, 0x72, 0xaa, 0xb5, 0xc7, 0x16, 0x56, 0xa9, 0x24, 0xfa, 0x30, 0xf8,
0x4c, 0xbb, 0x3e, 0x5b, 0xe5, 0xb8, 0xec, 0x7f, 0xa5, 0xc9, 0x5f, 0x09, 0x90, 0x67, 0x5b, 0x53,
0xb1, 0x63, 0x99, 0x43, 0x27, 0xf0, 0xb2, 0x14, 0x02, 0x2f, 0x4b, 0x11, 0x92, 0x23, 0xdb, 0xfb,
0x1e, 0x92, 0x9f, 0xe4, 0x84, 0x3a, 0xb8, 0x6b, 0x63, 0x97, 0x1f, 0x6e, 0xbe, 0x9a, 0x42, 0x49,
0x5d, 0x02, 0x8a, 0x0a, 0xf9, 0x46, 0xb5, 0x72, 0x74, 0xd4, 0x37, 0xf0, 0xd0, 0x2d, 0x8f, 0xdc,
0x33, 0x72, 0x2f, 0xec, 0xd2, 0xd5, 0x14, 0x4b, 0x86, 0x11, 0xaa, 0x3a, 0xba, 0x0d, 0xab, 0x9c,
0xc9, 0x21, 0x30, 0x5c, 0x2b, 0x8c, 0xd8, 0xa2, 0x34, 0xf9, 0xd7, 0x02, 0x6c, 0xd7, 0xc6, 0x4d,
0xdb, 0xfc, 0x1c, 0x77, 0xdd, 0x86, 0xdd, 0x6b, 0x61, 0xcd, 0xee, 0x9e, 0x79, 0x15, 0xb7, 0x09,
0x69, 0xf3, 0xf4, 0xd4, 0xc1, 0x2e, 0x35, 0x9e, 0x52, 0xf9, 0x8a, 0x7c, 0x40, 0xfa, 0xc6, 0xc0,
0x60, 0x26, 0x53, 0x2a, 0x5b, 0x90, 0xed, 0x6b, 0x4e, 0x97, 0x6e, 0x29, 0xa3, 0x92, 0x9f, 0xe8,
0x18, 0x96, 0x5f, 0x8d, 0xb0, 0x6d, 0x60, 0x72, 0x18, 0x48, 0xae, 0xef, 0xcd, 0xde, 0x68, 0x04,
0xc0, 0xa7, 0x23, 0x6c, 0x8f, 0x55, 0x4f, 0x59, 0xfe, 0xbd, 0x00, 0x5b, 0x31, 0x42, 0xe8, 0x18,
0x92, 0x5f, 0xe0, 0x31, 0x2f, 0x83, 0xe2, 0x82, 0xf6, 0x9f, 0xe2, 0x31, 0x3d, 0x98, 0x6f, 0x84,
0x44, 0xe1, 0x8a, 0x4a, 0x0c, 0xa0, 0x07, 0x90, 0x1e, 0x60, 0xf7, 0xcc, 0xd4, 0x79, 0x79, 0xdc,
0x9a, 0x69, 0x8a, 0xa9, 0xd7, 0xa8, 0xa0, 0xca, 0x15, 0x48, 0x38, 0xce, 0xb5, 0xfe, 0xc8, 0xbb,
0xd5, 0xb3, 0x85, 0xfc, 0x2b, 0x01, 0x76, 0x66, 0x85, 0x96, 0x57, 0xd1, 0xe5, 0x62, 0x7b, 0x0b,
0x56, 0x5c, 0x93, 0x74, 0x2a, 0x1b, 0x3b, 0xa3, 0x3e, 0x2b, 0xa7, 0x94, 0x9a, 0xa3, 0x34, 0x95,
0x92, 0xd0, 0xfb, 0xa4, 0x45, 0x51, 0x66, 0x8a, 0xc6, 0x5a, 0x9a, 0xb9, 0x81, 0x86, 0xdd, 0x53,
0xb9, 0x9c, 0x7c, 0x0f, 0xd6, 0xaa, 0x4e, 0x59, 0x1f, 0x18, 0xc3, 0x09, 0xaa, 0x6d, 0xc8, 0x18,
0x4e, 0x47, 0x23, 0x34, 0x8a, 0x2b, 0xa3, 0x2e, 0x1b, 0x4c, 0x44, 0xbe, 0x0b, 0xc9, 0x86, 0xdd,
0x8b, 0x7c, 0xdb, 0x10, 0xa4, 0x7c, 0xef, 0x1d, 0xfa, 0x5b, 0xfe, 0x00, 0x56, 0x6b, 0xe3, 0x26,
0xb6, 0x07, 0x06, 0x9b, 0x08, 0xa1, 0x02, 0xe4, 0xac, 0xe9, 0x92, 0x1e, 0xfc, 0xac, 0xea, 0x27,
0x15, 0xed, 0xc0, 0x48, 0x8a, 0x8d, 0x70, 0x0a, 0xb0, 0xf7, 0xac, 0xa5, 0xa8, 0x2d, 0xa5, 0xd5,
0xaa, 0x36, 0xea, 0xad, 0x76, 0xb9, 0xad, 0x74, 0x9e, 0xd5, 0x5b, 0x4d, 0xe5, 0xa8, 0x7a, 0x5c,
0x55, 0x2a, 0xe2, 0x15, 0xb4, 0x0b, 0x5b, 0x11, 0x89, 0xf2, 0x51, 0xbb, 0xfa, 0x5c, 0x11, 0x05,
0x74, 0x13, 0x76, 0x23, 0xcc, 0xb6, 0xa2, 0xd6, 0xaa, 0xf5, 0x72, 0x5b, 0xa9, 0x88, 0x89, 0xe2,
0x2b, 0x10, 0xc9, 0x81, 0xf2, 0x36, 0x4f, 0x5a, 0x05, 0xda, 0x86, 0x6b, 0x94, 0xa6, 0xb4, 0x9a,
0x8d, 0x7a, 0x4b, 0x69, 0xbf, 0x68, 0x2a, 0x9d, 0xa3, 0x46, 0x45, 0x11, 0xaf, 0xa0, 0xeb, 0xb0,
0x1d, 0x61, 0x55, 0x2b, 0x9d, 0x76, 0xe3, 0xa9, 0x52, 0x17, 0x05, 0x74, 0x1b, 0x6e, 0xc6, 0xb2,
0xb9, 0x50, 0xa2, 0xf8, 0x5b, 0x7e, 0x31, 0x63, 0x1b, 0xdc, 0x81, 0x4d, 0x8a, 0xd0, 0xbf, 0x33,
0x85, 0x6f, 0xed, 0x2a, 0x88, 0x53, 0xde, 0x64, 0x4f, 0x9b, 0x80, 0xa6, 0xd4, 0x6a, 0x9d, 0xd3,
0x13, 0xe8, 0x1a, 0xac, 0x4f, 0xe9, 0x15, 0xe5, 0x44, 0x21, 0x3b, 0x4c, 0x06, 0x8d, 0x9c, 0x34,
0x8e, 0x9e, 0x2a, 0x15, 0x31, 0x15, 0x14, 0x6e, 0x3d, 0x6b, 0x35, 0x95, 0x7a, 0x45, 0x5c, 0x0a,
0x92, 0xab, 0xf5, 0x6a, 0xbb, 0x5a, 0x3e, 0x11, 0xd3, 0xc5, 0x1f, 0x42, 0x9a, 0xbd, 0x0b, 0x88,
0xf3, 0xc7, 0x4a, 0xbd, 0xa2, 0xa8, 0xa1, 0x2c, 0xac, 0xc3, 0x2a, 0xa7, 0x1f, 0x2b, 0xb5, 0xf2,
0x09, 0xc1, 0xb9, 0x06, 0x39, 0x4e, 0xa2, 0x84, 0x04, 0x42, 0x90, 0xe7, 0x84, 0x4a, 0xf5, 0x39,
0x49, 0x8a, 0x98, 0x2c, 0x56, 0x60, 0x99, 0x77, 0x68, 0xb4, 0x05, 0x1b, 0xb5, 0xe3, 0x32, 0x8d,
0x59, 0xd0, 0xf6, 0x1a, 0xe4, 0x3c, 0x46, 0xab, 0xd6, 0x62, 0x96, 0x3d, 0x42, 0xa3, 0xdd, 0x14,
0x13, 0xc5, 0x53, 0xc8, 0x78, 0x9d, 0x12, 0x49, 0x70, 0x95, 0xfc, 0x9e, 0x51, 0x29, 0x9b, 0x80,
0x26, 0x9c, 0x7a, 0xa3, 0xdd, 0x51, 0x95, 0x72, 0xe5, 0x85, 0x28, 0x10, 0x5c, 0x13, 0x3a, 0xa3,
0x25, 0x48, 0xd4, 0x7c, 0xb4, 0x5a, 0xe3, 0x39, 0x89, 0x65, 0xf1, 0x25, 0x5c, 0x9b, 0xd9, 0x48,
0xd0, 0x1d, 0xb8, 0x55, 0x7b, 0xd1, 0x54, 0x1b, 0xdf, 0x57, 0x8e, 0xda, 0x0d, 0xf5, 0x71, 0x4b,
0x29, 0xab, 0x47, 0x4f, 0x9e, 0x2a, 0x2f, 0x42, 0x08, 0x64, 0xb8, 0x31, 0x5b, 0xac, 0xa1, 0x3e,
0xee, 0xd4, 0xcb, 0x35, 0x45, 0x14, 0x8a, 0x3f, 0x86, 0x15, 0x7f, 0x87, 0x21, 0x61, 0x61, 0x72,
0x35, 0xa5, 0xfd, 0xa4, 0x51, 0xe9, 0x28, 0x9f, 0x3e, 0x2b, 0x9f, 0xb4, 0xc4, 0x2b, 0x68, 0x0f,
0xa4, 0x00, 0xa3, 0xd5, 0x2e, 0xab, 0xed, 0x56, 0xe7, 0x07, 0xd5, 0xf6, 0x13, 0x51, 0x20, 0x45,
0x1c, 0xe0, 0x1e, 0x35, 0xea, 0xed, 0x72, 0xb5, 0xde, 0x12, 0x13, 0x87, 0xbf, 0x91, 0x20, 0x47,
0xbe, 0x1d, 0x2d, 0x6c, 0x9f, 0x1b, 0x5d, 0x8c, 0x9e, 0xc2, 0xf2, 0x13, 0xac, 0xf5, 0xdd, 0xb3,
0x2f, 0xd1, 0x66, 0xe4, 0x76, 0xa6, 0x0c, 0x2c, 0x77, 0xbc, 0x13, 0x43, 0x97, 0xc5, 0x37, 0x7f,
0xfb, 0xe7, 0x2f, 0x12, 0x80, 0x32, 0xa5, 0x33, 0x6e, 0xe1, 0x31, 0x2c, 0xa9, 0x58, 0xd3, 0xc7,
0x97, 0x36, 0x95, 0xa7, 0xa6, 0x32, 0x28, 0x5d, 0xb2, 0xa9, 0x7e, 0x1d, 0x32, 0xcf, 0xf9, 0xe8,
0x3b, 0xd6, 0xd6, 0x56, 0x84, 0xde, 0xa2, 0x53, 0x76, 0x79, 0x9d, 0x1a, 0xcb, 0xa1, 0xec, 0x64,
0x7c, 0x8e, 0x7e, 0x02, 0xeb, 0x8f, 0xb1, 0xcb, 0x9e, 0x73, 0xde, 0x98, 0x3a, 0xd6, 0xf0, 0x9d,
0x45, 0x46, 0xde, 0x8e, 0xfc, 0xde, 0x9b, 0x3f, 0x4a, 0x6b, 0xb0, 0x4a, 0x64, 0xf0, 0xd0, 0x35,
0xba, 0x9a, 0x8b, 0x75, 0xea, 0x19, 0x21, 0xb1, 0x34, 0xc0, 0x25, 0x72, 0x29, 0xf0, 0x86, 0xe9,
0xe8, 0x4b, 0x10, 0x27, 0x00, 0xbc, 0xf1, 0x51, 0x9c, 0xff, 0x42, 0xac, 0x7f, 0xae, 0x29, 0xdf,
0x8b, 0x73, 0xbd, 0x81, 0xd6, 0x99, 0x5f, 0x02, 0xc0, 0xe2, 0x7e, 0x7e, 0x29, 0xc0, 0x06, 0xbb,
0x4d, 0x07, 0xfd, 0xdf, 0x9f, 0xed, 0x27, 0x66, 0x52, 0xb2, 0x00, 0xac, 0x52, 0x1c, 0xac, 0xcd,
0x9d, 0x28, 0xac, 0x87, 0x42, 0x11, 0xb9, 0x90, 0x9f, 0x44, 0x85, 0x0d, 0x19, 0xe2, 0x62, 0x12,
0x3f, 0x53, 0xa7, 0x7a, 0x72, 0x31, 0xce, 0xf5, 0x3a, 0x5a, 0x9b, 0xba, 0x66, 0x23, 0x8a, 0xaf,
0x05, 0x58, 0x67, 0x37, 0x63, 0xbf, 0xe7, 0xf7, 0xe6, 0x44, 0xc3, 0xff, 0x02, 0x9f, 0x0b, 0xe7,
0x7e, 0x1c, 0x9c, 0xab, 0x3b, 0x61, 0x38, 0x24, 0x0e, 0x3f, 0x17, 0x60, 0x3d, 0x32, 0x71, 0x88,
0xc9, 0x4f, 0xdc, 0x64, 0x22, 0xf6, 0x6c, 0x7d, 0x27, 0x0e, 0xcb, 0x9e, 0xbc, 0x15, 0xc2, 0x52,
0x62, 0x0f, 0xff, 0x31, 0xc1, 0xf4, 0x8d, 0x00, 0xd7, 0x55, 0xec, 0xe0, 0xa1, 0x5e, 0x1b, 0xfb,
0x06, 0x37, 0x5d, 0xfa, 0xc4, 0xab, 0x5d, 0x94, 0xab, 0x38, 0x20, 0xe5, 0x38, 0x20, 0xfb, 0xf2,
0xed, 0x08, 0x10, 0x9b, 0xba, 0x3e, 0xf7, 0xf9, 0x0c, 0x17, 0x0c, 0x9b, 0x6d, 0x5c, 0xbe, 0x60,
0xa8, 0xde, 0x82, 0x05, 0xc3, 0x26, 0x23, 0xe1, 0x82, 0x61, 0x9e, 0xe7, 0x15, 0x8c, 0x7f, 0x82,
0x30, 0x17, 0xce, 0x62, 0x05, 0x43, 0xe1, 0x90, 0x38, 0x7c, 0x1d, 0x2a, 0x98, 0x8b, 0x10, 0xcd,
0x9e, 0x69, 0xbc, 0x65, 0xb9, 0x50, 0x24, 0x71, 0xe5, 0xe2, 0x9b, 0x15, 0xb1, 0xd4, 0xb1, 0x69,
0xc5, 0xff, 0xa4, 0x5c, 0x38, 0x90, 0xd9, 0xe5, 0xe2, 0xef, 0xba, 0xde, 0xd4, 0xe3, 0xf2, 0x5d,
0xd7, 0x7b, 0xb0, 0x2f, 0xd6, 0x75, 0xf9, 0xeb, 0x3f, 0xd2, 0x75, 0x3d, 0xff, 0xf3, 0xba, 0x6e,
0x70, 0xda, 0xb1, 0x00, 0xac, 0xc5, 0xba, 0x2e, 0x87, 0x45, 0xa2, 0xf2, 0x0a, 0xb2, 0x34, 0x2a,
0xb5, 0x53, 0x2d, 0x3e, 0x1c, 0xb7, 0xe6, 0xbd, 0xca, 0x1d, 0xf9, 0x6e, 0x9c, 0x63, 0x11, 0xe5,
0xa7, 0x8e, 0xc9, 0xdb, 0x1d, 0xfd, 0x4c, 0x00, 0xd1, 0x3b, 0x41, 0x93, 0x41, 0xc3, 0xed, 0x99,
0x2e, 0x82, 0xb3, 0x8b, 0xd8, 0xea, 0x78, 0x10, 0xe7, 0xbc, 0xb0, 0xb3, 0xeb, 0xab, 0x0e, 0x6e,
0xcc, 0x29, 0xf1, 0xff, 0x06, 0x22, 0xfb, 0x1f, 0x43, 0xb6, 0xac, 0xeb, 0xe4, 0x51, 0xdf, 0x6e,
0xc6, 0xee, 0xff, 0x76, 0xdc, 0x40, 0xc1, 0x37, 0x09, 0xb8, 0x20, 0xf4, 0xf2, 0x7a, 0x20, 0x02,
0x25, 0xd3, 0xb5, 0x88, 0xeb, 0x37, 0x82, 0x7f, 0x5c, 0xd2, 0x6e, 0xa2, 0x5b, 0x17, 0xf5, 0x78,
0xea, 0x2c, 0x36, 0x02, 0xdf, 0x8d, 0x73, 0x7e, 0x63, 0x67, 0x3b, 0xe2, 0xdc, 0x7f, 0x54, 0xfb,
0xb0, 0xa2, 0xe2, 0x81, 0x79, 0x8e, 0xe7, 0x84, 0x20, 0xce, 0x71, 0xfc, 0x39, 0x28, 0x46, 0x77,
0x8d, 0x7e, 0x27, 0xc0, 0x06, 0xbf, 0xd3, 0xfa, 0x6e, 0xcf, 0x0e, 0x3a, 0x58, 0xf0, 0xa9, 0xee,
0x1d, 0x84, 0xd2, 0xc2, 0xf2, 0x3c, 0x39, 0xf1, 0x15, 0x22, 0xef, 0x96, 0x7a, 0x7d, 0xf3, 0xa5,
0xd6, 0x27, 0x77, 0x11, 0xa2, 0x6c, 0xda, 0x3d, 0xa7, 0xd4, 0x71, 0xa8, 0x3e, 0xeb, 0x1b, 0x50,
0x75, 0xaa, 0xda, 0x80, 0x3e, 0x7c, 0x63, 0xe3, 0x33, 0xfb, 0x4f, 0x23, 0x42, 0x2f, 0x6a, 0xf9,
0x20, 0x0e, 0xc6, 0x35, 0xb4, 0xe1, 0xc1, 0xe8, 0x18, 0x8e, 0xa1, 0x0d, 0xe8, 0xab, 0x1b, 0xfd,
0x54, 0x80, 0x2d, 0x7a, 0x3c, 0x3f, 0x63, 0xc6, 0xfd, 0xcf, 0xe8, 0x38, 0x24, 0x72, 0x5c, 0x6c,
0xa6, 0xba, 0xf2, 0x61, 0x1c, 0x8e, 0x6d, 0xb4, 0x55, 0xf2, 0xbd, 0xc4, 0x4b, 0xdc, 0x54, 0x69,
0x80, 0x1f, 0xfd, 0x59, 0xf8, 0xa6, 0xfc, 0x95, 0x80, 0x3e, 0x81, 0x0c, 0x79, 0x32, 0x14, 0xca,
0xcd, 0xaa, 0x5c, 0x44, 0xfb, 0x67, 0xae, 0x6b, 0x39, 0x0f, 0x4b, 0xa5, 0x9e, 0xe1, 0x9e, 0x8d,
0x5e, 0x1e, 0x74, 0xcd, 0x41, 0xa9, 0xab, 0x99, 0x53, 0x45, 0xeb, 0x8b, 0x5e, 0x89, 0xb8, 0x39,
0x4c, 0xbe, 0x7f, 0xf0, 0x41, 0x51, 0x48, 0x1c, 0x8a, 0x9a, 0x65, 0xf5, 0x79, 0x5b, 0x2e, 0x7d,
0xee, 0x98, 0xc3, 0x20, 0xa5, 0x67, 0x5b, 0xdd, 0x87, 0x11, 0x99, 0x87, 0x11, 0x99, 0xcf, 0xee,
0xce, 0xf3, 0x48, 0xff, 0x56, 0x87, 0x88, 0xbe, 0x4c, 0xd3, 0x08, 0x7d, 0xf8, 0xdf, 0x00, 0x00,
0x00, 0xff, 0xff, 0x64, 0x6c, 0xef, 0xe7, 0xe6, 0x23, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
const _ = grpc.SupportPackageIsVersion6
// AuthServiceClient is the client API for AuthService service.
//
@@ -2204,11 +2269,11 @@ type AuthServiceClient interface {
GetMyUserAddress(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*UserAddress, error)
UpdateMyUserAddress(ctx context.Context, in *UpdateUserAddressRequest, opts ...grpc.CallOption) (*UserAddress, error)
GetMyMfas(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*MultiFactors, error)
SetMyPassword(ctx context.Context, in *PasswordRequest, opts ...grpc.CallOption) (*empty.Empty, error)
//Password
ChangeMyPassword(ctx context.Context, in *PasswordChange, opts ...grpc.CallOption) (*empty.Empty, error)
// MFA
AddMfaOTP(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*MfaOtpResponse, error)
VerifyMfaOTP(ctx context.Context, in *VerifyMfaOtp, opts ...grpc.CallOption) (*MfaOtpResponse, error)
VerifyMfaOTP(ctx context.Context, in *VerifyMfaOtp, opts ...grpc.CallOption) (*empty.Empty, error)
RemoveMfaOTP(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error)
SearchMyProjectOrgs(ctx context.Context, in *MyProjectOrgSearchRequest, opts ...grpc.CallOption) (*MyProjectOrgSearchResponse, error)
IsIamAdmin(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*IsAdminResponse, error)
@@ -2217,10 +2282,10 @@ type AuthServiceClient interface {
}
type authServiceClient struct {
cc *grpc.ClientConn
cc grpc.ClientConnInterface
}
func NewAuthServiceClient(cc *grpc.ClientConn) AuthServiceClient {
func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient {
return &authServiceClient{cc}
}
@@ -2377,15 +2442,6 @@ func (c *authServiceClient) GetMyMfas(ctx context.Context, in *empty.Empty, opts
return out, nil
}
func (c *authServiceClient) SetMyPassword(ctx context.Context, in *PasswordRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
err := c.cc.Invoke(ctx, "/zitadel.auth.api.v1.AuthService/SetMyPassword", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *authServiceClient) ChangeMyPassword(ctx context.Context, in *PasswordChange, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
err := c.cc.Invoke(ctx, "/zitadel.auth.api.v1.AuthService/ChangeMyPassword", in, out, opts...)
@@ -2404,8 +2460,8 @@ func (c *authServiceClient) AddMfaOTP(ctx context.Context, in *empty.Empty, opts
return out, nil
}
func (c *authServiceClient) VerifyMfaOTP(ctx context.Context, in *VerifyMfaOtp, opts ...grpc.CallOption) (*MfaOtpResponse, error) {
out := new(MfaOtpResponse)
func (c *authServiceClient) VerifyMfaOTP(ctx context.Context, in *VerifyMfaOtp, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
err := c.cc.Invoke(ctx, "/zitadel.auth.api.v1.AuthService/VerifyMfaOTP", in, out, opts...)
if err != nil {
return nil, err
@@ -2471,11 +2527,11 @@ type AuthServiceServer interface {
GetMyUserAddress(context.Context, *empty.Empty) (*UserAddress, error)
UpdateMyUserAddress(context.Context, *UpdateUserAddressRequest) (*UserAddress, error)
GetMyMfas(context.Context, *empty.Empty) (*MultiFactors, error)
SetMyPassword(context.Context, *PasswordRequest) (*empty.Empty, error)
//Password
ChangeMyPassword(context.Context, *PasswordChange) (*empty.Empty, error)
// MFA
AddMfaOTP(context.Context, *empty.Empty) (*MfaOtpResponse, error)
VerifyMfaOTP(context.Context, *VerifyMfaOtp) (*MfaOtpResponse, error)
VerifyMfaOTP(context.Context, *VerifyMfaOtp) (*empty.Empty, error)
RemoveMfaOTP(context.Context, *empty.Empty) (*empty.Empty, error)
SearchMyProjectOrgs(context.Context, *MyProjectOrgSearchRequest) (*MyProjectOrgSearchResponse, error)
IsIamAdmin(context.Context, *empty.Empty) (*IsAdminResponse, error)
@@ -2538,16 +2594,13 @@ func (*UnimplementedAuthServiceServer) UpdateMyUserAddress(ctx context.Context,
func (*UnimplementedAuthServiceServer) GetMyMfas(ctx context.Context, req *empty.Empty) (*MultiFactors, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyMfas not implemented")
}
func (*UnimplementedAuthServiceServer) SetMyPassword(ctx context.Context, req *PasswordRequest) (*empty.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMyPassword not implemented")
}
func (*UnimplementedAuthServiceServer) ChangeMyPassword(ctx context.Context, req *PasswordChange) (*empty.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangeMyPassword not implemented")
}
func (*UnimplementedAuthServiceServer) AddMfaOTP(ctx context.Context, req *empty.Empty) (*MfaOtpResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddMfaOTP not implemented")
}
func (*UnimplementedAuthServiceServer) VerifyMfaOTP(ctx context.Context, req *VerifyMfaOtp) (*MfaOtpResponse, error) {
func (*UnimplementedAuthServiceServer) VerifyMfaOTP(ctx context.Context, req *VerifyMfaOtp) (*empty.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyMfaOTP not implemented")
}
func (*UnimplementedAuthServiceServer) RemoveMfaOTP(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
@@ -2873,24 +2926,6 @@ func _AuthService_GetMyMfas_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler)
}
func _AuthService_SetMyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PasswordRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AuthServiceServer).SetMyPassword(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/zitadel.auth.api.v1.AuthService/SetMyPassword",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthServiceServer).SetMyPassword(ctx, req.(*PasswordRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AuthService_ChangeMyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PasswordChange)
if err := dec(in); err != nil {
@@ -3089,10 +3124,6 @@ var _AuthService_serviceDesc = grpc.ServiceDesc{
MethodName: "GetMyMfas",
Handler: _AuthService_GetMyMfas_Handler,
},
{
MethodName: "SetMyPassword",
Handler: _AuthService_SetMyPassword_Handler,
},
{
MethodName: "ChangeMyPassword",
Handler: _AuthService_ChangeMyPassword_Handler,

View File

@@ -13,7 +13,6 @@ import (
"io"
"net/http"
"github.com/golang/protobuf/descriptor"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/empty"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
@@ -24,13 +23,11 @@ import (
"google.golang.org/grpc/status"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = descriptor.ForMessage
func request_AuthService_Healthz_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq empty.Empty
@@ -466,40 +463,6 @@ func local_request_AuthService_GetMyMfas_0(ctx context.Context, marshaler runtim
}
func request_AuthService_SetMyPassword_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PasswordRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SetMyPassword(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_AuthService_SetMyPassword_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PasswordRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SetMyPassword(ctx, &protoReq)
return msg, metadata, err
}
func request_AuthService_ChangeMyPassword_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PasswordChange
var metadata runtime.ServerMetadata
@@ -693,13 +656,13 @@ func local_request_AuthService_GetMyZitadelPermissions_0(ctx context.Context, ma
// RegisterAuthServiceHandlerServer registers the http handlers for service AuthService to "mux".
// UnaryRPC :call AuthServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthServiceServer) error {
func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthServiceServer, opts []grpc.DialOption) error {
mux.Handle("GET", pattern_AuthService_Healthz_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -719,7 +682,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -739,7 +702,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -759,7 +722,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -779,7 +742,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -799,7 +762,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -819,7 +782,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -839,7 +802,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -859,7 +822,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -879,7 +842,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -899,7 +862,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -919,7 +882,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -939,7 +902,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -959,7 +922,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -979,7 +942,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -999,7 +962,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1019,7 +982,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1035,31 +998,11 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("PUT", pattern_AuthService_SetMyPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_AuthService_SetMyPassword_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_AuthService_SetMyPassword_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_AuthService_ChangeMyPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1079,7 +1022,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1099,7 +1042,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1119,7 +1062,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1139,7 +1082,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1159,7 +1102,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1179,7 +1122,7 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@@ -1576,26 +1519,6 @@ func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("PUT", pattern_AuthService_SetMyPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_AuthService_SetMyPassword_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_AuthService_SetMyPassword_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_AuthService_ChangeMyPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1774,8 +1697,6 @@ var (
pattern_AuthService_GetMyMfas_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"users", "me", "mfas"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_AuthService_SetMyPassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"users", "me", "passwords"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_AuthService_ChangeMyPassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"users", "me", "passwords", "_change"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_AuthService_AddMfaOTP_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"users", "me", "mfa", "otp"}, "", runtime.AssumeColonVerbOpt(true)))
@@ -1826,8 +1747,6 @@ var (
forward_AuthService_GetMyMfas_0 = runtime.ForwardResponseMessage
forward_AuthService_SetMyPassword_0 = runtime.ForwardResponseMessage
forward_AuthService_ChangeMyPassword_0 = runtime.ForwardResponseMessage
forward_AuthService_AddMfaOTP_0 = runtime.ForwardResponseMessage

View File

@@ -308,7 +308,7 @@
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1MfaOtpResponse"
"properties": {}
}
}
},
@@ -343,34 +343,9 @@
]
}
},
"/users/me/passwords": {
"put": {
"operationId": "SetMyPassword",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"properties": {}
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1PasswordRequest"
}
}
],
"tags": [
"AuthService"
]
}
},
"/users/me/passwords/_change": {
"put": {
"summary": "Password",
"operationId": "ChangeMyPassword",
"responses": {
"200": {
@@ -732,14 +707,6 @@
}
}
},
"v1PasswordRequest": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
},
"v1SearchMethod": {
"type": "string",
"enum": [
@@ -832,6 +799,14 @@
"sequence": {
"type": "string",
"format": "uint64"
},
"creation_date": {
"type": "string",
"format": "date-time"
},
"change_date": {
"type": "string",
"format": "date-time"
}
}
},
@@ -851,6 +826,14 @@
"sequence": {
"type": "string",
"format": "uint64"
},
"creation_date": {
"type": "string",
"format": "date-time"
},
"change_date": {
"type": "string",
"format": "date-time"
}
}
},
@@ -870,6 +853,14 @@
"sequence": {
"type": "string",
"format": "uint64"
},
"creation_date": {
"type": "string",
"format": "date-time"
},
"change_date": {
"type": "string",
"format": "date-time"
}
}
},
@@ -903,6 +894,14 @@
"sequence": {
"type": "string",
"format": "uint64"
},
"creation_date": {
"type": "string",
"format": "date-time"
},
"change_date": {
"type": "string",
"format": "date-time"
}
}
},

View File

@@ -4,20 +4,28 @@ import (
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
auth_util "github.com/caos/zitadel/internal/api/auth"
grpc_util "github.com/caos/zitadel/internal/api/grpc"
"github.com/caos/zitadel/internal/api/grpc/server/middleware"
"github.com/caos/zitadel/internal/auth/auth"
"github.com/caos/zitadel/internal/auth/repository"
)
var _ AuthServiceServer = (*Server)(nil)
type Server struct {
port string
searchLimit int
repo repository.Repository
verifier *auth.TokenVerifier
authZ auth_util.Config
}
func StartServer(conf grpc_util.ServerConfig) *Server {
func StartServer(conf grpc_util.ServerConfig, authZ auth_util.Config, repo repository.Repository) *Server {
return &Server{
port: conf.Port,
repo: repo,
authZ: authZ,
verifier: auth.Start(),
}
}
@@ -31,6 +39,7 @@ func (s *Server) GRPCServer() (*grpc.Server, error) {
grpc.UnaryInterceptor(
grpc_middleware.ChainUnaryServer(
middleware.ErrorHandler(),
AuthService_Authorization_Interceptor(s.verifier, &s.authZ),
),
),
)

View File

@@ -2,24 +2,42 @@ package grpc
import (
"context"
"github.com/caos/zitadel/internal/errors"
"github.com/golang/protobuf/ptypes/empty"
"github.com/caos/zitadel/internal/errors"
)
func (s *Server) GetMyUserProfile(ctx context.Context, _ *empty.Empty) (*UserProfile, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-fis93", "Not implemented")
profile, err := s.repo.MyProfile(ctx)
if err != nil {
return nil, err
}
return profileFromModel(profile), nil
}
func (s *Server) GetMyUserEmail(ctx context.Context, _ *empty.Empty) (*UserEmail, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-93j5d", "Not implemented")
email, err := s.repo.MyEmail(ctx)
if err != nil {
return nil, err
}
return emailFromModel(email), nil
}
func (s *Server) GetMyUserPhone(ctx context.Context, _ *empty.Empty) (*UserPhone, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-Hj75G", "Not implemented")
phone, err := s.repo.MyPhone(ctx)
if err != nil {
return nil, err
}
return phoneFromModel(phone), nil
}
func (s *Server) GetMyUserAddress(ctx context.Context, _ *empty.Empty) (*UserAddress, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-21jd4", "Not implemented")
address, err := s.repo.MyAddress(ctx)
if err != nil {
return nil, err
}
return addressFromModel(address), nil
}
func (s *Server) GetMyMfas(ctx context.Context, _ *empty.Empty) (*MultiFactors, error) {
@@ -27,57 +45,76 @@ func (s *Server) GetMyMfas(ctx context.Context, _ *empty.Empty) (*MultiFactors,
}
func (s *Server) UpdateMyUserProfile(ctx context.Context, request *UpdateUserProfileRequest) (*UserProfile, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-dlep3", "Not implemented")
profile, err := s.repo.ChangeMyProfile(ctx, updateProfileToModel(ctx, request))
if err != nil {
return nil, err
}
return profileFromModel(profile), nil
}
func (s *Server) ChangeMyUserEmail(ctx context.Context, request *UpdateUserEmailRequest) (*UserEmail, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-lme45", "Not implemented")
email, err := s.repo.ChangeMyEmail(ctx, updateEmailToModel(ctx, request))
if err != nil {
return nil, err
}
return emailFromModel(email), nil
}
func (s *Server) VerifyMyUserEmail(ctx context.Context, request *VerifyMyUserEmailRequest) (*empty.Empty, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-poru7", "Not implemented")
}
func (s *Server) VerifyUserEmail(ctx context.Context, request *VerifyUserEmailRequest) (*empty.Empty, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-dlep3", "Not implemented")
err := s.repo.VerifyMyEmail(ctx, request.Code)
return &empty.Empty{}, err
}
func (s *Server) ResendMyEmailVerificationMail(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-dh69i", "Not implemented")
err := s.repo.ResendMyEmailVerificationMail(ctx)
return &empty.Empty{}, err
}
func (s *Server) ChangeMyUserPhone(ctx context.Context, request *UpdateUserPhoneRequest) (*UserPhone, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-dk45g", "Not implemented")
phone, err := s.repo.ChangeMyPhone(ctx, updatePhoneToModel(ctx, request))
if err != nil {
return nil, err
}
return phoneFromModel(phone), nil
}
func (s *Server) VerifyMyUserPhone(ctx context.Context, request *VerifyUserPhoneRequest) (*empty.Empty, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-ol6gE", "Not implemented")
err := s.repo.VerifyMyPhone(ctx, request.Code)
return &empty.Empty{}, err
}
func (s *Server) ResendMyPhoneVerificationCode(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-Wk8rf", "Not implemented")
err := s.repo.ResendMyPhoneVerificationCode(ctx)
return &empty.Empty{}, err
}
func (s *Server) UpdateMyUserAddress(ctx context.Context, request *UpdateUserAddressRequest) (*UserAddress, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-cmt7F", "Not implemented")
address, err := s.repo.ChangeMyAddress(ctx, updateAddressToModel(ctx, request))
if err != nil {
return nil, err
}
func (s *Server) SetMyPassword(ctx context.Context, request *PasswordRequest) (*empty.Empty, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-pl9c2", "Not implemented")
return addressFromModel(address), nil
}
func (s *Server) ChangeMyPassword(ctx context.Context, request *PasswordChange) (*empty.Empty, error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-dlo6G", "Not implemented")
err := s.repo.ChangeMyPassword(ctx, request.OldPassword, request.NewPassword)
return &empty.Empty{}, err
}
func (s *Server) AddMfaOTP(ctx context.Context, _ *empty.Empty) (_ *MfaOtpResponse, err error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-al35G", "Not implemented")
otp, err := s.repo.AddMyMfaOTP(ctx)
if err != nil {
return nil, err
}
return otpFromModel(otp), nil
}
func (s *Server) VerifyMfaOTP(ctx context.Context, request *VerifyMfaOtp) (_ *MfaOtpResponse, err error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-kgjZ7", "Not implemented")
func (s *Server) VerifyMfaOTP(ctx context.Context, request *VerifyMfaOtp) (*empty.Empty, error) {
err := s.repo.VerifyMyMfaOTP(ctx, request.Code)
return &empty.Empty{}, err
}
func (s *Server) RemoveMfaOTP(ctx context.Context, _ *empty.Empty) (_ *empty.Empty, err error) {
return nil, errors.ThrowUnimplemented(nil, "GRPC-9k46d", "Not implemented")
s.repo.RemoveMyMfaOTP(ctx)
return &empty.Empty{}, err
}

View File

@@ -0,0 +1,174 @@
package grpc
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/eventstore/models"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/golang/protobuf/ptypes"
"golang.org/x/text/language"
)
func profileFromModel(profile *usr_model.Profile) *UserProfile {
creationDate, err := ptypes.TimestampProto(profile.CreationDate)
logging.Log("GRPC-56t5s").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(profile.ChangeDate)
logging.Log("GRPC-K58ds").OnError(err).Debug("unable to parse timestamp")
return &UserProfile{
Id: profile.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate,
Sequence: profile.Sequence,
UserName: profile.UserName,
FirstName: profile.FirstName,
LastName: profile.LastName,
DisplayName: profile.DisplayName,
NickName: profile.NickName,
PreferredLanguage: profile.PreferredLanguage.String(),
Gender: genderFromModel(profile.Gender),
}
}
func updateProfileToModel(ctx context.Context, u *UpdateUserProfileRequest) *usr_model.Profile {
preferredLanguage, err := language.Parse(u.PreferredLanguage)
logging.Log("GRPC-lk73L").OnError(err).Debug("language malformed")
return &usr_model.Profile{
ObjectRoot: models.ObjectRoot{AggregateID: auth.GetCtxData(ctx).UserID},
FirstName: u.FirstName,
LastName: u.LastName,
NickName: u.NickName,
DisplayName: u.DisplayName,
PreferredLanguage: preferredLanguage,
Gender: genderToModel(u.Gender),
}
}
func emailFromModel(email *usr_model.Email) *UserEmail {
creationDate, err := ptypes.TimestampProto(email.CreationDate)
logging.Log("GRPC-sdoi3").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(email.ChangeDate)
logging.Log("GRPC-klJK3").OnError(err).Debug("unable to parse timestamp")
return &UserEmail{
Id: email.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate,
Sequence: email.Sequence,
Email: email.EmailAddress,
IsEmailVerified: email.IsEmailVerified,
}
}
func updateEmailToModel(ctx context.Context, e *UpdateUserEmailRequest) *usr_model.Email {
return &usr_model.Email{
ObjectRoot: models.ObjectRoot{AggregateID: auth.GetCtxData(ctx).UserID},
EmailAddress: e.Email,
}
}
func phoneFromModel(phone *usr_model.Phone) *UserPhone {
creationDate, err := ptypes.TimestampProto(phone.CreationDate)
logging.Log("GRPC-kjn5J").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(phone.ChangeDate)
logging.Log("GRPC-LKA9S").OnError(err).Debug("unable to parse timestamp")
return &UserPhone{
Id: phone.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate,
Sequence: phone.Sequence,
Phone: phone.PhoneNumber,
IsPhoneVerified: phone.IsPhoneVerified,
}
}
func updatePhoneToModel(ctx context.Context, e *UpdateUserPhoneRequest) *usr_model.Phone {
return &usr_model.Phone{
ObjectRoot: models.ObjectRoot{AggregateID: auth.GetCtxData(ctx).UserID},
PhoneNumber: e.Phone,
}
}
func addressFromModel(address *usr_model.Address) *UserAddress {
creationDate, err := ptypes.TimestampProto(address.CreationDate)
logging.Log("GRPC-65FRs").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(address.ChangeDate)
logging.Log("GRPC-aslk4").OnError(err).Debug("unable to parse timestamp")
return &UserAddress{
Id: address.AggregateID,
CreationDate: creationDate,
ChangeDate: changeDate,
Sequence: address.Sequence,
Country: address.Country,
StreetAddress: address.StreetAddress,
Region: address.Region,
PostalCode: address.PostalCode,
Locality: address.Locality,
}
}
func updateAddressToModel(ctx context.Context, address *UpdateUserAddressRequest) *usr_model.Address {
return &usr_model.Address{
ObjectRoot: models.ObjectRoot{AggregateID: auth.GetCtxData(ctx).UserID},
Country: address.Country,
StreetAddress: address.StreetAddress,
Region: address.Region,
PostalCode: address.PostalCode,
Locality: address.Locality,
}
}
func otpFromModel(otp *usr_model.OTP) *MfaOtpResponse {
return &MfaOtpResponse{
UserId: otp.AggregateID,
Url: otp.Url,
Secret: otp.SecretString,
State: mfaStateFromModel(otp.State),
}
}
func genderFromModel(gender usr_model.Gender) Gender {
switch gender {
case usr_model.GENDER_FEMALE:
return Gender_GENDER_FEMALE
case usr_model.GENDER_MALE:
return Gender_GENDER_MALE
case usr_model.GENDER_DIVERSE:
return Gender_GENDER_DIVERSE
default:
return Gender_GENDER_UNSPECIFIED
}
}
func genderToModel(gender Gender) usr_model.Gender {
switch gender {
case Gender_GENDER_FEMALE:
return usr_model.GENDER_FEMALE
case Gender_GENDER_MALE:
return usr_model.GENDER_MALE
case Gender_GENDER_DIVERSE:
return usr_model.GENDER_DIVERSE
default:
return usr_model.GENDER_UNDEFINED
}
}
func mfaStateFromModel(state usr_model.MfaState) MFAState {
switch state {
case usr_model.MFASTATE_READY:
return MFAState_MFASTATE_NOT_READY
case usr_model.MFASTATE_NOTREADY:
return MFAState_MFASTATE_NOT_READY
default:
return MFAState_MFASTATE_UNSPECIFIED
}
}

View File

@@ -200,18 +200,6 @@ service AuthService {
};
}
//Password
rpc SetMyPassword(PasswordRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
put: "/users/me/passwords"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "authenticated"
};
}
rpc ChangeMyPassword(PasswordChange) returns (google.protobuf.Empty) {
option (google.api.http) = {
put: "/users/me/passwords/_change"
@@ -234,7 +222,7 @@ service AuthService {
};
}
rpc VerifyMfaOTP(VerifyMfaOtp) returns (MfaOtpResponse) {
rpc VerifyMfaOTP(VerifyMfaOtp) returns (google.protobuf.Empty) {
option (google.api.http) = {
put: "/users/me/mfa/otp/_verify"
body: "*"
@@ -366,7 +354,9 @@ message UserProfile {
string display_name = 6;
string preferred_language = 7;
Gender gender = 8;
uint64 sequence = 26;
uint64 sequence = 9;
google.protobuf.Timestamp creation_date = 10;
google.protobuf.Timestamp change_date = 11;
}
message UpdateUserProfileRequest {
@@ -383,6 +373,8 @@ message UserEmail {
string email = 2;
bool isEmailVerified = 3;
uint64 sequence = 4;
google.protobuf.Timestamp creation_date = 5;
google.protobuf.Timestamp change_date = 6;
}
message VerifyMyUserEmailRequest {
@@ -403,6 +395,8 @@ message UserPhone {
string phone = 2;
bool is_phone_verified = 3;
uint64 sequence = 4;
google.protobuf.Timestamp creation_date = 5;
google.protobuf.Timestamp change_date = 6;
}
message UpdateUserPhoneRequest {
@@ -421,6 +415,8 @@ message UserAddress {
string region = 5;
string street_address = 6;
uint64 sequence = 7;
google.protobuf.Timestamp creation_date = 8;
google.protobuf.Timestamp change_date = 9;
}
message UpdateUserAddressRequest {

View File

@@ -2,17 +2,23 @@ package auth
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/api/auth"
app "github.com/caos/zitadel/internal/auth"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/pkg/auth/api"
)
type Config struct {
App app.Config
API api.Config
Repository eventsourcing.Config
}
func Start(ctx context.Context, config Config, authZ auth.Config, systemDefaults sd.SystemDefaults) {
api.Start(ctx, config.API)
repo, err := eventsourcing.Start(config.Repository, systemDefaults)
logging.Log("MAIN-9uBxp").OnError(err).Panic("unable to start app")
api.Start(ctx, config.API, authZ, repo)
}