mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat: add login names to relational users (#11667)
Introduce a new SQL migration that creates the `login_names` table and associated triggers/functions for managing login names based on domain policies and user updates. Manual tests confirm the functionality works as intended.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
domain "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
database "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
crypto "github.com/zitadel/zitadel/internal/crypto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
language "golang.org/x/text/language"
|
||||
)
|
||||
@@ -82,48 +83,6 @@ func (c *MockHumanUserRepositoryAddIdentityProviderLinkCall) DoAndReturn(f func(
|
||||
return c
|
||||
}
|
||||
|
||||
// AddMetadata mocks base method.
|
||||
func (m *MockHumanUserRepository) AddMetadata(metadata ...*domain.Metadata) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{}
|
||||
for _, a := range metadata {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AddMetadata", varargs...)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddMetadata indicates an expected call of AddMetadata.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) AddMetadata(metadata ...any) *MockHumanUserRepositoryAddMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMetadata", reflect.TypeOf((*MockHumanUserRepository)(nil).AddMetadata), metadata...)
|
||||
return &MockHumanUserRepositoryAddMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryAddMetadataCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryAddMetadataCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryAddMetadataCall) Return(arg0 database.Change) *MockHumanUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryAddMetadataCall) Do(f func(...*domain.Metadata) database.Change) *MockHumanUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryAddMetadataCall) DoAndReturn(f func(...*domain.Metadata) database.Change) *MockHumanUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddPasskey mocks base method.
|
||||
func (m *MockHumanUserRepository) AddPasskey(passkey *domain.Passkey) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -162,6 +121,44 @@ func (c *MockHumanUserRepositoryAddPasskeyCall) DoAndReturn(f func(*domain.Passk
|
||||
return c
|
||||
}
|
||||
|
||||
// AddRecoveryCodes mocks base method.
|
||||
func (m *MockHumanUserRepository) AddRecoveryCodes(codes []string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRecoveryCodes", codes)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRecoveryCodes indicates an expected call of AddRecoveryCodes.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) AddRecoveryCodes(codes any) *MockHumanUserRepositoryAddRecoveryCodesCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRecoveryCodes", reflect.TypeOf((*MockHumanUserRepository)(nil).AddRecoveryCodes), codes)
|
||||
return &MockHumanUserRepositoryAddRecoveryCodesCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryAddRecoveryCodesCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryAddRecoveryCodesCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryAddRecoveryCodesCall) Return(arg0 database.Change) *MockHumanUserRepositoryAddRecoveryCodesCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryAddRecoveryCodesCall) Do(f func([]string) database.Change) *MockHumanUserRepositoryAddRecoveryCodesCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryAddRecoveryCodesCall) DoAndReturn(f func([]string) database.Change) *MockHumanUserRepositoryAddRecoveryCodesCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// CreatedAtColumn mocks base method.
|
||||
func (m *MockHumanUserRepository) CreatedAtColumn() database.Column {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -960,6 +957,44 @@ func (c *MockHumanUserRepositoryIncrementPasswordFailedAttemptsCall) DoAndReturn
|
||||
return c
|
||||
}
|
||||
|
||||
// IncrementRecoveryCodeFailedAttempts mocks base method.
|
||||
func (m *MockHumanUserRepository) IncrementRecoveryCodeFailedAttempts() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IncrementRecoveryCodeFailedAttempts")
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IncrementRecoveryCodeFailedAttempts indicates an expected call of IncrementRecoveryCodeFailedAttempts.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) IncrementRecoveryCodeFailedAttempts() *MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementRecoveryCodeFailedAttempts", reflect.TypeOf((*MockHumanUserRepository)(nil).IncrementRecoveryCodeFailedAttempts))
|
||||
return &MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall) Return(arg0 database.Change) *MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall) Do(f func() database.Change) *MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall) DoAndReturn(f func() database.Change) *MockHumanUserRepositoryIncrementRecoveryCodeFailedAttemptsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// IncrementSMSOTPFailedAttempts mocks base method.
|
||||
func (m *MockHumanUserRepository) IncrementSMSOTPFailedAttempts() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -998,6 +1033,44 @@ func (c *MockHumanUserRepositoryIncrementSMSOTPFailedAttemptsCall) DoAndReturn(f
|
||||
return c
|
||||
}
|
||||
|
||||
// IncrementTOTPFailedAttempts mocks base method.
|
||||
func (m *MockHumanUserRepository) IncrementTOTPFailedAttempts() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IncrementTOTPFailedAttempts")
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IncrementTOTPFailedAttempts indicates an expected call of IncrementTOTPFailedAttempts.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) IncrementTOTPFailedAttempts() *MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementTOTPFailedAttempts", reflect.TypeOf((*MockHumanUserRepository)(nil).IncrementTOTPFailedAttempts))
|
||||
return &MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall) Return(arg0 database.Change) *MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall) Do(f func() database.Change) *MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall) DoAndReturn(f func() database.Change) *MockHumanUserRepositoryIncrementTOTPFailedAttemptsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// InstanceIDColumn mocks base method.
|
||||
func (m *MockHumanUserRepository) InstanceIDColumn() database.Column {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1530,6 +1603,44 @@ func (c *MockHumanUserRepositoryPrimaryKeyConditionCall) DoAndReturn(f func(stri
|
||||
return c
|
||||
}
|
||||
|
||||
// RemoveAllRecoveryCodes mocks base method.
|
||||
func (m *MockHumanUserRepository) RemoveAllRecoveryCodes() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveAllRecoveryCodes")
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveAllRecoveryCodes indicates an expected call of RemoveAllRecoveryCodes.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) RemoveAllRecoveryCodes() *MockHumanUserRepositoryRemoveAllRecoveryCodesCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveAllRecoveryCodes", reflect.TypeOf((*MockHumanUserRepository)(nil).RemoveAllRecoveryCodes))
|
||||
return &MockHumanUserRepositoryRemoveAllRecoveryCodesCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryRemoveAllRecoveryCodesCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryRemoveAllRecoveryCodesCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryRemoveAllRecoveryCodesCall) Return(arg0 database.Change) *MockHumanUserRepositoryRemoveAllRecoveryCodesCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryRemoveAllRecoveryCodesCall) Do(f func() database.Change) *MockHumanUserRepositoryRemoveAllRecoveryCodesCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryRemoveAllRecoveryCodesCall) DoAndReturn(f func() database.Change) *MockHumanUserRepositoryRemoveAllRecoveryCodesCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// RemoveIdentityProviderLink mocks base method.
|
||||
func (m *MockHumanUserRepository) RemoveIdentityProviderLink(providerID, providedUserID string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1682,6 +1793,44 @@ func (c *MockHumanUserRepositoryRemovePhoneCall) DoAndReturn(f func() database.C
|
||||
return c
|
||||
}
|
||||
|
||||
// RemoveRecoveryCode mocks base method.
|
||||
func (m *MockHumanUserRepository) RemoveRecoveryCode(code string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveRecoveryCode", code)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveRecoveryCode indicates an expected call of RemoveRecoveryCode.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) RemoveRecoveryCode(code any) *MockHumanUserRepositoryRemoveRecoveryCodeCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveRecoveryCode", reflect.TypeOf((*MockHumanUserRepository)(nil).RemoveRecoveryCode), code)
|
||||
return &MockHumanUserRepositoryRemoveRecoveryCodeCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryRemoveRecoveryCodeCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryRemoveRecoveryCodeCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryRemoveRecoveryCodeCall) Return(arg0 database.Change) *MockHumanUserRepositoryRemoveRecoveryCodeCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryRemoveRecoveryCodeCall) Do(f func(string) database.Change) *MockHumanUserRepositoryRemoveRecoveryCodeCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryRemoveRecoveryCodeCall) DoAndReturn(f func(string) database.Change) *MockHumanUserRepositoryRemoveRecoveryCodeCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// RemoveTOTP mocks base method.
|
||||
func (m *MockHumanUserRepository) RemoveTOTP() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1796,6 +1945,44 @@ func (c *MockHumanUserRepositoryResetPasswordFailedAttemptsCall) DoAndReturn(f f
|
||||
return c
|
||||
}
|
||||
|
||||
// ResetRecoveryCodeFailedAttempts mocks base method.
|
||||
func (m *MockHumanUserRepository) ResetRecoveryCodeFailedAttempts() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ResetRecoveryCodeFailedAttempts")
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ResetRecoveryCodeFailedAttempts indicates an expected call of ResetRecoveryCodeFailedAttempts.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) ResetRecoveryCodeFailedAttempts() *MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetRecoveryCodeFailedAttempts", reflect.TypeOf((*MockHumanUserRepository)(nil).ResetRecoveryCodeFailedAttempts))
|
||||
return &MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall) Return(arg0 database.Change) *MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall) Do(f func() database.Change) *MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall) DoAndReturn(f func() database.Change) *MockHumanUserRepositoryResetRecoveryCodeFailedAttemptsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ResetSMSOTPFailedAttempts mocks base method.
|
||||
func (m *MockHumanUserRepository) ResetSMSOTPFailedAttempts() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1834,6 +2021,44 @@ func (c *MockHumanUserRepositoryResetSMSOTPFailedAttemptsCall) DoAndReturn(f fun
|
||||
return c
|
||||
}
|
||||
|
||||
// ResetTOTPFailedAttempts mocks base method.
|
||||
func (m *MockHumanUserRepository) ResetTOTPFailedAttempts() database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ResetTOTPFailedAttempts")
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ResetTOTPFailedAttempts indicates an expected call of ResetTOTPFailedAttempts.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) ResetTOTPFailedAttempts() *MockHumanUserRepositoryResetTOTPFailedAttemptsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTOTPFailedAttempts", reflect.TypeOf((*MockHumanUserRepository)(nil).ResetTOTPFailedAttempts))
|
||||
return &MockHumanUserRepositoryResetTOTPFailedAttemptsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositoryResetTOTPFailedAttemptsCall wrap *gomock.Call
|
||||
type MockHumanUserRepositoryResetTOTPFailedAttemptsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositoryResetTOTPFailedAttemptsCall) Return(arg0 database.Change) *MockHumanUserRepositoryResetTOTPFailedAttemptsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositoryResetTOTPFailedAttemptsCall) Do(f func() database.Change) *MockHumanUserRepositoryResetTOTPFailedAttemptsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositoryResetTOTPFailedAttemptsCall) DoAndReturn(f func() database.Change) *MockHumanUserRepositoryResetTOTPFailedAttemptsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetAvatarKey mocks base method.
|
||||
func (m *MockHumanUserRepository) SetAvatarKey(avatarKey *string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -2138,6 +2363,44 @@ func (c *MockHumanUserRepositorySetIdentityProviderLinkUsernameCall) DoAndReturn
|
||||
return c
|
||||
}
|
||||
|
||||
// SetInviteVerification mocks base method.
|
||||
func (m *MockHumanUserRepository) SetInviteVerification(verification domain.VerificationType) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetInviteVerification", verification)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetInviteVerification indicates an expected call of SetInviteVerification.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) SetInviteVerification(verification any) *MockHumanUserRepositorySetInviteVerificationCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetInviteVerification", reflect.TypeOf((*MockHumanUserRepository)(nil).SetInviteVerification), verification)
|
||||
return &MockHumanUserRepositorySetInviteVerificationCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositorySetInviteVerificationCall wrap *gomock.Call
|
||||
type MockHumanUserRepositorySetInviteVerificationCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositorySetInviteVerificationCall) Return(arg0 database.Change) *MockHumanUserRepositorySetInviteVerificationCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositorySetInviteVerificationCall) Do(f func(domain.VerificationType) database.Change) *MockHumanUserRepositorySetInviteVerificationCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositorySetInviteVerificationCall) DoAndReturn(f func(domain.VerificationType) database.Change) *MockHumanUserRepositorySetInviteVerificationCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetLastName mocks base method.
|
||||
func (m *MockHumanUserRepository) SetLastName(lastName string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -2252,6 +2515,44 @@ func (c *MockHumanUserRepositorySetLastSuccessfulPasswordCheckCall) DoAndReturn(
|
||||
return c
|
||||
}
|
||||
|
||||
// SetLastSuccessfulRecoveryCodeCheck mocks base method.
|
||||
func (m *MockHumanUserRepository) SetLastSuccessfulRecoveryCodeCheck(checkedAt time.Time) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetLastSuccessfulRecoveryCodeCheck", checkedAt)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetLastSuccessfulRecoveryCodeCheck indicates an expected call of SetLastSuccessfulRecoveryCodeCheck.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) SetLastSuccessfulRecoveryCodeCheck(checkedAt any) *MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastSuccessfulRecoveryCodeCheck", reflect.TypeOf((*MockHumanUserRepository)(nil).SetLastSuccessfulRecoveryCodeCheck), checkedAt)
|
||||
return &MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall wrap *gomock.Call
|
||||
type MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall) Return(arg0 database.Change) *MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall) Do(f func(time.Time) database.Change) *MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall) DoAndReturn(f func(time.Time) database.Change) *MockHumanUserRepositorySetLastSuccessfulRecoveryCodeCheckCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetLastSuccessfulSMSOTPCheck mocks base method.
|
||||
func (m *MockHumanUserRepository) SetLastSuccessfulSMSOTPCheck(checkedAt time.Time) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -2328,6 +2629,48 @@ func (c *MockHumanUserRepositorySetLastSuccessfulTOTPCheckCall) DoAndReturn(f fu
|
||||
return c
|
||||
}
|
||||
|
||||
// SetMetadata mocks base method.
|
||||
func (m *MockHumanUserRepository) SetMetadata(metadata ...*domain.Metadata) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{}
|
||||
for _, a := range metadata {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SetMetadata", varargs...)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetMetadata indicates an expected call of SetMetadata.
|
||||
func (mr *MockHumanUserRepositoryMockRecorder) SetMetadata(metadata ...any) *MockHumanUserRepositorySetMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockHumanUserRepository)(nil).SetMetadata), metadata...)
|
||||
return &MockHumanUserRepositorySetMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
// MockHumanUserRepositorySetMetadataCall wrap *gomock.Call
|
||||
type MockHumanUserRepositorySetMetadataCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockHumanUserRepositorySetMetadataCall) Return(arg0 database.Change) *MockHumanUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositorySetMetadataCall) Do(f func(...*domain.Metadata) database.Change) *MockHumanUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositorySetMetadataCall) DoAndReturn(f func(...*domain.Metadata) database.Change) *MockHumanUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetNickname mocks base method.
|
||||
func (m *MockHumanUserRepository) SetNickname(nickname string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -2937,7 +3280,7 @@ func (c *MockHumanUserRepositorySetStateCall) DoAndReturn(f func(domain.UserStat
|
||||
}
|
||||
|
||||
// SetTOTPSecret mocks base method.
|
||||
func (m *MockHumanUserRepository) SetTOTPSecret(secret []byte) database.Change {
|
||||
func (m *MockHumanUserRepository) SetTOTPSecret(secret *crypto.CryptoValue) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetTOTPSecret", secret)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
@@ -2963,13 +3306,13 @@ func (c *MockHumanUserRepositorySetTOTPSecretCall) Return(arg0 database.Change)
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockHumanUserRepositorySetTOTPSecretCall) Do(f func([]byte) database.Change) *MockHumanUserRepositorySetTOTPSecretCall {
|
||||
func (c *MockHumanUserRepositorySetTOTPSecretCall) Do(f func(*crypto.CryptoValue) database.Change) *MockHumanUserRepositorySetTOTPSecretCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockHumanUserRepositorySetTOTPSecretCall) DoAndReturn(f func([]byte) database.Change) *MockHumanUserRepositorySetTOTPSecretCall {
|
||||
func (c *MockHumanUserRepositorySetTOTPSecretCall) DoAndReturn(f func(*crypto.CryptoValue) database.Change) *MockHumanUserRepositorySetTOTPSecretCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -81,48 +81,6 @@ func (c *MockMachineUserRepositoryAddKeyCall) DoAndReturn(f func(*domain.Machine
|
||||
return c
|
||||
}
|
||||
|
||||
// AddMetadata mocks base method.
|
||||
func (m *MockMachineUserRepository) AddMetadata(metadata ...*domain.Metadata) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{}
|
||||
for _, a := range metadata {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AddMetadata", varargs...)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddMetadata indicates an expected call of AddMetadata.
|
||||
func (mr *MockMachineUserRepositoryMockRecorder) AddMetadata(metadata ...any) *MockMachineUserRepositoryAddMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMetadata", reflect.TypeOf((*MockMachineUserRepository)(nil).AddMetadata), metadata...)
|
||||
return &MockMachineUserRepositoryAddMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
// MockMachineUserRepositoryAddMetadataCall wrap *gomock.Call
|
||||
type MockMachineUserRepositoryAddMetadataCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockMachineUserRepositoryAddMetadataCall) Return(arg0 database.Change) *MockMachineUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockMachineUserRepositoryAddMetadataCall) Do(f func(...*domain.Metadata) database.Change) *MockMachineUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockMachineUserRepositoryAddMetadataCall) DoAndReturn(f func(...*domain.Metadata) database.Change) *MockMachineUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddPersonalAccessToken mocks base method.
|
||||
func (m *MockMachineUserRepository) AddPersonalAccessToken(pat *domain.PersonalAccessToken) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -769,6 +727,48 @@ func (c *MockMachineUserRepositorySetDescriptionCall) DoAndReturn(f func(string)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetMetadata mocks base method.
|
||||
func (m *MockMachineUserRepository) SetMetadata(metadata ...*domain.Metadata) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{}
|
||||
for _, a := range metadata {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SetMetadata", varargs...)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetMetadata indicates an expected call of SetMetadata.
|
||||
func (mr *MockMachineUserRepositoryMockRecorder) SetMetadata(metadata ...any) *MockMachineUserRepositorySetMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockMachineUserRepository)(nil).SetMetadata), metadata...)
|
||||
return &MockMachineUserRepositorySetMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
// MockMachineUserRepositorySetMetadataCall wrap *gomock.Call
|
||||
type MockMachineUserRepositorySetMetadataCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockMachineUserRepositorySetMetadataCall) Return(arg0 database.Change) *MockMachineUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockMachineUserRepositorySetMetadataCall) Do(f func(...*domain.Metadata) database.Change) *MockMachineUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockMachineUserRepositorySetMetadataCall) DoAndReturn(f func(...*domain.Metadata) database.Change) *MockMachineUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetName mocks base method.
|
||||
func (m *MockMachineUserRepository) SetName(name string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -43,48 +43,6 @@ func (m *MockUserRepository) EXPECT() *MockUserRepositoryMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddMetadata mocks base method.
|
||||
func (m *MockUserRepository) AddMetadata(metadata ...*domain.Metadata) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{}
|
||||
for _, a := range metadata {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AddMetadata", varargs...)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddMetadata indicates an expected call of AddMetadata.
|
||||
func (mr *MockUserRepositoryMockRecorder) AddMetadata(metadata ...any) *MockUserRepositoryAddMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMetadata", reflect.TypeOf((*MockUserRepository)(nil).AddMetadata), metadata...)
|
||||
return &MockUserRepositoryAddMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
// MockUserRepositoryAddMetadataCall wrap *gomock.Call
|
||||
type MockUserRepositoryAddMetadataCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockUserRepositoryAddMetadataCall) Return(arg0 database.Change) *MockUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockUserRepositoryAddMetadataCall) Do(f func(...*domain.Metadata) database.Change) *MockUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockUserRepositoryAddMetadataCall) DoAndReturn(f func(...*domain.Metadata) database.Change) *MockUserRepositoryAddMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Create mocks base method.
|
||||
func (m *MockUserRepository) Create(ctx context.Context, client database.QueryExecutor, user *domain.User) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -782,6 +740,48 @@ func (c *MockUserRepositoryRemoveMetadataCall) DoAndReturn(f func(database.Condi
|
||||
return c
|
||||
}
|
||||
|
||||
// SetMetadata mocks base method.
|
||||
func (m *MockUserRepository) SetMetadata(metadata ...*domain.Metadata) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{}
|
||||
for _, a := range metadata {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SetMetadata", varargs...)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetMetadata indicates an expected call of SetMetadata.
|
||||
func (mr *MockUserRepositoryMockRecorder) SetMetadata(metadata ...any) *MockUserRepositorySetMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockUserRepository)(nil).SetMetadata), metadata...)
|
||||
return &MockUserRepositorySetMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
// MockUserRepositorySetMetadataCall wrap *gomock.Call
|
||||
type MockUserRepositorySetMetadataCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockUserRepositorySetMetadataCall) Return(arg0 database.Change) *MockUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockUserRepositorySetMetadataCall) Do(f func(...*domain.Metadata) database.Change) *MockUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockUserRepositorySetMetadataCall) DoAndReturn(f func(...*domain.Metadata) database.Change) *MockUserRepositorySetMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetState mocks base method.
|
||||
func (m *MockUserRepository) SetState(state domain.UserState) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -9,19 +9,25 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
|
||||
OrganizationID string `json:"organizationId,omitempty" db:"organization_id"`
|
||||
ID string `json:"id,omitempty" db:"id"`
|
||||
Username string `json:"username,omitempty" db:"username"`
|
||||
State UserState `json:"state,omitempty" db:"state"`
|
||||
CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
|
||||
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
|
||||
OrganizationID string `json:"organizationId,omitempty" db:"organization_id"`
|
||||
ID string `json:"id,omitempty" db:"id"`
|
||||
Username string `json:"username,omitempty" db:"username"`
|
||||
LoginNames []LoginName `json:"loginNames,omitempty" db:"login_names"`
|
||||
State UserState `json:"state,omitempty" db:"state"`
|
||||
CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
|
||||
|
||||
Machine *MachineUser `json:"machine,omitempty" db:"-"`
|
||||
Human *HumanUser `json:"human,omitempty" db:"-"`
|
||||
Metadata []*Metadata `json:"metadata,omitempty" db:"metadata"`
|
||||
}
|
||||
|
||||
type LoginName struct {
|
||||
LoginName string `json:"loginName,omitempty" db:"-"`
|
||||
IsPreferred bool `json:"isPreferred,omitempty" db:"-"`
|
||||
}
|
||||
|
||||
type MachineUser struct {
|
||||
Name string `json:"name,omitempty" db:"-"`
|
||||
Description string `json:"description,omitempty" db:"-"`
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 015_login_names/up.sql
|
||||
up015LoginNames string
|
||||
//go:embed 015_login_names/down.sql
|
||||
down015LoginNames string
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerSQLMigration(15, up015LoginNames, down015LoginNames)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP TABLE IF EXISTS zitadel.login_names CASCADE;
|
||||
DROP FUNCTION IF EXISTS zitadel.apply_domain_policy_manipulation_to_login_names();
|
||||
DROP FUNCTION IF EXISTS zitadel.apply_user_insert_to_login_names();
|
||||
DROP FUNCTION IF EXISTS zitadel.apply_user_update_to_login_names();
|
||||
DROP FUNCTION IF EXISTS zitadel.apply_domain_manipulation_to_login_names();
|
||||
@@ -0,0 +1,300 @@
|
||||
CREATE TABLE zitadel.login_names(
|
||||
instance_id TEXT NOT NULL
|
||||
, organization_id TEXT -- is set if the setting for loginNameIncludesDomain is set on organization level, otherwise null
|
||||
, user_id TEXT NOT NULL
|
||||
|
||||
, username TEXT NOT NULL CHECK (username <> '')
|
||||
, domain TEXT
|
||||
, login_name TEXT GENERATED ALWAYS AS (CASE WHEN domain IS NOT NULL THEN username || '@' || domain ELSE username END) STORED
|
||||
, is_preferred BOOLEAN NOT NULL DEFAULT FALSE
|
||||
|
||||
, used_setting TEXT NOT NULL
|
||||
|
||||
, PRIMARY KEY (instance_id, user_id, login_name)
|
||||
, FOREIGN KEY (instance_id, user_id) REFERENCES zitadel.users(instance_id, id) ON DELETE CASCADE
|
||||
, FOREIGN KEY (instance_id, organization_id, domain) REFERENCES zitadel.org_domains(instance_id, org_id, domain) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_login_names_instance_login_name ON zitadel.login_names(instance_id, lower(login_name));
|
||||
CREATE INDEX idx_login_names_instance_user ON zitadel.login_names(instance_id, user_id);
|
||||
CREATE INDEX idx_login_names_setting ON zitadel.login_names(instance_id, used_setting);
|
||||
CREATE INDEX idx_login_names_domain ON zitadel.login_names(instance_id, domain); -- used for cleanup of login names when a domain is deleted
|
||||
|
||||
CREATE OR REPLACE FUNCTION zitadel.apply_domain_manipulation_to_login_names() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
setting zitadel.settings%ROWTYPE;
|
||||
BEGIN
|
||||
IF (NOT NEW.is_verified) THEN
|
||||
IF TG_OP = 'UPDATE' AND OLD.is_verified THEN
|
||||
-- this case can currently not happen but is added for completeness and future-proofing.
|
||||
RAISE NOTICE 'Domain changed from verified to unverified, removing associated login names';
|
||||
DELETE FROM zitadel.login_names
|
||||
WHERE
|
||||
login_names.instance_id = NEW.instance_id
|
||||
AND login_names.organization_id = NEW.org_id
|
||||
AND login_names.domain = NEW.domain;
|
||||
ELSE
|
||||
RAISE NOTICE 'Domain is not verified, skipping login name manipulation';
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
SELECT settings.*
|
||||
INTO setting
|
||||
FROM zitadel.settings
|
||||
WHERE
|
||||
settings.instance_id = NEW.instance_id
|
||||
AND settings.type = 'domain'
|
||||
AND (settings.organization_id IS NULL OR settings.organization_id = NEW.org_id)
|
||||
ORDER BY settings.organization_id NULLS LAST
|
||||
LIMIT 1;
|
||||
|
||||
IF NOT (setting.settings->'loginNameIncludesDomain')::BOOLEAN THEN
|
||||
RAISE NOTICE 'skip adding domain because loginNameIncludesDomain is false for setting';
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
-- Lock based on the setting scope (organization-specific or instance-level/global).
|
||||
-- The lock is released at transaction end.
|
||||
PERFORM pg_advisory_xact_lock(
|
||||
hashtext('zitadel.login_names')
|
||||
, hashtext(setting.instance_id || ':' || COALESCE(setting.organization_id, 'global'))
|
||||
);
|
||||
|
||||
IF NEW.is_primary IS DISTINCT FROM OLD.is_primary THEN
|
||||
RAISE NOTICE 'primary domain changed, updating preferred login name';
|
||||
UPDATE zitadel.login_names
|
||||
SET is_preferred = NEW.is_primary
|
||||
WHERE
|
||||
login_names.instance_id = NEW.instance_id
|
||||
AND login_names.domain = NEW.domain;
|
||||
END IF;
|
||||
|
||||
IF NEW.domain IS DISTINCT FROM OLD.domain THEN
|
||||
RAISE NOTICE 'domain changed, updating login names';
|
||||
UPDATE zitadel.login_names
|
||||
SET domain = NEW.domain
|
||||
WHERE
|
||||
login_names.instance_id = NEW.instance_id
|
||||
AND login_names.organization_id = NEW.org_id
|
||||
AND login_names.domain = OLD.domain;
|
||||
END IF;
|
||||
|
||||
IF NEW.is_verified IS DISTINCT FROM OLD.is_verified THEN
|
||||
RAISE NOTICE 'Domain verification changed, inserting login names for verified domain';
|
||||
INSERT INTO zitadel.login_names(instance_id, organization_id, user_id, username, domain, is_preferred, used_setting)
|
||||
SELECT
|
||||
NEW.instance_id
|
||||
, NEW.org_id
|
||||
, users.id
|
||||
, users.username
|
||||
, NEW.domain
|
||||
, NEW.is_primary
|
||||
, setting.id
|
||||
FROM
|
||||
zitadel.users
|
||||
WHERE
|
||||
users.instance_id = NEW.instance_id
|
||||
AND users.organization_id = NEW.org_id;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_apply_domain_manipulation_to_login_names
|
||||
AFTER INSERT OR UPDATE ON zitadel.org_domains
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION zitadel.apply_domain_manipulation_to_login_names();
|
||||
|
||||
CREATE OR REPLACE FUNCTION zitadel.apply_user_update_to_login_names() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Lock global scope and organization scope to serialize with instance-level
|
||||
-- and org-level setting/domain manipulations.
|
||||
-- The lock is released at transaction end.
|
||||
PERFORM pg_advisory_xact_lock(hashtext('zitadel.login_names'), hashtext(NEW.instance_id || ':global'));
|
||||
PERFORM pg_advisory_xact_lock(hashtext('zitadel.login_names'), hashtext(NEW.instance_id || ':' || NEW.organization_id));
|
||||
|
||||
UPDATE
|
||||
zitadel.login_names
|
||||
SET
|
||||
username = NEW.username
|
||||
WHERE
|
||||
login_names.instance_id = NEW.instance_id
|
||||
AND login_names.user_id = NEW.id;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_apply_user_update_to_login_names
|
||||
AFTER UPDATE ON zitadel.users
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.username IS DISTINCT FROM OLD.username)
|
||||
EXECUTE FUNCTION zitadel.apply_user_update_to_login_names();
|
||||
|
||||
CREATE OR REPLACE FUNCTION zitadel.apply_user_insert_to_login_names() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
setting zitadel.settings%ROWTYPE;
|
||||
BEGIN
|
||||
SELECT settings.*
|
||||
INTO setting
|
||||
FROM
|
||||
zitadel.settings
|
||||
WHERE
|
||||
settings.instance_id = NEW.instance_id
|
||||
AND settings.type = 'domain'
|
||||
AND (
|
||||
settings.organization_id IS NULL
|
||||
OR settings.organization_id = NEW.organization_id
|
||||
)
|
||||
ORDER BY settings.organization_id NULLS LAST
|
||||
LIMIT 1;
|
||||
|
||||
-- Lock based on the setting scope (organization-specific or instance-level/global).
|
||||
-- The lock is released at transaction end.
|
||||
PERFORM pg_advisory_xact_lock(
|
||||
hashtext('zitadel.login_names')
|
||||
, hashtext(setting.instance_id || ':' || COALESCE(setting.organization_id, 'global'))
|
||||
);
|
||||
|
||||
IF NOT (setting.settings->'loginNameIncludesDomain')::BOOLEAN THEN
|
||||
RAISE NOTICE 'inserting username as login name';
|
||||
INSERT INTO zitadel.login_names(instance_id, organization_id, user_id, username, is_preferred, used_setting)
|
||||
VALUES (NEW.instance_id, NEW.organization_id, NEW.id, NEW.username, TRUE, setting.id);
|
||||
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'inserting login names';
|
||||
INSERT INTO zitadel.login_names(instance_id, organization_id, user_id, username, domain, is_preferred, used_setting)
|
||||
SELECT
|
||||
NEW.instance_id
|
||||
, NEW.organization_id
|
||||
, NEW.id
|
||||
, NEW.username
|
||||
, org_domains.domain
|
||||
, org_domains.is_primary IS NULL OR org_domains.is_primary
|
||||
, setting.id
|
||||
FROM
|
||||
zitadel.org_domains
|
||||
WHERE
|
||||
org_domains.instance_id = NEW.instance_id
|
||||
AND org_domains.org_id = NEW.organization_id
|
||||
AND org_domains.is_verified = TRUE;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_apply_user_insert_to_login_names
|
||||
AFTER INSERT ON zitadel.users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION zitadel.apply_user_insert_to_login_names();
|
||||
|
||||
CREATE OR REPLACE FUNCTION zitadel.apply_domain_policy_manipulation_to_login_names() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
old_lock_key INT4;
|
||||
new_lock_key INT4;
|
||||
BEGIN
|
||||
CASE TG_OP
|
||||
-- insert and delete can only happen on organization level therefore we can load the instance level setting as OLD or NEW.
|
||||
WHEN 'DELETE' THEN
|
||||
SELECT
|
||||
settings.*
|
||||
INTO
|
||||
NEW
|
||||
FROM
|
||||
zitadel.settings
|
||||
WHERE
|
||||
settings.instance_id = OLD.instance_id
|
||||
AND settings.type = 'domain'
|
||||
AND settings.organization_id IS NULL;
|
||||
WHEN 'INSERT' THEN
|
||||
SELECT
|
||||
settings.*
|
||||
INTO
|
||||
OLD
|
||||
FROM
|
||||
zitadel.settings
|
||||
WHERE
|
||||
settings.instance_id = NEW.instance_id
|
||||
AND settings.type = 'domain'
|
||||
AND settings.organization_id IS NULL;
|
||||
ELSE
|
||||
-- OLD and NEW are already populated, do nothing
|
||||
END CASE;
|
||||
|
||||
-- Lock both OLD and NEW setting scopes to serialize transitions across settings.
|
||||
-- Acquire in deterministic order to avoid deadlocks.
|
||||
old_lock_key := hashtext(OLD.instance_id || ':' || COALESCE(OLD.organization_id, 'global'));
|
||||
new_lock_key := hashtext(NEW.instance_id || ':' || COALESCE(NEW.organization_id, 'global'));
|
||||
IF old_lock_key = new_lock_key THEN
|
||||
PERFORM pg_advisory_xact_lock(hashtext('zitadel.login_names'), old_lock_key);
|
||||
ELSE
|
||||
PERFORM pg_advisory_xact_lock(hashtext('zitadel.login_names'), new_lock_key);
|
||||
PERFORM pg_advisory_xact_lock(hashtext('zitadel.login_names'), old_lock_key);
|
||||
END IF;
|
||||
|
||||
IF (OLD.settings->'loginNameIncludesDomain')::BOOLEAN IS NOT DISTINCT FROM (NEW.settings->'loginNameIncludesDomain')::BOOLEAN THEN
|
||||
-- field not changed but the setting id did change.
|
||||
IF TG_OP = 'DELETE' OR TG_OP = 'INSERT' THEN
|
||||
UPDATE
|
||||
zitadel.login_names
|
||||
SET
|
||||
used_setting = NEW.id
|
||||
WHERE
|
||||
login_names.instance_id = OLD.instance_id
|
||||
AND login_names.used_setting = OLD.id
|
||||
AND (NEW.organization_id IS NULL OR login_names.organization_id = NEW.organization_id);
|
||||
END IF;
|
||||
|
||||
-- no change in the setting, so we can skip the update
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'recompute login names';
|
||||
|
||||
WITH affected_login_names AS (
|
||||
DELETE FROM zitadel.login_names
|
||||
WHERE
|
||||
login_names.instance_id = NEW.instance_id
|
||||
AND login_names.used_setting = OLD.id
|
||||
AND (NEW.organization_id IS NULL OR login_names.organization_id = NEW.organization_id)
|
||||
RETURNING instance_id, organization_id, user_id
|
||||
)
|
||||
INSERT INTO zitadel.login_names(instance_id, organization_id, user_id, username, domain, is_preferred, used_setting)
|
||||
SELECT DISTINCT ON (affected_login_names.instance_id, affected_login_names.user_id, users.username, org_domains.domain)
|
||||
affected_login_names.instance_id
|
||||
, affected_login_names.organization_id
|
||||
, affected_login_names.user_id
|
||||
, users.username
|
||||
, org_domains.domain
|
||||
, org_domains.is_primary IS NULL OR org_domains.is_primary
|
||||
, NEW.id
|
||||
FROM
|
||||
affected_login_names
|
||||
JOIN zitadel.users ON
|
||||
users.instance_id = affected_login_names.instance_id
|
||||
AND users.id = affected_login_names.user_id
|
||||
LEFT JOIN zitadel.org_domains ON
|
||||
org_domains.instance_id = affected_login_names.instance_id
|
||||
AND org_domains.org_id = affected_login_names.organization_id
|
||||
AND org_domains.is_verified
|
||||
AND (NEW.settings->'loginNameIncludesDomain')::BOOLEAN;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_apply_domain_policy_manipulation_to_login_names
|
||||
AFTER INSERT OR UPDATE ON zitadel.settings
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.type = 'domain')
|
||||
EXECUTE FUNCTION zitadel.apply_domain_policy_manipulation_to_login_names();
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_apply_domain_policy_removed_to_login_names
|
||||
AFTER DELETE ON zitadel.settings
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.type = 'domain')
|
||||
EXECUTE FUNCTION zitadel.apply_domain_policy_manipulation_to_login_names();
|
||||
@@ -186,7 +186,7 @@ func createProjectGrant(t *testing.T, tx database.Transaction, instanceID, grant
|
||||
return projectGrant.ID
|
||||
}
|
||||
|
||||
func createIdentityProvider(t *testing.T, tx database.Transaction, instanceID, orgID string) string {
|
||||
func createIdentityProvider(t *testing.T, tx database.QueryExecutor, instanceID, orgID string) string {
|
||||
t.Helper()
|
||||
idp := domain.IdentityProvider{
|
||||
InstanceID: instanceID,
|
||||
|
||||
@@ -30,6 +30,7 @@ type user struct {
|
||||
userMetadata
|
||||
userPasskey
|
||||
userIdentityProviderLink
|
||||
userLoginName
|
||||
}
|
||||
|
||||
func (u user) HumanRepository() domain.HumanUserRepository {
|
||||
@@ -71,6 +72,8 @@ var queryUserStmt = "SELECT users.instance_id, users.organization_id, users.id,
|
||||
", users.state, users.created_at, users.updated_at" +
|
||||
// metadata
|
||||
`, jsonb_agg(DISTINCT jsonb_build_object('instanceId', user_metadata.instance_id, 'key', user_metadata.key, 'value', encode(user_metadata.value, 'base64'), 'createdAt', user_metadata.created_at, 'updatedAt', user_metadata.updated_at)) FILTER (WHERE user_metadata.user_id IS NOT NULL) AS metadata` +
|
||||
// login names
|
||||
`, jsonb_agg(DISTINCT jsonb_build_object('loginName', login_names.login_name, 'isPreferred', login_names.is_preferred)) FILTER (WHERE login_names.user_id IS NOT NULL) AS login_names` +
|
||||
// machine
|
||||
`, CASE WHEN users.type = 'machine' THEN jsonb_build_object('name', users.name` +
|
||||
`, 'description', users.description, 'secret', users.secret` +
|
||||
@@ -147,6 +150,7 @@ func (u user) appendQueryOpts(opts []database.QueryOption) []database.QueryOptio
|
||||
u.joinVerifications(),
|
||||
u.joinPasskeys(),
|
||||
u.joinIdentityProviderLinks(),
|
||||
u.joinLoginNames(),
|
||||
database.WithGroupBy(u.PrimaryKeyColumns()...),
|
||||
)
|
||||
}
|
||||
@@ -228,11 +232,6 @@ func (u user) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(u.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
// LoginNameCondition implements [domain.UserRepository].
|
||||
func (u user) LoginNameCondition(op database.TextOperation, loginName string) database.Condition {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// OrganizationIDCondition implements [domain.UserRepository].
|
||||
func (u user) OrganizationIDCondition(orgID string) database.Condition {
|
||||
return database.NewTextCondition(u.organizationIDColumn(), database.TextOperationEqual, orgID)
|
||||
@@ -478,3 +477,13 @@ func (u user) joinIdentityProviderLinks() database.QueryOption {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (u user) joinLoginNames() database.QueryOption {
|
||||
return database.WithLeftJoin(
|
||||
u.userLoginName.qualifiedTableName(),
|
||||
database.And(
|
||||
database.NewColumnCondition(u.InstanceIDColumn(), u.userLoginName.instanceIDColumn()),
|
||||
database.NewColumnCondition(u.IDColumn(), u.userLoginName.userIDColumn()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package repository
|
||||
|
||||
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
|
||||
type userLoginName struct{}
|
||||
|
||||
func (u userLoginName) qualifiedTableName() string {
|
||||
return "zitadel.login_names"
|
||||
}
|
||||
|
||||
func (u userLoginName) unqualifiedTableName() string {
|
||||
return "login_names"
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// LoginNameCondition implements [domain.UserRepository].
|
||||
func (u userLoginName) LoginNameCondition(op database.TextOperation, loginName string) database.Condition {
|
||||
return database.NewTextCondition(u.loginNameColumn(), op, loginName)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (u userLoginName) loginNameColumn() database.Column {
|
||||
return database.NewColumn(u.unqualifiedTableName(), "login_name")
|
||||
}
|
||||
|
||||
func (u userLoginName) instanceIDColumn() database.Column {
|
||||
return database.NewColumn(u.unqualifiedTableName(), "instance_id")
|
||||
}
|
||||
|
||||
func (u userLoginName) userIDColumn() database.Column {
|
||||
return database.NewColumn(u.unqualifiedTableName(), "user_id")
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
)
|
||||
|
||||
func TestUserLoginNameManipulations(t *testing.T) {
|
||||
tx, err := pool.Begin(t.Context(), nil)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := tx.Rollback(context.Background())
|
||||
if err != nil {
|
||||
t.Log("failed to rollback transaction:", err)
|
||||
}
|
||||
})
|
||||
|
||||
orgDomainRepo := repository.OrganizationDomainRepository()
|
||||
userRepo := repository.UserRepository()
|
||||
settingsRepo := repository.DomainSettingsRepository()
|
||||
|
||||
instanceID := createInstance(t, tx)
|
||||
err = settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
org1 := createOrganization(t, tx, instanceID)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID,
|
||||
OrgID: org1,
|
||||
Domain: "verified.example.com",
|
||||
IsVerified: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID,
|
||||
OrgID: org1,
|
||||
Domain: "primary.example.com",
|
||||
IsVerified: true,
|
||||
IsPrimary: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID,
|
||||
OrgID: org1,
|
||||
Domain: "unverified.example.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &org1,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(true),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
org2 := createOrganization(t, tx, instanceID)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID,
|
||||
OrgID: org2,
|
||||
Domain: "primary.example2.com",
|
||||
IsVerified: true,
|
||||
IsPrimary: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
loginNames := setupLoginNameTestUsers(t, tx, instanceID, org1, org2)
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
opts []database.QueryOption
|
||||
expectedLoginNames map[string][]domain.LoginName
|
||||
before func(t *testing.T, tx database.QueryExecutor)
|
||||
}{
|
||||
{
|
||||
name: "list all users",
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: maps.Clone(loginNames),
|
||||
},
|
||||
{
|
||||
name: "update usernames",
|
||||
before: func(t *testing.T, tx database.QueryExecutor) {
|
||||
for i := range 100 {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID, strconv.Itoa(i)),
|
||||
userRepo.SetUsername("updated-user"+strconv.Itoa(i)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: expectedLoginNames(loginNames, func(id string, loginNames []domain.LoginName) []domain.LoginName {
|
||||
updated := make([]domain.LoginName, len(loginNames))
|
||||
for i, ln := range loginNames {
|
||||
updated[i] = ln
|
||||
updated[i].LoginName = "updated-" + ln.LoginName
|
||||
}
|
||||
return updated
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "add org setting without impact",
|
||||
before: func(t *testing.T, tx database.QueryExecutor) {
|
||||
err := settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &org2,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
RequireOrgDomainVerification: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: maps.Clone(loginNames),
|
||||
},
|
||||
{
|
||||
name: "add and update org setting without impact",
|
||||
before: func(t *testing.T, tx database.QueryExecutor) {
|
||||
err := settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &org2,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
RequireOrgDomainVerification: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &org2,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
RequireOrgDomainVerification: gu.Ptr(true),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: maps.Clone(loginNames),
|
||||
},
|
||||
{
|
||||
name: "add without impact and update with impact org setting",
|
||||
before: func(t *testing.T, tx database.QueryExecutor) {
|
||||
err := settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &org2,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
RequireOrgDomainVerification: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &org2,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(true),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: expectedLoginNames(loginNames, func(id string, loginNames []domain.LoginName) []domain.LoginName {
|
||||
if len(loginNames) == 2 {
|
||||
return loginNames
|
||||
}
|
||||
return []domain.LoginName{
|
||||
{
|
||||
LoginName: fmt.Sprintf("user%[email protected]", id),
|
||||
IsPreferred: true,
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change org setting with impact",
|
||||
before: func(t *testing.T, tx database.QueryExecutor) {
|
||||
err := settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &org1,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: expectedLoginNames(loginNames, func(id string, loginNames []domain.LoginName) []domain.LoginName {
|
||||
if len(loginNames) == 1 {
|
||||
return loginNames
|
||||
}
|
||||
return []domain.LoginName{
|
||||
{
|
||||
LoginName: "user" + id,
|
||||
IsPreferred: true,
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "reset org setting",
|
||||
before: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := settingsRepo.Delete(t.Context(), tx, settingsRepo.UniqueCondition(instanceID, &org1, domain.SettingTypeDomain, domain.SettingStateActive))
|
||||
require.NoError(t, err)
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: expectedLoginNames(loginNames, func(id string, loginNames []domain.LoginName) []domain.LoginName {
|
||||
if len(loginNames) == 1 {
|
||||
return loginNames
|
||||
}
|
||||
return []domain.LoginName{
|
||||
{
|
||||
LoginName: "user" + id,
|
||||
IsPreferred: true,
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "change instance setting",
|
||||
before: func(t *testing.T, tx database.QueryExecutor) {
|
||||
err := settingsRepo.Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
Type: domain.SettingTypeDomain,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(true),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(userRepo.InstanceIDCondition(instanceID)),
|
||||
},
|
||||
expectedLoginNames: expectedLoginNames(loginNames, func(id string, loginNames []domain.LoginName) []domain.LoginName {
|
||||
if len(loginNames) == 2 {
|
||||
return loginNames
|
||||
}
|
||||
return []domain.LoginName{
|
||||
{
|
||||
LoginName: fmt.Sprintf("user%[email protected]", id),
|
||||
IsPreferred: true,
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
savepoint, err := tx.Begin(t.Context())
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := savepoint.Rollback(context.Background())
|
||||
if err != nil {
|
||||
t.Log("failed to rollback savepoint:", err)
|
||||
}
|
||||
})
|
||||
|
||||
if tt.before != nil {
|
||||
tt.before(t, savepoint)
|
||||
}
|
||||
|
||||
users, err := userRepo.List(t.Context(), savepoint, tt.opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, user := range users {
|
||||
expected, ok := tt.expectedLoginNames[user.ID]
|
||||
require.True(t, ok, "unexpected user ID: %s", user.ID)
|
||||
assertLoginNames(t, expected, user.LoginNames)
|
||||
delete(tt.expectedLoginNames, user.ID)
|
||||
}
|
||||
assert.Empty(t, tt.expectedLoginNames, "some expected users were not found")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupLoginNameTestUsers(t *testing.T, tx database.QueryExecutor, instanceID, org1, org2 string) map[string][]domain.LoginName {
|
||||
userRepo := repository.UserRepository()
|
||||
|
||||
expectedLoginNames := make(map[string][]domain.LoginName, 100)
|
||||
for i := range 100 {
|
||||
orgID := org1
|
||||
id := strconv.Itoa(i)
|
||||
|
||||
expectedLoginNames[id] = []domain.LoginName{
|
||||
{
|
||||
LoginName: fmt.Sprintf("user%[email protected]", i),
|
||||
IsPreferred: false,
|
||||
},
|
||||
{
|
||||
LoginName: fmt.Sprintf("user%[email protected]", i),
|
||||
IsPreferred: true,
|
||||
},
|
||||
}
|
||||
|
||||
if i%2 == 0 {
|
||||
orgID = org2
|
||||
expectedLoginNames[id] = []domain.LoginName{
|
||||
{
|
||||
LoginName: "user" + id,
|
||||
IsPreferred: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
err := userRepo.Create(t.Context(), tx, &domain.User{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: orgID,
|
||||
ID: id,
|
||||
Username: "user" + id,
|
||||
State: domain.UserStateActive,
|
||||
Machine: &domain.MachineUser{
|
||||
Name: "machine" + id,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return expectedLoginNames
|
||||
}
|
||||
|
||||
func expectedLoginNames(loginNames map[string][]domain.LoginName, replace func(id string, loginNames []domain.LoginName) []domain.LoginName) map[string][]domain.LoginName {
|
||||
updated := maps.Clone(loginNames)
|
||||
for id, userLoginNames := range updated {
|
||||
updated[id] = replace(id, userLoginNames)
|
||||
}
|
||||
return updated
|
||||
}
|
||||
@@ -26,12 +26,66 @@ func Test_user_Get(t *testing.T) {
|
||||
t.Cleanup(rollback)
|
||||
|
||||
userRepo := repository.UserRepository()
|
||||
orgDomainRepo := repository.OrganizationDomainRepository()
|
||||
|
||||
instanceID := createInstance(t, tx)
|
||||
|
||||
err := repository.DomainSettingsRepository().Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
Type: domain.SettingTypeDomain,
|
||||
State: domain.SettingStateActive,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(false),
|
||||
RequireOrgDomainVerification: gu.Ptr(false),
|
||||
SMTPSenderAddressMatchesInstanceDomain: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
orgID1 := createOrganization(t, tx, instanceID)
|
||||
orgID2 := createOrganization(t, tx, instanceID)
|
||||
|
||||
err = repository.DomainSettingsRepository().Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: &orgID2,
|
||||
Type: domain.SettingTypeDomain,
|
||||
State: domain.SettingStateActive,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(true),
|
||||
RequireOrgDomainVerification: gu.Ptr(false),
|
||||
SMTPSenderAddressMatchesInstanceDomain: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID2,
|
||||
Domain: "primary.zitadel.com",
|
||||
IsVerified: true,
|
||||
IsPrimary: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID2,
|
||||
Domain: "verified.zitadel.com",
|
||||
IsVerified: true,
|
||||
IsPrimary: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID2,
|
||||
Domain: "unverified.zitadel.com",
|
||||
IsVerified: false,
|
||||
IsPrimary: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().Round(time.Millisecond)
|
||||
|
||||
human := &domain.User{
|
||||
@@ -42,6 +96,12 @@ func Test_user_Get(t *testing.T) {
|
||||
State: domain.UserStateActive,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
LoginNames: []domain.LoginName{
|
||||
{
|
||||
LoginName: "human-user",
|
||||
IsPreferred: true,
|
||||
},
|
||||
},
|
||||
Human: &domain.HumanUser{
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
@@ -66,20 +126,28 @@ func Test_user_Get(t *testing.T) {
|
||||
State: domain.UserStateActive,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
LoginNames: []domain.LoginName{
|
||||
{
|
||||
LoginName: "[email protected]",
|
||||
IsPreferred: true,
|
||||
},
|
||||
{
|
||||
LoginName: "[email protected]",
|
||||
},
|
||||
},
|
||||
Machine: &domain.MachineUser{
|
||||
Name: "My Machine",
|
||||
Description: "This is my machine user",
|
||||
},
|
||||
}
|
||||
|
||||
err := userRepo.Create(t.Context(), tx, human)
|
||||
err = userRepo.Create(t.Context(), tx, human)
|
||||
require.NoError(t, err)
|
||||
err = userRepo.Create(t.Context(), tx, machine)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T, tx database.Transaction) error
|
||||
condition database.Condition
|
||||
wantErr error
|
||||
expected *domain.User
|
||||
@@ -120,10 +188,6 @@ func Test_user_Get(t *testing.T) {
|
||||
t.Log("rollback savepoint failed", err)
|
||||
}
|
||||
})
|
||||
if tt.setup != nil {
|
||||
err := tt.setup(t, savepoint)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
user, err := userRepo.Get(t.Context(), savepoint, database.WithCondition(tt.condition))
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
@@ -536,10 +600,57 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
|
||||
userRepo := repository.UserRepository()
|
||||
humanRepo := repository.HumanUserRepository()
|
||||
orgDomainRepo := repository.OrganizationDomainRepository()
|
||||
|
||||
instanceID1 := createInstance(t, tx)
|
||||
|
||||
err := repository.DomainSettingsRepository().Set(t.Context(), tx, &domain.DomainSettings{
|
||||
Settings: domain.Settings{
|
||||
InstanceID: instanceID1,
|
||||
Type: domain.SettingTypeDomain,
|
||||
State: domain.SettingStateActive,
|
||||
},
|
||||
DomainSettingsAttributes: domain.DomainSettingsAttributes{
|
||||
LoginNameIncludesDomain: gu.Ptr(true),
|
||||
RequireOrgDomainVerification: gu.Ptr(false),
|
||||
SMTPSenderAddressMatchesInstanceDomain: gu.Ptr(false),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
instance1OrgID1 := createOrganization(t, tx, instanceID1)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID1,
|
||||
OrgID: instance1OrgID1,
|
||||
Domain: "org1.zitadel.com",
|
||||
IsVerified: true,
|
||||
IsPrimary: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
instance1OrgID2 := createOrganization(t, tx, instanceID1)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID1,
|
||||
OrgID: instance1OrgID2,
|
||||
Domain: "org2-primary.zitadel.com",
|
||||
IsVerified: true,
|
||||
IsPrimary: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID1,
|
||||
OrgID: instance1OrgID2,
|
||||
Domain: "org2-verified.zitadel.com",
|
||||
IsVerified: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = orgDomainRepo.Add(t.Context(), tx, &domain.AddOrganizationDomain{
|
||||
InstanceID: instanceID1,
|
||||
OrgID: instance1OrgID2,
|
||||
Domain: "org2-unverified.zitadel.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
idpID := createIdentityProvider(t, tx, instanceID1, instance1OrgID1)
|
||||
|
||||
instanceID2 := createInstance(t, tx)
|
||||
@@ -645,7 +756,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T, tx database.Transaction)
|
||||
setup func(t *testing.T, tx database.QueryExecutor)
|
||||
opts []database.QueryOption
|
||||
want want
|
||||
}{
|
||||
@@ -711,7 +822,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by state",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
userRepo.SetState(domain.UserStateInactive),
|
||||
)
|
||||
@@ -739,7 +850,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by type",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
@@ -752,25 +863,24 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
ids: []string{"m1", "m2"},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "by login name",
|
||||
// setup: func(t *testing.T, tx database.Transaction) {
|
||||
// },
|
||||
// opts: []database.QueryOption{
|
||||
// database.WithCondition(database.And(
|
||||
// userRepo.InstanceIDCondition(instanceID1),
|
||||
// )),
|
||||
// },
|
||||
// want: want{
|
||||
// err: nil,
|
||||
// ids: []string{},
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: "by login name found",
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
userRepo.InstanceIDCondition(instanceID1),
|
||||
userRepo.LoginNameCondition(database.TextOperationEqual, "[email protected]"),
|
||||
)),
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
ids: []string{"h2"},
|
||||
},
|
||||
},
|
||||
|
||||
// metadata conditions
|
||||
{
|
||||
name: "by metadata key",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
userRepo.SetMetadata(
|
||||
&domain.Metadata{
|
||||
@@ -812,7 +922,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by metadata value",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
userRepo.SetMetadata(
|
||||
&domain.Metadata{
|
||||
@@ -864,7 +974,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by metadata key and value",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
userRepo.SetMetadata(
|
||||
&domain.Metadata{
|
||||
@@ -921,7 +1031,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
// human conditions
|
||||
{
|
||||
name: "by display name",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
@@ -936,7 +1046,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by first name",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
@@ -951,7 +1061,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by last name",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
@@ -966,7 +1076,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by nickname",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
@@ -981,7 +1091,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by preferred language",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
},
|
||||
opts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
@@ -998,7 +1108,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
// phone conditions
|
||||
{
|
||||
name: "by phone",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := humanRepo.Update(t.Context(), tx, humanRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.SetPhone("+1234567890"),
|
||||
humanRepo.SetPhoneVerification(&domain.VerificationTypeSkipped{}),
|
||||
@@ -1025,7 +1135,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by unverified phone",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := humanRepo.Update(t.Context(), tx, humanRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.SetPhone("+1234567890"),
|
||||
humanRepo.SetPhoneVerification(&domain.VerificationTypeSkipped{}),
|
||||
@@ -1052,7 +1162,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by verified phone",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := humanRepo.Update(t.Context(), tx, humanRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.SetPhone("+1234567890"),
|
||||
humanRepo.SetPhoneVerification(&domain.VerificationTypeSkipped{}),
|
||||
@@ -1081,7 +1191,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
// human email conditions
|
||||
{
|
||||
name: "by email",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := humanRepo.Update(t.Context(), tx, humanRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.SetEmail("[email protected]"),
|
||||
humanRepo.SetEmailVerification(&domain.VerificationTypeSkipped{}),
|
||||
@@ -1108,7 +1218,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by unverified email",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := humanRepo.Update(t.Context(), tx, humanRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.SetEmail("[email protected]"),
|
||||
humanRepo.SetEmailVerification(&domain.VerificationTypeSkipped{}),
|
||||
@@ -1135,7 +1245,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by verified email",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := humanRepo.Update(t.Context(), tx, humanRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.SetEmail("[email protected]"),
|
||||
humanRepo.SetEmailVerification(&domain.VerificationTypeSkipped{}),
|
||||
@@ -1164,7 +1274,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
// passkey conditions
|
||||
{
|
||||
name: "by passkey same challenge",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddPasskey(
|
||||
&domain.Passkey{
|
||||
@@ -1224,7 +1334,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by passkey challenge",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddPasskey(
|
||||
&domain.Passkey{
|
||||
@@ -1284,7 +1394,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by passkey id",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddPasskey(
|
||||
&domain.Passkey{
|
||||
@@ -1344,7 +1454,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by passkey key id",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddPasskey(
|
||||
&domain.Passkey{
|
||||
@@ -1404,7 +1514,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by passkey type",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddPasskey(
|
||||
&domain.Passkey{
|
||||
@@ -1466,7 +1576,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
// idp link conditions
|
||||
{
|
||||
name: "by idp link provider id",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddIdentityProviderLink(&domain.IdentityProviderLink{
|
||||
ProviderID: idpID,
|
||||
@@ -1505,7 +1615,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by idp link provided user id",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddIdentityProviderLink(&domain.IdentityProviderLink{
|
||||
ProviderID: idpID,
|
||||
@@ -1544,7 +1654,7 @@ func Test_user_ListConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by idp link username",
|
||||
setup: func(t *testing.T, tx database.Transaction) {
|
||||
setup: func(t *testing.T, tx database.QueryExecutor) {
|
||||
_, err := userRepo.Update(t.Context(), tx, userRepo.PrimaryKeyCondition(instanceID1, "h1"),
|
||||
humanRepo.AddIdentityProviderLink(&domain.IdentityProviderLink{
|
||||
ProviderID: idpID,
|
||||
@@ -4395,6 +4505,7 @@ func assertUser(t *testing.T, expected, actual *domain.User) {
|
||||
require.Equal(t, expected.Human == nil, actual.Human == nil, "human check")
|
||||
|
||||
assertMetadata(t, expected.Metadata, actual.Metadata)
|
||||
assertLoginNames(t, expected.LoginNames, actual.LoginNames)
|
||||
|
||||
if expected.Machine != nil {
|
||||
assertMachineUser(t, expected.Machine, actual.Machine)
|
||||
@@ -4409,6 +4520,25 @@ func assertUser(t *testing.T, expected, actual *domain.User) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
func assertLoginNames(t *testing.T, expected, gotten []domain.LoginName) {
|
||||
t.Helper()
|
||||
|
||||
assert.Len(t, gotten, len(expected), "login names length mismatch")
|
||||
for _, exp := range expected {
|
||||
var actual domain.LoginName
|
||||
gotten = slices.DeleteFunc(gotten, func(ln domain.LoginName) bool {
|
||||
if ln.LoginName != exp.LoginName {
|
||||
return false
|
||||
}
|
||||
actual = ln
|
||||
return true
|
||||
})
|
||||
require.NotEmpty(t, actual, "login name %q not found", exp.LoginName)
|
||||
assert.Equal(t, exp.IsPreferred, actual.IsPreferred, "login name %q is preferred", exp.LoginName)
|
||||
}
|
||||
assert.Empty(t, gotten, "unmatched login names found")
|
||||
}
|
||||
|
||||
func assertMachineUser(t *testing.T, expected, actual *domain.MachineUser) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user