mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat(relational): implement session repository (#11109)
# Which Problems Are Solved As part of going to a relation storage model, we need to implement the repository for handling sessions and their events. # How the Problems Are Solved - Adds a migration step to create the necessary tables and types in the database. - Adds the repository implementation for sessions. - Implements a projection handler for session events to handle the events written by the old command handlers. - Updates the `Session` type and `SessionRepository` interface: - added `CreatorID` - Adjusted `Delete` method to return amount of affected columns - Removed sub-repository clumns from the main interface - Added `CreatorIDCondition`, `ExpirationCondition`, `ExistsFactor` and `ExistsMetadata` - Removed `SetUserAgent` - Methods of `SessionFactor` and `SessionChallenge` have been made public - fixed enumer on `SessionFactorType` - Adds a `JSON` type to the repository package to easy can JSON columns, resp. jsonb object inside a property of a `JSONArray` object. - Adds a `NoChange` interface, which can be implemented to signal that a `Change` interface implementation is not writing any changes to the statement builder, e.g. `CTEChange` # Additional Changes - Fixes the operators enums. `TextOperations` had wrong comments and `NumberOperations` additionally also had from comparisons. - Fixes downgrade of migration step 008. # Additional Context - closes https://github.com/zitadel/zitadel/issues/10212
This commit is contained in:
@@ -23,7 +23,6 @@ import (
|
||||
type MockSessionRepository struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSessionRepositoryMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryMockRecorder is the mock recorder for MockSessionRepository.
|
||||
@@ -44,17 +43,17 @@ func (m *MockSessionRepository) EXPECT() *MockSessionRepositoryMockRecorder {
|
||||
}
|
||||
|
||||
// ClearFactor mocks base method.
|
||||
func (m *MockSessionRepository) ClearFactor(factor domain.SessionFactorType) database.Change {
|
||||
func (m *MockSessionRepository) ClearFactor(arg0 domain.SessionFactorType) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ClearFactor", factor)
|
||||
ret := m.ctrl.Call(m, "ClearFactor", arg0)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ClearFactor indicates an expected call of ClearFactor.
|
||||
func (mr *MockSessionRepositoryMockRecorder) ClearFactor(factor any) *MockSessionRepositoryClearFactorCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) ClearFactor(arg0 any) *MockSessionRepositoryClearFactorCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearFactor", reflect.TypeOf((*MockSessionRepository)(nil).ClearFactor), factor)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearFactor", reflect.TypeOf((*MockSessionRepository)(nil).ClearFactor), arg0)
|
||||
return &MockSessionRepositoryClearFactorCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -82,17 +81,17 @@ func (c *MockSessionRepositoryClearFactorCall) DoAndReturn(f func(domain.Session
|
||||
}
|
||||
|
||||
// Create mocks base method.
|
||||
func (m *MockSessionRepository) Create(ctx context.Context, client database.QueryExecutor, user *domain.Session) error {
|
||||
func (m *MockSessionRepository) Create(arg0 context.Context, arg1 database.QueryExecutor, arg2 *domain.Session) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Create", ctx, client, user)
|
||||
ret := m.ctrl.Call(m, "Create", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Create indicates an expected call of Create.
|
||||
func (mr *MockSessionRepositoryMockRecorder) Create(ctx, client, user any) *MockSessionRepositoryCreateCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) Create(arg0, arg1, arg2 any) *MockSessionRepositoryCreateCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSessionRepository)(nil).Create), ctx, client, user)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSessionRepository)(nil).Create), arg0, arg1, arg2)
|
||||
return &MockSessionRepositoryCreateCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -158,17 +157,17 @@ func (c *MockSessionRepositoryCreatedAtColumnCall) DoAndReturn(f func() database
|
||||
}
|
||||
|
||||
// CreatedAtCondition mocks base method.
|
||||
func (m *MockSessionRepository) CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition {
|
||||
func (m *MockSessionRepository) CreatedAtCondition(arg0 database.NumberOperation, arg1 time.Time) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreatedAtCondition", op, createdAt)
|
||||
ret := m.ctrl.Call(m, "CreatedAtCondition", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreatedAtCondition indicates an expected call of CreatedAtCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) CreatedAtCondition(op, createdAt any) *MockSessionRepositoryCreatedAtConditionCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) CreatedAtCondition(arg0, arg1 any) *MockSessionRepositoryCreatedAtConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatedAtCondition", reflect.TypeOf((*MockSessionRepository)(nil).CreatedAtCondition), op, createdAt)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatedAtCondition", reflect.TypeOf((*MockSessionRepository)(nil).CreatedAtCondition), arg0, arg1)
|
||||
return &MockSessionRepositoryCreatedAtConditionCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -195,18 +194,57 @@ func (c *MockSessionRepositoryCreatedAtConditionCall) DoAndReturn(f func(databas
|
||||
return c
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockSessionRepository) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) error {
|
||||
// CreatorIDCondition mocks base method.
|
||||
func (m *MockSessionRepository) CreatorIDCondition(arg0 string) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", ctx, client, condition)
|
||||
ret0, _ := ret[0].(error)
|
||||
ret := m.ctrl.Call(m, "CreatorIDCondition", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockSessionRepositoryMockRecorder) Delete(ctx, client, condition any) *MockSessionRepositoryDeleteCall {
|
||||
// CreatorIDCondition indicates an expected call of CreatorIDCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) CreatorIDCondition(arg0 any) *MockSessionRepositoryCreatorIDConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSessionRepository)(nil).Delete), ctx, client, condition)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatorIDCondition", reflect.TypeOf((*MockSessionRepository)(nil).CreatorIDCondition), arg0)
|
||||
return &MockSessionRepositoryCreatorIDConditionCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryCreatorIDConditionCall wrap *gomock.Call
|
||||
type MockSessionRepositoryCreatorIDConditionCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryCreatorIDConditionCall) Return(arg0 database.Condition) *MockSessionRepositoryCreatorIDConditionCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryCreatorIDConditionCall) Do(f func(string) database.Condition) *MockSessionRepositoryCreatorIDConditionCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryCreatorIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockSessionRepositoryCreatorIDConditionCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockSessionRepository) Delete(arg0 context.Context, arg1 database.QueryExecutor, arg2 database.Condition) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockSessionRepositoryMockRecorder) Delete(arg0, arg1, arg2 any) *MockSessionRepositoryDeleteCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSessionRepository)(nil).Delete), arg0, arg1, arg2)
|
||||
return &MockSessionRepositoryDeleteCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -216,19 +254,95 @@ type MockSessionRepositoryDeleteCall struct {
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryDeleteCall) Return(arg0 error) *MockSessionRepositoryDeleteCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
func (c *MockSessionRepositoryDeleteCall) Return(arg0 int64, arg1 error) *MockSessionRepositoryDeleteCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryDeleteCall) Do(f func(context.Context, database.QueryExecutor, database.Condition) error) *MockSessionRepositoryDeleteCall {
|
||||
func (c *MockSessionRepositoryDeleteCall) Do(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockSessionRepositoryDeleteCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryDeleteCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition) error) *MockSessionRepositoryDeleteCall {
|
||||
func (c *MockSessionRepositoryDeleteCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockSessionRepositoryDeleteCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ExistsFactor mocks base method.
|
||||
func (m *MockSessionRepository) ExistsFactor(arg0 database.Condition) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ExistsFactor", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ExistsFactor indicates an expected call of ExistsFactor.
|
||||
func (mr *MockSessionRepositoryMockRecorder) ExistsFactor(arg0 any) *MockSessionRepositoryExistsFactorCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExistsFactor", reflect.TypeOf((*MockSessionRepository)(nil).ExistsFactor), arg0)
|
||||
return &MockSessionRepositoryExistsFactorCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryExistsFactorCall wrap *gomock.Call
|
||||
type MockSessionRepositoryExistsFactorCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryExistsFactorCall) Return(arg0 database.Condition) *MockSessionRepositoryExistsFactorCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryExistsFactorCall) Do(f func(database.Condition) database.Condition) *MockSessionRepositoryExistsFactorCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryExistsFactorCall) DoAndReturn(f func(database.Condition) database.Condition) *MockSessionRepositoryExistsFactorCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ExistsMetadata mocks base method.
|
||||
func (m *MockSessionRepository) ExistsMetadata(arg0 database.Condition) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ExistsMetadata", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ExistsMetadata indicates an expected call of ExistsMetadata.
|
||||
func (mr *MockSessionRepositoryMockRecorder) ExistsMetadata(arg0 any) *MockSessionRepositoryExistsMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExistsMetadata", reflect.TypeOf((*MockSessionRepository)(nil).ExistsMetadata), arg0)
|
||||
return &MockSessionRepositoryExistsMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryExistsMetadataCall wrap *gomock.Call
|
||||
type MockSessionRepositoryExistsMetadataCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryExistsMetadataCall) Return(arg0 database.Condition) *MockSessionRepositoryExistsMetadataCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryExistsMetadataCall) Do(f func(database.Condition) database.Condition) *MockSessionRepositoryExistsMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryExistsMetadataCall) DoAndReturn(f func(database.Condition) database.Condition) *MockSessionRepositoryExistsMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
@@ -271,40 +385,40 @@ func (c *MockSessionRepositoryExpirationColumnCall) DoAndReturn(f func() databas
|
||||
return c
|
||||
}
|
||||
|
||||
// FactorColumns mocks base method.
|
||||
func (m *MockSessionRepository) FactorColumns() domain.SessionFactorColumns {
|
||||
// ExpirationCondition mocks base method.
|
||||
func (m *MockSessionRepository) ExpirationCondition(arg0 database.NumberOperation, arg1 time.Time) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FactorColumns")
|
||||
ret0, _ := ret[0].(domain.SessionFactorColumns)
|
||||
ret := m.ctrl.Call(m, "ExpirationCondition", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// FactorColumns indicates an expected call of FactorColumns.
|
||||
func (mr *MockSessionRepositoryMockRecorder) FactorColumns() *MockSessionRepositoryFactorColumnsCall {
|
||||
// ExpirationCondition indicates an expected call of ExpirationCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) ExpirationCondition(arg0, arg1 any) *MockSessionRepositoryExpirationConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FactorColumns", reflect.TypeOf((*MockSessionRepository)(nil).FactorColumns))
|
||||
return &MockSessionRepositoryFactorColumnsCall{Call: call}
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExpirationCondition", reflect.TypeOf((*MockSessionRepository)(nil).ExpirationCondition), arg0, arg1)
|
||||
return &MockSessionRepositoryExpirationConditionCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryFactorColumnsCall wrap *gomock.Call
|
||||
type MockSessionRepositoryFactorColumnsCall struct {
|
||||
// MockSessionRepositoryExpirationConditionCall wrap *gomock.Call
|
||||
type MockSessionRepositoryExpirationConditionCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryFactorColumnsCall) Return(arg0 domain.SessionFactorColumns) *MockSessionRepositoryFactorColumnsCall {
|
||||
func (c *MockSessionRepositoryExpirationConditionCall) Return(arg0 database.Condition) *MockSessionRepositoryExpirationConditionCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryFactorColumnsCall) Do(f func() domain.SessionFactorColumns) *MockSessionRepositoryFactorColumnsCall {
|
||||
func (c *MockSessionRepositoryExpirationConditionCall) Do(f func(database.NumberOperation, time.Time) database.Condition) *MockSessionRepositoryExpirationConditionCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryFactorColumnsCall) DoAndReturn(f func() domain.SessionFactorColumns) *MockSessionRepositoryFactorColumnsCall {
|
||||
func (c *MockSessionRepositoryExpirationConditionCall) DoAndReturn(f func(database.NumberOperation, time.Time) database.Condition) *MockSessionRepositoryExpirationConditionCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
@@ -348,10 +462,10 @@ func (c *MockSessionRepositoryFactorConditionsCall) DoAndReturn(f func() domain.
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
func (m *MockSessionRepository) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Session, error) {
|
||||
func (m *MockSessionRepository) Get(arg0 context.Context, arg1 database.QueryExecutor, arg2 ...database.QueryOption) (*domain.Session, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{ctx, client}
|
||||
for _, a := range opts {
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Get", varargs...)
|
||||
@@ -361,9 +475,9 @@ func (m *MockSessionRepository) Get(ctx context.Context, client database.QueryEx
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get.
|
||||
func (mr *MockSessionRepositoryMockRecorder) Get(ctx, client any, opts ...any) *MockSessionRepositoryGetCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) Get(arg0, arg1 any, arg2 ...any) *MockSessionRepositoryGetCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{ctx, client}, opts...)
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSessionRepository)(nil).Get), varargs...)
|
||||
return &MockSessionRepositoryGetCall{Call: call}
|
||||
}
|
||||
@@ -430,17 +544,17 @@ func (c *MockSessionRepositoryIDColumnCall) DoAndReturn(f func() database.Column
|
||||
}
|
||||
|
||||
// IDCondition mocks base method.
|
||||
func (m *MockSessionRepository) IDCondition(sessionID string) database.Condition {
|
||||
func (m *MockSessionRepository) IDCondition(arg0 string) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IDCondition", sessionID)
|
||||
ret := m.ctrl.Call(m, "IDCondition", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IDCondition indicates an expected call of IDCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) IDCondition(sessionID any) *MockSessionRepositoryIDConditionCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) IDCondition(arg0 any) *MockSessionRepositoryIDConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDCondition", reflect.TypeOf((*MockSessionRepository)(nil).IDCondition), sessionID)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDCondition", reflect.TypeOf((*MockSessionRepository)(nil).IDCondition), arg0)
|
||||
return &MockSessionRepositoryIDConditionCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -506,17 +620,17 @@ func (c *MockSessionRepositoryInstanceIDColumnCall) DoAndReturn(f func() databas
|
||||
}
|
||||
|
||||
// InstanceIDCondition mocks base method.
|
||||
func (m *MockSessionRepository) InstanceIDCondition(instanceID string) database.Condition {
|
||||
func (m *MockSessionRepository) InstanceIDCondition(arg0 string) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InstanceIDCondition", instanceID)
|
||||
ret := m.ctrl.Call(m, "InstanceIDCondition", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InstanceIDCondition indicates an expected call of InstanceIDCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) InstanceIDCondition(instanceID any) *MockSessionRepositoryInstanceIDConditionCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) InstanceIDCondition(arg0 any) *MockSessionRepositoryInstanceIDConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDCondition", reflect.TypeOf((*MockSessionRepository)(nil).InstanceIDCondition), instanceID)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDCondition", reflect.TypeOf((*MockSessionRepository)(nil).InstanceIDCondition), arg0)
|
||||
return &MockSessionRepositoryInstanceIDConditionCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -582,10 +696,10 @@ func (c *MockSessionRepositoryLifetimeColumnCall) DoAndReturn(f func() database.
|
||||
}
|
||||
|
||||
// List mocks base method.
|
||||
func (m *MockSessionRepository) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Session, error) {
|
||||
func (m *MockSessionRepository) List(arg0 context.Context, arg1 database.QueryExecutor, arg2 ...database.QueryOption) ([]*domain.Session, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{ctx, client}
|
||||
for _, a := range opts {
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "List", varargs...)
|
||||
@@ -595,9 +709,9 @@ func (m *MockSessionRepository) List(ctx context.Context, client database.QueryE
|
||||
}
|
||||
|
||||
// List indicates an expected call of List.
|
||||
func (mr *MockSessionRepositoryMockRecorder) List(ctx, client any, opts ...any) *MockSessionRepositoryListCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) List(arg0, arg1 any, arg2 ...any) *MockSessionRepositoryListCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{ctx, client}, opts...)
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockSessionRepository)(nil).List), varargs...)
|
||||
return &MockSessionRepositoryListCall{Call: call}
|
||||
}
|
||||
@@ -625,44 +739,6 @@ func (c *MockSessionRepositoryListCall) DoAndReturn(f func(context.Context, data
|
||||
return c
|
||||
}
|
||||
|
||||
// MetadataColumns mocks base method.
|
||||
func (m *MockSessionRepository) MetadataColumns() domain.SessionMetadataColumns {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "MetadataColumns")
|
||||
ret0, _ := ret[0].(domain.SessionMetadataColumns)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// MetadataColumns indicates an expected call of MetadataColumns.
|
||||
func (mr *MockSessionRepositoryMockRecorder) MetadataColumns() *MockSessionRepositoryMetadataColumnsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MetadataColumns", reflect.TypeOf((*MockSessionRepository)(nil).MetadataColumns))
|
||||
return &MockSessionRepositoryMetadataColumnsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryMetadataColumnsCall wrap *gomock.Call
|
||||
type MockSessionRepositoryMetadataColumnsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryMetadataColumnsCall) Return(arg0 domain.SessionMetadataColumns) *MockSessionRepositoryMetadataColumnsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryMetadataColumnsCall) Do(f func() domain.SessionMetadataColumns) *MockSessionRepositoryMetadataColumnsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryMetadataColumnsCall) DoAndReturn(f func() domain.SessionMetadataColumns) *MockSessionRepositoryMetadataColumnsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MetadataConditions mocks base method.
|
||||
func (m *MockSessionRepository) MetadataConditions() domain.SessionMetadataConditions {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -740,17 +816,17 @@ func (c *MockSessionRepositoryPrimaryKeyColumnsCall) DoAndReturn(f func() []data
|
||||
}
|
||||
|
||||
// PrimaryKeyCondition mocks base method.
|
||||
func (m *MockSessionRepository) PrimaryKeyCondition(instanceID, sessionID string) database.Condition {
|
||||
func (m *MockSessionRepository) PrimaryKeyCondition(arg0, arg1 string) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PrimaryKeyCondition", instanceID, sessionID)
|
||||
ret := m.ctrl.Call(m, "PrimaryKeyCondition", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// PrimaryKeyCondition indicates an expected call of PrimaryKeyCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) PrimaryKeyCondition(instanceID, sessionID any) *MockSessionRepositoryPrimaryKeyConditionCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) PrimaryKeyCondition(arg0, arg1 any) *MockSessionRepositoryPrimaryKeyConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryKeyCondition", reflect.TypeOf((*MockSessionRepository)(nil).PrimaryKeyCondition), instanceID, sessionID)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryKeyCondition", reflect.TypeOf((*MockSessionRepository)(nil).PrimaryKeyCondition), arg0, arg1)
|
||||
return &MockSessionRepositoryPrimaryKeyConditionCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -778,17 +854,17 @@ func (c *MockSessionRepositoryPrimaryKeyConditionCall) DoAndReturn(f func(string
|
||||
}
|
||||
|
||||
// SetChallenge mocks base method.
|
||||
func (m *MockSessionRepository) SetChallenge(challenge domain.SessionChallenge) database.Change {
|
||||
func (m *MockSessionRepository) SetChallenge(arg0 domain.SessionChallenge) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetChallenge", challenge)
|
||||
ret := m.ctrl.Call(m, "SetChallenge", arg0)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetChallenge indicates an expected call of SetChallenge.
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetChallenge(challenge any) *MockSessionRepositorySetChallengeCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetChallenge(arg0 any) *MockSessionRepositorySetChallengeCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetChallenge", reflect.TypeOf((*MockSessionRepository)(nil).SetChallenge), challenge)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetChallenge", reflect.TypeOf((*MockSessionRepository)(nil).SetChallenge), arg0)
|
||||
return &MockSessionRepositorySetChallengeCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -816,17 +892,17 @@ func (c *MockSessionRepositorySetChallengeCall) DoAndReturn(f func(domain.Sessio
|
||||
}
|
||||
|
||||
// SetFactor mocks base method.
|
||||
func (m *MockSessionRepository) SetFactor(factor domain.SessionFactor) database.Change {
|
||||
func (m *MockSessionRepository) SetFactor(arg0 domain.SessionFactor) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetFactor", factor)
|
||||
ret := m.ctrl.Call(m, "SetFactor", arg0)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetFactor indicates an expected call of SetFactor.
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetFactor(factor any) *MockSessionRepositorySetFactorCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetFactor(arg0 any) *MockSessionRepositorySetFactorCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFactor", reflect.TypeOf((*MockSessionRepository)(nil).SetFactor), factor)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFactor", reflect.TypeOf((*MockSessionRepository)(nil).SetFactor), arg0)
|
||||
return &MockSessionRepositorySetFactorCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -854,17 +930,17 @@ func (c *MockSessionRepositorySetFactorCall) DoAndReturn(f func(domain.SessionFa
|
||||
}
|
||||
|
||||
// SetLifetime mocks base method.
|
||||
func (m *MockSessionRepository) SetLifetime(lifetime time.Duration) database.Change {
|
||||
func (m *MockSessionRepository) SetLifetime(arg0 time.Duration) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetLifetime", lifetime)
|
||||
ret := m.ctrl.Call(m, "SetLifetime", arg0)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetLifetime indicates an expected call of SetLifetime.
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetLifetime(lifetime any) *MockSessionRepositorySetLifetimeCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetLifetime(arg0 any) *MockSessionRepositorySetLifetimeCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLifetime", reflect.TypeOf((*MockSessionRepository)(nil).SetLifetime), lifetime)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLifetime", reflect.TypeOf((*MockSessionRepository)(nil).SetLifetime), arg0)
|
||||
return &MockSessionRepositorySetLifetimeCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -892,17 +968,17 @@ func (c *MockSessionRepositorySetLifetimeCall) DoAndReturn(f func(time.Duration)
|
||||
}
|
||||
|
||||
// SetMetadata mocks base method.
|
||||
func (m *MockSessionRepository) SetMetadata(metadata []domain.SessionMetadata) database.Change {
|
||||
func (m *MockSessionRepository) SetMetadata(arg0 []*domain.SessionMetadata) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetMetadata", metadata)
|
||||
ret := m.ctrl.Call(m, "SetMetadata", arg0)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetMetadata indicates an expected call of SetMetadata.
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetMetadata(metadata any) *MockSessionRepositorySetMetadataCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetMetadata(arg0 any) *MockSessionRepositorySetMetadataCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockSessionRepository)(nil).SetMetadata), metadata)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockSessionRepository)(nil).SetMetadata), arg0)
|
||||
return &MockSessionRepositorySetMetadataCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -918,29 +994,29 @@ func (c *MockSessionRepositorySetMetadataCall) Return(arg0 database.Change) *Moc
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositorySetMetadataCall) Do(f func([]domain.SessionMetadata) database.Change) *MockSessionRepositorySetMetadataCall {
|
||||
func (c *MockSessionRepositorySetMetadataCall) Do(f func([]*domain.SessionMetadata) database.Change) *MockSessionRepositorySetMetadataCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositorySetMetadataCall) DoAndReturn(f func([]domain.SessionMetadata) database.Change) *MockSessionRepositorySetMetadataCall {
|
||||
func (c *MockSessionRepositorySetMetadataCall) DoAndReturn(f func([]*domain.SessionMetadata) database.Change) *MockSessionRepositorySetMetadataCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetToken mocks base method.
|
||||
func (m *MockSessionRepository) SetToken(token string) database.Change {
|
||||
func (m *MockSessionRepository) SetToken(arg0 string) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetToken", token)
|
||||
ret := m.ctrl.Call(m, "SetToken", arg0)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetToken indicates an expected call of SetToken.
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetToken(token any) *MockSessionRepositorySetTokenCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetToken(arg0 any) *MockSessionRepositorySetTokenCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetToken", reflect.TypeOf((*MockSessionRepository)(nil).SetToken), token)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetToken", reflect.TypeOf((*MockSessionRepository)(nil).SetToken), arg0)
|
||||
return &MockSessionRepositorySetTokenCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -968,17 +1044,17 @@ func (c *MockSessionRepositorySetTokenCall) DoAndReturn(f func(string) database.
|
||||
}
|
||||
|
||||
// SetUpdatedAt mocks base method.
|
||||
func (m *MockSessionRepository) SetUpdatedAt(updatedAt time.Time) database.Change {
|
||||
func (m *MockSessionRepository) SetUpdatedAt(arg0 time.Time) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetUpdatedAt", updatedAt)
|
||||
ret := m.ctrl.Call(m, "SetUpdatedAt", arg0)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetUpdatedAt indicates an expected call of SetUpdatedAt.
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetUpdatedAt(updatedAt any) *MockSessionRepositorySetUpdatedAtCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetUpdatedAt(arg0 any) *MockSessionRepositorySetUpdatedAtCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUpdatedAt", reflect.TypeOf((*MockSessionRepository)(nil).SetUpdatedAt), updatedAt)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUpdatedAt", reflect.TypeOf((*MockSessionRepository)(nil).SetUpdatedAt), arg0)
|
||||
return &MockSessionRepositorySetUpdatedAtCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -1005,44 +1081,6 @@ func (c *MockSessionRepositorySetUpdatedAtCall) DoAndReturn(f func(time.Time) da
|
||||
return c
|
||||
}
|
||||
|
||||
// SetUserAgent mocks base method.
|
||||
func (m *MockSessionRepository) SetUserAgent(userAgent domain.SessionUserAgent) database.Change {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetUserAgent", userAgent)
|
||||
ret0, _ := ret[0].(database.Change)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetUserAgent indicates an expected call of SetUserAgent.
|
||||
func (mr *MockSessionRepositoryMockRecorder) SetUserAgent(userAgent any) *MockSessionRepositorySetUserAgentCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUserAgent", reflect.TypeOf((*MockSessionRepository)(nil).SetUserAgent), userAgent)
|
||||
return &MockSessionRepositorySetUserAgentCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositorySetUserAgentCall wrap *gomock.Call
|
||||
type MockSessionRepositorySetUserAgentCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositorySetUserAgentCall) Return(arg0 database.Change) *MockSessionRepositorySetUserAgentCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositorySetUserAgentCall) Do(f func(domain.SessionUserAgent) database.Change) *MockSessionRepositorySetUserAgentCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositorySetUserAgentCall) DoAndReturn(f func(domain.SessionUserAgent) database.Change) *MockSessionRepositorySetUserAgentCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// TokenColumn mocks base method.
|
||||
func (m *MockSessionRepository) TokenColumn() database.Column {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1082,10 +1120,10 @@ func (c *MockSessionRepositoryTokenColumnCall) DoAndReturn(f func() database.Col
|
||||
}
|
||||
|
||||
// Update mocks base method.
|
||||
func (m *MockSessionRepository) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
|
||||
func (m *MockSessionRepository) Update(arg0 context.Context, arg1 database.QueryExecutor, arg2 database.Condition, arg3 ...database.Change) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{ctx, client, condition}
|
||||
for _, a := range changes {
|
||||
varargs := []any{arg0, arg1, arg2}
|
||||
for _, a := range arg3 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Update", varargs...)
|
||||
@@ -1095,9 +1133,9 @@ func (m *MockSessionRepository) Update(ctx context.Context, client database.Quer
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update.
|
||||
func (mr *MockSessionRepositoryMockRecorder) Update(ctx, client, condition any, changes ...any) *MockSessionRepositoryUpdateCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) Update(arg0, arg1, arg2 any, arg3 ...any) *MockSessionRepositoryUpdateCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{ctx, client, condition}, changes...)
|
||||
varargs := append([]any{arg0, arg1, arg2}, arg3...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSessionRepository)(nil).Update), varargs...)
|
||||
return &MockSessionRepositoryUpdateCall{Call: call}
|
||||
}
|
||||
@@ -1164,17 +1202,17 @@ func (c *MockSessionRepositoryUpdatedAtColumnCall) DoAndReturn(f func() database
|
||||
}
|
||||
|
||||
// UpdatedAtCondition mocks base method.
|
||||
func (m *MockSessionRepository) UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition {
|
||||
func (m *MockSessionRepository) UpdatedAtCondition(arg0 database.NumberOperation, arg1 time.Time) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdatedAtCondition", op, updatedAt)
|
||||
ret := m.ctrl.Call(m, "UpdatedAtCondition", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdatedAtCondition indicates an expected call of UpdatedAtCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) UpdatedAtCondition(op, updatedAt any) *MockSessionRepositoryUpdatedAtConditionCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) UpdatedAtCondition(arg0, arg1 any) *MockSessionRepositoryUpdatedAtConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedAtCondition", reflect.TypeOf((*MockSessionRepository)(nil).UpdatedAtCondition), op, updatedAt)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedAtCondition", reflect.TypeOf((*MockSessionRepository)(nil).UpdatedAtCondition), arg0, arg1)
|
||||
return &MockSessionRepositoryUpdatedAtConditionCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -1201,94 +1239,18 @@ func (c *MockSessionRepositoryUpdatedAtConditionCall) DoAndReturn(f func(databas
|
||||
return c
|
||||
}
|
||||
|
||||
// UserAgentColumns mocks base method.
|
||||
func (m *MockSessionRepository) UserAgentColumns() domain.SessionUserAgentColumns {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UserAgentColumns")
|
||||
ret0, _ := ret[0].(domain.SessionUserAgentColumns)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UserAgentColumns indicates an expected call of UserAgentColumns.
|
||||
func (mr *MockSessionRepositoryMockRecorder) UserAgentColumns() *MockSessionRepositoryUserAgentColumnsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserAgentColumns", reflect.TypeOf((*MockSessionRepository)(nil).UserAgentColumns))
|
||||
return &MockSessionRepositoryUserAgentColumnsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryUserAgentColumnsCall wrap *gomock.Call
|
||||
type MockSessionRepositoryUserAgentColumnsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryUserAgentColumnsCall) Return(arg0 domain.SessionUserAgentColumns) *MockSessionRepositoryUserAgentColumnsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryUserAgentColumnsCall) Do(f func() domain.SessionUserAgentColumns) *MockSessionRepositoryUserAgentColumnsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryUserAgentColumnsCall) DoAndReturn(f func() domain.SessionUserAgentColumns) *MockSessionRepositoryUserAgentColumnsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// UserAgentConditions mocks base method.
|
||||
func (m *MockSessionRepository) UserAgentConditions() domain.SessionUserAgentConditions {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UserAgentConditions")
|
||||
ret0, _ := ret[0].(domain.SessionUserAgentConditions)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UserAgentConditions indicates an expected call of UserAgentConditions.
|
||||
func (mr *MockSessionRepositoryMockRecorder) UserAgentConditions() *MockSessionRepositoryUserAgentConditionsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserAgentConditions", reflect.TypeOf((*MockSessionRepository)(nil).UserAgentConditions))
|
||||
return &MockSessionRepositoryUserAgentConditionsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSessionRepositoryUserAgentConditionsCall wrap *gomock.Call
|
||||
type MockSessionRepositoryUserAgentConditionsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSessionRepositoryUserAgentConditionsCall) Return(arg0 domain.SessionUserAgentConditions) *MockSessionRepositoryUserAgentConditionsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSessionRepositoryUserAgentConditionsCall) Do(f func() domain.SessionUserAgentConditions) *MockSessionRepositoryUserAgentConditionsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSessionRepositoryUserAgentConditionsCall) DoAndReturn(f func() domain.SessionUserAgentConditions) *MockSessionRepositoryUserAgentConditionsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// UserAgentIDCondition mocks base method.
|
||||
func (m *MockSessionRepository) UserAgentIDCondition(userAgentID string) database.Condition {
|
||||
func (m *MockSessionRepository) UserAgentIDCondition(arg0 string) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UserAgentIDCondition", userAgentID)
|
||||
ret := m.ctrl.Call(m, "UserAgentIDCondition", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UserAgentIDCondition indicates an expected call of UserAgentIDCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) UserAgentIDCondition(userAgentID any) *MockSessionRepositoryUserAgentIDConditionCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) UserAgentIDCondition(arg0 any) *MockSessionRepositoryUserAgentIDConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserAgentIDCondition", reflect.TypeOf((*MockSessionRepository)(nil).UserAgentIDCondition), userAgentID)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserAgentIDCondition", reflect.TypeOf((*MockSessionRepository)(nil).UserAgentIDCondition), arg0)
|
||||
return &MockSessionRepositoryUserAgentIDConditionCall{Call: call}
|
||||
}
|
||||
|
||||
@@ -1354,17 +1316,17 @@ func (c *MockSessionRepositoryUserIDColumnCall) DoAndReturn(f func() database.Co
|
||||
}
|
||||
|
||||
// UserIDCondition mocks base method.
|
||||
func (m *MockSessionRepository) UserIDCondition(userID string) database.Condition {
|
||||
func (m *MockSessionRepository) UserIDCondition(arg0 string) database.Condition {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UserIDCondition", userID)
|
||||
ret := m.ctrl.Call(m, "UserIDCondition", arg0)
|
||||
ret0, _ := ret[0].(database.Condition)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UserIDCondition indicates an expected call of UserIDCondition.
|
||||
func (mr *MockSessionRepositoryMockRecorder) UserIDCondition(userID any) *MockSessionRepositoryUserIDConditionCall {
|
||||
func (mr *MockSessionRepositoryMockRecorder) UserIDCondition(arg0 any) *MockSessionRepositoryUserIDConditionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserIDCondition", reflect.TypeOf((*MockSessionRepository)(nil).UserIDCondition), userID)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserIDCondition", reflect.TypeOf((*MockSessionRepository)(nil).UserIDCondition), arg0)
|
||||
return &MockSessionRepositoryUserIDConditionCall{Call: call}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,18 +8,19 @@ import (
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
InstanceID string `json:"instance_id,omitempty" db:"instance_id"`
|
||||
ID string `json:"id,omitempty" db:"id"`
|
||||
Token string `json:"token,omitempty" db:"token"`
|
||||
Lifetime time.Duration `json:"lifetime,omitempty" db:"lifetime"`
|
||||
Expiration time.Time `json:"expiration,omitzero" db:"expiration"`
|
||||
UserID string `json:"user_id,omitempty" db:"user_id"`
|
||||
CreatedAt time.Time `json:"created_at,omitzero" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitzero" db:"updated_at"`
|
||||
Factors SessionFactors `json:"factors,omitempty"`
|
||||
Challenges SessionChallenges `json:"challenges,omitempty"`
|
||||
Metadata []SessionMetadata `json:"metadata,omitempty"`
|
||||
UserAgent *SessionUserAgent `json:"user_agent,omitempty"`
|
||||
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
|
||||
ID string `json:"id,omitempty" db:"id"`
|
||||
TokenID string `json:"tokenId,omitempty" db:"token_id"`
|
||||
Lifetime time.Duration `json:"lifetime,omitempty" db:"lifetime"`
|
||||
Expiration time.Time `json:"expiration,omitzero" db:"expiration"`
|
||||
UserID string `json:"userId,omitempty" db:"user_id"`
|
||||
CreatorID string `json:"creatorId,omitempty" db:"creator_id"`
|
||||
CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
|
||||
Factors SessionFactors `json:"factors,omitempty"`
|
||||
Challenges SessionChallenges `json:"challenges,omitempty"`
|
||||
Metadata []*SessionMetadata `json:"metadata,omitempty"`
|
||||
UserAgent *SessionUserAgent `json:"userAgent,omitempty"`
|
||||
}
|
||||
|
||||
//go:generate mockgen -typed -package domainmock -destination ./mock/session.mock.go . SessionRepository
|
||||
@@ -41,7 +42,7 @@ type SessionRepository interface {
|
||||
// The condition must include at least the instanceID of the session to update.
|
||||
Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error)
|
||||
// Delete removes sessions based on the given condition.
|
||||
Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) error
|
||||
Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error)
|
||||
}
|
||||
|
||||
// sessionColumns define all the columns of the session table.
|
||||
@@ -50,8 +51,8 @@ type sessionColumns interface {
|
||||
InstanceIDColumn() database.Column
|
||||
// IDColumn returns the column for the id field.
|
||||
IDColumn() database.Column
|
||||
// TokenColumn returns the column for the token field.
|
||||
TokenColumn() database.Column
|
||||
// TokenIDColumn returns the column for the token id field.
|
||||
TokenIDColumn() database.Column
|
||||
// LifetimeColumn returns the column for the lifetime field.
|
||||
LifetimeColumn() database.Column
|
||||
// ExpirationColumn returns the column for the expiration field.
|
||||
@@ -62,13 +63,6 @@ type sessionColumns interface {
|
||||
CreatedAtColumn() database.Column
|
||||
// UpdatedAtColumn returns the column for the updated at field.
|
||||
UpdatedAtColumn() database.Column
|
||||
|
||||
// FactorColumns returns the columns for the factors fields.
|
||||
FactorColumns() SessionFactorColumns
|
||||
// MetadataColumns returns the columns for the metadata fields.
|
||||
MetadataColumns() SessionMetadataColumns
|
||||
// UserAgentColumns returns the columns for the user agent fields.
|
||||
UserAgentColumns() SessionUserAgentColumns
|
||||
}
|
||||
|
||||
// sessionConditions define all the conditions for the session table.
|
||||
@@ -83,17 +77,22 @@ type sessionConditions interface {
|
||||
UserAgentIDCondition(userAgentID string) database.Condition
|
||||
// UserIDCondition returns an equal filter on the user id field.
|
||||
UserIDCondition(userID string) database.Condition
|
||||
// CreatorIDCondition returns an equal filter on the creator id field.
|
||||
CreatorIDCondition(creatorID string) database.Condition
|
||||
// ExpirationCondition returns a filter on the expiration field.
|
||||
ExpirationCondition(op database.NumberOperation, expiration time.Time) database.Condition
|
||||
// CreatedAtCondition returns a filter on the created at field.
|
||||
CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition
|
||||
// UpdatedAtCondition returns a filter on the updated at field.
|
||||
UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition
|
||||
|
||||
// ExistsFactor returns a filter on the session's factors.
|
||||
ExistsFactor(condition database.Condition) database.Condition
|
||||
// FactorConditions returns the conditions for the factors fields.
|
||||
FactorConditions() SessionFactorConditions
|
||||
// ExistsMetadata returns a filter on the session's metadata.
|
||||
ExistsMetadata(condition database.Condition) database.Condition
|
||||
// MetadataConditions returns the conditions for the metadata fields.
|
||||
MetadataConditions() SessionMetadataConditions
|
||||
// UserAgentConditions returns the conditions for the user agent fields.
|
||||
UserAgentConditions() SessionUserAgentConditions
|
||||
}
|
||||
|
||||
type sessionChanges interface {
|
||||
@@ -101,7 +100,7 @@ type sessionChanges interface {
|
||||
// Only use this when reducing events,
|
||||
// during regular updates the DB sets this column automatically.
|
||||
SetUpdatedAt(updatedAt time.Time) database.Change
|
||||
// SetToken sets the token field of the session.
|
||||
// SetToken sets the token id field of the session.
|
||||
SetToken(token string) database.Change
|
||||
// SetLifetime sets the lifetime field of the session and will update the computed expiration field.
|
||||
SetLifetime(lifetime time.Duration) database.Change
|
||||
@@ -110,10 +109,8 @@ type sessionChanges interface {
|
||||
SetChallenge(challenge SessionChallenge) database.Change
|
||||
// SetFactor adds or updates the factor of the corresponding type.
|
||||
SetFactor(factor SessionFactor) database.Change
|
||||
// ClearFactor resets the factor of the corresponding type.
|
||||
// ClearFactor resets the factor's verification.
|
||||
ClearFactor(factor SessionFactorType) database.Change
|
||||
// SetMetadata adds or updates the metadata of the session.
|
||||
SetMetadata(metadata []SessionMetadata) database.Change
|
||||
// SetUserAgent adds or updates the user agent of the session.
|
||||
SetUserAgent(userAgent SessionUserAgent) database.Change
|
||||
SetMetadata(metadata []*SessionMetadata) database.Change
|
||||
}
|
||||
|
||||
@@ -8,94 +8,112 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
type SessionFactorType int
|
||||
//go:generate enumer -type=SessionFactorType -trimprefix SessionFactorType -transform snake -linecomment -sql
|
||||
type SessionFactorType uint8
|
||||
|
||||
const (
|
||||
SessionFactorTypeUnknown SessionFactorType = iota
|
||||
SessionFactorTypeUser
|
||||
SessionFactorTypePassword
|
||||
SessionFactorTypePasskey // formerly known as WebAuthn
|
||||
// SessionFactorTypePasskey was formerly known as WebAuthn
|
||||
SessionFactorTypePasskey
|
||||
SessionFactorTypeIdentityProviderIntent
|
||||
// SessionFactorTypeTOTP used to be called OTP in some places
|
||||
SessionFactorTypeTOTP
|
||||
SessionFactorTypeOTPSMS
|
||||
SessionFactorTypeOTPSMS // otp_sms
|
||||
SessionFactorTypeOTPEmail
|
||||
SessionFactorTypeRecoveryCode
|
||||
)
|
||||
|
||||
type SessionFactor interface {
|
||||
sessionFactorType() SessionFactorType
|
||||
SessionFactorType() SessionFactorType
|
||||
}
|
||||
|
||||
type SessionFactorUser struct {
|
||||
UserID string
|
||||
LastVerifiedAt time.Time
|
||||
UserID string `json:"userId,omitempty" db:"user_id"`
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
}
|
||||
|
||||
// sessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorUser) sessionFactorType() SessionFactorType {
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorUser) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypeUser
|
||||
}
|
||||
|
||||
type SessionFactorPassword struct {
|
||||
LastVerifiedAt time.Time
|
||||
LastFailedAt time.Time
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
LastFailedAt time.Time `json:"lastFailedAt,omitzero" db:"last_failed_at"`
|
||||
}
|
||||
|
||||
// sessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorPassword) sessionFactorType() SessionFactorType {
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorPassword) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypePassword
|
||||
}
|
||||
|
||||
type SessionFactorIdentityProviderIntent struct {
|
||||
LastVerifiedAt time.Time
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
}
|
||||
|
||||
// sessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorIdentityProviderIntent) sessionFactorType() SessionFactorType {
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorIdentityProviderIntent) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypeIdentityProviderIntent
|
||||
}
|
||||
|
||||
type SessionFactorPasskey struct {
|
||||
LastVerifiedAt time.Time
|
||||
UserVerified bool
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
UserVerified bool `json:"userVerified,omitempty" db:"user_verified"`
|
||||
}
|
||||
|
||||
// sessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorPasskey) sessionFactorType() SessionFactorType {
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorPasskey) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypePasskey
|
||||
}
|
||||
|
||||
type SessionFactorTOTP struct {
|
||||
LastVerifiedAt time.Time
|
||||
LastFailedAt time.Time
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
LastFailedAt time.Time `json:"lastFailedAt,omitzero" db:"last_failed_at"`
|
||||
}
|
||||
|
||||
// sessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorTOTP) sessionFactorType() SessionFactorType {
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorTOTP) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypeTOTP
|
||||
}
|
||||
|
||||
type SessionFactorOTPSMS struct {
|
||||
LastVerifiedAt time.Time
|
||||
LastFailedAt time.Time
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
LastFailedAt time.Time `json:"lastFailedAt,omitzero" db:"last_failed_at"`
|
||||
}
|
||||
|
||||
// sessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorOTPSMS) sessionFactorType() SessionFactorType {
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorOTPSMS) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypeOTPSMS
|
||||
}
|
||||
|
||||
type SessionFactorOTPEmail struct {
|
||||
LastVerifiedAt time.Time
|
||||
LastFailedAt time.Time
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
LastFailedAt time.Time `json:"lastFailedAt,omitzero" db:"last_failed_at"`
|
||||
}
|
||||
|
||||
// sessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorOTPEmail) sessionFactorType() SessionFactorType {
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorOTPEmail) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypeOTPEmail
|
||||
}
|
||||
|
||||
type SessionFactorRecoveryCode struct {
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt,omitzero" db:"last_verified_at"`
|
||||
LastFailedAt time.Time `json:"lastFailedAt,omitzero" db:"last_failed_at"`
|
||||
}
|
||||
|
||||
// SessionFactorType implements [SessionFactor].
|
||||
func (s *SessionFactorRecoveryCode) SessionFactorType() SessionFactorType {
|
||||
return SessionFactorTypeRecoveryCode
|
||||
}
|
||||
|
||||
type SessionFactors []SessionFactor
|
||||
|
||||
func (s *SessionFactors) AppendTo(factor SessionFactor) {
|
||||
*s = append(*s, factor)
|
||||
}
|
||||
|
||||
// GetUserFactor returns the user factor from the factors.
|
||||
func (s SessionFactors) GetUserFactor() *SessionFactorUser {
|
||||
factor, _ := GetFactor[*SessionFactorUser](s)
|
||||
@@ -138,6 +156,12 @@ func (s SessionFactors) GetOTPEmailFactor() *SessionFactorOTPEmail {
|
||||
return factor
|
||||
}
|
||||
|
||||
// GetRecoveryCodeFactor returns the recovery code factor from the factors.
|
||||
func (s SessionFactors) GetRecoveryCodeFactor() *SessionFactorRecoveryCode {
|
||||
factor, _ := GetFactor[*SessionFactorRecoveryCode](s)
|
||||
return factor
|
||||
}
|
||||
|
||||
// GetFactor returns the factor of the given type from the factors.
|
||||
func GetFactor[T SessionFactor](factors SessionFactors) (T, bool) {
|
||||
var nilT T
|
||||
@@ -150,52 +174,56 @@ func GetFactor[T SessionFactor](factors SessionFactors) (T, bool) {
|
||||
}
|
||||
|
||||
type SessionChallenge interface {
|
||||
sessionChallengeType() SessionFactorType
|
||||
SessionChallengeType() SessionFactorType
|
||||
}
|
||||
|
||||
type SessionChallengePasskey struct {
|
||||
LastChallengedAt time.Time
|
||||
Challenge string
|
||||
AllowedCredentialIDs [][]byte
|
||||
UserVerification domain.UserVerificationRequirement
|
||||
RPID string
|
||||
LastChallengedAt time.Time `json:"lastChallengedAt,omitzero" db:"last_challenged_at"`
|
||||
Challenge string `json:"challenge,omitempty" db:"challenge"`
|
||||
AllowedCredentialIDs [][]byte `json:"allowedCredentialIDs,omitempty" db:"allowed_credential_ids"`
|
||||
UserVerification domain.UserVerificationRequirement `json:"userVerification,omitempty" db:"user_verification"`
|
||||
RPID string `json:"rpId,omitempty" db:"rp_id"`
|
||||
}
|
||||
|
||||
// sessionChallengeType implements [SessionChallenge].
|
||||
func (s *SessionChallengePasskey) sessionChallengeType() SessionFactorType {
|
||||
// SessionChallengeType implements [SessionChallenge].
|
||||
func (s *SessionChallengePasskey) SessionChallengeType() SessionFactorType {
|
||||
return SessionFactorTypePasskey
|
||||
}
|
||||
|
||||
type SessionChallengeOTPSMS struct {
|
||||
LastChallengedAt time.Time
|
||||
Code *crypto.CryptoValue
|
||||
Expiry time.Duration
|
||||
CodeReturned bool
|
||||
GeneratorID string
|
||||
TriggeredAtOrigin string
|
||||
LastChallengedAt time.Time `json:"lastChallengedAt,omitzero" db:"last_challenged_at"`
|
||||
Code *crypto.CryptoValue `json:"code,omitempty" db:"code"`
|
||||
Expiry time.Duration `json:"expiry,omitzero" db:"expiry"`
|
||||
CodeReturned bool `json:"codeReturned,omitempty" db:"code_returned"`
|
||||
GeneratorID string `json:"generatorId,omitempty" db:"generator_id"`
|
||||
TriggeredAtOrigin string `json:"triggeredAtOrigin,omitempty" db:"triggered_at_origin"`
|
||||
}
|
||||
|
||||
// sessionChallengeType implements [SessionChallenge].
|
||||
func (s *SessionChallengeOTPSMS) sessionChallengeType() SessionFactorType {
|
||||
// SessionChallengeType implements [SessionChallenge].
|
||||
func (s *SessionChallengeOTPSMS) SessionChallengeType() SessionFactorType {
|
||||
return SessionFactorTypeOTPSMS
|
||||
}
|
||||
|
||||
type SessionChallengeOTPEmail struct {
|
||||
LastChallengedAt time.Time
|
||||
Code *crypto.CryptoValue
|
||||
Expiry time.Duration
|
||||
CodeReturned bool
|
||||
URLTmpl string
|
||||
TriggeredAtOrigin string
|
||||
LastChallengedAt time.Time `json:"lastChallengedAt,omitzero" db:"last_challenged_at"`
|
||||
Code *crypto.CryptoValue `json:"code,omitempty" db:"code"`
|
||||
Expiry time.Duration `json:"expiry,omitzero" db:"expiry"`
|
||||
CodeReturned bool `json:"codeReturned,omitempty" db:"code_returned"`
|
||||
URLTemplate string `json:"urlTemplate,omitempty" db:"url_template"`
|
||||
TriggeredAtOrigin string `json:"triggeredAtOrigin,omitempty" db:"triggered_at_origin"`
|
||||
}
|
||||
|
||||
// sessionChallengeType implements [SessionChallenge].
|
||||
func (s *SessionChallengeOTPEmail) sessionChallengeType() SessionFactorType {
|
||||
// SessionChallengeType implements [SessionChallenge].
|
||||
func (s *SessionChallengeOTPEmail) SessionChallengeType() SessionFactorType {
|
||||
return SessionFactorTypeOTPEmail
|
||||
}
|
||||
|
||||
type SessionChallenges []SessionChallenge
|
||||
|
||||
func (s *SessionChallenges) AppendTo(challenge SessionChallenge) {
|
||||
*s = append(*s, challenge)
|
||||
}
|
||||
|
||||
// GetPasskeyChallenge returns the passkey challenge from the challenges.
|
||||
func (s SessionChallenges) GetPasskeyChallenge() *SessionChallengePasskey {
|
||||
challenge, _ := GetChallenge[*SessionChallengePasskey](s)
|
||||
@@ -225,30 +253,9 @@ func GetChallenge[T SessionChallenge](challenges SessionChallenges) (T, bool) {
|
||||
return nilT, false
|
||||
}
|
||||
|
||||
type SessionFactorColumns interface {
|
||||
// InstanceIDColumn returns the column for the instance id field.
|
||||
InstanceIDColumn() database.Column
|
||||
// SessionIDColumn returns the column for the session id field.
|
||||
SessionIDColumn() database.Column
|
||||
// FactorTypeColumn returns the column for the factor type field.
|
||||
FactorTypeColumn() database.Column
|
||||
// LastChallengedAtColumn returns the column for the last challenged at field.
|
||||
LastChallengedAtColumn() database.Column
|
||||
// LastFailedAtColumn returns the column for the last failed at field.
|
||||
LastFailedAtColumn() database.Column
|
||||
// LastVerifiedAtColumn returns the column for the last verified at field.
|
||||
LastVerifiedAtColumn() database.Column
|
||||
}
|
||||
|
||||
type SessionFactorConditions interface {
|
||||
// PrimaryKeyCondition returns a filter on the primary key fields.
|
||||
PrimaryKeyCondition(instanceID, sessionID string, factorType SessionFactorType) database.Condition
|
||||
// InstanceIDCondition returns an equal filter on the instance id field.
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// SessionIDCondition returns an equal filter on the session id field.
|
||||
SessionIDCondition(sessionID string) database.Condition
|
||||
// FactorTypeCondition returns an equal filter on the factor type field.
|
||||
FactorTypeCondition(factorType SessionFactorType) database.Condition
|
||||
// FactorLastVerifiedBeforeCondition returns a filter on the factor last verified field before the given time.
|
||||
FactorLastVerifiedBeforeCondition(lastVerifiedAt time.Time) database.Condition
|
||||
// LastVerifiedBeforeCondition returns a filter on the factor last verified field before the given time.
|
||||
LastVerifiedBeforeCondition(lastVerifiedAt time.Time) database.Condition
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type SessionUserAgent struct {
|
||||
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
|
||||
FingerprintID *string `json:"fingerprintId,omitempty" db:"fingerprint_id"`
|
||||
Description *string `json:"description,omitempty" db:"description"`
|
||||
IP net.IP `json:"ip,omitempty" db:"ip"`
|
||||
Header http.Header `json:"header,omitempty" db:"headers"`
|
||||
Header http.Header `json:"headers,omitempty" db:"headers"`
|
||||
}
|
||||
|
||||
type SessionUserAgentColumns interface {
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// Code generated by "enumer -type=SessionFactorType -trimprefix SessionFactorType -transform snake -linecomment -sql"; DO NOT EDIT.
|
||||
|
||||
package domain
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _SessionFactorTypeName = "unknownuserpasswordpasskeyidentity_provider_intenttotpotp_smsotp_emailrecovery_code"
|
||||
|
||||
var _SessionFactorTypeIndex = [...]uint8{0, 7, 11, 19, 26, 50, 54, 61, 70, 83}
|
||||
|
||||
const _SessionFactorTypeLowerName = "unknownuserpasswordpasskeyidentity_provider_intenttotpotp_smsotp_emailrecovery_code"
|
||||
|
||||
func (i SessionFactorType) String() string {
|
||||
if i >= SessionFactorType(len(_SessionFactorTypeIndex)-1) {
|
||||
return fmt.Sprintf("SessionFactorType(%d)", i)
|
||||
}
|
||||
return _SessionFactorTypeName[_SessionFactorTypeIndex[i]:_SessionFactorTypeIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _SessionFactorTypeNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[SessionFactorTypeUnknown-(0)]
|
||||
_ = x[SessionFactorTypeUser-(1)]
|
||||
_ = x[SessionFactorTypePassword-(2)]
|
||||
_ = x[SessionFactorTypePasskey-(3)]
|
||||
_ = x[SessionFactorTypeIdentityProviderIntent-(4)]
|
||||
_ = x[SessionFactorTypeTOTP-(5)]
|
||||
_ = x[SessionFactorTypeOTPSMS-(6)]
|
||||
_ = x[SessionFactorTypeOTPEmail-(7)]
|
||||
_ = x[SessionFactorTypeRecoveryCode-(8)]
|
||||
}
|
||||
|
||||
var _SessionFactorTypeValues = []SessionFactorType{SessionFactorTypeUnknown, SessionFactorTypeUser, SessionFactorTypePassword, SessionFactorTypePasskey, SessionFactorTypeIdentityProviderIntent, SessionFactorTypeTOTP, SessionFactorTypeOTPSMS, SessionFactorTypeOTPEmail, SessionFactorTypeRecoveryCode}
|
||||
|
||||
var _SessionFactorTypeNameToValueMap = map[string]SessionFactorType{
|
||||
_SessionFactorTypeName[0:7]: SessionFactorTypeUnknown,
|
||||
_SessionFactorTypeLowerName[0:7]: SessionFactorTypeUnknown,
|
||||
_SessionFactorTypeName[7:11]: SessionFactorTypeUser,
|
||||
_SessionFactorTypeLowerName[7:11]: SessionFactorTypeUser,
|
||||
_SessionFactorTypeName[11:19]: SessionFactorTypePassword,
|
||||
_SessionFactorTypeLowerName[11:19]: SessionFactorTypePassword,
|
||||
_SessionFactorTypeName[19:26]: SessionFactorTypePasskey,
|
||||
_SessionFactorTypeLowerName[19:26]: SessionFactorTypePasskey,
|
||||
_SessionFactorTypeName[26:50]: SessionFactorTypeIdentityProviderIntent,
|
||||
_SessionFactorTypeLowerName[26:50]: SessionFactorTypeIdentityProviderIntent,
|
||||
_SessionFactorTypeName[50:54]: SessionFactorTypeTOTP,
|
||||
_SessionFactorTypeLowerName[50:54]: SessionFactorTypeTOTP,
|
||||
_SessionFactorTypeName[54:61]: SessionFactorTypeOTPSMS,
|
||||
_SessionFactorTypeLowerName[54:61]: SessionFactorTypeOTPSMS,
|
||||
_SessionFactorTypeName[61:70]: SessionFactorTypeOTPEmail,
|
||||
_SessionFactorTypeLowerName[61:70]: SessionFactorTypeOTPEmail,
|
||||
_SessionFactorTypeName[70:83]: SessionFactorTypeRecoveryCode,
|
||||
_SessionFactorTypeLowerName[70:83]: SessionFactorTypeRecoveryCode,
|
||||
}
|
||||
|
||||
var _SessionFactorTypeNames = []string{
|
||||
_SessionFactorTypeName[0:7],
|
||||
_SessionFactorTypeName[7:11],
|
||||
_SessionFactorTypeName[11:19],
|
||||
_SessionFactorTypeName[19:26],
|
||||
_SessionFactorTypeName[26:50],
|
||||
_SessionFactorTypeName[50:54],
|
||||
_SessionFactorTypeName[54:61],
|
||||
_SessionFactorTypeName[61:70],
|
||||
_SessionFactorTypeName[70:83],
|
||||
}
|
||||
|
||||
// SessionFactorTypeString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func SessionFactorTypeString(s string) (SessionFactorType, error) {
|
||||
if val, ok := _SessionFactorTypeNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _SessionFactorTypeNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to SessionFactorType values", s)
|
||||
}
|
||||
|
||||
// SessionFactorTypeValues returns all values of the enum
|
||||
func SessionFactorTypeValues() []SessionFactorType {
|
||||
return _SessionFactorTypeValues
|
||||
}
|
||||
|
||||
// SessionFactorTypeStrings returns a slice of all String values of the enum
|
||||
func SessionFactorTypeStrings() []string {
|
||||
strs := make([]string, len(_SessionFactorTypeNames))
|
||||
copy(strs, _SessionFactorTypeNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsASessionFactorType returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i SessionFactorType) IsASessionFactorType() bool {
|
||||
for _, v := range _SessionFactorTypeValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i SessionFactorType) Value() (driver.Value, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
func (i *SessionFactorType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var str string
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
str = string(v)
|
||||
case string:
|
||||
str = v
|
||||
case fmt.Stringer:
|
||||
str = v.String()
|
||||
default:
|
||||
return fmt.Errorf("invalid value of SessionFactorType: %[1]T(%[1]v)", value)
|
||||
}
|
||||
|
||||
val, err := SessionFactorTypeString(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*i = val
|
||||
return nil
|
||||
}
|
||||
@@ -17,6 +17,12 @@ type Change interface {
|
||||
IsOnColumn(col Column) bool
|
||||
}
|
||||
|
||||
// NoChange can be implemented by Changes that might not result in any actual change,
|
||||
// resp. when the change has no set statement, e.g. [CTEChange] with no change function.
|
||||
type NoChange interface {
|
||||
NoChange() bool
|
||||
}
|
||||
|
||||
type change[V Value] struct {
|
||||
column Column
|
||||
value V
|
||||
@@ -88,15 +94,23 @@ func (c Changes) IsOnColumn(col Column) bool {
|
||||
}
|
||||
|
||||
// Write implements [Change].
|
||||
func (m Changes) Write(builder *StatementBuilder) error {
|
||||
for i, change := range m {
|
||||
if i > 0 {
|
||||
func (c Changes) Write(builder *StatementBuilder) error {
|
||||
var hadChanges bool
|
||||
for _, change := range c {
|
||||
ch, ok := change.(NoChange)
|
||||
hasChanges := !ok || !ch.NoChange()
|
||||
// if the previous change actually wrote a change to the builder
|
||||
// and this change has changes, add a comma
|
||||
if hadChanges && hasChanges {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
err := change.Write(builder)
|
||||
if err != nil {
|
||||
if err := change.Write(builder); err != nil {
|
||||
return err
|
||||
}
|
||||
// if the change had changes, mark that we had changes for the next iteration
|
||||
if hasChanges {
|
||||
hadChanges = hasChanges
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -123,6 +137,21 @@ func (c Changes) String() string {
|
||||
return "database.Changes"
|
||||
}
|
||||
|
||||
// NoChange implements [NoChange].
|
||||
// It returns false if any of the changes writes to the builder.
|
||||
func (c Changes) NoChange() bool {
|
||||
for _, change := range c {
|
||||
no, ok := change.(NoChange)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if no.NoChange() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var _ Change = Changes(nil)
|
||||
|
||||
func NewChangeToNull(col Column) Change {
|
||||
@@ -319,7 +348,7 @@ func (c *cteChange) Matches(x any) bool {
|
||||
return slices.Equal(expectedCTEBuilder.Args(), actualCTEBuilder.Args())
|
||||
}
|
||||
|
||||
// Name implements [CTEChange].
|
||||
// SetName implements [CTEChange].
|
||||
func (c *cteChange) SetName(name string) {
|
||||
c.name = name
|
||||
}
|
||||
@@ -342,3 +371,9 @@ func (c *cteChange) Write(builder *StatementBuilder) error {
|
||||
func (c *cteChange) WriteCTE(builder *StatementBuilder) {
|
||||
c.cte(builder)
|
||||
}
|
||||
|
||||
// NoChange implements [NoChange].
|
||||
// It returns true if there is no change function defined.
|
||||
func (c *cteChange) NoChange() bool {
|
||||
return c.change == nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
DROP TYPE zitadel.project_grant_state;
|
||||
DROP TYPE IF EXISTS zitadel.project_grant_state CASCADE ;
|
||||
DROP TABLE IF EXISTS zitadel.project_grant_roles;
|
||||
DROP TABLE IF EXISTS zitadel.project_grants;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 011_session_table/up.sql
|
||||
up011SessionTable string
|
||||
//go:embed 011_session_table/down.sql
|
||||
down011SessionTable string
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerSQLMigration(11, up011SessionTable, down011SessionTable)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
DROP TABLE IF EXISTS zitadel.session_metadata CASCADE;
|
||||
DROP TABLE IF EXISTS zitadel.session_user_agents CASCADE;
|
||||
DROP TYPE IF EXISTS zitadel.session_factor_type CASCADE;
|
||||
DROP TABLE IF EXISTS zitadel.session_factors CASCADE;
|
||||
DROP TABLE IF EXISTS zitadel.sessions CASCADE;
|
||||
DROP FUNCTION IF EXISTS zitadel.update_expiration();
|
||||
@@ -0,0 +1,93 @@
|
||||
CREATE TABLE zitadel.session_user_agents (
|
||||
instance_id TEXT NOT NULL
|
||||
, fingerprint_id TEXT NOT NULL CHECK (fingerprint_id <> '')
|
||||
, ip INET
|
||||
, description TEXT
|
||||
, headers JSONB
|
||||
|
||||
, PRIMARY KEY (instance_id, fingerprint_id)
|
||||
);
|
||||
|
||||
CREATE TABLE zitadel.sessions (
|
||||
instance_id TEXT NOT NULL
|
||||
, id TEXT NOT NULL CHECK (id <> '')
|
||||
, token_id TEXT
|
||||
, user_agent_id TEXT
|
||||
, lifetime INTERVAL
|
||||
, expiration TIMESTAMPTZ
|
||||
, user_id TEXT -- this column is used for referential integrity
|
||||
, creator_id TEXT
|
||||
, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
||||
, updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
||||
|
||||
, PRIMARY KEY (instance_id, id)
|
||||
, FOREIGN KEY (instance_id) REFERENCES zitadel.instances(id)
|
||||
-- , FOREIGN KEY (instance_id, user_id) REFERENCES zitadel.users(instance_id, id) ON DELETE CASCADE
|
||||
, FOREIGN KEY (instance_id, user_agent_id) REFERENCES zitadel.session_user_agents(instance_id, fingerprint_id) ON DELETE SET NULL (user_agent_id)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION zitadel.update_expiration()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.expiration := NEW.updated_at + NEW.lifetime;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER set_expiration
|
||||
BEFORE INSERT OR UPDATE OF lifetime ON zitadel.sessions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION zitadel.update_expiration();
|
||||
|
||||
CREATE TYPE zitadel.session_factor_type AS ENUM (
|
||||
'user',
|
||||
'password',
|
||||
'passkey', -- is also a challenge
|
||||
'identity_provider_intent',
|
||||
'totp',
|
||||
'otp_sms', -- is also a challenge
|
||||
'otp_email', -- is also a challenge
|
||||
'recovery_code'
|
||||
);
|
||||
|
||||
CREATE TABLE zitadel.session_factors (
|
||||
instance_id TEXT NOT NULL
|
||||
, session_id TEXT NOT NULL
|
||||
, type zitadel.session_factor_type NOT NULL
|
||||
, last_challenged_at TIMESTAMPTZ -- this is only set if the type is a challenge
|
||||
, challenged_payload JSONB
|
||||
, last_verified_at TIMESTAMPTZ
|
||||
, verified_payload JSONB
|
||||
|
||||
, PRIMARY KEY (instance_id, session_id, type)
|
||||
, FOREIGN KEY (instance_id, session_id) REFERENCES zitadel.sessions(instance_id, id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE zitadel.session_metadata (
|
||||
instance_id TEXT NOT NULL
|
||||
, session_id TEXT NOT NULL
|
||||
, key TEXT NOT NULL CHECK (key <> '')
|
||||
, value BYTEA NOT NULL
|
||||
|
||||
, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
|
||||
, PRIMARY KEY (instance_id, session_id, key)
|
||||
|
||||
, CONSTRAINT fk_session_metadata_session FOREIGN KEY (instance_id, session_id) REFERENCES zitadel.sessions (instance_id, id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_session_metadata_key ON zitadel.session_metadata (key);
|
||||
CREATE INDEX idx_session_metadata_value ON zitadel.session_metadata (sha256(value));
|
||||
|
||||
-- TODO(adlerhurst): these indexes can currently not be used by Postgres, because of the type conversion
|
||||
-- the value can be a json but doesn't have to be.
|
||||
-- CREATE INDEX idx_session_metadata_value_number ON zitadel.session_metadata ((value::NUMERIC)) WHERE jsonb_typeof(value) = 'number';
|
||||
-- CREATE INDEX idx_session_metadata_value_string ON zitadel.session_metadata ((value#>>'{}')) WHERE jsonb_typeof(value) = 'string';
|
||||
-- CREATE INDEX idx_session_metadata_value_boolean ON zitadel.session_metadata ((value::BOOLEAN)) WHERE jsonb_typeof(value) = 'boolean';
|
||||
|
||||
CREATE TRIGGER trg_set_updated_at_session_metadata
|
||||
BEFORE INSERT OR UPDATE ON zitadel.session_metadata
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.updated_at IS NULL)
|
||||
EXECUTE FUNCTION zitadel.set_updated_at();
|
||||
@@ -22,8 +22,9 @@ import (
|
||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
v2beta_project "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
@@ -51,9 +52,10 @@ var (
|
||||
SystemClient system.SystemServiceClient
|
||||
OrgClient v2beta_org.OrganizationServiceClient
|
||||
ProjectClient v2beta_project.ProjectServiceClient
|
||||
SessionClient session.SessionServiceClient
|
||||
UserClient user.UserServiceClient
|
||||
AdminClient admin.AdminServiceClient
|
||||
MgmtClient mgmt.ManagementServiceClient
|
||||
UserClient user_v2.UserServiceClient
|
||||
AuthorizationClient authorization.AuthorizationServiceClient
|
||||
)
|
||||
|
||||
@@ -71,9 +73,10 @@ func TestMain(m *testing.M) {
|
||||
SystemClient = integration.SystemClient()
|
||||
OrgClient = Instance.Client.OrgV2beta
|
||||
ProjectClient = Instance.Client.Projectv2Beta
|
||||
SessionClient = Instance.Client.SessionV2
|
||||
UserClient = Instance.Client.UserV2
|
||||
AdminClient = Instance.Client.Admin
|
||||
MgmtClient = Instance.Client.Mgmt
|
||||
UserClient = Instance.Client.UserV2
|
||||
AuthorizationClient = Instance.Client.AuthorizationV2
|
||||
|
||||
defer func() {
|
||||
|
||||
@@ -0,0 +1,626 @@
|
||||
//go:build integration
|
||||
|
||||
package events_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
zdomain "github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/internal/integration/sink"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_SessionReduces(t *testing.T) {
|
||||
instanceID := Instance.ID()
|
||||
sessionRepo := repository.SessionRepository()
|
||||
|
||||
email := integration.Email()
|
||||
testUser := Instance.CreateUserTypeHuman(CTX, email)
|
||||
_, err := UserClient.VerifyEmail(CTX, &user.VerifyEmailRequest{
|
||||
UserId: testUser.GetId(),
|
||||
VerificationCode: testUser.GetEmailCode(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = UserClient.SetPassword(CTX, &user.SetPasswordRequest{
|
||||
UserId: testUser.GetId(),
|
||||
NewPassword: &user.Password{
|
||||
Password: integration.UserPassword,
|
||||
},
|
||||
Verification: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
totpSecret := registerTOTP(CTX, t, testUser.GetId())
|
||||
Instance.RegisterUserPasskey(CTX, testUser.GetId())
|
||||
registerOTPEmail(CTX, t, testUser.GetId())
|
||||
_, err = UserClient.SetPhone(CTX, &user.SetPhoneRequest{
|
||||
UserId: testUser.GetId(),
|
||||
Phone: integration.Phone(),
|
||||
Verification: &user.SetPhoneRequest_IsVerified{
|
||||
IsVerified: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
registerOTPSMS(CTX, t, testUser.GetId())
|
||||
|
||||
idpID := Instance.AddGenericOAuthProvider(CTX, integration.IDPName()).GetId()
|
||||
idpUserID := integration.ID()
|
||||
_, err = UserClient.AddIDPLink(CTX, &user.AddIDPLinkRequest{
|
||||
UserId: testUser.GetId(),
|
||||
IdpLink: &user.IDPLink{
|
||||
IdpId: idpID,
|
||||
UserId: idpUserID,
|
||||
UserName: integration.Username(),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
recoveryCodes := createRecoveryCodes(CTX, t, testUser.GetId())
|
||||
|
||||
lifetime := time.Hour
|
||||
creatorID := "tester"
|
||||
userAgent := &domain.SessionUserAgent{
|
||||
FingerprintID: gu.Ptr(integration.ID()),
|
||||
Description: gu.Ptr("description"),
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Header: http.Header{
|
||||
"User-Agent": []string{"ZITADEL-Integration-Test"},
|
||||
},
|
||||
}
|
||||
createdSession, err := SessionClient.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: nil,
|
||||
Metadata: nil,
|
||||
Challenges: nil,
|
||||
UserAgent: &session.UserAgent{
|
||||
FingerprintId: userAgent.FingerprintID,
|
||||
Ip: gu.Ptr(userAgent.IP.String()),
|
||||
Description: userAgent.Description,
|
||||
Header: map[string]*session.UserAgent_HeaderValues{
|
||||
"User-Agent": {Values: []string{"ZITADEL-Integration-Test"}},
|
||||
},
|
||||
},
|
||||
Lifetime: durationpb.New(lifetime),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 10*time.Second)
|
||||
var updatedSession *session.SetSessionResponse
|
||||
|
||||
t.Run("create session reduces", func(t *testing.T) {
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbSession, err := sessionRepo.Get(CTX, pool, database.WithCondition(
|
||||
sessionRepo.PrimaryKeyCondition(instanceID, createdSession.GetSessionId()),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assertSessionsEqual(collect, &domain.Session{
|
||||
InstanceID: instanceID,
|
||||
ID: createdSession.GetSessionId(),
|
||||
TokenID: createdSession.GetSessionToken(),
|
||||
Lifetime: lifetime,
|
||||
Expiration: createdSession.GetDetails().GetChangeDate().AsTime().Add(lifetime),
|
||||
UserID: "",
|
||||
CreatorID: creatorID,
|
||||
CreatedAt: createdSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UpdatedAt: createdSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Factors: nil,
|
||||
Challenges: nil,
|
||||
Metadata: nil,
|
||||
UserAgent: userAgent,
|
||||
}, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
metadata := []*domain.SessionMetadata{
|
||||
{
|
||||
Metadata: domain.Metadata{
|
||||
InstanceID: instanceID,
|
||||
Key: "key1",
|
||||
Value: []byte("value1"),
|
||||
},
|
||||
SessionID: createdSession.GetSessionId(),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("metadata set reduces", func(t *testing.T) {
|
||||
updatedSession, err = SessionClient.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createdSession.GetSessionId(),
|
||||
Checks: nil,
|
||||
Metadata: map[string][]byte{"key1": []byte("value1")},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbSession, err := sessionRepo.Get(CTX, pool, database.WithCondition(
|
||||
sessionRepo.PrimaryKeyCondition(instanceID, createdSession.GetSessionId()),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assertSessionsEqual(collect, &domain.Session{
|
||||
InstanceID: instanceID,
|
||||
ID: createdSession.GetSessionId(),
|
||||
TokenID: createdSession.GetSessionToken(),
|
||||
Lifetime: lifetime,
|
||||
Expiration: createdSession.GetDetails().GetChangeDate().AsTime().Add(lifetime),
|
||||
UserID: "",
|
||||
CreatorID: creatorID,
|
||||
CreatedAt: createdSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UpdatedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Factors: nil,
|
||||
Challenges: nil,
|
||||
Metadata: metadata,
|
||||
UserAgent: userAgent,
|
||||
}, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
var lifetimeUpdated time.Time
|
||||
|
||||
t.Run("lifetime set reduces", func(t *testing.T) {
|
||||
updatedSession, err = SessionClient.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createdSession.GetSessionId(),
|
||||
Lifetime: durationpb.New(lifetime),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
lifetimeUpdated = updatedSession.GetDetails().GetChangeDate().AsTime()
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbSession, err := sessionRepo.Get(CTX, pool, database.WithCondition(
|
||||
sessionRepo.PrimaryKeyCondition(instanceID, createdSession.GetSessionId()),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assertSessionsEqual(collect, &domain.Session{
|
||||
InstanceID: instanceID,
|
||||
ID: createdSession.GetSessionId(),
|
||||
TokenID: createdSession.GetSessionToken(),
|
||||
Lifetime: lifetime,
|
||||
Expiration: lifetimeUpdated.Add(lifetime),
|
||||
UserID: "",
|
||||
CreatorID: creatorID,
|
||||
CreatedAt: createdSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UpdatedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Factors: nil,
|
||||
Challenges: nil,
|
||||
Metadata: metadata,
|
||||
UserAgent: userAgent,
|
||||
}, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
var firstFactorCheckTime time.Time
|
||||
|
||||
t.Run("checks set reduces", func(t *testing.T) {
|
||||
_, err := url.Parse(Instance.CreateIntent(CTX, idpID).GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
expiry := time.Now().Add(1 * time.Hour)
|
||||
intentID, intentToken, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, idpUserID, testUser.GetId(), expiry)
|
||||
require.NoError(t, err)
|
||||
updatedSession, err = SessionClient.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createdSession.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_LoginName{
|
||||
LoginName: email,
|
||||
},
|
||||
},
|
||||
Password: &session.CheckPassword{
|
||||
Password: integration.UserPassword,
|
||||
},
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: intentToken,
|
||||
},
|
||||
Totp: &session.CheckTOTP{
|
||||
Code: func() string {
|
||||
code, err := totp.GenerateCode(totpSecret, time.Now())
|
||||
require.NoError(t, err)
|
||||
return code
|
||||
}(),
|
||||
},
|
||||
RecoveryCode: &session.CheckRecoveryCode{
|
||||
Code: recoveryCodes[0],
|
||||
},
|
||||
},
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
Domain: Instance.Domain,
|
||||
UserVerificationRequirement: session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
},
|
||||
OtpEmail: &session.RequestChallenges_OTPEmail{
|
||||
DeliveryType: &session.RequestChallenges_OTPEmail_ReturnCode_{
|
||||
ReturnCode: &session.RequestChallenges_OTPEmail_ReturnCode{},
|
||||
},
|
||||
},
|
||||
OtpSms: &session.RequestChallenges_OTPSMS{
|
||||
ReturnCode: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
firstFactorCheckTime = updatedSession.GetDetails().GetChangeDate().AsTime()
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbSession, err := sessionRepo.Get(CTX, pool, database.WithCondition(
|
||||
sessionRepo.PrimaryKeyCondition(instanceID, createdSession.GetSessionId()),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assertSessionsEqual(collect, &domain.Session{
|
||||
InstanceID: instanceID,
|
||||
ID: createdSession.GetSessionId(),
|
||||
TokenID: createdSession.GetSessionToken(),
|
||||
Lifetime: lifetime,
|
||||
Expiration: lifetimeUpdated.Add(lifetime),
|
||||
UserID: testUser.GetId(),
|
||||
CreatorID: creatorID,
|
||||
CreatedAt: createdSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UpdatedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Factors: domain.SessionFactors{
|
||||
&domain.SessionFactorUser{
|
||||
UserID: testUser.GetId(),
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorPassword{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorIdentityProviderIntent{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorTOTP{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorRecoveryCode{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
},
|
||||
Challenges: domain.SessionChallenges{
|
||||
&domain.SessionChallengePasskey{
|
||||
LastChallengedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Challenge: "challenge", // placeholder
|
||||
AllowedCredentialIDs: [][]byte{Instance.WebAuthN.KeyID()},
|
||||
UserVerification: zdomain.UserVerificationRequirementRequired,
|
||||
RPID: Instance.Domain,
|
||||
},
|
||||
&domain.SessionChallengeOTPEmail{
|
||||
LastChallengedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Code: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "",
|
||||
KeyID: "",
|
||||
Crypted: nil,
|
||||
},
|
||||
Expiry: 0,
|
||||
CodeReturned: true,
|
||||
URLTemplate: "",
|
||||
TriggeredAtOrigin: "",
|
||||
},
|
||||
&domain.SessionChallengeOTPSMS{
|
||||
LastChallengedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Code: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "",
|
||||
KeyID: "",
|
||||
Crypted: nil,
|
||||
},
|
||||
Expiry: 0,
|
||||
CodeReturned: true,
|
||||
GeneratorID: "",
|
||||
TriggeredAtOrigin: "",
|
||||
},
|
||||
},
|
||||
Metadata: metadata,
|
||||
UserAgent: userAgent,
|
||||
}, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
t.Run("checks with challenges set reduces", func(t *testing.T) {
|
||||
assertionData, err := Instance.WebAuthN.CreateAssertionResponse(updatedSession.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedSession, err = SessionClient.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createdSession.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
WebAuthN: &session.CheckWebAuthN{
|
||||
CredentialAssertionData: assertionData,
|
||||
},
|
||||
OtpEmail: &session.CheckOTP{
|
||||
Code: updatedSession.GetChallenges().GetOtpEmail(),
|
||||
},
|
||||
OtpSms: &session.CheckOTP{
|
||||
Code: updatedSession.GetChallenges().GetOtpSms(),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbSession, err := sessionRepo.Get(CTX, pool, database.WithCondition(
|
||||
sessionRepo.PrimaryKeyCondition(instanceID, createdSession.GetSessionId()),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assertSessionsEqual(collect, &domain.Session{
|
||||
InstanceID: instanceID,
|
||||
ID: createdSession.GetSessionId(),
|
||||
TokenID: createdSession.GetSessionToken(),
|
||||
Lifetime: lifetime,
|
||||
Expiration: lifetimeUpdated.Add(lifetime),
|
||||
UserID: testUser.GetId(),
|
||||
CreatorID: creatorID,
|
||||
CreatedAt: createdSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UpdatedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
Factors: domain.SessionFactors{
|
||||
&domain.SessionFactorUser{
|
||||
UserID: testUser.GetId(),
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorPassword{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorIdentityProviderIntent{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorPasskey{
|
||||
LastVerifiedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UserVerified: true,
|
||||
},
|
||||
&domain.SessionFactorTOTP{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorOTPEmail{
|
||||
LastVerifiedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
},
|
||||
&domain.SessionFactorOTPSMS{
|
||||
LastVerifiedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
},
|
||||
&domain.SessionFactorRecoveryCode{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
},
|
||||
Challenges: domain.SessionChallenges{},
|
||||
Metadata: metadata,
|
||||
UserAgent: userAgent,
|
||||
}, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
t.Run("password changed reduces", func(t *testing.T) {
|
||||
passwordSet, err := UserClient.SetPassword(CTX, &user.SetPasswordRequest{
|
||||
UserId: testUser.GetId(),
|
||||
NewPassword: &user.Password{
|
||||
Password: integration.UserPassword + integration.RandString(5),
|
||||
},
|
||||
Verification: &user.SetPasswordRequest_CurrentPassword{
|
||||
CurrentPassword: integration.UserPassword,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbSession, err := sessionRepo.Get(CTX, pool, database.WithCondition(
|
||||
sessionRepo.PrimaryKeyCondition(instanceID, createdSession.GetSessionId()),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assertSessionsEqual(collect, &domain.Session{
|
||||
InstanceID: instanceID,
|
||||
ID: createdSession.GetSessionId(),
|
||||
TokenID: createdSession.GetSessionToken(),
|
||||
Lifetime: lifetime,
|
||||
Expiration: lifetimeUpdated.Add(lifetime),
|
||||
UserID: testUser.GetId(),
|
||||
CreatorID: creatorID,
|
||||
CreatedAt: createdSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UpdatedAt: passwordSet.GetDetails().GetChangeDate().AsTime(),
|
||||
Factors: domain.SessionFactors{
|
||||
&domain.SessionFactorUser{
|
||||
UserID: testUser.GetId(),
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
// password factor removed
|
||||
&domain.SessionFactorIdentityProviderIntent{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorPasskey{
|
||||
LastVerifiedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
UserVerified: true,
|
||||
},
|
||||
&domain.SessionFactorTOTP{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
&domain.SessionFactorOTPEmail{
|
||||
LastVerifiedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
},
|
||||
&domain.SessionFactorOTPSMS{
|
||||
LastVerifiedAt: updatedSession.GetDetails().GetChangeDate().AsTime(),
|
||||
},
|
||||
&domain.SessionFactorRecoveryCode{
|
||||
LastVerifiedAt: firstFactorCheckTime,
|
||||
},
|
||||
},
|
||||
Challenges: domain.SessionChallenges{},
|
||||
Metadata: metadata,
|
||||
UserAgent: userAgent,
|
||||
}, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
t.Run("terminated reduces", func(t *testing.T) {
|
||||
_, err := SessionClient.DeleteSession(CTX, &session.DeleteSessionRequest{
|
||||
SessionId: createdSession.GetSessionId(),
|
||||
SessionToken: gu.Ptr(updatedSession.GetSessionToken()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbSession, err := sessionRepo.Get(CTX, pool, database.WithCondition(
|
||||
sessionRepo.PrimaryKeyCondition(instanceID, createdSession.GetSessionId()),
|
||||
))
|
||||
require.ErrorIs(collect, err, new(database.NoRowFoundError))
|
||||
assert.Nil(collect, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
}
|
||||
|
||||
func assertSessionsEqual(t *assert.CollectT, expected, actual *domain.Session) {
|
||||
t.Helper()
|
||||
assert.Equal(t, expected.InstanceID, actual.InstanceID)
|
||||
assert.Equal(t, expected.ID, actual.ID)
|
||||
assert.NotNil(t, actual.TokenID)
|
||||
assert.Equal(t, expected.Lifetime, actual.Lifetime)
|
||||
assert.Equal(t, expected.Expiration.UTC(), actual.Expiration.UTC())
|
||||
assert.Equal(t, expected.UserID, actual.UserID)
|
||||
assert.Equal(t, expected.CreatorID, actual.CreatorID)
|
||||
assert.NotNil(t, actual.CreatedAt)
|
||||
assert.NotNil(t, actual.UpdatedAt)
|
||||
assertSessionFactorsEqual(t, expected.Factors, actual.Factors)
|
||||
assertSessionChallengesEqual(t, expected.Challenges, actual.Challenges)
|
||||
assertSessionMetadataEqual(t, expected.Metadata, actual.Metadata)
|
||||
assert.Equal(t, expected.UserAgent, actual.UserAgent)
|
||||
}
|
||||
|
||||
func assertSessionMetadataEqual(t *assert.CollectT, expected, actual []*domain.SessionMetadata) {
|
||||
t.Helper()
|
||||
assert.Len(t, actual, len(expected))
|
||||
for _, exp := range expected {
|
||||
found := false
|
||||
for _, act := range actual {
|
||||
if exp.InstanceID == act.InstanceID &&
|
||||
exp.SessionID == act.SessionID &&
|
||||
exp.Key == act.Key &&
|
||||
bytes.Equal(exp.Value, act.Value) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Truef(t, found, "expected metadata not found: %+v", exp)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSessionFactorsEqual(t *assert.CollectT, expected, actual domain.SessionFactors) {
|
||||
t.Helper()
|
||||
assert.Len(t, actual, len(expected))
|
||||
for _, exp := range expected {
|
||||
found := false
|
||||
for _, act := range actual {
|
||||
if exp.SessionFactorType() == act.SessionFactorType() {
|
||||
switch expTyped := exp.(type) {
|
||||
case *domain.SessionFactorUser:
|
||||
actTyped := act.(*domain.SessionFactorUser)
|
||||
assert.Equal(t, expTyped.UserID, actTyped.UserID)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
case *domain.SessionFactorPassword:
|
||||
actTyped := act.(*domain.SessionFactorPassword)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
case *domain.SessionFactorIdentityProviderIntent:
|
||||
actTyped := act.(*domain.SessionFactorIdentityProviderIntent)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
case *domain.SessionFactorPasskey:
|
||||
actTyped := act.(*domain.SessionFactorPasskey)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
assert.Equal(t, expTyped.UserVerified, actTyped.UserVerified)
|
||||
case *domain.SessionFactorTOTP:
|
||||
actTyped := act.(*domain.SessionFactorTOTP)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
case *domain.SessionFactorOTPEmail:
|
||||
actTyped := act.(*domain.SessionFactorOTPEmail)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
case *domain.SessionFactorOTPSMS:
|
||||
actTyped := act.(*domain.SessionFactorOTPSMS)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
case *domain.SessionFactorRecoveryCode:
|
||||
actTyped := act.(*domain.SessionFactorRecoveryCode)
|
||||
assert.Equal(t, expTyped.LastVerifiedAt.UTC(), actTyped.LastVerifiedAt.UTC())
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Truef(t, found, "expected factor not found: %+v", exp)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSessionChallengesEqual(t *assert.CollectT, expected, actual domain.SessionChallenges) {
|
||||
t.Helper()
|
||||
assert.Len(t, actual, len(expected))
|
||||
for _, exp := range expected {
|
||||
found := false
|
||||
for _, act := range actual {
|
||||
if exp.SessionChallengeType() == act.SessionChallengeType() {
|
||||
switch expTyped := exp.(type) {
|
||||
case *domain.SessionChallengePasskey:
|
||||
actTyped := act.(*domain.SessionChallengePasskey)
|
||||
assert.Equal(t, expTyped.LastChallengedAt.UTC(), actTyped.LastChallengedAt.UTC())
|
||||
assert.NotEmpty(t, actTyped.Challenge)
|
||||
assert.Equal(t, expTyped.AllowedCredentialIDs, actTyped.AllowedCredentialIDs)
|
||||
assert.Equal(t, expTyped.RPID, actTyped.RPID)
|
||||
assert.Equal(t, expTyped.UserVerification, actTyped.UserVerification)
|
||||
case *domain.SessionChallengeOTPEmail:
|
||||
actTyped := act.(*domain.SessionChallengeOTPEmail)
|
||||
assert.Equal(t, expTyped.LastChallengedAt.UTC(), actTyped.LastChallengedAt.UTC())
|
||||
assert.Equal(t, expTyped.Code.CryptoType, actTyped.Code.CryptoType)
|
||||
case *domain.SessionChallengeOTPSMS:
|
||||
actTyped := act.(*domain.SessionChallengeOTPSMS)
|
||||
assert.Equal(t, expTyped.LastChallengedAt.UTC(), actTyped.LastChallengedAt.UTC())
|
||||
assert.Equal(t, expTyped.Code.CryptoType, actTyped.Code.CryptoType)
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Truef(t, found, "expected challenge not found: %+v", exp)
|
||||
}
|
||||
}
|
||||
|
||||
func registerTOTP(ctx context.Context, t *testing.T, userID string) (secret string) {
|
||||
resp, err := UserClient.RegisterTOTP(ctx, &user.RegisterTOTPRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
secret = resp.GetSecret()
|
||||
code, err := totp.GenerateCode(secret, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = UserClient.VerifyTOTPRegistration(ctx, &user.VerifyTOTPRegistrationRequest{
|
||||
UserId: userID,
|
||||
Code: code,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return secret
|
||||
}
|
||||
|
||||
func registerOTPSMS(ctx context.Context, t *testing.T, userID string) {
|
||||
_, err := UserClient.AddOTPSMS(ctx, &user.AddOTPSMSRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func registerOTPEmail(ctx context.Context, t *testing.T, userID string) {
|
||||
_, err := UserClient.AddOTPEmail(ctx, &user.AddOTPEmailRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func createRecoveryCodes(ctx context.Context, t *testing.T, userID string) (secret []string) {
|
||||
resp, err := UserClient.GenerateRecoveryCodes(ctx, &user.GenerateRecoveryCodesRequest{
|
||||
UserId: userID,
|
||||
Count: 5,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return resp.GetRecoveryCodes()
|
||||
}
|
||||
@@ -60,9 +60,9 @@ const (
|
||||
|
||||
// TextOperationContainsIgnoreCase checks if the first string contains the second ignoring case
|
||||
TextOperationContainsIgnoreCase // LIKE
|
||||
// TextOperationEndsWith checks if the first string ends with the second ignoring case
|
||||
// TextOperationEndsWithIgnoreCase checks if the first string ends with the second ignoring case
|
||||
TextOperationEndsWithIgnoreCase // LIKE
|
||||
// TextOperationStartsWith checks if the first string starts with the second ignoring case
|
||||
// TextOperationStartsWithIgnoreCase checks if the first string starts with the second ignoring case
|
||||
TextOperationStartsWithIgnoreCase // LIKE
|
||||
// TextOperationEqualIgnoreCase compares two strings for equality ignoring case
|
||||
TextOperationEqualIgnoreCase // =
|
||||
@@ -133,11 +133,11 @@ const (
|
||||
// NumberOperationLessThan compares two numbers to check if the first is less than the second.
|
||||
NumberOperationLessThan // <
|
||||
// NumberOperationLessThanOrEqual compares two numbers to check if the first is less than or equal to the second.
|
||||
NumberOperationAtLeast // <=
|
||||
NumberOperationLessThanOrEqual // <=
|
||||
// NumberOperationGreaterThan compares two numbers to check if the first is greater than the second.
|
||||
NumberOperationGreaterThan // >
|
||||
// NumberOperationGreaterThanOrEqual compares two numbers to check if the first is greater than or equal to the second.
|
||||
NumberOperationAtMost // >=
|
||||
NumberOperationGreaterThanOrEqual // >=
|
||||
)
|
||||
|
||||
func writeNumberOperation[T Number](builder *StatementBuilder, col Column, op NumberOperation, value any) {
|
||||
|
||||
@@ -28,12 +28,12 @@ func _NumberOperationNoOp() {
|
||||
_ = x[NumberOperationEqual-(1)]
|
||||
_ = x[NumberOperationNotEqual-(2)]
|
||||
_ = x[NumberOperationLessThan-(3)]
|
||||
_ = x[NumberOperationAtLeast-(4)]
|
||||
_ = x[NumberOperationLessThanOrEqual-(4)]
|
||||
_ = x[NumberOperationGreaterThan-(5)]
|
||||
_ = x[NumberOperationAtMost-(6)]
|
||||
_ = x[NumberOperationGreaterThanOrEqual-(6)]
|
||||
}
|
||||
|
||||
var _NumberOperationValues = []NumberOperation{NumberOperationEqual, NumberOperationNotEqual, NumberOperationLessThan, NumberOperationAtLeast, NumberOperationGreaterThan, NumberOperationAtMost}
|
||||
var _NumberOperationValues = []NumberOperation{NumberOperationEqual, NumberOperationNotEqual, NumberOperationLessThan, NumberOperationLessThanOrEqual, NumberOperationGreaterThan, NumberOperationGreaterThanOrEqual}
|
||||
|
||||
var _NumberOperationNameToValueMap = map[string]NumberOperation{
|
||||
_NumberOperationName[0:1]: NumberOperationEqual,
|
||||
@@ -42,12 +42,12 @@ var _NumberOperationNameToValueMap = map[string]NumberOperation{
|
||||
_NumberOperationLowerName[1:3]: NumberOperationNotEqual,
|
||||
_NumberOperationName[3:4]: NumberOperationLessThan,
|
||||
_NumberOperationLowerName[3:4]: NumberOperationLessThan,
|
||||
_NumberOperationName[4:6]: NumberOperationAtLeast,
|
||||
_NumberOperationLowerName[4:6]: NumberOperationAtLeast,
|
||||
_NumberOperationName[4:6]: NumberOperationLessThanOrEqual,
|
||||
_NumberOperationLowerName[4:6]: NumberOperationLessThanOrEqual,
|
||||
_NumberOperationName[6:7]: NumberOperationGreaterThan,
|
||||
_NumberOperationLowerName[6:7]: NumberOperationGreaterThan,
|
||||
_NumberOperationName[7:9]: NumberOperationAtMost,
|
||||
_NumberOperationLowerName[7:9]: NumberOperationAtMost,
|
||||
_NumberOperationName[7:9]: NumberOperationGreaterThanOrEqual,
|
||||
_NumberOperationLowerName[7:9]: NumberOperationGreaterThanOrEqual,
|
||||
}
|
||||
|
||||
var _NumberOperationNames = []string{
|
||||
|
||||
@@ -181,7 +181,7 @@ func Test_writeOperation(t *testing.T) {
|
||||
{
|
||||
name: "number less than or equal",
|
||||
write: func(builder *StatementBuilder) {
|
||||
writeNumberOperation[int](builder, NewColumn("table", "column"), NumberOperationAtLeast, 123)
|
||||
writeNumberOperation[int](builder, NewColumn("table", "column"), NumberOperationLessThanOrEqual, 123)
|
||||
},
|
||||
want: want{
|
||||
stmt: "table.column <= $1",
|
||||
@@ -201,7 +201,7 @@ func Test_writeOperation(t *testing.T) {
|
||||
{
|
||||
name: "number greater than or equal",
|
||||
write: func(builder *StatementBuilder) {
|
||||
writeNumberOperation[int](builder, NewColumn("table", "column"), NumberOperationAtMost, 123)
|
||||
writeNumberOperation[int](builder, NewColumn("table", "column"), NumberOperationGreaterThanOrEqual, 123)
|
||||
},
|
||||
want: want{
|
||||
stmt: "table.column >= $1",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type JSON[T any] struct {
|
||||
Value T
|
||||
}
|
||||
|
||||
func (j *JSON[T]) Scan(src any) (err error) {
|
||||
var rawJSON []byte
|
||||
switch s := src.(type) {
|
||||
case string:
|
||||
rawJSON = []byte(s)
|
||||
case []byte:
|
||||
rawJSON = s
|
||||
case nil:
|
||||
return nil
|
||||
default:
|
||||
return ErrScanSource
|
||||
}
|
||||
if err = j.UnmarshalJSON(rawJSON); err != nil {
|
||||
return database.NewScanError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON allows encoding/json to decode nested JSON into JSON[T] fields.
|
||||
// This makes [json.Unmarshal] work for fields inside aggregated JSON (e.g. JSON arrays).
|
||||
func (j *JSON[T]) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 || string(data) == "null" {
|
||||
return nil
|
||||
}
|
||||
var v T
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
j.Value = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped value or null if nil.
|
||||
func (j JSON[T]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(j.Value)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
)
|
||||
|
||||
func TestJSON_Scan(t *testing.T) {
|
||||
type want struct {
|
||||
err error
|
||||
res *testObject
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
src any
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
src: nil,
|
||||
want: want{
|
||||
err: nil,
|
||||
res: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "[]byte with data",
|
||||
src: []byte(`{"id":"1","number":1,"active":true}`),
|
||||
want: want{
|
||||
err: nil,
|
||||
res: &testObject{ID: "1", Number: 1, Active: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "[]byte without data",
|
||||
src: []byte(`{}`),
|
||||
want: want{
|
||||
err: nil,
|
||||
res: &testObject{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil []byte",
|
||||
src: []byte(nil),
|
||||
want: want{
|
||||
err: nil,
|
||||
res: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string with data",
|
||||
src: `{"id":"2","number":2,"active":false}`,
|
||||
want: want{
|
||||
err: nil,
|
||||
res: &testObject{ID: "2", Number: 2, Active: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string without data",
|
||||
src: `{}`,
|
||||
want: want{
|
||||
err: nil,
|
||||
res: &testObject{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
src: ``,
|
||||
want: want{
|
||||
err: nil,
|
||||
res: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unsupported type",
|
||||
src: 12345,
|
||||
want: want{
|
||||
err: repository.ErrScanSource,
|
||||
res: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
src: []byte(`this is not json`),
|
||||
want: want{
|
||||
err: new(database.ScanError),
|
||||
res: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var a repository.JSON[testObject]
|
||||
gotErr := a.Scan(tt.src)
|
||||
require.ErrorIs(t, gotErr, tt.want.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// repository
|
||||
// -------------------------------------------------------------
|
||||
|
||||
var _ domain.SessionRepository = (*session)(nil)
|
||||
|
||||
type session struct {
|
||||
factorRepo sessionFactor
|
||||
metadataRepo sessionMetadata
|
||||
userAgentRepo sessionUserAgent
|
||||
}
|
||||
|
||||
func (s session) qualifiedTableName() string {
|
||||
return "zitadel.sessions"
|
||||
}
|
||||
|
||||
func (s session) unqualifiedTableName() string {
|
||||
return "sessions"
|
||||
}
|
||||
|
||||
func SessionRepository() domain.SessionRepository {
|
||||
return new(session)
|
||||
}
|
||||
|
||||
const querySessionStmt = `
|
||||
SELECT
|
||||
sessions.instance_id
|
||||
, sessions.id
|
||||
, sessions.token_id
|
||||
, sessions.lifetime
|
||||
, sessions.expiration
|
||||
, sessions.user_id
|
||||
, sessions.creator_id
|
||||
, sessions.created_at
|
||||
, sessions.updated_at
|
||||
, jsonb_agg(distinct
|
||||
jsonb_build_object(
|
||||
'instanceId', session_factors.instance_id
|
||||
, 'sessionId', session_factors.session_id
|
||||
, 'type', session_factors.type
|
||||
, 'lastChallengedAt', session_factors.last_challenged_at
|
||||
, 'challengedPayload', session_factors.challenged_payload
|
||||
, 'lastVerifiedAt', session_factors.last_verified_at
|
||||
, 'verifiedPayload', session_factors.verified_payload
|
||||
)
|
||||
)
|
||||
FILTER (WHERE session_factors.session_id IS NOT NULL) AS factors
|
||||
, jsonb_agg(distinct
|
||||
jsonb_build_object(
|
||||
'instanceId', session_metadata.instance_id
|
||||
, 'sessionId', session_metadata.session_id
|
||||
, 'key', session_metadata.key
|
||||
, 'value', encode(session_metadata.value, 'base64')
|
||||
, 'createdAt', session_metadata.created_at
|
||||
, 'updatedAt', session_metadata.updated_at
|
||||
)
|
||||
)
|
||||
FILTER (WHERE session_metadata.session_id IS NOT NULL) AS metadata
|
||||
, CASE WHEN session_user_agents.fingerprint_id IS NOT NULL THEN
|
||||
jsonb_build_object(
|
||||
'fingerprintId', session_user_agents.fingerprint_id
|
||||
, 'description', session_user_agents.description
|
||||
, 'ip', session_user_agents.ip
|
||||
, 'headers', session_user_agents.headers
|
||||
)
|
||||
END as user_agent
|
||||
FROM zitadel.sessions`
|
||||
|
||||
// Get implements [domain.SessionRepository].
|
||||
func (s session) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Session, error) {
|
||||
opts = append(opts,
|
||||
s.joinFactors(),
|
||||
s.joinMetadata(),
|
||||
s.joinUserAgent(),
|
||||
database.WithGroupBy(
|
||||
s.InstanceIDColumn(),
|
||||
s.IDColumn(),
|
||||
s.userAgentRepo.fingerprintIDColumn(),
|
||||
s.userAgentRepo.descriptionColumn(),
|
||||
s.userAgentRepo.ipColumn(),
|
||||
s.userAgentRepo.headersColumn(),
|
||||
),
|
||||
)
|
||||
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
if !options.Condition.IsRestrictingColumn(s.InstanceIDColumn()) {
|
||||
return nil, database.NewMissingConditionError(s.InstanceIDColumn())
|
||||
}
|
||||
|
||||
var builder database.StatementBuilder
|
||||
builder.WriteString(querySessionStmt)
|
||||
options.Write(&builder)
|
||||
|
||||
return scanSession(ctx, client, &builder)
|
||||
}
|
||||
|
||||
// List implements [domain.SessionRepository].
|
||||
func (s session) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Session, error) {
|
||||
opts = append(opts,
|
||||
s.joinFactors(),
|
||||
s.joinMetadata(),
|
||||
s.joinUserAgent(),
|
||||
database.WithGroupBy(
|
||||
s.InstanceIDColumn(),
|
||||
s.IDColumn(),
|
||||
s.userAgentRepo.fingerprintIDColumn(),
|
||||
s.userAgentRepo.descriptionColumn(),
|
||||
s.userAgentRepo.ipColumn(),
|
||||
s.userAgentRepo.headersColumn(),
|
||||
),
|
||||
)
|
||||
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
if options.Condition == nil || !options.Condition.IsRestrictingColumn(s.InstanceIDColumn()) {
|
||||
return nil, database.NewMissingConditionError(s.InstanceIDColumn())
|
||||
}
|
||||
|
||||
var builder database.StatementBuilder
|
||||
builder.WriteString(querySessionStmt)
|
||||
options.Write(&builder)
|
||||
|
||||
return scanSessions(ctx, client, &builder)
|
||||
}
|
||||
|
||||
const upsertSessionUserAgentStmt = `
|
||||
WITH user_agent AS (
|
||||
INSERT INTO zitadel.session_user_agents(
|
||||
instance_id, fingerprint_id, description, ip, headers
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (instance_id, fingerprint_id)
|
||||
DO UPDATE SET description = EXCLUDED.description, ip = EXCLUDED.ip, headers = excluded.headers
|
||||
) `
|
||||
|
||||
// Create implements [domain.SessionRepository].
|
||||
func (s session) Create(ctx context.Context, client database.QueryExecutor, session *domain.Session) error {
|
||||
var createdAt any = database.DefaultInstruction
|
||||
if !session.CreatedAt.IsZero() {
|
||||
createdAt = session.CreatedAt
|
||||
}
|
||||
var updatedAt = createdAt
|
||||
if !session.UpdatedAt.IsZero() {
|
||||
updatedAt = session.UpdatedAt
|
||||
}
|
||||
|
||||
builder := new(database.StatementBuilder)
|
||||
var fingerprintID *string
|
||||
if session.UserAgent != nil {
|
||||
builder = database.NewStatementBuilder(upsertSessionUserAgentStmt,
|
||||
session.InstanceID,
|
||||
session.UserAgent.FingerprintID,
|
||||
session.UserAgent.Description,
|
||||
session.UserAgent.IP,
|
||||
session.UserAgent.Header,
|
||||
)
|
||||
fingerprintID = session.UserAgent.FingerprintID
|
||||
}
|
||||
builder.WriteString(`INSERT INTO ` + s.qualifiedTableName() + ` (instance_id, id, lifetime, creator_id, user_agent_id, created_at, updated_at) VALUES ( `)
|
||||
builder.WriteArgs(session.InstanceID, session.ID, session.Lifetime, session.CreatorID, fingerprintID, createdAt, updatedAt)
|
||||
builder.WriteString(` ) RETURNING created_at, updated_at`)
|
||||
return client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&session.CreatedAt, &session.UpdatedAt)
|
||||
}
|
||||
|
||||
// Update implements [domain.SessionRepository].
|
||||
func (s session) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
|
||||
if len(changes) == 0 {
|
||||
return 0, database.ErrNoChanges
|
||||
}
|
||||
if !condition.IsRestrictingColumn(s.InstanceIDColumn()) {
|
||||
return 0, database.NewMissingConditionError(s.InstanceIDColumn())
|
||||
}
|
||||
if !database.Changes(changes).IsOnColumn(s.UpdatedAtColumn()) {
|
||||
changes = append(changes, database.NewChange(s.UpdatedAtColumn(), database.DefaultInstruction))
|
||||
}
|
||||
|
||||
var builder database.StatementBuilder
|
||||
builder.WriteString("WITH existing_session AS (SELECT * FROM zitadel.sessions ")
|
||||
writeCondition(&builder, condition)
|
||||
builder.WriteString(") ")
|
||||
for i, change := range changes {
|
||||
sessionCTE(change, i, 0, &builder)
|
||||
}
|
||||
builder.WriteString("UPDATE zitadel.sessions SET ")
|
||||
if err := database.Changes(changes).Write(&builder); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writeCondition(&builder, condition)
|
||||
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
}
|
||||
|
||||
func sessionCTE(change database.Change, i, j int, builder *database.StatementBuilder) {
|
||||
if multi, ok := change.(database.Changes); ok {
|
||||
for j, ch := range multi {
|
||||
sessionCTE(ch, i, j, builder)
|
||||
}
|
||||
}
|
||||
if cte, ok := change.(database.CTEChange); ok {
|
||||
name := fmt.Sprintf("cte_%d_%d", i, j)
|
||||
fmt.Fprintf(builder, ", %s as (", name)
|
||||
cte.SetName(name)
|
||||
cte.WriteCTE(builder)
|
||||
builder.WriteString(") ")
|
||||
}
|
||||
}
|
||||
|
||||
// Delete implements [domain.SessionRepository].
|
||||
func (s session) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
|
||||
if !condition.IsRestrictingColumn(s.InstanceIDColumn()) {
|
||||
return 0, database.NewMissingConditionError(s.InstanceIDColumn())
|
||||
}
|
||||
var builder database.StatementBuilder
|
||||
builder.WriteString("DELETE FROM zitadel.sessions")
|
||||
writeCondition(&builder, condition)
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// changes
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// SetUpdatedAt implements [domain.sessionChanges].
|
||||
func (s session) SetUpdatedAt(updatedAt time.Time) database.Change {
|
||||
return database.NewChange(s.UpdatedAtColumn(), updatedAt)
|
||||
}
|
||||
|
||||
// SetToken implements [domain.sessionChanges].
|
||||
func (s session) SetToken(token string) database.Change {
|
||||
return database.NewChange(s.TokenIDColumn(), token)
|
||||
}
|
||||
|
||||
// SetLifetime implements [domain.sessionChanges].
|
||||
func (s session) SetLifetime(lifetime time.Duration) database.Change {
|
||||
return database.NewChange(s.LifetimeColumn(), lifetime)
|
||||
}
|
||||
|
||||
// SetChallenge implements [domain.sessionChanges].
|
||||
func (s session) SetChallenge(challenge domain.SessionChallenge) database.Change {
|
||||
switch c := challenge.(type) {
|
||||
case *domain.SessionChallengePasskey:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_challenged_at, challenged_payload) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypePasskey, c.LastChallengedAt, c)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_challenged_at = EXCLUDED.last_challenged_at, challenged_payload = EXCLUDED.challenged_payload")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionChallengeOTPSMS:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_challenged_at, challenged_payload) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeOTPSMS, c.LastChallengedAt, c)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_challenged_at = EXCLUDED.last_challenged_at, challenged_payload = EXCLUDED.challenged_payload")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionChallengeOTPEmail:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_challenged_at, challenged_payload) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeOTPEmail, c.LastChallengedAt, c)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_challenged_at = EXCLUDED.last_challenged_at, challenged_payload = EXCLUDED.challenged_payload")
|
||||
}, nil,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFactor implements [domain.sessionChanges].
|
||||
func (s session) SetFactor(factor domain.SessionFactor) database.Change {
|
||||
switch f := factor.(type) {
|
||||
case *domain.SessionFactorUser:
|
||||
return database.NewChanges(
|
||||
database.NewChange(s.UserIDColumn(), f.UserID),
|
||||
database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at, verified_payload) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeUser, f.LastVerifiedAt, f)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at, verified_payload = EXCLUDED.verified_payload")
|
||||
}, nil,
|
||||
),
|
||||
)
|
||||
case *domain.SessionFactorPassword:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypePassword, f.LastVerifiedAt)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionFactorIdentityProviderIntent:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeIdentityProviderIntent, f.LastVerifiedAt)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionFactorPasskey:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at, verified_payload) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypePasskey, f.LastVerifiedAt, f)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at, verified_payload = EXCLUDED.verified_payload")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionFactorTOTP:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeTOTP, f.LastVerifiedAt)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionFactorOTPSMS:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeOTPSMS, f.LastVerifiedAt)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionFactorOTPEmail:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeOTPEmail, f.LastVerifiedAt)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at")
|
||||
}, nil,
|
||||
)
|
||||
case *domain.SessionFactorRecoveryCode:
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_factors (instance_id, session_id, type, last_verified_at) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(domain.SessionFactorTypeRecoveryCode, f.LastVerifiedAt)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, type) DO UPDATE SET last_verified_at = EXCLUDED.last_verified_at")
|
||||
}, nil,
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ClearFactor implements [domain.sessionChanges].
|
||||
func (s session) ClearFactor(factorType domain.SessionFactorType) database.Change {
|
||||
return database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("UPDATE zitadel.session_factors sf SET last_verified_at = NULL FROM existing_session es WHERE sf.instance_id = es.instance_id AND sf.session_id = es.id AND sf.type = ")
|
||||
builder.WriteArg(factorType)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// SetMetadata implements [domain.sessionChanges].
|
||||
func (s session) SetMetadata(metadata []*domain.SessionMetadata) database.Change {
|
||||
changes := make([]database.Change, len(metadata)+1)
|
||||
keys := make([]any, len(metadata))
|
||||
for i, md := range metadata {
|
||||
keys[i] = md.Key
|
||||
changes[i] = database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("INSERT INTO zitadel.session_metadata (instance_id, session_id, key, value) SELECT instance_id, id, ")
|
||||
builder.WriteArgs(md.Key, md.Value)
|
||||
builder.WriteString(" FROM existing_session ON CONFLICT (instance_id, session_id, key) DO UPDATE SET value = EXCLUDED.value")
|
||||
}, nil,
|
||||
)
|
||||
}
|
||||
changes[len(metadata)] = database.NewCTEChange(
|
||||
func(builder *database.StatementBuilder) {
|
||||
builder.WriteString("DELETE FROM zitadel.session_metadata WHERE instance_id = (SELECT instance_id FROM existing_session) AND session_id = (SELECT id from existing_session) AND key NOT IN (")
|
||||
builder.WriteArgs(keys...)
|
||||
builder.WriteString(")")
|
||||
}, nil)
|
||||
return database.NewChanges(changes...)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyCondition implements [domain.sessionConditions].
|
||||
func (s session) PrimaryKeyCondition(instanceID, sessionID string) database.Condition {
|
||||
return database.And(
|
||||
s.InstanceIDCondition(instanceID),
|
||||
s.IDCondition(sessionID),
|
||||
)
|
||||
}
|
||||
|
||||
// InstanceIDCondition implements [domain.sessionConditions].
|
||||
func (s session) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(s.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
// IDCondition implements [domain.sessionConditions].
|
||||
func (s session) IDCondition(sessionID string) database.Condition {
|
||||
return database.NewTextCondition(s.IDColumn(), database.TextOperationEqual, sessionID)
|
||||
}
|
||||
|
||||
// UserAgentIDCondition implements [domain.sessionConditions].
|
||||
func (s session) UserAgentIDCondition(userAgentID string) database.Condition {
|
||||
return database.NewTextCondition(s.userAgentIDColumn(), database.TextOperationEqual, userAgentID)
|
||||
}
|
||||
|
||||
// UserIDCondition implements [domain.sessionConditions].
|
||||
func (s session) UserIDCondition(userID string) database.Condition {
|
||||
return database.NewTextCondition(s.UserIDColumn(), database.TextOperationEqual, userID)
|
||||
}
|
||||
|
||||
// CreatorIDCondition implements [domain.sessionConditions].
|
||||
func (s session) CreatorIDCondition(creatorID string) database.Condition {
|
||||
return database.NewTextCondition(s.CreatorIDColumn(), database.TextOperationEqual, creatorID)
|
||||
}
|
||||
|
||||
// ExpirationCondition implements [domain.sessionConditions].
|
||||
func (s session) ExpirationCondition(op database.NumberOperation, expiration time.Time) database.Condition {
|
||||
return database.NewNumberCondition(s.ExpirationColumn(), op, expiration)
|
||||
}
|
||||
|
||||
// CreatedAtCondition implements [domain.sessionConditions].
|
||||
func (s session) CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(s.CreatedAtColumn(), op, createdAt)
|
||||
}
|
||||
|
||||
// UpdatedAtCondition implements [domain.sessionConditions].
|
||||
func (s session) UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(s.UpdatedAtColumn(), op, updatedAt)
|
||||
}
|
||||
|
||||
// ExistsFactor implements [domain.sessionConditions].
|
||||
func (s session) ExistsFactor(cond database.Condition) database.Condition {
|
||||
return database.Exists(
|
||||
s.factorRepo.qualifiedTableName(),
|
||||
database.And(
|
||||
database.NewColumnCondition(s.InstanceIDColumn(), s.factorRepo.instanceIDColumn()),
|
||||
database.NewColumnCondition(s.IDColumn(), s.factorRepo.sessionIDColumn()),
|
||||
cond,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// FactorConditions implements [domain.sessionConditions].
|
||||
func (s session) FactorConditions() domain.SessionFactorConditions {
|
||||
return s.factorRepo
|
||||
}
|
||||
|
||||
// ExistsMetadata implements [domain.sessionConditions].
|
||||
func (s session) ExistsMetadata(cond database.Condition) database.Condition {
|
||||
return database.Exists(
|
||||
s.metadataRepo.qualifiedTableName(),
|
||||
database.And(
|
||||
database.NewColumnCondition(s.InstanceIDColumn(), s.metadataRepo.instanceIDColumn()),
|
||||
database.NewColumnCondition(s.IDColumn(), s.metadataRepo.sessionIDColumn()),
|
||||
cond,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// MetadataConditions implements [domain.sessionConditions].
|
||||
func (s session) MetadataConditions() domain.SessionMetadataConditions {
|
||||
return s.metadataRepo
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyColumns implements [domain.Repository].
|
||||
func (s session) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
s.InstanceIDColumn(),
|
||||
s.IDColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
// InstanceIDColumn implements [domain.sessionColumns].
|
||||
func (s session) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "instance_id")
|
||||
}
|
||||
|
||||
// IDColumn implements [domain.sessionColumns].
|
||||
func (s session) IDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "id")
|
||||
}
|
||||
|
||||
// TokenIDColumn implements [domain.sessionColumns].
|
||||
func (s session) TokenIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "token_id")
|
||||
}
|
||||
|
||||
// LifetimeColumn implements [domain.sessionColumns].
|
||||
func (s session) LifetimeColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "lifetime")
|
||||
}
|
||||
|
||||
// ExpirationColumn implements [domain.sessionColumns].
|
||||
func (s session) ExpirationColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "expiration")
|
||||
}
|
||||
|
||||
// UserIDColumn implements [domain.sessionColumns].
|
||||
func (s session) UserIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "user_id")
|
||||
}
|
||||
|
||||
// UserAgentIDColumn implements [domain.sessionColumns].
|
||||
func (s session) userAgentIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "user_agent_id")
|
||||
}
|
||||
|
||||
// CreatorIDColumn implements [domain.sessionColumns].
|
||||
func (s session) CreatorIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "creator_id")
|
||||
}
|
||||
|
||||
// CreatedAtColumn implements [domain.sessionColumns].
|
||||
func (s session) CreatedAtColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "created_at")
|
||||
}
|
||||
|
||||
// UpdatedAtColumn implements [domain.sessionColumns].
|
||||
func (s session) UpdatedAtColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "updated_at")
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// scanners
|
||||
// -------------------------------------------------------------
|
||||
|
||||
type rawSession struct {
|
||||
*domain.Session
|
||||
TokenID *string `json:"tokenID" db:"token_id"`
|
||||
Lifetime *time.Duration `json:"lifetime" db:"lifetime"`
|
||||
Expiration *time.Time `json:"expiration" db:"expiration"`
|
||||
UserID *string `json:"userID" db:"user_id"`
|
||||
CreatorID *string `json:"creatorID" db:"creator_id"`
|
||||
Factors JSONArray[rawFactor] `json:"factors,omitempty" db:"factors"`
|
||||
Metadata JSONArray[domain.SessionMetadata] `json:"metadata,omitempty" db:"metadata"`
|
||||
}
|
||||
|
||||
func scanSession(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.Session, error) {
|
||||
rows, err := querier.Query(ctx, builder.String(), builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw := new(rawSession)
|
||||
if err := rows.(database.CollectableRows).CollectExactlyOneRow(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rawSessionToDomain(raw)
|
||||
}
|
||||
|
||||
func scanSessions(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) ([]*domain.Session, error) {
|
||||
rows, err := querier.Query(ctx, builder.String(), builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sessions []*rawSession
|
||||
if err := rows.(database.CollectableRows).Collect(&sessions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*domain.Session, len(sessions))
|
||||
for i, session := range sessions {
|
||||
result[i], err = rawSessionToDomain(session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func rawSessionToDomain(raw *rawSession) (*domain.Session, error) {
|
||||
raw.Session.TokenID = gu.Value(raw.TokenID)
|
||||
raw.Session.Lifetime = gu.Value(raw.Lifetime)
|
||||
raw.Session.Expiration = gu.Value(raw.Expiration)
|
||||
raw.Session.UserID = gu.Value(raw.UserID)
|
||||
raw.Session.CreatorID = gu.Value(raw.CreatorID)
|
||||
|
||||
for _, factor := range raw.Factors {
|
||||
f, ch, err := factor.ToDomain()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if f != nil {
|
||||
raw.Session.Factors.AppendTo(f)
|
||||
}
|
||||
if ch != nil {
|
||||
raw.Challenges.AppendTo(ch)
|
||||
}
|
||||
}
|
||||
raw.Session.Metadata = raw.Metadata
|
||||
return raw.Session, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// sub repositories
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (s session) joinUserAgent() database.QueryOption {
|
||||
columns := make([]database.Condition, 0, 3)
|
||||
columns = append(columns,
|
||||
database.NewColumnCondition(s.InstanceIDColumn(), s.userAgentRepo.instanceIDColumn()),
|
||||
database.NewColumnCondition(s.userAgentIDColumn(), s.userAgentRepo.fingerprintIDColumn()),
|
||||
)
|
||||
return database.WithLeftJoin(
|
||||
s.userAgentRepo.qualifiedTableName(),
|
||||
database.And(columns...),
|
||||
)
|
||||
}
|
||||
|
||||
func (s session) joinFactors() database.QueryOption {
|
||||
columns := make([]database.Condition, 0, 3)
|
||||
columns = append(columns,
|
||||
database.NewColumnCondition(s.InstanceIDColumn(), s.factorRepo.instanceIDColumn()),
|
||||
database.NewColumnCondition(s.IDColumn(), s.factorRepo.sessionIDColumn()),
|
||||
)
|
||||
return database.WithLeftJoin(
|
||||
s.factorRepo.qualifiedTableName(),
|
||||
database.And(columns...),
|
||||
)
|
||||
}
|
||||
|
||||
func (s session) joinMetadata() database.QueryOption {
|
||||
columns := make([]database.Condition, 0, 3)
|
||||
columns = append(columns,
|
||||
database.NewColumnCondition(s.InstanceIDColumn(), s.metadataRepo.instanceIDColumn()),
|
||||
database.NewColumnCondition(s.IDColumn(), s.metadataRepo.sessionIDColumn()),
|
||||
)
|
||||
return database.WithLeftJoin(
|
||||
s.metadataRepo.qualifiedTableName(),
|
||||
database.And(columns...),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type sessionFactor struct {
|
||||
}
|
||||
|
||||
func (s sessionFactor) qualifiedTableName() string {
|
||||
return "zitadel.session_factors"
|
||||
}
|
||||
|
||||
func (s sessionFactor) unqualifiedTableName() string {
|
||||
return "session_factors"
|
||||
}
|
||||
|
||||
func (s sessionFactor) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
s.instanceIDColumn(),
|
||||
s.sessionIDColumn(),
|
||||
s.typeColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s sessionFactor) instanceIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "instance_id")
|
||||
}
|
||||
|
||||
func (s sessionFactor) sessionIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "session_id")
|
||||
}
|
||||
|
||||
func (s sessionFactor) typeColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "type")
|
||||
}
|
||||
|
||||
func (s sessionFactor) lastVerifiedAtColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "last_verified_at")
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (s sessionFactor) FactorTypeCondition(factorType domain.SessionFactorType) database.Condition {
|
||||
return database.NewNumberCondition(s.typeColumn(), database.NumberOperationEqual, factorType)
|
||||
}
|
||||
|
||||
func (s sessionFactor) LastVerifiedBeforeCondition(lastVerifiedAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(s.lastVerifiedAtColumn(), database.NumberOperationLessThan, lastVerifiedAt)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// scanners
|
||||
// -------------------------------------------------------------
|
||||
|
||||
type rawFactor struct {
|
||||
Type string `json:"type" db:"type"`
|
||||
LastChallengedAt time.Time `json:"lastChallengedAt" db:"last_challenged_at"`
|
||||
ChallengedPayload JSON[json.RawMessage] `json:"challengedPayload" db:"challenged_payload"`
|
||||
LastVerifiedAt time.Time `json:"lastVerifiedAt" db:"last_verified_at"`
|
||||
VerifiedPayload JSON[json.RawMessage] `json:"verifiedPayload" db:"verified_payload"`
|
||||
}
|
||||
|
||||
func (f *rawFactor) ToDomain() (domain.SessionFactor, domain.SessionChallenge, error) {
|
||||
factorType, err := domain.SessionFactorTypeString(f.Type)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
switch factorType {
|
||||
case domain.SessionFactorTypeUser:
|
||||
return f.userFactorToDomain()
|
||||
case domain.SessionFactorTypePassword:
|
||||
return f.passwordFactorToDomain()
|
||||
case domain.SessionFactorTypePasskey:
|
||||
return f.passkeyFactorToDomain()
|
||||
case domain.SessionFactorTypeIdentityProviderIntent:
|
||||
return f.identityProviderIntentFactorToDomain()
|
||||
case domain.SessionFactorTypeTOTP:
|
||||
return f.totpFactorToDomain()
|
||||
case domain.SessionFactorTypeOTPSMS:
|
||||
return f.otpSMSFactorToDomain()
|
||||
case domain.SessionFactorTypeOTPEmail:
|
||||
return f.otpEmailFactorToDomain()
|
||||
case domain.SessionFactorTypeRecoveryCode:
|
||||
return f.recoveryCodeFactorToDomain()
|
||||
case domain.SessionFactorTypeUnknown:
|
||||
return nil, nil, nil
|
||||
default:
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *rawFactor) userFactorToDomain() (factor domain.SessionFactor, _ domain.SessionChallenge, err error) {
|
||||
if f.LastVerifiedAt.IsZero() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
factor = new(domain.SessionFactorUser)
|
||||
if err := json.Unmarshal(f.VerifiedPayload.Value, factor); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return factor, nil, nil
|
||||
}
|
||||
|
||||
func (f *rawFactor) passwordFactorToDomain() (domain.SessionFactor, domain.SessionChallenge, error) {
|
||||
if f.LastVerifiedAt.IsZero() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return &domain.SessionFactorPassword{
|
||||
LastVerifiedAt: f.LastVerifiedAt,
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func (f *rawFactor) passkeyFactorToDomain() (factor domain.SessionFactor, challenge domain.SessionChallenge, err error) {
|
||||
if !f.LastChallengedAt.IsZero() && f.LastChallengedAt.After(f.LastVerifiedAt) {
|
||||
challenge = new(domain.SessionChallengePasskey)
|
||||
if err := json.Unmarshal(f.ChallengedPayload.Value, &challenge); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if !f.LastVerifiedAt.IsZero() {
|
||||
factor = new(domain.SessionFactorPasskey)
|
||||
if err := json.Unmarshal(f.VerifiedPayload.Value, factor); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return factor, challenge, nil
|
||||
}
|
||||
|
||||
func (f *rawFactor) identityProviderIntentFactorToDomain() (domain.SessionFactor, domain.SessionChallenge, error) {
|
||||
if f.LastVerifiedAt.IsZero() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return &domain.SessionFactorIdentityProviderIntent{
|
||||
LastVerifiedAt: f.LastVerifiedAt,
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func (f *rawFactor) totpFactorToDomain() (domain.SessionFactor, domain.SessionChallenge, error) {
|
||||
if f.LastVerifiedAt.IsZero() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return &domain.SessionFactorTOTP{
|
||||
LastVerifiedAt: f.LastVerifiedAt,
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func (f *rawFactor) otpSMSFactorToDomain() (factor domain.SessionFactor, challenge domain.SessionChallenge, err error) {
|
||||
if !f.LastChallengedAt.IsZero() && f.LastChallengedAt.After(f.LastVerifiedAt) {
|
||||
challenge = new(domain.SessionChallengeOTPSMS)
|
||||
if err := json.Unmarshal(f.ChallengedPayload.Value, &challenge); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if !f.LastVerifiedAt.IsZero() {
|
||||
factor = &domain.SessionFactorOTPSMS{
|
||||
LastVerifiedAt: f.LastVerifiedAt,
|
||||
}
|
||||
}
|
||||
return factor, challenge, nil
|
||||
}
|
||||
|
||||
func (f *rawFactor) otpEmailFactorToDomain() (factor domain.SessionFactor, challenge domain.SessionChallenge, err error) {
|
||||
if !f.LastChallengedAt.IsZero() && f.LastChallengedAt.After(f.LastVerifiedAt) {
|
||||
challenge = new(domain.SessionChallengeOTPEmail)
|
||||
if err := json.Unmarshal(f.ChallengedPayload.Value, &challenge); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if !f.LastVerifiedAt.IsZero() {
|
||||
factor = &domain.SessionFactorOTPEmail{
|
||||
LastVerifiedAt: f.LastVerifiedAt,
|
||||
}
|
||||
}
|
||||
return factor, challenge, nil
|
||||
}
|
||||
|
||||
func (f *rawFactor) recoveryCodeFactorToDomain() (domain.SessionFactor, domain.SessionChallenge, error) {
|
||||
if f.LastVerifiedAt.IsZero() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return &domain.SessionFactorRecoveryCode{
|
||||
LastVerifiedAt: f.LastVerifiedAt,
|
||||
}, nil, nil
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package repository
|
||||
|
||||
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
|
||||
type sessionMetadata struct {
|
||||
}
|
||||
|
||||
func (s sessionMetadata) qualifiedTableName() string {
|
||||
return "zitadel.session_metadata"
|
||||
}
|
||||
|
||||
func (s sessionMetadata) unqualifiedTableName() string {
|
||||
return "session_metadata"
|
||||
}
|
||||
|
||||
func (s sessionMetadata) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
s.instanceIDColumn(),
|
||||
s.sessionIDColumn(),
|
||||
s.keyColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s sessionMetadata) instanceIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "instance_id")
|
||||
}
|
||||
|
||||
func (s sessionMetadata) sessionIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "session_id")
|
||||
}
|
||||
|
||||
func (s sessionMetadata) keyColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "key")
|
||||
}
|
||||
|
||||
func (s sessionMetadata) valueColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "value")
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// PrimaryKeyCondition implements [domain.SessionMetadataRepository].
|
||||
func (s sessionMetadata) PrimaryKeyCondition(instanceID string, sessionID string, key string) database.Condition {
|
||||
return database.And(
|
||||
s.InstanceIDCondition(instanceID),
|
||||
s.SessionIDCondition(sessionID),
|
||||
s.KeyCondition(database.TextOperationEqual, key),
|
||||
)
|
||||
}
|
||||
|
||||
// InstanceIDCondition implements [domain.SessionMetadataRepository].
|
||||
func (s sessionMetadata) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(s.instanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
// SessionIDCondition implements [domain.SessionMetadataRepository].
|
||||
func (s sessionMetadata) SessionIDCondition(sessionID string) database.Condition {
|
||||
return database.NewTextCondition(s.sessionIDColumn(), database.TextOperationEqual, sessionID)
|
||||
}
|
||||
|
||||
// KeyCondition implements [domain.SessionMetadataRepository].
|
||||
func (s sessionMetadata) KeyCondition(op database.TextOperation, key string) database.Condition {
|
||||
return database.NewTextCondition(s.keyColumn(), op, key)
|
||||
}
|
||||
|
||||
// ValueCondition implements [domain.SessionMetadataRepository].
|
||||
func (s sessionMetadata) ValueCondition(op database.BytesOperation, value []byte) database.Condition {
|
||||
return database.NewBytesCondition[[]byte](database.SHA256Column(s.valueColumn()), op, database.SHA256Value(value))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type sessionUserAgent struct {
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) qualifiedTableName() string {
|
||||
return "zitadel.session_user_agents"
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) unqualifiedTableName() string {
|
||||
return "session_user_agents"
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
s.instanceIDColumn(),
|
||||
s.fingerprintIDColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) instanceIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "instance_id")
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) fingerprintIDColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "fingerprint_id")
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) descriptionColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "description")
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) ipColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "ip")
|
||||
}
|
||||
|
||||
func (s sessionUserAgent) headersColumn() database.Column {
|
||||
return database.NewColumn(s.unqualifiedTableName(), "headers")
|
||||
}
|
||||
@@ -99,6 +99,7 @@ var (
|
||||
OrganizationMetadataRelationalProjection *handler.Handler
|
||||
AuthorizationRelationalProjection *handler.Handler
|
||||
ProjectGrantRelationalProjection *handler.Handler
|
||||
SessionRelationalProjection *handler.Handler
|
||||
SettingsRelationalProjection *handler.Handler
|
||||
|
||||
ProjectGrantFields *handler.FieldHandler
|
||||
@@ -228,6 +229,7 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore,
|
||||
OrganizationMetadataRelationalProjection = newOrgMetadataRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["organization_metadata_relational"]))
|
||||
AuthorizationRelationalProjection = newAuthorizationRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["authorizations_relational"]))
|
||||
ProjectGrantRelationalProjection = newProjectGrantRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["project_grant_relational"]))
|
||||
SessionRelationalProjection = newSessionRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["session_relational"]))
|
||||
|
||||
newProjectionsList()
|
||||
newFieldsList()
|
||||
@@ -424,5 +426,6 @@ func newProjectionsList() {
|
||||
OrganizationMetadataRelationalProjection,
|
||||
AuthorizationRelationalProjection,
|
||||
ProjectGrantRelationalProjection,
|
||||
SessionRelationalProjection,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
v3_sql "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/sql"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
SessionsRelationalProjectionTable = "zitadel.sessions"
|
||||
)
|
||||
|
||||
type sessionRelationalProjection struct{}
|
||||
|
||||
func (*sessionRelationalProjection) Name() string {
|
||||
return SessionsRelationalProjectionTable
|
||||
}
|
||||
|
||||
func newSessionRelationalProjection(ctx context.Context, config handler.Config) *handler.Handler {
|
||||
return handler.NewHandler(ctx, &config, new(sessionRelationalProjection))
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) Reducers() []handler.AggregateReducer {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: session.AggregateType,
|
||||
EventReducers: []handler.EventReducer{
|
||||
{
|
||||
Event: session.AddedType,
|
||||
Reduce: p.reduceSessionAdded,
|
||||
},
|
||||
{
|
||||
Event: session.UserCheckedType,
|
||||
Reduce: p.reduceUserChecked,
|
||||
},
|
||||
{
|
||||
Event: session.PasswordCheckedType,
|
||||
Reduce: p.reducePasswordChecked,
|
||||
},
|
||||
{
|
||||
Event: session.IntentCheckedType,
|
||||
Reduce: p.reduceIntentChecked,
|
||||
},
|
||||
{
|
||||
Event: session.WebAuthNChallengedType,
|
||||
Reduce: p.reduceWebAuthNChallenged,
|
||||
},
|
||||
{
|
||||
Event: session.WebAuthNCheckedType,
|
||||
Reduce: p.reduceWebAuthNChecked,
|
||||
},
|
||||
{
|
||||
Event: session.TOTPCheckedType,
|
||||
Reduce: p.reduceTOTPChecked,
|
||||
},
|
||||
{
|
||||
Event: session.OTPSMSChallengedType,
|
||||
Reduce: p.reduceOTPSMSChallenged,
|
||||
},
|
||||
{
|
||||
Event: session.OTPSMSCheckedType,
|
||||
Reduce: p.reduceOTPSMSChecked,
|
||||
},
|
||||
{
|
||||
Event: session.OTPEmailChallengedType,
|
||||
Reduce: p.reduceOTPEmailChallenged,
|
||||
},
|
||||
{
|
||||
Event: session.OTPEmailCheckedType,
|
||||
Reduce: p.reduceOTPEmailChecked,
|
||||
},
|
||||
{
|
||||
Event: session.RecoveryCodeCheckedType,
|
||||
Reduce: p.reduceRecoveryCodeChecked,
|
||||
},
|
||||
{
|
||||
Event: session.TokenSetType,
|
||||
Reduce: p.reduceTokenSet,
|
||||
},
|
||||
{
|
||||
Event: session.MetadataSetType,
|
||||
Reduce: p.reduceMetadataSet,
|
||||
},
|
||||
{
|
||||
Event: session.LifetimeSetType,
|
||||
Reduce: p.reduceLifetimeSet,
|
||||
},
|
||||
{
|
||||
Event: session.TerminateType,
|
||||
Reduce: p.reduceSessionTerminated,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Aggregate: instance.AggregateType,
|
||||
EventReducers: []handler.EventReducer{
|
||||
{
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(SMSColumnInstanceID),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Aggregate: user.AggregateType,
|
||||
EventReducers: []handler.EventReducer{
|
||||
{
|
||||
Event: user.HumanPasswordChangedType,
|
||||
Reduce: p.reducePasswordChanged,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceSessionAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.AddedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-iZGH3", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
var userAgent *domain.SessionUserAgent
|
||||
if e.UserAgent != nil {
|
||||
userAgent = &domain.SessionUserAgent{
|
||||
FingerprintID: e.UserAgent.FingerprintID,
|
||||
Description: e.UserAgent.Description,
|
||||
IP: e.UserAgent.IP,
|
||||
Header: e.UserAgent.Header,
|
||||
}
|
||||
}
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
return sessionRepo.Create(ctx, v3Tx, &domain.Session{
|
||||
InstanceID: e.Aggregate().InstanceID,
|
||||
ID: e.Aggregate().ID,
|
||||
CreatorID: e.User,
|
||||
CreatedAt: e.CreationDate(),
|
||||
UpdatedAt: e.CreationDate(),
|
||||
UserAgent: userAgent,
|
||||
})
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceUserChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.UserCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-UHE92", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorUser{
|
||||
UserID: e.UserID,
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reducePasswordChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.PasswordCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-3krfa", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorPassword{
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceIntentChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.IntentCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-ajkd2", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorIdentityProviderIntent{
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceWebAuthNChallenged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.WebAuthNChallengedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-do35d", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetChallenge(&domain.SessionChallengePasskey{
|
||||
LastChallengedAt: e.CreatedAt(),
|
||||
Challenge: e.Challenge,
|
||||
AllowedCredentialIDs: e.AllowedCrentialIDs,
|
||||
UserVerification: e.UserVerification,
|
||||
RPID: e.RPID,
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
func (p *sessionRelationalProjection) reduceWebAuthNChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.WebAuthNCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-djk22", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorPasskey{
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
UserVerified: e.UserVerified,
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceTOTPChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.TOTPCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-cklr9", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorTOTP{
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceOTPSMSChallenged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.OTPSMSChallengedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-fk4f9", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetChallenge(&domain.SessionChallengeOTPSMS{
|
||||
LastChallengedAt: e.CreatedAt(),
|
||||
Code: e.Code,
|
||||
Expiry: e.Expiry,
|
||||
CodeReturned: e.CodeReturned,
|
||||
GeneratorID: e.GeneratorID,
|
||||
TriggeredAtOrigin: e.TriggeredAtOrigin,
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceOTPSMSChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.OTPSMSCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-dk39f", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorOTPSMS{
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceOTPEmailChallenged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.OTPEmailChallengedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-fkf93", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetChallenge(&domain.SessionChallengeOTPEmail{
|
||||
LastChallengedAt: e.CreatedAt(),
|
||||
Code: e.Code,
|
||||
Expiry: e.Expiry,
|
||||
CodeReturned: e.ReturnCode,
|
||||
URLTemplate: e.URLTmpl,
|
||||
TriggeredAtOrigin: e.TriggeredAtOrigin,
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceOTPEmailChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.OTPEmailCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-3kll0", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorOTPEmail{
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceRecoveryCodeChecked(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.RecoveryCodeCheckedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-fk45a", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetFactor(&domain.SessionFactorRecoveryCode{
|
||||
LastVerifiedAt: e.CreatedAt(),
|
||||
}),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceTokenSet(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.TokenSetEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-asddt", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetToken(e.TokenID),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceMetadataSet(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.MetadataSetEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadataList := make([]*domain.SessionMetadata, 0, len(e.Metadata))
|
||||
for key, value := range e.Metadata {
|
||||
metadataList = append(metadataList, &domain.SessionMetadata{
|
||||
Metadata: domain.Metadata{
|
||||
Key: key,
|
||||
Value: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-iZGH3", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetMetadata(metadataList),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceLifetimeSet(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.LifetimeSetEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-iZGH3", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.SetLifetime(e.Lifetime),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceSessionTerminated(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*session.TerminateEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-iZGH3", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := sessionRepo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID)
|
||||
_, err := sessionRepo.Delete(ctx, v3Tx, condition)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reducePasswordChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*user.HumanPasswordChangedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-iZGH3", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := database.And(
|
||||
sessionRepo.InstanceIDCondition(e.Aggregate().InstanceID),
|
||||
sessionRepo.UserIDCondition(e.Aggregate().ID),
|
||||
sessionRepo.ExistsFactor(
|
||||
database.And(
|
||||
sessionRepo.FactorConditions().FactorTypeCondition(domain.SessionFactorTypePassword),
|
||||
sessionRepo.FactorConditions().LastVerifiedBeforeCondition(e.CreatedAt()),
|
||||
),
|
||||
),
|
||||
)
|
||||
_, err = sessionRepo.Update(ctx, v3Tx, condition,
|
||||
sessionRepo.SetUpdatedAt(e.CreatedAt()),
|
||||
sessionRepo.ClearFactor(domain.SessionFactorTypePassword),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
@@ -80,3 +80,7 @@ func (c *Client) CreateAssertionResponse(optionsPb *structpb.Struct, verifyUser
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) KeyID() []byte {
|
||||
return c.credential.ID
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user