diff --git a/consent/manager.go b/consent/manager.go index 69b62ed8b9e..45cb00240b7 100644 --- a/consent/manager.go +++ b/consent/manager.go @@ -41,7 +41,7 @@ type ( // Cookie management GetRememberedLoginSession(ctx context.Context, loginSessionFromCookie *flow.LoginSession, id string) (*flow.LoginSession, error) CreateLoginSession(ctx context.Context, session *flow.LoginSession) error - DeleteLoginSession(ctx context.Context, id string) error + DeleteLoginSession(ctx context.Context, id string) (deletedSession *flow.LoginSession, err error) RevokeSubjectLoginSession(ctx context.Context, user string) error ConfirmLoginSession(ctx context.Context, session *flow.LoginSession, id string, authTime time.Time, subject string, remember bool) error diff --git a/consent/manager_test_helpers.go b/consent/manager_test_helpers.go index 2d84bf071d5..588aef92174 100644 --- a/consent/manager_test_helpers.go +++ b/consent/manager_test_helpers.go @@ -324,8 +324,12 @@ func TestHelperNID(r interface { require.NoError(t, err) require.Error(t, t2InvalidNID.ConfirmLoginSession(ctx, &testLS, testLS.ID, time.Now(), testLS.Subject, true)) require.NoError(t, t1ValidNID.ConfirmLoginSession(ctx, &testLS, testLS.ID, time.Now(), testLS.Subject, true)) - require.Error(t, t2InvalidNID.DeleteLoginSession(ctx, testLS.ID)) - require.NoError(t, t1ValidNID.DeleteLoginSession(ctx, testLS.ID)) + ls, err := t2InvalidNID.DeleteLoginSession(ctx, testLS.ID) + require.Error(t, err) + assert.Nil(t, ls) + ls, err = t1ValidNID.DeleteLoginSession(ctx, testLS.ID) + require.NoError(t, err) + assert.Equal(t, testLS.ID, ls.ID) } } @@ -429,8 +433,9 @@ func ManagerTests(deps Deps, m Manager, clientManager client.Manager, fositeMana }, } { t.Run("case=delete-get-"+tc.id, func(t *testing.T) { - err := m.DeleteLoginSession(ctx, tc.id) + ls, err := m.DeleteLoginSession(ctx, tc.id) require.NoError(t, err) + assert.EqualValues(t, tc.id, ls.ID) _, err = m.GetRememberedLoginSession(ctx, nil, tc.id) require.Error(t, err) @@ -1083,7 +1088,8 @@ func ManagerTests(deps Deps, m Manager, clientManager client.Manager, fositeMana require.NoError(t, err) assert.EqualValues(t, expected.ID, result.ID) - require.NoError(t, m.DeleteLoginSession(ctx, s.ID)) + _, err = m.DeleteLoginSession(ctx, s.ID) + require.NoError(t, err) result, err = m.GetConsentRequest(ctx, expected.ID) require.NoError(t, err) diff --git a/consent/registry.go b/consent/registry.go index 447e345ee5b..59e626efaad 100644 --- a/consent/registry.go +++ b/consent/registry.go @@ -9,6 +9,7 @@ import ( "github.com/ory/fosite/handler/openid" "github.com/ory/hydra/v2/aead" "github.com/ory/hydra/v2/client" + "github.com/ory/hydra/v2/internal/kratos" "github.com/ory/hydra/v2/x" ) @@ -17,6 +18,7 @@ type InternalRegistry interface { x.RegistryCookieStore x.RegistryLogger x.HTTPClientProvider + kratos.Provider Registry client.Registry diff --git a/consent/strategy_default.go b/consent/strategy_default.go index cc30d98cc5d..97a6efddd48 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -312,7 +312,9 @@ func (s *DefaultStrategy) revokeAuthenticationSession(ctx context.Context, w htt return nil } - return s.r.ConsentManager().DeleteLoginSession(r.Context(), sid) + _, err = s.r.ConsentManager().DeleteLoginSession(r.Context(), sid) + + return err } func (s *DefaultStrategy) revokeAuthenticationCookie(w http.ResponseWriter, r *http.Request, ss sessions.Store) (string, error) { @@ -457,6 +459,7 @@ func (s *DefaultStrategy) verifyAuthentication( return nil, fosite.ErrAccessDenied.WithHint("The login session cookie was not found or malformed.") } + loginSession.KratosSessionID = f.KratosSessionID if err := s.r.ConsentManager().ConfirmLoginSession(ctx, loginSession, sessionID, time.Time(session.AuthenticatedAt), session.Subject, session.Remember); err != nil { return nil, err } @@ -730,7 +733,8 @@ func (s *DefaultStrategy) generateFrontChannelLogoutURLs(ctx context.Context, su return urls, nil } -func (s *DefaultStrategy) executeBackChannelLogout(ctx context.Context, r *http.Request, subject, sid string) error { +func (s *DefaultStrategy) executeBackChannelLogout(r *http.Request, subject, sid string) error { + ctx := r.Context() clients, err := s.r.ConsentManager().ListUserAuthenticatedClientsWithBackChannelLogout(ctx, subject, sid) if err != nil { return err @@ -990,8 +994,9 @@ func (s *DefaultStrategy) issueLogoutVerifier(ctx context.Context, w http.Respon return nil, errorsx.WithStack(ErrAbortOAuth2Request) } -func (s *DefaultStrategy) performBackChannelLogoutAndDeleteSession(_ context.Context, r *http.Request, subject string, sid string) error { - if err := s.executeBackChannelLogout(r.Context(), r, subject, sid); err != nil { +func (s *DefaultStrategy) performBackChannelLogoutAndDeleteSession(r *http.Request, subject string, sid string) error { + ctx := r.Context() + if err := s.executeBackChannelLogout(r, subject, sid); err != nil { return err } @@ -1000,10 +1005,12 @@ func (s *DefaultStrategy) performBackChannelLogoutAndDeleteSession(_ context.Con // // executeBackChannelLogout only fails on system errors so not on URL errors, so this should be fine // even if an upstream URL fails! - if err := s.r.ConsentManager().DeleteLoginSession(r.Context(), sid); errors.Is(err, sqlcon.ErrNoRows) { + if session, err := s.r.ConsentManager().DeleteLoginSession(ctx, sid); errors.Is(err, sqlcon.ErrNoRows) { // This is ok (session probably already revoked), do nothing! } else if err != nil { return err + } else { + _ = s.r.Kratos().DisableSession(ctx, session.KratosSessionID.String()) } return nil @@ -1058,7 +1065,7 @@ func (s *DefaultStrategy) completeLogout(ctx context.Context, w http.ResponseWri return nil, err } - if err := s.performBackChannelLogoutAndDeleteSession(r.Context(), r, lr.Subject, lr.SessionID); err != nil { + if err := s.performBackChannelLogoutAndDeleteSession(r, lr.Subject, lr.SessionID); err != nil { return nil, err } @@ -1095,7 +1102,7 @@ func (s *DefaultStrategy) HandleHeadlessLogout(ctx context.Context, _ http.Respo return lsErr } - if err := s.performBackChannelLogoutAndDeleteSession(r.Context(), r, loginSession.Subject, sid); err != nil { + if err := s.performBackChannelLogoutAndDeleteSession(r, loginSession.Subject, sid); err != nil { return err } diff --git a/consent/strategy_default_test.go b/consent/strategy_default_test.go index 3d8efab8130..870c96ffb50 100644 --- a/consent/strategy_default_test.go +++ b/consent/strategy_default_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/cookiejar" "net/http/httptest" + "net/url" "testing" hydra "github.com/ory/hydra-client-go/v2" @@ -17,8 +18,6 @@ import ( "github.com/ory/fosite/token/jwt" "github.com/ory/x/urlx" - "net/url" - "github.com/google/uuid" "github.com/tidwall/gjson" diff --git a/consent/strategy_logout_test.go b/consent/strategy_logout_test.go index 3bd5f911811..0d36d6cce05 100644 --- a/consent/strategy_logout_test.go +++ b/consent/strategy_logout_test.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "github.com/ory/hydra/v2/internal/kratos" "github.com/ory/x/pointerx" "github.com/stretchr/testify/assert" @@ -35,9 +36,11 @@ import ( func TestLogoutFlows(t *testing.T) { ctx := context.Background() + fakeKratos := kratos.NewFake() reg := internal.NewMockedRegistry(t, &contextx.Default{}) reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "opaque") reg.Config().MustSet(ctx, config.KeyConsentRequestMaxAge, time.Hour) + reg.WithKratos(fakeKratos) defaultRedirectedMessage := "redirected to default server" postLogoutCallback := func(w http.ResponseWriter, r *http.Request) { @@ -181,7 +184,7 @@ func TestLogoutFlows(t *testing.T) { checkAndAcceptLoginHandler(t, adminApi, subject, func(t *testing.T, res *hydra.OAuth2LoginRequest, err error) hydra.AcceptOAuth2LoginRequest { require.NoError(t, err) //res.Payload.SessionID - return hydra.AcceptOAuth2LoginRequest{Remember: pointerx.Bool(true)} + return hydra.AcceptOAuth2LoginRequest{Remember: pointerx.Bool(true), SessionId: pointerx.Ptr(kratos.FakeSessionID)} }), checkAndAcceptConsentHandler(t, adminApi, func(t *testing.T, res *hydra.OAuth2ConsentRequest, err error) hydra.AcceptOAuth2ConsentRequest { require.NoError(t, err) @@ -476,6 +479,7 @@ func TestLogoutFlows(t *testing.T) { }) t.Run("case=should return to default post logout because session was revoked in browser context", func(t *testing.T) { + fakeKratos.Reset() c := createSampleClient(t) sid := make(chan string) acceptLoginAsAndWatchSid(t, subject, sid) @@ -518,9 +522,13 @@ func TestLogoutFlows(t *testing.T) { assert.NotEmpty(t, res.Request.URL.Query().Get("code")) wg.Wait() + + assert.True(t, fakeKratos.DisableSessionWasCalled) + assert.Equal(t, fakeKratos.LastDisabledSession, kratos.FakeSessionID) }) t.Run("case=should execute backchannel logout in headless flow with sid", func(t *testing.T) { + fakeKratos.Reset() numSidConsumers := 2 sid := make(chan string, numSidConsumers) acceptLoginAsAndWatchSidForConsumers(t, subject, sid, true, numSidConsumers) @@ -535,22 +543,31 @@ func TestLogoutFlows(t *testing.T) { logoutViaHeadlessAndExpectNoContent(t, createBrowserWithSession(t, c), url.Values{"sid": {<-sid}}) backChannelWG.Wait() // we want to ensure that all back channels have been called! + assert.True(t, fakeKratos.DisableSessionWasCalled) + assert.Equal(t, fakeKratos.LastDisabledSession, kratos.FakeSessionID) }) t.Run("case=should logout in headless flow with non-existing sid", func(t *testing.T) { + fakeKratos.Reset() logoutViaHeadlessAndExpectNoContent(t, browserWithoutSession, url.Values{"sid": {"non-existing-sid"}}) + assert.False(t, fakeKratos.DisableSessionWasCalled) }) t.Run("case=should logout in headless flow with session that has remember=false", func(t *testing.T) { + fakeKratos.Reset() sid := make(chan string) acceptLoginAsAndWatchSidForConsumers(t, subject, sid, false, 1) c := createSampleClient(t) logoutViaHeadlessAndExpectNoContent(t, createBrowserWithSession(t, c), url.Values{"sid": {<-sid}}) + assert.True(t, fakeKratos.DisableSessionWasCalled) + assert.Equal(t, fakeKratos.LastDisabledSession, kratos.FakeSessionID) }) t.Run("case=should fail headless logout because neither sid nor subject were provided", func(t *testing.T) { + fakeKratos.Reset() logoutViaHeadlessAndExpectError(t, browserWithoutSession, url.Values{}, `Either 'subject' or 'sid' query parameters need to be defined.`) + assert.False(t, fakeKratos.DisableSessionWasCalled) }) } diff --git a/driver/config/provider.go b/driver/config/provider.go index 509ba9c9b72..ba0e75df457 100644 --- a/driver/config/provider.go +++ b/driver/config/provider.go @@ -80,6 +80,7 @@ const ( KeyPublicURL = "urls.self.public" KeyAdminURL = "urls.self.admin" KeyIssuerURL = "urls.self.issuer" + KeyKratosAdminURL = "urls.kratos.admin" KeyAccessTokenStrategy = "strategies.access_token" KeyJWTScopeClaimStrategy = "strategies.jwt.scope_claim" KeyDBIgnoreUnknownTableColumns = "db.ignore_unknown_table_columns" @@ -388,6 +389,12 @@ func (p *DefaultProvider) IssuerURL(ctx context.Context) *url.URL { ) } +func (p *DefaultProvider) KratosAdminURL(ctx context.Context) (*url.URL, bool) { + u := p.getProvider(ctx).RequestURIF(KeyKratosAdminURL, nil) + + return u, u != nil +} + func (p *DefaultProvider) OAuth2ClientRegistrationURL(ctx context.Context) *url.URL { return p.getProvider(ctx).RequestURIF(KeyOAuth2ClientRegistrationURL, new(url.URL)) } diff --git a/driver/registry.go b/driver/registry.go index 4c956c4cd48..81ef48a2e19 100644 --- a/driver/registry.go +++ b/driver/registry.go @@ -10,6 +10,7 @@ import ( "go.opentelemetry.io/otel/trace" + "github.com/ory/hydra/v2/internal/kratos" "github.com/ory/x/httprouterx" "github.com/ory/hydra/v2/aead" @@ -53,6 +54,7 @@ type Registry interface { WithLogger(l *logrusx.Logger) Registry WithTracer(t trace.Tracer) Registry WithTracerWrapper(TracerWrapper) Registry + WithKratos(k kratos.Client) Registry x.HTTPClientProvider GetJWKSFetcherStrategy() fosite.JWKSFetcherStrategy @@ -71,6 +73,8 @@ type Registry interface { x.TracingProvider FlowCipher() *aead.XChaCha20Poly1305 + kratos.Provider + RegisterRoutes(ctx context.Context, admin *httprouterx.RouterAdmin, public *httprouterx.RouterPublic) ClientHandler() *client.Handler KeyHandler() *jwk.Handler diff --git a/driver/registry_base.go b/driver/registry_base.go index 910084ced52..0f85fb50be5 100644 --- a/driver/registry_base.go +++ b/driver/registry_base.go @@ -28,6 +28,7 @@ import ( "github.com/ory/hydra/v2/driver/config" "github.com/ory/hydra/v2/fositex" "github.com/ory/hydra/v2/hsm" + "github.com/ory/hydra/v2/internal/kratos" "github.com/ory/hydra/v2/jwk" "github.com/ory/hydra/v2/oauth2" "github.com/ory/hydra/v2/oauth2/trust" @@ -88,6 +89,7 @@ type RegistryBase struct { hmacs *foauth2.HMACSHAStrategy fc *fositex.Config publicCORS *cors.Cors + kratos kratos.Client } func (m *RegistryBase) GetJWKSFetcherStrategy() fosite.JWKSFetcherStrategy { @@ -201,6 +203,11 @@ func (m *RegistryBase) WithTracerWrapper(wrapper TracerWrapper) Registry { return m.r } +func (m *RegistryBase) WithKratos(k kratos.Client) Registry { + m.kratos = k + return m.r +} + func (m *RegistryBase) Logger() *logrusx.Logger { if m.l == nil { m.l = logrusx.New("Ory Hydra", m.BuildVersion()) @@ -552,3 +559,10 @@ func (m *RegistryBase) HSMContext() hsm.Context { func (m *RegistrySQL) ClientAuthenticator() x.ClientAuthenticator { return m.OAuth2Provider().(*fosite.Fosite) } + +func (m *RegistryBase) Kratos() kratos.Client { + if m.kratos == nil { + m.kratos = kratos.New(m) + } + return m.kratos +} diff --git a/flow/consent_types.go b/flow/consent_types.go index 89e56ef8aa7..6986bdfbd1e 100644 --- a/flow/consent_types.go +++ b/flow/consent_types.go @@ -42,11 +42,12 @@ type OAuth2RedirectTo struct { // swagger:ignore type LoginSession struct { - ID string `db:"id"` - NID uuid.UUID `db:"nid"` - AuthenticatedAt sqlxx.NullTime `db:"authenticated_at"` - Subject string `db:"subject"` - Remember bool `db:"remember"` + ID string `db:"id"` + NID uuid.UUID `db:"nid"` + AuthenticatedAt sqlxx.NullTime `db:"authenticated_at"` + Subject string `db:"subject"` + KratosSessionID sqlxx.NullString `db:"kratos_session_id"` + Remember bool `db:"remember"` } func (LoginSession) TableName() string { @@ -292,6 +293,12 @@ type HandledLoginRequest struct { // required: true Subject string `json:"subject"` + // KratosSessionID is the session ID of the end-user that authenticated. + // If specified, we will use this value to propagate the logout. + // + // required: false + KratosSessionID string `json:"session_id"` + // ForceSubjectIdentifier forces the "pairwise" user ID of the end-user that authenticated. The "pairwise" user ID refers to the // (Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID // Connect specification. It allows you to set an obfuscated subject ("user") identifier that is unique to the client. diff --git a/flow/flow.go b/flow/flow.go index 0868e7f5f14..566948066ca 100644 --- a/flow/flow.go +++ b/flow/flow.go @@ -136,6 +136,8 @@ type Flow struct { // channel logout. Its value can generally be used to associate consecutive login requests by a certain user. SessionID sqlxx.NullString `db:"login_session_id"` + KratosSessionID sqlxx.NullString `db:"kratos_session_id"` + LoginVerifier string `db:"login_verifier"` LoginCSRF string `db:"login_csrf"` @@ -291,6 +293,7 @@ func (f *Flow) HandleLoginRequest(h *HandledLoginRequest) error { f.ForceSubjectIdentifier = h.ForceSubjectIdentifier f.LoginError = h.Error + f.KratosSessionID = sqlxx.NullString(h.KratosSessionID) f.LoginRemember = h.Remember f.LoginRememberFor = h.RememberFor f.LoginExtendSessionLifespan = h.ExtendSessionLifespan @@ -311,6 +314,7 @@ func (f *Flow) GetHandledLoginRequest() HandledLoginRequest { ACR: f.ACR, AMR: f.AMR, Subject: f.Subject, + KratosSessionID: f.KratosSessionID.String(), ForceSubjectIdentifier: f.ForceSubjectIdentifier, Context: f.Context, WasHandled: f.LoginWasUsed, diff --git a/flow/flow_test.go b/flow/flow_test.go index 6358c47595b..9cfe514ca50 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -42,6 +42,7 @@ func (f *Flow) setHandledLoginRequest(r *HandledLoginRequest) { f.ACR = r.ACR f.AMR = r.AMR f.Subject = r.Subject + f.KratosSessionID = sqlxx.NullString(r.KratosSessionID) f.ForceSubjectIdentifier = r.ForceSubjectIdentifier f.Context = r.Context f.LoginWasUsed = r.WasHandled diff --git a/go.mod b/go.mod index 35d4722dd2d..c5eb822b48a 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/ory/herodot v0.10.3-0.20230626083119-d7e5192f0d88 github.com/ory/hydra-client-go/v2 v2.1.1 github.com/ory/jsonschema/v3 v3.0.8 + github.com/ory/kratos-client-go v0.13.1 github.com/ory/x v0.0.575 github.com/pborman/uuid v1.2.1 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 2d89b2a5f3f..ece661bf778 100644 --- a/go.sum +++ b/go.sum @@ -646,6 +646,8 @@ github.com/ory/herodot v0.10.3-0.20230626083119-d7e5192f0d88 h1:J0CIFKdpUeqKbVMw github.com/ory/herodot v0.10.3-0.20230626083119-d7e5192f0d88/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= github.com/ory/jsonschema/v3 v3.0.8 h1:Ssdb3eJ4lDZ/+XnGkvQS/te0p+EkolqwTsDOCxr/FmU= github.com/ory/jsonschema/v3 v3.0.8/go.mod h1:ZPzqjDkwd3QTnb2Z6PAS+OTvBE2x5i6m25wCGx54W/0= +github.com/ory/kratos-client-go v0.13.1 h1:o+pFV9ZRMFSBa4QeNJYbJeLz036UWU4p+7yfKghK+0E= +github.com/ory/kratos-client-go v0.13.1/go.mod h1:hkrFJuHSBQw+qN6Ks0faOAYhAKwtpjvhCZzsQ7g/Ufc= github.com/ory/x v0.0.575 h1:LvOeR+YlJ6/JtvIJvSwMoDBY/i3GACUe7HpWXHGNUTA= github.com/ory/x v0.0.575/go.mod h1:aeJFTlvDLGYSABzPS3z5SeLcYC52Ek7uGZiuYGcTMSU= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= diff --git a/internal/httpclient/api/openapi.yaml b/internal/httpclient/api/openapi.yaml index 0841ffa5e9d..c3cc32a129f 100644 --- a/internal/httpclient/api/openapi.yaml +++ b/internal/httpclient/api/openapi.yaml @@ -2001,6 +2001,10 @@ components: \ for the duration of the browser session (using a session cookie)." format: int64 type: integer + session_id: + description: "KratosSessionID is the session ID of the end-user that authenticated.\n\ + If specified, we will use this value to propagate the logout." + type: string subject: description: Subject is the user ID of the end-user that authenticated. type: string diff --git a/internal/httpclient/docs/AcceptOAuth2LoginRequest.md b/internal/httpclient/docs/AcceptOAuth2LoginRequest.md index 5c41c4923bd..5a885b50aea 100644 --- a/internal/httpclient/docs/AcceptOAuth2LoginRequest.md +++ b/internal/httpclient/docs/AcceptOAuth2LoginRequest.md @@ -11,6 +11,7 @@ Name | Type | Description | Notes **ForceSubjectIdentifier** | Pointer to **string** | ForceSubjectIdentifier forces the \"pairwise\" user ID of the end-user that authenticated. The \"pairwise\" user ID refers to the (Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID Connect specification. It allows you to set an obfuscated subject (\"user\") identifier that is unique to the client. Please note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the sub claim in the OAuth 2.0 Introspection. Per default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself you can use this field. Please note that setting this field has no effect if `pairwise` is not configured in ORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via `subject_type` key in the client's configuration). Please also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies that you have to compute this value on every authentication process (probably depending on the client ID or some other unique value). If you fail to compute the proper value, then authentication processes which have id_token_hint set might fail. | [optional] **Remember** | Pointer to **bool** | Remember, if set to true, tells ORY Hydra to remember this user by telling the user agent (browser) to store a cookie with authentication data. If the same user performs another OAuth 2.0 Authorization Request, he/she will not be asked to log in again. | [optional] **RememberFor** | Pointer to **int64** | RememberFor sets how long the authentication should be remembered for in seconds. If set to `0`, the authorization will be remembered for the duration of the browser session (using a session cookie). | [optional] +**SessionId** | Pointer to **string** | KratosSessionID is the session ID of the end-user that authenticated. If specified, we will use this value to propagate the logout. | [optional] **Subject** | **string** | Subject is the user ID of the end-user that authenticated. | ## Methods @@ -217,6 +218,31 @@ SetRememberFor sets RememberFor field to given value. HasRememberFor returns a boolean if a field has been set. +### GetSessionId + +`func (o *AcceptOAuth2LoginRequest) GetSessionId() string` + +GetSessionId returns the SessionId field if non-nil, zero value otherwise. + +### GetSessionIdOk + +`func (o *AcceptOAuth2LoginRequest) GetSessionIdOk() (*string, bool)` + +GetSessionIdOk returns a tuple with the SessionId field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetSessionId + +`func (o *AcceptOAuth2LoginRequest) SetSessionId(v string)` + +SetSessionId sets SessionId field to given value. + +### HasSessionId + +`func (o *AcceptOAuth2LoginRequest) HasSessionId() bool` + +HasSessionId returns a boolean if a field has been set. + ### GetSubject `func (o *AcceptOAuth2LoginRequest) GetSubject() string` diff --git a/internal/httpclient/model_accept_o_auth2_login_request.go b/internal/httpclient/model_accept_o_auth2_login_request.go index 36720ee04b3..8194a09614a 100644 --- a/internal/httpclient/model_accept_o_auth2_login_request.go +++ b/internal/httpclient/model_accept_o_auth2_login_request.go @@ -29,6 +29,8 @@ type AcceptOAuth2LoginRequest struct { Remember *bool `json:"remember,omitempty"` // RememberFor sets how long the authentication should be remembered for in seconds. If set to `0`, the authorization will be remembered for the duration of the browser session (using a session cookie). RememberFor *int64 `json:"remember_for,omitempty"` + // KratosSessionID is the session ID of the end-user that authenticated. If specified, we will use this value to propagate the logout. + SessionId *string `json:"session_id,omitempty"` // Subject is the user ID of the end-user that authenticated. Subject string `json:"subject"` } @@ -276,6 +278,38 @@ func (o *AcceptOAuth2LoginRequest) SetRememberFor(v int64) { o.RememberFor = &v } +// GetSessionId returns the SessionId field value if set, zero value otherwise. +func (o *AcceptOAuth2LoginRequest) GetSessionId() string { + if o == nil || o.SessionId == nil { + var ret string + return ret + } + return *o.SessionId +} + +// GetSessionIdOk returns a tuple with the SessionId field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *AcceptOAuth2LoginRequest) GetSessionIdOk() (*string, bool) { + if o == nil || o.SessionId == nil { + return nil, false + } + return o.SessionId, true +} + +// HasSessionId returns a boolean if a field has been set. +func (o *AcceptOAuth2LoginRequest) HasSessionId() bool { + if o != nil && o.SessionId != nil { + return true + } + + return false +} + +// SetSessionId gets a reference to the given string and assigns it to the SessionId field. +func (o *AcceptOAuth2LoginRequest) SetSessionId(v string) { + o.SessionId = &v +} + // GetSubject returns the Subject field value func (o *AcceptOAuth2LoginRequest) GetSubject() string { if o == nil { @@ -323,6 +357,9 @@ func (o AcceptOAuth2LoginRequest) MarshalJSON() ([]byte, error) { if o.RememberFor != nil { toSerialize["remember_for"] = o.RememberFor } + if o.SessionId != nil { + toSerialize["session_id"] = o.SessionId + } if true { toSerialize["subject"] = o.Subject } diff --git a/internal/kratos/fake_kratos.go b/internal/kratos/fake_kratos.go new file mode 100644 index 00000000000..a356146e4df --- /dev/null +++ b/internal/kratos/fake_kratos.go @@ -0,0 +1,35 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package kratos + +import "context" + +type ( + FakeKratos struct { + DisableSessionWasCalled bool + LastDisabledSession string + } +) + +const ( + FakeSessionID = "fake-kratos-session-id" +) + +var _ Client = new(FakeKratos) + +func NewFake() *FakeKratos { + return &FakeKratos{} +} + +func (f *FakeKratos) DisableSession(_ context.Context, kratosSessionID string) error { + f.DisableSessionWasCalled = true + f.LastDisabledSession = kratosSessionID + + return nil +} + +func (f *FakeKratos) Reset() { + f.DisableSessionWasCalled = false + f.LastDisabledSession = "" +} diff --git a/internal/kratos/kratos.go b/internal/kratos/kratos.go new file mode 100644 index 00000000000..d7db5b771c8 --- /dev/null +++ b/internal/kratos/kratos.go @@ -0,0 +1,69 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package kratos + +import ( + "context" + "net/url" + + "go.opentelemetry.io/otel/attribute" + + "github.com/ory/hydra/v2/driver/config" + "github.com/ory/hydra/v2/x" + client "github.com/ory/kratos-client-go" + "github.com/ory/x/otelx" +) + +type ( + dependencies interface { + config.Provider + x.HTTPClientProvider + x.TracingProvider + } + Provider interface { + Kratos() Client + } + Client interface { + DisableSession(ctx context.Context, kratosSessionID string) error + } + Default struct { + dependencies + } +) + +func New(d dependencies) Client { + return &Default{dependencies: d} +} + +func (k *Default) DisableSession(ctx context.Context, kratosSessionID string) (err error) { + ctx, span := k.Tracer(ctx).Tracer().Start(ctx, "kratos.DisableSession") + otelx.End(span, &err) + + adminURL, ok := k.Config().KratosAdminURL(ctx) + if !ok { + span.SetAttributes(attribute.Bool("skipped", true)) + span.SetAttributes(attribute.String("reason", "kratos admin url not set")) + return nil + } + + if kratosSessionID == "" { + span.SetAttributes(attribute.Bool("skipped", true)) + span.SetAttributes(attribute.String("reason", "kratos session ID is empty")) + return nil + } + + kratos := client.NewAPIClient(k.clientConfiguration(ctx, adminURL)) + _, err = kratos.IdentityApi.DisableSession(ctx, kratosSessionID).Execute() + + return err + +} + +func (k *Default) clientConfiguration(ctx context.Context, adminURL *url.URL) *client.Configuration { + configuration := client.NewConfiguration() + configuration.Servers = client.ServerConfigurations{{URL: adminURL.String()}} + configuration.HTTPClient = k.HTTPClient(ctx).StandardClient() + + return configuration +} diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0001.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0001.json index a642f6606bf..9ed28857755 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0001.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0001.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0001", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0002.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0002.json index 65d132ab888..aeac476a9ab 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0002.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0002.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0002", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0003.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0003.json index dfa9ae21e86..76ff6e432b5 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0003.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0003.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0003", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0004.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0004.json index 4411c2064a5..a8688ae9c31 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0004.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0004.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0004", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0005.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0005.json index cba9dcf6125..2e21492c6d3 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0005.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0005.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0005", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0006.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0006.json index 2081943455f..1a6e76f87da 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0006.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0006.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0006", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0007.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0007.json index c701732aac3..619fcc9b034 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0007.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0007.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0007", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0008.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0008.json index b8fc116b728..b6ef3fc1632 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0008.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0008.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0008", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0009.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0009.json index 57cee5ecb1e..7cb33bb7c7d 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0009.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0009.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0009", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0010.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0010.json index 3e6b4d5fa10..93539884214 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0010.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0010.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0010", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0011.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0011.json index 1344d7158fb..f0ee5752657 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0011.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0011.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0011", + "KratosSessionID": "", "Remember": false } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0012.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0012.json index 876277e2440..bc7c53ecc7a 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0012.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0012.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0012", + "KratosSessionID": "", "Remember": false } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0013.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0013.json index 404a1b4fe37..7b9795b58ce 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0013.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0013.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0013", + "KratosSessionID": "", "Remember": false } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0014.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0014.json index 472bef86c13..d25982c1b71 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0014.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0014.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0014", + "KratosSessionID": "", "Remember": false } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0015.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0015.json index 1f82a76af6b..74f67ac3124 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0015.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0015.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0015", + "KratosSessionID": "", "Remember": false } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0016.json b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0016.json index e4e49d49af9..4ee258ff5d4 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0016.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_authentication_session/auth_session-0016.json @@ -3,5 +3,6 @@ "NID": "00000000-0000-0000-0000-000000000000", "AuthenticatedAt": null, "Subject": "subject-0016", + "KratosSessionID": "", "Remember": true } diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json index d89f26c7d42..a42275d0e7e 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0001.json @@ -14,6 +14,7 @@ "ClientID": "", "RequestURL": "http://request/0001", "SessionID": "", + "KratosSessionID": "", "LoginVerifier": "verifier-0001", "LoginCSRF": "csrf-0001", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json index 369ba83ba25..15477fa8d96 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0002.json @@ -14,6 +14,7 @@ "ClientID": "", "RequestURL": "http://request/0002", "SessionID": "", + "KratosSessionID": "", "LoginVerifier": "verifier-0002", "LoginCSRF": "csrf-0002", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json index 66718c0ba27..ff40b309384 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0003.json @@ -14,6 +14,7 @@ "ClientID": "", "RequestURL": "http://request/0003", "SessionID": "auth_session-0003", + "KratosSessionID": "", "LoginVerifier": "verifier-0003", "LoginCSRF": "csrf-0003", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json index e707616aa87..53fd97cd38c 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0004.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0004", "SessionID": "auth_session-0004", + "KratosSessionID": "", "LoginVerifier": "verifier-0004", "LoginCSRF": "csrf-0004", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json index fcc4760db32..f30f13352bb 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0005.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0005", "SessionID": "auth_session-0005", + "KratosSessionID": "", "LoginVerifier": "verifier-0005", "LoginCSRF": "csrf-0005", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json index 825ca5b9b00..b17f4f62df6 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0006.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0006", "SessionID": "auth_session-0006", + "KratosSessionID": "", "LoginVerifier": "verifier-0006", "LoginCSRF": "csrf-0006", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json index 1d20de4190f..a6428804cb3 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0007.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0007", "SessionID": "auth_session-0007", + "KratosSessionID": "", "LoginVerifier": "verifier-0007", "LoginCSRF": "csrf-0007", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json index 3ed3dad5245..595ecbe13ee 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0008.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0008", "SessionID": "auth_session-0008", + "KratosSessionID": "", "LoginVerifier": "verifier-0008", "LoginCSRF": "csrf-0008", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json index 61f8bbabf0c..4c56c49cc06 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0009.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0009", "SessionID": "auth_session-0009", + "KratosSessionID": "", "LoginVerifier": "verifier-0009", "LoginCSRF": "csrf-0009", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json index a886dd0aefe..82592b03db4 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0010.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0010", "SessionID": "auth_session-0010", + "KratosSessionID": "", "LoginVerifier": "verifier-0010", "LoginCSRF": "csrf-0010", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json index dda3212a8d7..7d68c1cacde 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0011.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0011", "SessionID": "auth_session-0011", + "KratosSessionID": "", "LoginVerifier": "verifier-0011", "LoginCSRF": "csrf-0011", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json index d6491837a10..eb93e192387 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0012.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0012", "SessionID": "auth_session-0012", + "KratosSessionID": "", "LoginVerifier": "verifier-0012", "LoginCSRF": "csrf-0012", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json index 89ca9f7daf4..0238bb794f7 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0013.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0013", "SessionID": "auth_session-0013", + "KratosSessionID": "", "LoginVerifier": "verifier-0013", "LoginCSRF": "csrf-0013", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json index d020259b581..d3c832a270c 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0014.json @@ -16,6 +16,7 @@ "ClientID": "", "RequestURL": "http://request/0014", "SessionID": "auth_session-0014", + "KratosSessionID": "", "LoginVerifier": "verifier-0014", "LoginCSRF": "csrf-0014", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json index 78ee82f16d5..b7aedcb245a 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0015.json @@ -18,6 +18,7 @@ "ClientID": "", "RequestURL": "http://request/0015", "SessionID": "auth_session-0015", + "KratosSessionID": "", "LoginVerifier": "verifier-0015", "LoginCSRF": "csrf-0015", "LoginInitializedAt": null, diff --git a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0016.json b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0016.json index e3bddee39a1..09a9125013d 100644 --- a/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0016.json +++ b/persistence/sql/migratest/fixtures/hydra_oauth2_flow/challenge-0016.json @@ -18,6 +18,7 @@ "ClientID": "", "RequestURL": "http://request/0016", "SessionID": "auth_session-0016", + "KratosSessionID": "", "LoginVerifier": "verifier-0016", "LoginCSRF": "csrf-0016", "LoginInitializedAt": null, diff --git a/persistence/sql/migrations/20230809122501000001_add_kratos_session_id.down.sql b/persistence/sql/migrations/20230809122501000001_add_kratos_session_id.down.sql new file mode 100644 index 00000000000..84b7cfe9b41 --- /dev/null +++ b/persistence/sql/migrations/20230809122501000001_add_kratos_session_id.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE hydra_oauth2_flow DROP COLUMN kratos_session_id; +ALTER TABLE hydra_oauth2_authentication_session DROP COLUMN kratos_session_id; \ No newline at end of file diff --git a/persistence/sql/migrations/20230809122501000001_add_kratos_session_id.up.sql b/persistence/sql/migrations/20230809122501000001_add_kratos_session_id.up.sql new file mode 100644 index 00000000000..11ecf8165f0 --- /dev/null +++ b/persistence/sql/migrations/20230809122501000001_add_kratos_session_id.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE hydra_oauth2_flow ADD COLUMN kratos_session_id VARCHAR(40); +ALTER TABLE hydra_oauth2_authentication_session ADD COLUMN kratos_session_id VARCHAR(40); \ No newline at end of file diff --git a/persistence/sql/persister_consent.go b/persistence/sql/persister_consent.go index bd401d56423..11ca9b28a46 100644 --- a/persistence/sql/persister_consent.go +++ b/persistence/sql/persister_consent.go @@ -14,6 +14,7 @@ import ( "github.com/gofrs/uuid" "github.com/ory/hydra/v2/oauth2/flowctx" + "github.com/ory/x/otelx" "github.com/ory/x/sqlxx" "github.com/ory/x/errorsx" @@ -421,13 +422,19 @@ func (p *Persister) ConfirmLoginSession(ctx context.Context, session *flow.Login return p.CreateWithNetwork(ctx, session) } + var kratosSessionID string + if session != nil { + kratosSessionID = session.KratosSessionID.String() + } + // In some unit tests, we still confirm the login session without data from the cookie. We can remove this case // once all tests are fixed. n, err := p.Connection(ctx).Where("id = ? AND nid = ?", id, p.NetworkID(ctx)).UpdateQuery(&flow.LoginSession{ AuthenticatedAt: sqlxx.NullTime(authenticatedAt), Subject: subject, Remember: remember, - }, "authenticated_at", "subject", "remember") + KratosSessionID: sqlxx.NullString(kratosSessionID), + }, "authenticated_at", "subject", "remember", "kratos_session_id") if err != nil { return sqlcon.HandleError(err) } @@ -450,16 +457,24 @@ func (p *Persister) CreateLoginSession(ctx context.Context, session *flow.LoginS return nil } -func (p *Persister) DeleteLoginSession(ctx context.Context, id string) error { +func (p *Persister) DeleteLoginSession(ctx context.Context, id string) (deletedSession *flow.LoginSession, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteLoginSession") - defer span.End() + defer otelx.End(span, &err) - count, err := p.Connection(ctx).RawQuery("DELETE FROM hydra_oauth2_authentication_session WHERE id=? AND nid=?", id, p.NetworkID(ctx)).ExecWithCount() - if count == 0 { - return errorsx.WithStack(x.ErrNotFound) - } else { - return sqlcon.HandleError(err) + var session flow.LoginSession + + err = p.Connection(ctx).RawQuery( + `DELETE FROM hydra_oauth2_authentication_session + WHERE id = ? AND nid = ? + RETURNING *`, + id, + p.NetworkID(ctx), + ).First(&session) + if err != nil { + return nil, sqlcon.HandleError(err) } + + return &session, nil } func (p *Persister) FindGrantedAndRememberedConsentRequests(ctx context.Context, client, subject string) (rs []flow.AcceptOAuth2ConsentRequest, err error) { diff --git a/persistence/sql/persister_nid_test.go b/persistence/sql/persister_nid_test.go index 83fad7c1452..e3c72bd3856 100644 --- a/persistence/sql/persister_nid_test.go +++ b/persistence/sql/persister_nid_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/ory/hydra/v2/persistence" "github.com/ory/x/uuidx" @@ -632,14 +634,22 @@ func (s *PersisterTestSuite) TestDeleteLoginSession() { t := s.T() for k, r := range s.registries { t.Run(k, func(t *testing.T) { - ls := flow.LoginSession{ID: uuid.Must(uuid.NewV4()).String(), Remember: true} + ls := flow.LoginSession{ + ID: uuid.Must(uuid.NewV4()).String(), + Remember: true, + KratosSessionID: sqlxx.NullString(uuid.Must(uuid.NewV4()).String()), + } persistLoginSession(s.t1, t, r.Persister(), &ls) - require.Error(t, r.Persister().DeleteLoginSession(s.t2, ls.ID)) - _, err := r.Persister().GetRememberedLoginSession(s.t1, nil, ls.ID) + deletedLS, err := r.Persister().DeleteLoginSession(s.t2, ls.ID) + require.Error(t, err) + assert.Nil(t, deletedLS) + _, err = r.Persister().GetRememberedLoginSession(s.t1, nil, ls.ID) require.NoError(t, err) - require.NoError(t, r.Persister().DeleteLoginSession(s.t1, ls.ID)) + deletedLS, err = r.Persister().DeleteLoginSession(s.t1, ls.ID) + require.NoError(t, err) + assert.Equal(t, ls, *deletedLS) _, err = r.Persister().GetRememberedLoginSession(s.t1, nil, ls.ID) require.Error(t, err) }) diff --git a/spec/api.json b/spec/api.json index 0c4a3d7077f..e0e3463623d 100644 --- a/spec/api.json +++ b/spec/api.json @@ -216,6 +216,10 @@ "format": "int64", "type": "integer" }, + "session_id": { + "description": "KratosSessionID is the session ID of the end-user that authenticated.\nIf specified, we will use this value to propagate the logout.", + "type": "string" + }, "subject": { "description": "Subject is the user ID of the end-user that authenticated.", "type": "string" diff --git a/spec/config.json b/spec/config.json index cbdba33cdfd..116c0c75247 100644 --- a/spec/config.json +++ b/spec/config.json @@ -760,6 +760,21 @@ "https://my-example.app/logout-successful", "/ui" ] + }, + "kratos": { + "type": "object", + "additionalProperties": false, + "properties": { + "admin": { + "type": "string", + "title": "The admin URL of the ORY Kratos instance.", + "description": "If set, ORY Hydra will use this URL to log out the user in addition to removing the Hydra session.", + "format": "uri-reference", + "examples": [ + "https://kratos.example.com/admin" + ] + } + } } } }, diff --git a/spec/swagger.json b/spec/swagger.json index 48e19616d07..9579048a636 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -2246,6 +2246,10 @@ "type": "integer", "format": "int64" }, + "session_id": { + "description": "KratosSessionID is the session ID of the end-user that authenticated.\nIf specified, we will use this value to propagate the logout.", + "type": "string" + }, "subject": { "description": "Subject is the user ID of the end-user that authenticated.", "type": "string"