Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions backend/cmd/server/bootstrap/01-default-resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions backend/cmd/server/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion backend/dbscripts/configdb/postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion backend/dbscripts/configdb/sqlite.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 5 additions & 6 deletions backend/internal/actorprovider/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}

Expand Down
8 changes: 3 additions & 5 deletions backend/internal/actorprovider/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 0 additions & 1 deletion backend/internal/application/declarative_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 0 additions & 5 deletions backend/internal/application/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 0 additions & 2 deletions backend/internal/application/model/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ type BasicApplicationDTO struct {
RecoveryFlowID string
IsRecoveryFlowEnabled bool
SignOutFlowID string
IsSignOutFlowEnabled bool
ThemeID string
LayoutID string
Template string
Expand Down Expand Up @@ -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."`
Expand Down
6 changes: 0 additions & 6 deletions backend/internal/application/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
14 changes: 0 additions & 14 deletions backend/internal/flow/flowexec/error_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
}
4 changes: 1 addition & 3 deletions backend/internal/flow/flowexec/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
17 changes: 17 additions & 0 deletions backend/internal/flow/flowexec/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 16 additions & 4 deletions backend/internal/inboundclient/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
return nil
}
Expand Down Expand Up @@ -1653,7 +1666,6 @@ func (s *inboundClientService) walkReferencedFlows(
c.IsRecoveryFlowEnabled = false
case providers.FlowTypeSignOut:
c.SignOutFlowID = t.FlowID
c.IsSignOutFlowEnabled = false
}
continue
}
Expand Down
80 changes: 78 additions & 2 deletions backend/internal/inboundclient/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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() {
Expand Down
Loading
Loading