From 0dbe44e85c32ec27104ef5f4c61cd80757f36658 Mon Sep 17 00:00:00 2001 From: Maduranga Siriwardena Date: Fri, 24 Jul 2026 17:30:00 +0530 Subject: [PATCH] Apply a default sign-out flow automatically instead of an enable toggle Remove the per-application sign-out enable option and apply a server-level default sign-out flow when an application does not pin its own, mirroring how the default authentication flow works. - Add flow.default_signout_flow_handle config (default "default-flow") - Resolve the default sign-out flow in resolveFlowDefaults and drop the IsSignOutFlowEnabled gate in getFlowGraph - Remove IsSignOutFlowEnabled from models, store, and the IS_SIGNOUT_FLOW_ENABLED config DB column - Rename the bundled sign-out flow handle console-signout-flow to default-flow - Drop the enable toggle from the console SignOutFlowSection and fix its i18n keys (signoutFlow -> signOutFlow) so the section title reads "Sign Out Flow" like the other flow sections --- .../bootstrap/01-default-resources.yaml | 3 +- backend/cmd/server/config/default.json | 1 + backend/dbscripts/configdb/postgres.sql | 1 - backend/dbscripts/configdb/sqlite.sql | 1 - backend/internal/actorprovider/utils.go | 11 ++- backend/internal/actorprovider/utils_test.go | 8 +- .../application/declarative_resource.go | 1 - backend/internal/application/handler.go | 5 -- .../internal/application/model/application.go | 2 - backend/internal/application/service.go | 6 -- .../internal/flow/flowexec/error_constants.go | 14 ---- backend/internal/flow/flowexec/service.go | 4 +- .../internal/flow/flowexec/service_test.go | 17 ++++ backend/internal/inboundclient/service.go | 20 ++++- .../internal/inboundclient/service_test.go | 80 ++++++++++++++++++- backend/internal/inboundclient/store.go | 20 ++--- .../internal/inboundclient/store_constants.go | 14 ++-- backend/internal/system/i18n/core/defaults.go | 2 - backend/internal/system/importer/service.go | 1 - backend/pkg/thunderidengine/config/config.go | 1 + .../pkg/thunderidengine/providers/model.go | 2 - docs/content/deployment/configuration.mdx | 1 + .../flows-settings/SignOutFlowSection.tsx | 16 ++-- .../__tests__/SignOutFlowSection.test.tsx | 47 ++--------- .../applications/models/application.ts | 6 -- frontend/packages/i18n/src/locales/en-US.ts | 2 +- install/helm/conf/deployment.yaml | 1 + install/helm/values.yaml | 1 + .../samples/resource.yaml | 1 + .../templates/thunderid-resourcetype.yaml | 7 ++ tests/integration/oauth/sso/suite_test.go | 1 - 31 files changed, 162 insertions(+), 135 deletions(-) diff --git a/backend/cmd/server/bootstrap/01-default-resources.yaml b/backend/cmd/server/bootstrap/01-default-resources.yaml index 4fdff2a173..e4a9035aee 100644 --- a/backend/cmd/server/bootstrap/01-default-resources.yaml +++ b/backend/cmd/server/bootstrap/01-default-resources.yaml @@ -602,7 +602,7 @@ nodes: resource_type: flow id: 01900000-0000-7000-8000-00000000006a name: Default Sign Out Flow -handle: console-signout-flow +handle: default-flow flowType: SIGNOUT nodes: - id: start @@ -4988,7 +4988,6 @@ authFlowId: 01900000-0000-7000-8000-000000000068 registrationFlowId: 01900000-0000-7000-8000-000000000069 isRegistrationFlowEnabled: false signOutFlowId: 01900000-0000-7000-8000-00000000006a -isSignOutFlowEnabled: true recoveryFlowId: 01900000-0000-7000-8000-000000000070 isRecoveryFlowEnabled: false allowedUserTypes: diff --git a/backend/cmd/server/config/default.json b/backend/cmd/server/config/default.json index d37deef43f..2178a39dbd 100644 --- a/backend/cmd/server/config/default.json +++ b/backend/cmd/server/config/default.json @@ -170,6 +170,7 @@ }, "flow": { "default_auth_flow_handle": "default-flow", + "default_signout_flow_handle": "default-flow", "user_onboarding_flow_handle": "default-flow", "max_version_history": 10, "auto_infer_registration": false, diff --git a/backend/dbscripts/configdb/postgres.sql b/backend/dbscripts/configdb/postgres.sql index d4dda24b6f..44a4650ead 100644 --- a/backend/dbscripts/configdb/postgres.sql +++ b/backend/dbscripts/configdb/postgres.sql @@ -104,7 +104,6 @@ CREATE TABLE "INBOUND_CLIENT" ( RECOVERY_FLOW_ID VARCHAR(100), IS_RECOVERY_FLOW_ENABLED CHAR(1) DEFAULT '0', SIGNOUT_FLOW_ID VARCHAR(100), - IS_SIGNOUT_FLOW_ENABLED CHAR(1) DEFAULT '0', THEME_ID VARCHAR(36), LAYOUT_ID VARCHAR(36), PROPERTIES JSONB, diff --git a/backend/dbscripts/configdb/sqlite.sql b/backend/dbscripts/configdb/sqlite.sql index a05d6091e8..42ba253ba8 100644 --- a/backend/dbscripts/configdb/sqlite.sql +++ b/backend/dbscripts/configdb/sqlite.sql @@ -104,7 +104,6 @@ CREATE TABLE "INBOUND_CLIENT" ( RECOVERY_FLOW_ID VARCHAR(100), IS_RECOVERY_FLOW_ENABLED CHAR(1) DEFAULT '0', SIGNOUT_FLOW_ID VARCHAR(100), - IS_SIGNOUT_FLOW_ENABLED CHAR(1) DEFAULT '0', THEME_ID VARCHAR(36), LAYOUT_ID VARCHAR(36), PROPERTIES TEXT, diff --git a/backend/internal/actorprovider/utils.go b/backend/internal/actorprovider/utils.go index 147d939e31..c009021f0b 100644 --- a/backend/internal/actorprovider/utils.go +++ b/backend/internal/actorprovider/utils.go @@ -54,12 +54,11 @@ func assembleApplication( app := &providers.Application{ ID: client.ID, InboundAuthProfile: providers.InboundAuthProfile{ - AuthFlowID: client.AuthFlowID, - SignOutFlowID: client.SignOutFlowID, - IsSignOutFlowEnabled: client.IsSignOutFlowEnabled, - Assertion: client.Assertion, - LoginConsent: client.LoginConsent, - AllowedUserTypes: client.AllowedUserTypes, + AuthFlowID: client.AuthFlowID, + SignOutFlowID: client.SignOutFlowID, + Assertion: client.Assertion, + LoginConsent: client.LoginConsent, + AllowedUserTypes: client.AllowedUserTypes, }, } diff --git a/backend/internal/actorprovider/utils_test.go b/backend/internal/actorprovider/utils_test.go index 6504102d3e..2de08d3131 100644 --- a/backend/internal/actorprovider/utils_test.go +++ b/backend/internal/actorprovider/utils_test.go @@ -142,17 +142,15 @@ func (s *UtilsTestSuite) TestAssembleApplication_NoClientID() { func (s *UtilsTestSuite) TestAssembleApplication_CarriesFlowIDs() { client := &providers.InboundClient{ - ID: "app-1", - AuthFlowID: "auth-flow", - SignOutFlowID: "signout-flow", - IsSignOutFlowEnabled: true, + ID: "app-1", + AuthFlowID: "auth-flow", + SignOutFlowID: "signout-flow", } app := assembleApplication(client, nil) s.Equal("auth-flow", app.AuthFlowID) s.Equal("signout-flow", app.SignOutFlowID) - s.True(app.IsSignOutFlowEnabled) } func (s *UtilsTestSuite) TestBuildApplication_NotFound() { diff --git a/backend/internal/application/declarative_resource.go b/backend/internal/application/declarative_resource.go index 7ab4ccd23f..9b17b17480 100644 --- a/backend/internal/application/declarative_resource.go +++ b/backend/internal/application/declarative_resource.go @@ -185,7 +185,6 @@ func parseToApplicationDTO(data []byte) (*model.ApplicationDTO, error) { IsRecoveryFlowEnabled: appRequest.IsRecoveryFlowEnabled, SignOutFlowID: appRequest.SignOutFlowID, SignOutFlowHandle: appRequest.SignOutFlowHandle, - IsSignOutFlowEnabled: appRequest.IsSignOutFlowEnabled, ThemeID: appRequest.ThemeID, LayoutID: appRequest.LayoutID, Assertion: appRequest.Assertion, diff --git a/backend/internal/application/handler.go b/backend/internal/application/handler.go index f4c08b610d..8a38008ca3 100644 --- a/backend/internal/application/handler.go +++ b/backend/internal/application/handler.go @@ -77,7 +77,6 @@ func (ah *applicationHandler) HandleApplicationPostRequest(w http.ResponseWriter RecoveryFlowID: appRequest.RecoveryFlowID, IsRecoveryFlowEnabled: appRequest.IsRecoveryFlowEnabled, SignOutFlowID: appRequest.SignOutFlowID, - IsSignOutFlowEnabled: appRequest.IsSignOutFlowEnabled, ThemeID: appRequest.ThemeID, LayoutID: appRequest.LayoutID, Assertion: appRequest.Assertion, @@ -115,7 +114,6 @@ func (ah *applicationHandler) HandleApplicationPostRequest(w http.ResponseWriter RecoveryFlowID: createdAppDTO.RecoveryFlowID, IsRecoveryFlowEnabled: createdAppDTO.IsRecoveryFlowEnabled, SignOutFlowID: createdAppDTO.SignOutFlowID, - IsSignOutFlowEnabled: createdAppDTO.IsSignOutFlowEnabled, ThemeID: createdAppDTO.ThemeID, LayoutID: createdAppDTO.LayoutID, Assertion: createdAppDTO.Assertion, @@ -196,7 +194,6 @@ func (ah *applicationHandler) HandleApplicationGetRequest(w http.ResponseWriter, RecoveryFlowID: appDTO.RecoveryFlowID, IsRecoveryFlowEnabled: appDTO.IsRecoveryFlowEnabled, SignOutFlowID: appDTO.SignOutFlowID, - IsSignOutFlowEnabled: appDTO.IsSignOutFlowEnabled, ThemeID: appDTO.ThemeID, LayoutID: appDTO.LayoutID, Assertion: appDTO.Assertion, @@ -339,7 +336,6 @@ func (ah *applicationHandler) HandleApplicationPutRequest(w http.ResponseWriter, RecoveryFlowID: appRequest.RecoveryFlowID, IsRecoveryFlowEnabled: appRequest.IsRecoveryFlowEnabled, SignOutFlowID: appRequest.SignOutFlowID, - IsSignOutFlowEnabled: appRequest.IsSignOutFlowEnabled, ThemeID: appRequest.ThemeID, LayoutID: appRequest.LayoutID, Assertion: appRequest.Assertion, @@ -377,7 +373,6 @@ func (ah *applicationHandler) HandleApplicationPutRequest(w http.ResponseWriter, RecoveryFlowID: updatedAppDTO.RecoveryFlowID, IsRecoveryFlowEnabled: updatedAppDTO.IsRecoveryFlowEnabled, SignOutFlowID: updatedAppDTO.SignOutFlowID, - IsSignOutFlowEnabled: updatedAppDTO.IsSignOutFlowEnabled, ThemeID: updatedAppDTO.ThemeID, LayoutID: updatedAppDTO.LayoutID, Assertion: updatedAppDTO.Assertion, diff --git a/backend/internal/application/model/application.go b/backend/internal/application/model/application.go index e19b328584..a181a7a968 100644 --- a/backend/internal/application/model/application.go +++ b/backend/internal/application/model/application.go @@ -58,7 +58,6 @@ type BasicApplicationDTO struct { RecoveryFlowID string IsRecoveryFlowEnabled bool SignOutFlowID string - IsSignOutFlowEnabled bool ThemeID string LayoutID string Template string @@ -178,7 +177,6 @@ type BasicApplicationResponse struct { RecoveryFlowID string `json:"recoveryFlowId,omitempty" jsonschema:"Recovery Flow ID."` IsRecoveryFlowEnabled bool `json:"isRecoveryFlowEnabled" jsonschema:"Recovery enabled status."` SignOutFlowID string `json:"signOutFlowId,omitempty" jsonschema:"Sign-out flow ID."` - IsSignOutFlowEnabled bool `json:"isSignOutFlowEnabled" jsonschema:"Sign-out enabled status."` ThemeID string `json:"themeId,omitempty" jsonschema:"Theme ID."` LayoutID string `json:"layoutId,omitempty" jsonschema:"Layout ID."` Template string `json:"template,omitempty" jsonschema:"Application Template."` diff --git a/backend/internal/application/service.go b/backend/internal/application/service.go index 8789b525dc..1fe0bcb33f 100644 --- a/backend/internal/application/service.go +++ b/backend/internal/application/service.go @@ -810,7 +810,6 @@ func toInboundClient(dto *model.ApplicationProcessedDTO) inboundmodel.InboundCli RecoveryFlowID: dto.RecoveryFlowID, IsRecoveryFlowEnabled: dto.IsRecoveryFlowEnabled, SignOutFlowID: dto.SignOutFlowID, - IsSignOutFlowEnabled: dto.IsSignOutFlowEnabled, ThemeID: dto.ThemeID, LayoutID: dto.LayoutID, Assertion: dto.Assertion, @@ -863,7 +862,6 @@ func toProcessedDTO( RecoveryFlowID: dao.RecoveryFlowID, IsRecoveryFlowEnabled: dao.IsRecoveryFlowEnabled, SignOutFlowID: dao.SignOutFlowID, - IsSignOutFlowEnabled: dao.IsSignOutFlowEnabled, ThemeID: dao.ThemeID, LayoutID: dao.LayoutID, Assertion: dao.Assertion, @@ -1783,7 +1781,6 @@ func buildApplicationResponse(dto *model.ApplicationProcessedDTO) *providers.App RecoveryFlowID: dto.RecoveryFlowID, IsRecoveryFlowEnabled: dto.IsRecoveryFlowEnabled, SignOutFlowID: dto.SignOutFlowID, - IsSignOutFlowEnabled: dto.IsSignOutFlowEnabled, ThemeID: dto.ThemeID, LayoutID: dto.LayoutID, Assertion: dto.Assertion, @@ -1842,7 +1839,6 @@ func buildBasicApplicationResponse( RecoveryFlowID: cfg.RecoveryFlowID, IsRecoveryFlowEnabled: cfg.IsRecoveryFlowEnabled, SignOutFlowID: cfg.SignOutFlowID, - IsSignOutFlowEnabled: cfg.IsSignOutFlowEnabled, ThemeID: cfg.ThemeID, LayoutID: cfg.LayoutID, IsReadOnly: cfg.IsReadOnly, @@ -1892,7 +1888,6 @@ func buildBaseApplicationProcessedDTO(appID string, app *model.ApplicationDTO, RecoveryFlowID: app.RecoveryFlowID, IsRecoveryFlowEnabled: app.IsRecoveryFlowEnabled, SignOutFlowID: app.SignOutFlowID, - IsSignOutFlowEnabled: app.IsSignOutFlowEnabled, ThemeID: app.ThemeID, LayoutID: app.LayoutID, Assertion: assertion, @@ -1976,7 +1971,6 @@ func buildReturnApplicationDTO( RecoveryFlowID: app.RecoveryFlowID, IsRecoveryFlowEnabled: app.IsRecoveryFlowEnabled, SignOutFlowID: app.SignOutFlowID, - IsSignOutFlowEnabled: app.IsSignOutFlowEnabled, ThemeID: app.ThemeID, LayoutID: app.LayoutID, Assertion: assertion, diff --git a/backend/internal/flow/flowexec/error_constants.go b/backend/internal/flow/flowexec/error_constants.go index 9b089ca4c5..92fd365e32 100644 --- a/backend/internal/flow/flowexec/error_constants.go +++ b/backend/internal/flow/flowexec/error_constants.go @@ -239,17 +239,3 @@ var ErrorAttestationInvalid = tidcommon.ServiceError{ DefaultValue: "The provided attestation token is invalid", }, } - -// ErrorSignOutFlowDisabled defines the error response for sign-out flow disabled errors. -var ErrorSignOutFlowDisabled = tidcommon.ServiceError{ - Code: "FES-1016", - Type: tidcommon.ClientErrorType, - Error: tidcommon.I18nMessage{ - Key: "error.flowexecservice.signout_not_allowed", - DefaultValue: "Sign out not allowed", - }, - ErrorDescription: tidcommon.I18nMessage{ - Key: "error.flowexecservice.signout_not_allowed_description", - DefaultValue: "Sign out flow is disabled for the application", - }, -} diff --git a/backend/internal/flow/flowexec/service.go b/backend/internal/flow/flowexec/service.go index 9e20f7060b..16fbdb2560 100644 --- a/backend/internal/flow/flowexec/service.go +++ b/backend/internal/flow/flowexec/service.go @@ -681,9 +681,7 @@ func (s *flowExecService) getFlowGraph(ctx context.Context, appID string, flowTy } if flowType == providers.FlowTypeSignOut { - if !client.IsSignOutFlowEnabled { - return "", &ErrorSignOutFlowDisabled - } else if client.SignOutFlowID == "" { + if client.SignOutFlowID == "" { logger.Error(ctx, "Sign-out flow is not configured for the application", log.String("appID", appID)) return "", &tidcommon.InternalServerError diff --git a/backend/internal/flow/flowexec/service_test.go b/backend/internal/flow/flowexec/service_test.go index 5f0a28124d..ef2904c610 100644 --- a/backend/internal/flow/flowexec/service_test.go +++ b/backend/internal/flow/flowexec/service_test.go @@ -2035,6 +2035,23 @@ func (s *ServiceTestSuite) TestGetFlowGraph_RegistrationAndRecovery() { }, expectedCode: ErrorRecoveryFlowDisabled.Code, }, + { + name: "signout flow configured", + flowType: providers.FlowTypeSignOut, + client: &inboundmodel.InboundClient{ + ID: appID, + SignOutFlowID: "signout-graph-1", + }, + expectedGraph: "signout-graph-1", + }, + { + name: "signout flow not configured", + flowType: providers.FlowTypeSignOut, + client: &inboundmodel.InboundClient{ + ID: appID, + }, + expectedCode: tidcommon.InternalServerError.Code, + }, { name: "empty app id", flowType: providers.FlowTypeAuthentication, diff --git a/backend/internal/inboundclient/service.go b/backend/internal/inboundclient/service.go index e11914627f..845f39b1f2 100644 --- a/backend/internal/inboundclient/service.go +++ b/backend/internal/inboundclient/service.go @@ -586,7 +586,7 @@ func BuildOAuthClient( return client } -// resolveFlowDefaults fills AuthFlowID, RegistrationFlowID, and RecoveryFlowID with system +// resolveFlowDefaults fills AuthFlowID, RegistrationFlowID, RecoveryFlowID, and SignOutFlowID with system // defaults when empty, using the auth flow's handle to locate matching flows of each type. func (s *inboundClientService) resolveFlowDefaults(ctx context.Context, c *inboundmodel.InboundClient) error { if s.flowMgt == nil || c == nil { @@ -625,8 +625,21 @@ func (s *inboundClientService) resolveFlowDefaults(ctx context.Context, c *inbou c.IsRecoveryFlowEnabled = false } if c.SignOutFlowID == "" { - // If a sign-out flow is not defined, disable sign-out for the application. - c.IsSignOutFlowEnabled = false + defaultHandle := config.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle + if defaultHandle != "" { + flow, svcErr := s.flowMgt.GetFlowByHandle(ctx, defaultHandle, providers.FlowTypeSignOut) + switch { + case svcErr == nil: + c.SignOutFlowID = flow.ID + case svcErr.Type == tidcommon.ServerErrorType: + return ErrFKFlowServerError + case svcErr.Code == flowmgt.ErrorFlowNotFound.Code: + // Sign-out is optional; if the default sign-out flow does not exist, leave it + // unconfigured rather than failing. + default: + return ErrFKFlowDefinitionRetrievalFailed + } + } } return nil } @@ -1653,7 +1666,6 @@ func (s *inboundClientService) walkReferencedFlows( c.IsRecoveryFlowEnabled = false case providers.FlowTypeSignOut: c.SignOutFlowID = t.FlowID - c.IsSignOutFlowEnabled = false } continue } diff --git a/backend/internal/inboundclient/service_test.go b/backend/internal/inboundclient/service_test.go index b905cc4c78..0fc9726745 100644 --- a/backend/internal/inboundclient/service_test.go +++ b/backend/internal/inboundclient/service_test.go @@ -1560,6 +1560,81 @@ func (suite *InboundClientServiceTestSuite) TestResolveFlowDefaults_RecoveryFlow assert.Equal(suite.T(), "recovery-1", c.RecoveryFlowID) } +func (suite *InboundClientServiceTestSuite) TestResolveFlowDefaults_AppliesDefaultSignOutFlowWhenEmpty() { + originalSignOutHandle := sysconfig.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle + suite.T().Cleanup(func() { + sysconfig.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle = originalSignOutHandle + }) + sysconfig.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle = testDefaultSignOutFlowHandle + flowMgt := flowmgtmock.NewFlowMgtServiceInterfaceMock(suite.T()) + flowMgt.EXPECT().GetFlowByHandle(mock.Anything, testDefaultSignOutFlowHandle, providers.FlowTypeSignOut). + Return(&providers.CompleteFlowDefinition{ID: "signout-default"}, nil).Once() + svc := &inboundClientService{flowMgt: flowMgt} + c := &inboundmodel.InboundClient{ID: "p1", AuthFlowID: "auth-1"} + err := svc.resolveFlowDefaults(context.Background(), c) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "signout-default", c.SignOutFlowID) +} + +func (suite *InboundClientServiceTestSuite) TestResolveFlowDefaults_KeepsConfiguredSignOutFlow() { + svc := &inboundClientService{flowMgt: flowmgtmock.NewFlowMgtServiceInterfaceMock(suite.T())} + c := &inboundmodel.InboundClient{ID: "p1", AuthFlowID: "auth-1", SignOutFlowID: "signout-1"} + err := svc.resolveFlowDefaults(context.Background(), c) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "signout-1", c.SignOutFlowID) +} + +// The default sign-out flow lookup maps a server error to ErrFKFlowServerError, treats a +// not-found flow as optional (skipped), and surfaces any other retrieval error. +func (suite *InboundClientServiceTestSuite) TestResolveFlowDefaults_DefaultSignOutFlowLookupErrors() { + originalSignOutHandle := sysconfig.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle + suite.T().Cleanup(func() { + sysconfig.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle = originalSignOutHandle + }) + sysconfig.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle = testDefaultSignOutFlowHandle + + tests := []struct { + name string + lookupErr *tidcommon.ServiceError + expectedErr error + }{ + {"server error", &tidcommon.ServiceError{Type: tidcommon.ServerErrorType, Code: "SRV"}, ErrFKFlowServerError}, + {"not found is skipped", &flowmgt.ErrorFlowNotFound, nil}, + { + "other retrieval error", + &tidcommon.ServiceError{Type: tidcommon.ClientErrorType, Code: "OTHER"}, + ErrFKFlowDefinitionRetrievalFailed, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + flowMgt := flowmgtmock.NewFlowMgtServiceInterfaceMock(suite.T()) + flowMgt.EXPECT().GetFlowByHandle(mock.Anything, testDefaultSignOutFlowHandle, providers.FlowTypeSignOut). + Return(nil, tt.lookupErr).Once() + svc := &inboundClientService{flowMgt: flowMgt} + c := &inboundmodel.InboundClient{ID: "p1", AuthFlowID: "auth-1"} + err := svc.resolveFlowDefaults(context.Background(), c) + if tt.expectedErr != nil { + assert.ErrorIs(suite.T(), err, tt.expectedErr) + } else { + assert.NoError(suite.T(), err) + } + assert.Empty(suite.T(), c.SignOutFlowID) + }) + } +} + +// When no default sign-out flow handle is configured, resolution does not attempt a lookup. +func (suite *InboundClientServiceTestSuite) TestResolveFlowDefaults_NoDefaultSignOutFlowHandleConfigured() { + sysconfig.GetServerRuntime().Config.Flow.DefaultSignOutFlowHandle = "" + svc := &inboundClientService{flowMgt: flowmgtmock.NewFlowMgtServiceInterfaceMock(suite.T())} + c := &inboundmodel.InboundClient{ID: "p1", AuthFlowID: "auth-1"} + err := svc.resolveFlowDefaults(context.Background(), c) + assert.NoError(suite.T(), err) + assert.Empty(suite.T(), c.SignOutFlowID) +} + // ----- ResolveInboundAuthProfileHandles ----- func (suite *InboundClientServiceTestSuite) TestResolveInboundAuthProfileHandles_NilFlowMgtIsNoOp() { @@ -2062,6 +2137,8 @@ func (suite *InboundClientServiceTestSuite) TestGetOAuthClientByClientID_NilEnti const testServiceEntityID = "ent-1" +const testDefaultSignOutFlowHandle = "default-flow" + func (suite *InboundClientServiceTestSuite) TestGetOAuthClientByClientID_GetEntityNotFound() { id := testServiceEntityID ep := entityprovidermock.NewEntityProviderInterfaceMock(suite.T()) @@ -2611,10 +2688,9 @@ func (suite *InboundClientServiceTestSuite) TestReconcileReferencedFlows_AutoFil flowMgt.EXPECT().GetReachableCallTargets(mock.Anything, "auth").Return( []flowmgt.CallTarget{{FlowID: "so-b", FlowType: providers.FlowTypeSignOut}}, nil) svc := &inboundClientService{flowMgt: flowMgt} - c := &inboundmodel.InboundClient{AuthFlowID: "auth", IsSignOutFlowEnabled: true} + c := &inboundmodel.InboundClient{AuthFlowID: "auth"} suite.Require().NoError(svc.reconcileReferencedFlows(context.Background(), c)) assert.Equal(suite.T(), "so-b", c.SignOutFlowID) - assert.False(suite.T(), c.IsSignOutFlowEnabled) } func (suite *InboundClientServiceTestSuite) TestReconcileReferencedFlows_MatchingBindingPreservesEnableFlag() { diff --git a/backend/internal/inboundclient/store.go b/backend/internal/inboundclient/store.go index 693c5f54e6..5fb4583d91 100644 --- a/backend/internal/inboundclient/store.go +++ b/backend/internal/inboundclient/store.go @@ -107,7 +107,6 @@ func marshalInboundClient(c inboundmodel.InboundClient) ( propertiesBytes interface{}, isRegistrationEnabledStr string, isRecoveryEnabledStr string, - isSignOutEnabledStr string, recoveryFlowID, signOutFlowID, registrationFlowID, themeID, layoutID interface{}, err error, ) { @@ -120,12 +119,11 @@ func marshalInboundClient(c inboundmodel.InboundClient) ( } propertiesBytes, err = marshalNullableJSON(blob) if err != nil { - return nil, "", "", "", nil, nil, nil, nil, nil, fmt.Errorf("failed to marshal properties: %w", err) + return nil, "", "", nil, nil, nil, nil, nil, fmt.Errorf("failed to marshal properties: %w", err) } isRegistrationEnabledStr = utils.BoolToNumString(c.IsRegistrationFlowEnabled) isRecoveryEnabledStr = utils.BoolToNumString(c.IsRecoveryFlowEnabled) - isSignOutEnabledStr = utils.BoolToNumString(c.IsSignOutFlowEnabled) if c.RecoveryFlowID != "" { recoveryFlowID = c.RecoveryFlowID @@ -143,7 +141,7 @@ func marshalInboundClient(c inboundmodel.InboundClient) ( layoutID = c.LayoutID } - return propertiesBytes, isRegistrationEnabledStr, isRecoveryEnabledStr, isSignOutEnabledStr, + return propertiesBytes, isRegistrationEnabledStr, isRecoveryEnabledStr, recoveryFlowID, signOutFlowID, registrationFlowID, themeID, layoutID, nil } @@ -154,7 +152,7 @@ func (st *store) CreateInboundClient(ctx context.Context, client inboundmodel.In return fmt.Errorf("failed to get database client: %w", err) } - propsBytes, isRegEnabledStr, isRecoveryEnabledStr, isSignOutEnabledStr, recoveryFlowID, + propsBytes, isRegEnabledStr, isRecoveryEnabledStr, recoveryFlowID, signOutFlowID, registrationFlowID, themeID, layoutID, marshalErr := marshalInboundClient(client) if marshalErr != nil { return marshalErr @@ -162,7 +160,7 @@ func (st *store) CreateInboundClient(ctx context.Context, client inboundmodel.In _, err = dbClient.ExecuteContext(ctx, queryCreateInboundClient, client.ID, client.AuthFlowID, registrationFlowID, isRegEnabledStr, - recoveryFlowID, isRecoveryEnabledStr, signOutFlowID, isSignOutEnabledStr, + recoveryFlowID, isRecoveryEnabledStr, signOutFlowID, themeID, layoutID, propsBytes, st.deploymentID) if err != nil { return fmt.Errorf("failed to insert inbound client: %w", err) @@ -354,7 +352,7 @@ func (st *store) UpdateInboundClient(ctx context.Context, client inboundmodel.In return fmt.Errorf("failed to get database client: %w", err) } - propsBytes, isRegEnabledStr, isRecoveryEnabledStr, isSignOutEnabledStr, recoveryFlowID, + propsBytes, isRegEnabledStr, isRecoveryEnabledStr, recoveryFlowID, signOutFlowID, registrationFlowID, themeID, layoutID, marshalErr := marshalInboundClient(client) if marshalErr != nil { return marshalErr @@ -362,7 +360,7 @@ func (st *store) UpdateInboundClient(ctx context.Context, client inboundmodel.In rowsAffected, err := dbClient.ExecuteContext(ctx, queryUpdateInboundClientByEntityID, client.ID, client.AuthFlowID, registrationFlowID, isRegEnabledStr, - recoveryFlowID, isRecoveryEnabledStr, signOutFlowID, isSignOutEnabledStr, + recoveryFlowID, isRecoveryEnabledStr, signOutFlowID, themeID, layoutID, propsBytes, st.deploymentID) if err != nil { return fmt.Errorf("failed to update inbound client: %w", err) @@ -485,11 +483,6 @@ func buildInboundClientFromRow(ctx context.Context, row map[string]interface{}) isRecoveryFlowEnabled = utils.NumStringToBool(val) } - isSignOutFlowEnabled := false - if val := parseStringOrBytesColumn(row, "is_signout_flow_enabled"); val != "" { - isSignOutFlowEnabled = utils.NumStringToBool(val) - } - client := &inboundmodel.InboundClient{ ID: entityID, AuthFlowID: authFlowID, @@ -498,7 +491,6 @@ func buildInboundClientFromRow(ctx context.Context, row map[string]interface{}) RecoveryFlowID: recoveryFlowID, IsRecoveryFlowEnabled: isRecoveryFlowEnabled, SignOutFlowID: signOutFlowID, - IsSignOutFlowEnabled: isSignOutFlowEnabled, ThemeID: themeID, LayoutID: layoutID, } diff --git a/backend/internal/inboundclient/store_constants.go b/backend/internal/inboundclient/store_constants.go index 7fe7501a34..78f86ea10f 100644 --- a/backend/internal/inboundclient/store_constants.go +++ b/backend/internal/inboundclient/store_constants.go @@ -26,9 +26,9 @@ var ( ID: "ASQ-INBC_MGT-01", Query: `INSERT INTO "INBOUND_CLIENT" (ENTITY_ID, AUTH_FLOW_ID, REGISTRATION_FLOW_ID, ` + `IS_REGISTRATION_FLOW_ENABLED, RECOVERY_FLOW_ID, IS_RECOVERY_FLOW_ENABLED, ` + - `SIGNOUT_FLOW_ID, IS_SIGNOUT_FLOW_ENABLED, ` + + `SIGNOUT_FLOW_ID, ` + `THEME_ID, LAYOUT_ID, PROPERTIES, DEPLOYMENT_ID) ` + - `VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, + `VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, } // queryCreateOAuthProfile creates a new OAuth inbound profile entry keyed by entity ID. queryCreateOAuthProfile = dbmodel.DBQuery{ @@ -40,7 +40,7 @@ var ( ID: "ASQ-INBC_MGT-03", Query: `SELECT app.ENTITY_ID, app.AUTH_FLOW_ID, app.REGISTRATION_FLOW_ID, ` + `app.IS_REGISTRATION_FLOW_ENABLED, app.RECOVERY_FLOW_ID, app.IS_RECOVERY_FLOW_ENABLED, ` + - `app.SIGNOUT_FLOW_ID, app.IS_SIGNOUT_FLOW_ENABLED, ` + + `app.SIGNOUT_FLOW_ID, ` + `app.THEME_ID, app.LAYOUT_ID, app.PROPERTIES ` + `FROM "INBOUND_CLIENT" app WHERE app.ENTITY_ID = $1 AND app.DEPLOYMENT_ID = $2`, } @@ -55,7 +55,7 @@ var ( ID: "ASQ-INBC_MGT-06", Query: `SELECT app.ENTITY_ID, app.AUTH_FLOW_ID, app.REGISTRATION_FLOW_ID, ` + `app.IS_REGISTRATION_FLOW_ENABLED, app.RECOVERY_FLOW_ID, app.IS_RECOVERY_FLOW_ENABLED, ` + - `app.SIGNOUT_FLOW_ID, app.IS_SIGNOUT_FLOW_ENABLED, ` + + `app.SIGNOUT_FLOW_ID, ` + `app.THEME_ID, app.LAYOUT_ID, app.PROPERTIES ` + `FROM "INBOUND_CLIENT" app WHERE app.DEPLOYMENT_ID = $1 LIMIT $2`, } @@ -64,9 +64,9 @@ var ( ID: "ASQ-INBC_MGT-07", Query: `UPDATE "INBOUND_CLIENT" SET AUTH_FLOW_ID=$2, REGISTRATION_FLOW_ID=$3, ` + `IS_REGISTRATION_FLOW_ENABLED=$4, RECOVERY_FLOW_ID=$5, IS_RECOVERY_FLOW_ENABLED=$6, ` + - `SIGNOUT_FLOW_ID=$7, IS_SIGNOUT_FLOW_ENABLED=$8, ` + - `THEME_ID=$9, LAYOUT_ID=$10, PROPERTIES=$11 ` + - `WHERE ENTITY_ID = $1 AND DEPLOYMENT_ID = $12`, + `SIGNOUT_FLOW_ID=$7, ` + + `THEME_ID=$8, LAYOUT_ID=$9, PROPERTIES=$10 ` + + `WHERE ENTITY_ID = $1 AND DEPLOYMENT_ID = $11`, } // queryUpdateOAuthProfileByEntityID updates an OAuth inbound profile by entity ID. queryUpdateOAuthProfileByEntityID = dbmodel.DBQuery{ diff --git a/backend/internal/system/i18n/core/defaults.go b/backend/internal/system/i18n/core/defaults.go index c79c6064bc..b041681551 100644 --- a/backend/internal/system/i18n/core/defaults.go +++ b/backend/internal/system/i18n/core/defaults.go @@ -559,8 +559,6 @@ var defaultMessages = map[string]string{ "error.flowexecservice.recovery_not_allowed_description": "Recovery flow is disabled for the application", "error.flowexecservice.registration_not_allowed": "Registration not allowed", "error.flowexecservice.registration_not_allowed_description": "Registration flow is disabled for the application", - "error.flowexecservice.signout_not_allowed": "Sign out not allowed", - "error.flowexecservice.signout_not_allowed_description": "Sign out flow is disabled for the application", "error.flowmetaservice.application_fetch_failed_description": "Failed to retrieve application information", "error.flowmetaservice.application_not_found_description": "The specified application does not exist", "error.flowmetaservice.internal_server_error": "Internal server error", diff --git a/backend/internal/system/importer/service.go b/backend/internal/system/importer/service.go index 2a11cdc528..01f548009b 100644 --- a/backend/internal/system/importer/service.go +++ b/backend/internal/system/importer/service.go @@ -965,7 +965,6 @@ func applicationRequestToDTO(req *appmodel.ApplicationRequestWithID) *appmodel.A IsRecoveryFlowEnabled: req.IsRecoveryFlowEnabled, SignOutFlowID: req.SignOutFlowID, SignOutFlowHandle: req.SignOutFlowHandle, - IsSignOutFlowEnabled: req.IsSignOutFlowEnabled, ThemeID: req.ThemeID, LayoutID: req.LayoutID, Assertion: req.Assertion, diff --git a/backend/pkg/thunderidengine/config/config.go b/backend/pkg/thunderidengine/config/config.go index 1a2e4929a5..1e46e650c1 100644 --- a/backend/pkg/thunderidengine/config/config.go +++ b/backend/pkg/thunderidengine/config/config.go @@ -276,6 +276,7 @@ type TokenExchangeConfig struct { // FlowConfig holds the configuration details for the flow service. type FlowConfig struct { DefaultAuthFlowHandle string `yaml:"default_auth_flow_handle" json:"default_auth_flow_handle"` + DefaultSignOutFlowHandle string `yaml:"default_signout_flow_handle" json:"default_signout_flow_handle"` UserOnboardingFlowHandle string `yaml:"user_onboarding_flow_handle" json:"user_onboarding_flow_handle"` MaxVersionHistory int `yaml:"max_version_history" json:"max_version_history"` AutoInferRegistration bool `yaml:"auto_infer_registration" json:"auto_infer_registration"` diff --git a/backend/pkg/thunderidengine/providers/model.go b/backend/pkg/thunderidengine/providers/model.go index dcd44ea8ac..f9b14390aa 100644 --- a/backend/pkg/thunderidengine/providers/model.go +++ b/backend/pkg/thunderidengine/providers/model.go @@ -698,7 +698,6 @@ type InboundClient struct { RecoveryFlowID string IsRecoveryFlowEnabled bool SignOutFlowID string - IsSignOutFlowEnabled bool ThemeID string LayoutID string Assertion *AssertionConfig @@ -1015,7 +1014,6 @@ type InboundAuthProfile struct { IsRecoveryFlowEnabled bool `json:"isRecoveryFlowEnabled" yaml:"isRecoveryFlowEnabled" jsonschema:"Enable self-service recovery. Set to true to allow users to recover their accounts (e.g., password reset). Requires recoveryFlowId or recoveryFlowHandle to be set."` SignOutFlowID string `json:"signOutFlowId,omitempty" yaml:"signOutFlowId,omitempty" jsonschema:"Sign-out flow ID. Optional. Specifies the flow that terminates the SSO session established by the authentication flow."` SignOutFlowHandle string `json:"signOutFlowHandle,omitempty" yaml:"signOutFlowHandle,omitempty" jsonschema:"Sign-out flow handle. Optional. Alternative to signOutFlowId — resolved to an ID at import time."` - IsSignOutFlowEnabled bool `json:"isSignOutFlowEnabled" yaml:"isSignOutFlowEnabled" jsonschema:"Enable sign-out. Set to true to allow terminating the SSO session for this application. Requires signOutFlowId or signOutFlowHandle to be set."` ThemeID string `json:"themeId,omitempty" yaml:"themeId,omitempty" jsonschema:"Theme configuration ID. Optional. Customizes the visual styling of login pages."` LayoutID string `json:"layoutId,omitempty" yaml:"layoutId,omitempty" jsonschema:"Layout configuration ID. Optional. Customizes the screen structure and component positioning of login pages."` Assertion *AssertionConfig `json:"assertion,omitempty" yaml:"assertion,omitempty" jsonschema:"Assertion configuration. Optional. Customize assertion validity periods and included user attributes."` diff --git a/docs/content/deployment/configuration.mdx b/docs/content/deployment/configuration.mdx index 5e03130c20..19a0d73816 100644 --- a/docs/content/deployment/configuration.mdx +++ b/docs/content/deployment/configuration.mdx @@ -470,6 +470,7 @@ Authentication and registration flow settings. | Setting | Default | Description | |---------|---------|-------------| | `flow.default_auth_flow_handle` | `default-flow` | Handle of the default authentication flow | +| `flow.default_signout_flow_handle` | `default-flow` | Handle of the default sign-out flow applied when an application does not pin its own | | `flow.user_onboarding_flow_handle` | `default-flow` | Handle of the default user onboarding flow | | `flow.max_version_history` | `10` | Maximum number of flow versions to retain | | `flow.auto_infer_registration` | `true` | If `true`, automatically infers registration from authentication flows | diff --git a/frontend/apps/console/src/features/applications/components/edit-application/flows-settings/SignOutFlowSection.tsx b/frontend/apps/console/src/features/applications/components/edit-application/flows-settings/SignOutFlowSection.tsx index 612ac483ba..1d1ee9a6d4 100644 --- a/frontend/apps/console/src/features/applications/components/edit-application/flows-settings/SignOutFlowSection.tsx +++ b/frontend/apps/console/src/features/applications/components/edit-application/flows-settings/SignOutFlowSection.tsx @@ -53,7 +53,6 @@ interface SignOutFlowSectionProps { * Section component for selecting the signout flow. * * Provides: - * - Toggle switch to enable/disable signout * - Autocomplete dropdown to select from available signout flows * - Loading state while fetching flows * @@ -73,20 +72,17 @@ export default function SignOutFlowSection({ return ( onFieldChange('isSignOutFlowEnabled', enabled)} > {(editedApp.signOutFlowId ?? application.signOutFlowId) && ( ( ; -// Mock the SettingsCard so the toggle is a simple button +// Mock the Components vi.mock('@thunderid/components', () => ({ - SettingsCard: ({ - title, - description, - enabled = false, - onToggle = undefined, - children, - }: { - title: string; - description: string; - enabled?: boolean; - onToggle?: (enabled: boolean) => void; - children: React.ReactNode; - }) => ( + SettingsCard: ({title, description, children}: {title: string; description: string; children: React.ReactNode}) => (
{title}
{description}
- {onToggle && ( - - )} {children}
), @@ -63,7 +46,6 @@ describe('SignOutFlowSection', () => { id: 'app-123', name: 'Test App', signOutFlowId: 'signout-flow-1', - isSignOutFlowEnabled: true, } as Application; const mockSignOutFlows = [ @@ -89,15 +71,14 @@ describe('SignOutFlowSection', () => { expect(useGetFlows).toHaveBeenCalledWith({flowType: 'SIGNOUT'}); }); - it('should render the autocomplete and toggle', () => { + it('should render the autocomplete', () => { mockFlows(mockSignOutFlows); render( , ); - expect(screen.getByPlaceholderText('Select a signout flow')).toBeInTheDocument(); - expect(screen.getByTestId('toggle-button')).toHaveTextContent('Toggle: ON'); + expect(screen.getByPlaceholderText('Select a sign-out flow')).toBeInTheDocument(); }); it('should show a loading indicator while fetching flows', () => { @@ -121,7 +102,7 @@ describe('SignOutFlowSection', () => { /> , ); - expect(screen.getByPlaceholderText('Select a signout flow')).toHaveValue('Custom SignOut Flow'); + expect(screen.getByPlaceholderText('Select a sign-out flow')).toHaveValue('Custom SignOut Flow'); }); it('should show the info alert only when a signout flow is selected', () => { @@ -145,32 +126,20 @@ describe('SignOutFlowSection', () => { expect(screen.queryByRole('alert')).not.toBeInTheDocument(); }); - it('should call onFieldChange when the toggle is clicked', async () => { - const user = userEvent.setup(); - mockFlows(mockSignOutFlows); - render( - - - , - ); - await user.click(screen.getByTestId('toggle-button')); - expect(mockOnFieldChange).toHaveBeenCalledWith('isSignOutFlowEnabled', false); - }); - it('should call onFieldChange with the selected signout flow id', async () => { const user = userEvent.setup(); mockFlows(mockSignOutFlows); render( , ); - await user.click(screen.getByPlaceholderText('Select a signout flow')); + await user.click(screen.getByPlaceholderText('Select a sign-out flow')); await waitFor(() => { expect(screen.getByText('Custom SignOut Flow')).toBeInTheDocument(); }); @@ -190,6 +159,6 @@ describe('SignOutFlowSection', () => { /> , ); - expect(screen.getByPlaceholderText('Select a signout flow')).toBeDisabled(); + expect(screen.getByPlaceholderText('Select a sign-out flow')).toBeDisabled(); }); }); diff --git a/frontend/apps/console/src/features/applications/models/application.ts b/frontend/apps/console/src/features/applications/models/application.ts index 0c324d5a6c..37d9834d5d 100644 --- a/frontend/apps/console/src/features/applications/models/application.ts +++ b/frontend/apps/console/src/features/applications/models/application.ts @@ -226,12 +226,6 @@ export interface Application { */ signOutFlowId?: string; - /** - * Whether signout flow is enabled - * @example true - */ - isSignOutFlowEnabled?: boolean; - /** * User attributes to include * @example ['email', 'username', 'given_name', 'family_name', 'roles'] diff --git a/frontend/packages/i18n/src/locales/en-US.ts b/frontend/packages/i18n/src/locales/en-US.ts index 7b08679fbd..29f7e378b6 100644 --- a/frontend/packages/i18n/src/locales/en-US.ts +++ b/frontend/packages/i18n/src/locales/en-US.ts @@ -2603,7 +2603,7 @@ const translations = { 'edit.flows.recoveryFlow.alert': 'To modify the selected flow, <0>open the flow builder. To create a new flow, visit the <1>Flows page.', 'edit.flows.labels.signOutFlow': 'Sign Out Flow', - 'edit.flows.labels.signOutFlow.description': 'Confirm and terminate the SSO session when people sign out.', + 'edit.flows.labels.signOutFlow.description': 'Choose the flow that handles user sign-out and session termination.', 'edit.flows.signOutFlow.placeholder': 'Select a sign-out flow', 'edit.flows.signOutFlow.hint': 'Select the flow that runs when a user signs out of this {{entity}}.', 'edit.flows.signOutFlow.alert': diff --git a/install/helm/conf/deployment.yaml b/install/helm/conf/deployment.yaml index 01328263a3..7975566c93 100644 --- a/install/helm/conf/deployment.yaml +++ b/install/helm/conf/deployment.yaml @@ -262,6 +262,7 @@ oauth: flow: default_auth_flow_handle: {{ .Values.configuration.flow.defaultAuthFlowHandle | quote }} + default_signout_flow_handle: {{ .Values.configuration.flow.defaultSignOutFlowHandle | quote }} max_version_history: {{ .Values.configuration.flow.maxVersionHistory }} auto_infer_registration: {{ .Values.configuration.flow.autoInferRegistration }} {{- if .Values.configuration.flow.executors }} diff --git a/install/helm/values.yaml b/install/helm/values.yaml index 5dd8a5a13c..51e6f5a00a 100644 --- a/install/helm/values.yaml +++ b/install/helm/values.yaml @@ -439,6 +439,7 @@ configuration: # Flow configuration flow: defaultAuthFlowHandle: "default-flow" + defaultSignOutFlowHandle: "default-flow" maxVersionHistory: 3 autoInferRegistration: true # Optional whitelist of built-in executor names to register at startup. diff --git a/install/openchoreo/thunderid-oc-resourcetype/samples/resource.yaml b/install/openchoreo/thunderid-oc-resourcetype/samples/resource.yaml index c29e0b4a7e..22113d93e8 100644 --- a/install/openchoreo/thunderid-oc-resourcetype/samples/resource.yaml +++ b/install/openchoreo/thunderid-oc-resourcetype/samples/resource.yaml @@ -122,6 +122,7 @@ spec: # dbType: sqlite # sqlite | postgres # port: 8090 # server port (container, Service, route, probe) # defaultAuthFlowHandle: "" # flow used when an application pins no authFlowId + # defaultSignOutFlowHandle: "" # flow used when an application pins no signOutFlowId # declarativeResourcesEnabled: true # false: services default to database-backed stores # gate: # clientBase: "/gate" diff --git a/install/openchoreo/thunderid-oc-resourcetype/templates/thunderid-resourcetype.yaml b/install/openchoreo/thunderid-oc-resourcetype/templates/thunderid-resourcetype.yaml index 077fd1997a..4921c59e09 100644 --- a/install/openchoreo/thunderid-oc-resourcetype/templates/thunderid-resourcetype.yaml +++ b/install/openchoreo/thunderid-oc-resourcetype/templates/thunderid-resourcetype.yaml @@ -188,6 +188,11 @@ spec: defaultAuthFlowHandle: type: string default: "" + # Handle of the flow used when an application does not pin its + # own signOutFlowId. Empty inherits the server default. + defaultSignOutFlowHandle: + type: string + default: "" # Global declarative mode: services without an explicit # stores.* override behave as "declarative" when true. Set to # false to opt services back to database-backed stores by @@ -466,6 +471,7 @@ spec: flow: default_auth_flow_handle: "${parameters.runtime.defaultAuthFlowHandle}" + default_signout_flow_handle: "${parameters.runtime.defaultSignOutFlowHandle}" max_version_history: 3 auto_infer_registration: true store: "${parameters.runtime.stores.flow}" @@ -585,6 +591,7 @@ spec: flow: default_auth_flow_handle: "${parameters.runtime.defaultAuthFlowHandle}" + default_signout_flow_handle: "${parameters.runtime.defaultSignOutFlowHandle}" max_version_history: 3 auto_infer_registration: true store: "${parameters.runtime.stores.flow}" diff --git a/tests/integration/oauth/sso/suite_test.go b/tests/integration/oauth/sso/suite_test.go index 5001ce678b..d969be94e6 100644 --- a/tests/integration/oauth/sso/suite_test.go +++ b/tests/integration/oauth/sso/suite_test.go @@ -350,7 +350,6 @@ func (ts *SSOLogoutTestSuite) createApplication() string { "authFlowId": ts.authFlowID, "isRegistrationFlowEnabled": false, "signOutFlowId": ts.signOutFlowID, - "isSignOutFlowEnabled": true, "allowedUserTypes": []string{testUserType.Name}, "inboundAuthConfig": []map[string]interface{}{ {