diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 6210e4dbf9..a2fed644fe 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -187,4 +187,15 @@ class Auth::SessionsController < Devise::SessionsController def second_factor_attempts_key(user) "2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}" end + + def respond_to_on_destroy + respond_to do |format| + format.json do + render json: { + redirect_to: after_sign_out_path_for(resource_name), + }, status: 200 + end + format.all { super } + end + end end diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/log_out.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/log_out.tsx index 48d24182ed..ec61146877 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/log_out.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/log_out.tsx @@ -25,7 +25,7 @@ export const ConfirmLogOutModal: React.FC = ({ const intl = useIntl(); const onConfirm = useCallback(() => { - logOut(); + void logOut(); }, []); return ( diff --git a/app/javascript/mastodon/utils/log_out.ts b/app/javascript/mastodon/utils/log_out.ts index b08a61a6a2..f8e98f1be9 100644 --- a/app/javascript/mastodon/utils/log_out.ts +++ b/app/javascript/mastodon/utils/log_out.ts @@ -1,36 +1,20 @@ -export const logOut = () => { - const form = document.createElement('form'); +import api from 'mastodon/api'; - const methodInput = document.createElement('input'); - methodInput.setAttribute('name', '_method'); - methodInput.setAttribute('value', 'delete'); - methodInput.setAttribute('type', 'hidden'); - form.appendChild(methodInput); +export async function logOut() { + try { + const response = await api(false).delete<{ redirect_to?: string }>( + '/auth/sign_out', + { headers: { Accept: 'application/json' }, withCredentials: true }, + ); - const csrfToken = document.querySelector( - 'meta[name=csrf-token]', - ); - - const csrfParam = document.querySelector( - 'meta[name=csrf-param]', - ); - - if (csrfParam && csrfToken) { - const csrfInput = document.createElement('input'); - csrfInput.setAttribute('name', csrfParam.content); - csrfInput.setAttribute('value', csrfToken.content); - csrfInput.setAttribute('type', 'hidden'); - form.appendChild(csrfInput); + if (response.status === 200 && response.data.redirect_to) + window.location.href = response.data.redirect_to; + else + console.error( + 'Failed to log out, got an unexpected non-redirect response from the server', + response, + ); + } catch (error) { + console.error('Failed to log out, response was an error', error); } - - const submitButton = document.createElement('input'); - submitButton.setAttribute('type', 'submit'); - form.appendChild(submitButton); - - form.method = 'post'; - form.action = '/auth/sign_out'; - form.style.display = 'none'; - - document.body.appendChild(form); - submitButton.click(); -}; +} diff --git a/spec/requests/log_out_spec.rb b/spec/requests/log_out_spec.rb new file mode 100644 index 0000000000..62ca1ac547 --- /dev/null +++ b/spec/requests/log_out_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Log Out' do + include RoutingHelper + + describe 'DELETE /auth/sign_out' do + let(:user) { Fabricate(:user) } + + before do + sign_in user + end + + it 'Logs out the user and redirect' do + delete '/auth/sign_out' + + expect(response).to redirect_to('/auth/sign_in') + end + + it 'Logs out the user and return a page to redirect to with a JSON request' do + delete '/auth/sign_out', headers: { 'HTTP_ACCEPT' => 'application/json' } + + expect(response).to have_http_status(200) + expect(response.media_type).to eq 'application/json' + + expect(body_as_json[:redirect_to]).to eq '/auth/sign_in' + end + end +end diff --git a/spec/system/log_out_spec.rb b/spec/system/log_out_spec.rb new file mode 100644 index 0000000000..376f19712e --- /dev/null +++ b/spec/system/log_out_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Log out' do + include ProfileStories + + before do + as_a_logged_in_user + end + + describe 'Logging out from the preferences' do + it 'logs the user out' do + visit settings_path + + within '.sidebar' do + click_on 'Logout' + end + + expect(page).to have_title(I18n.t('auth.login')) + expect(page).to have_current_path('/auth/sign_in') + end + end + + describe 'Logging out from the JS app', :js, :streaming do + it 'logs the user out' do + visit root_path + + within '.navigation-bar' do + click_on 'Menu' + end + + within '.dropdown-menu' do + click_on 'Logout' + end + + click_on 'Log out' + + expect(page).to have_title(I18n.t('auth.login')) + expect(page).to have_current_path('/auth/sign_in') + end + end +end