From 19e0e2e35b1df52a6ecd1cc0bf9fcddc270f1355 Mon Sep 17 00:00:00 2001 From: Doron Sharon Date: Tue, 7 May 2024 12:58:50 +0300 Subject: [PATCH] Add the ability to logout with a token (#435) --- descope/internal/auth/auth.go | 20 +++++++ descope/internal/auth/auth_test.go | 52 +++++++++++++++++++ descope/sdk/auth.go | 9 ++++ .../tests/mocks/auth/authenticationmock.go | 20 +++++++ 4 files changed, 101 insertions(+) diff --git a/descope/internal/auth/auth.go b/descope/internal/auth/auth.go index 2fa1114f..eae52237 100644 --- a/descope/internal/auth/auth.go +++ b/descope/internal/auth/auth.go @@ -100,6 +100,16 @@ func (auth *authenticationService) WebAuthn() sdk.WebAuthn { } func (auth *authenticationService) Logout(request *http.Request, w http.ResponseWriter) error { + return auth.logout(request, w) +} + +func (auth *authenticationService) LogoutWithToken(refreshToken string, w http.ResponseWriter) error { + request := &http.Request{Header: http.Header{}} + request.AddCookie(&http.Cookie{Name: descope.RefreshCookieName, Value: refreshToken}) + return auth.logout(request, w) +} + +func (auth *authenticationService) logout(request *http.Request, w http.ResponseWriter) error { if request == nil { return utils.NewInvalidArgumentError("request") } @@ -153,6 +163,16 @@ func (auth *authenticationService) Logout(request *http.Request, w http.Response } func (auth *authenticationService) LogoutAll(request *http.Request, w http.ResponseWriter) error { + return auth.logoutAll(request, w) +} + +func (auth *authenticationService) LogoutAllWithToken(refreshToken string, w http.ResponseWriter) error { + request := &http.Request{Header: http.Header{}} + request.AddCookie(&http.Cookie{Name: descope.RefreshCookieName, Value: refreshToken}) + return auth.logoutAll(request, w) +} + +func (auth *authenticationService) logoutAll(request *http.Request, w http.ResponseWriter) error { if request == nil { return utils.NewInvalidArgumentError("request") } diff --git a/descope/internal/auth/auth_test.go b/descope/internal/auth/auth_test.go index 97af9aef..1dcdffba 100644 --- a/descope/internal/auth/auth_test.go +++ b/descope/internal/auth/auth_test.go @@ -650,6 +650,32 @@ func TestLogout(t *testing.T) { require.NoError(t, err) } +func TestLogoutWithToken(t *testing.T) { + a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(mockAuthSessionBody))}, nil + }) + require.NoError(t, err) + + w := httptest.NewRecorder() + err = a.LogoutWithToken(jwtRTokenValid, w) + strictCookies(t, w) + require.NoError(t, err) + require.Len(t, w.Result().Cookies(), 2) + c1 := w.Result().Cookies()[0] + assert.Empty(t, c1.Value) + assert.EqualValues(t, descope.SessionCookieName, c1.Name) + assert.EqualValues(t, "/my-path", c1.Path) + assert.EqualValues(t, "my-domain", c1.Domain) + c2 := w.Result().Cookies()[1] + assert.Empty(t, c2.Value) + assert.EqualValues(t, descope.RefreshCookieName, c2.Name) + assert.EqualValues(t, "/my-path", c2.Path) + assert.EqualValues(t, "my-domain", c2.Domain) + + err = a.LogoutWithToken(jwtRTokenValid, nil) + require.NoError(t, err) +} + func TestLogoutAll(t *testing.T) { a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(mockAuthSessionBody))}, nil @@ -678,6 +704,32 @@ func TestLogoutAll(t *testing.T) { require.NoError(t, err) } +func TestLogoutAllWithToken(t *testing.T) { + a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(mockAuthSessionBody))}, nil + }) + require.NoError(t, err) + + w := httptest.NewRecorder() + err = a.LogoutAllWithToken(jwtRTokenValid, w) + strictCookies(t, w) + require.NoError(t, err) + require.Len(t, w.Result().Cookies(), 2) + c1 := w.Result().Cookies()[0] + assert.Empty(t, c1.Value) + assert.EqualValues(t, descope.SessionCookieName, c1.Name) + assert.EqualValues(t, "/my-path", c1.Path) + assert.EqualValues(t, "my-domain", c1.Domain) + c2 := w.Result().Cookies()[1] + assert.Empty(t, c2.Value) + assert.EqualValues(t, descope.RefreshCookieName, c2.Name) + assert.EqualValues(t, "/my-path", c2.Path) + assert.EqualValues(t, "my-domain", c2.Domain) + + err = a.LogoutAllWithToken(jwtRTokenValid, nil) + require.NoError(t, err) +} + func TestLogoutNoClaims(t *testing.T) { a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { return &http.Response{StatusCode: http.StatusOK}, nil diff --git a/descope/sdk/auth.go b/descope/sdk/auth.go index 93853759..210ff51e 100644 --- a/descope/sdk/auth.go +++ b/descope/sdk/auth.go @@ -382,11 +382,20 @@ type Authentication interface { // Use the ResponseWriter (optional) to apply the cookies to the response automatically. Logout(request *http.Request, w http.ResponseWriter) error + // LogoutWithToken - Logs out from the current session and deletes the session and refresh cookies in the http response. + // Use the ResponseWriter (optional) to apply the cookies to the response automatically. + LogoutWithToken(refreshToken string, w http.ResponseWriter) error + // LogoutAll - Use to perform logout from all active sessions for the request user. This will revoke the given tokens // and if given options will also remove existing session on the given response sent to the client. // Use the ResponseWriter (optional) to apply the cookies to the response automatically. LogoutAll(request *http.Request, w http.ResponseWriter) error + // LogoutAllWithToken - Use to perform logout from all active sessions for the request user. This will revoke the given tokens + // and if given options will also remove existing session on the given response sent to the client. + // Use the ResponseWriter (optional) to apply the cookies to the response automatically. + LogoutAllWithToken(refreshToken string, w http.ResponseWriter) error + // Me - Use to retrieve current session user details. The request requires a valid refresh token. // returns the user details or error if the refresh token is not valid. Me(request *http.Request) (*descope.UserResponse, error) diff --git a/descope/tests/mocks/auth/authenticationmock.go b/descope/tests/mocks/auth/authenticationmock.go index ec6be8b4..0daa5917 100644 --- a/descope/tests/mocks/auth/authenticationmock.go +++ b/descope/tests/mocks/auth/authenticationmock.go @@ -630,9 +630,15 @@ type MockSession struct { LogoutAssert func(r *http.Request, w http.ResponseWriter) LogoutError error + LogoutWithTokenAssert func(refreshToken string, w http.ResponseWriter) + LogoutWithTokenError error + LogoutAllAssert func(r *http.Request, w http.ResponseWriter) LogoutAllError error + LogoutAllWithTokenAssert func(refreshToken string, w http.ResponseWriter) + LogoutAllWithTokenError error + MeAssert func(r *http.Request) MeError error MeResponse *descope.UserResponse @@ -801,6 +807,13 @@ func (m *MockSession) Logout(r *http.Request, w http.ResponseWriter) error { return m.LogoutError } +func (m *MockSession) LogoutWithToken(refreshToken string, w http.ResponseWriter) error { + if m.LogoutWithTokenAssert != nil { + m.LogoutWithTokenAssert(refreshToken, w) + } + return m.LogoutWithTokenError +} + func (m *MockSession) LogoutAll(r *http.Request, w http.ResponseWriter) error { if m.LogoutAllAssert != nil { m.LogoutAllAssert(r, w) @@ -808,6 +821,13 @@ func (m *MockSession) LogoutAll(r *http.Request, w http.ResponseWriter) error { return m.LogoutAllError } +func (m *MockSession) LogoutAllWithToken(refreshToken string, w http.ResponseWriter) error { + if m.LogoutAllWithTokenAssert != nil { + m.LogoutAllWithTokenAssert(refreshToken, w) + } + return m.LogoutAllWithTokenError +} + func (m *MockSession) Me(r *http.Request) (*descope.UserResponse, error) { if m.MeAssert != nil { m.MeAssert(r)