Skip to content

Commit c250c6c

Browse files
authored
Merge pull request #49939 from nextcloud/backport/49208/stable29
[stable29] fix: Redirect user to login if session is terminated
2 parents d5b1c5b + fbbbd2a commit c250c6c

File tree

5 files changed

+139
-9
lines changed

5 files changed

+139
-9
lines changed

core/src/utils/xhr-request.js

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
* along with this program. If not, see <http://www.gnu.org/licenses/>.
2020
*/
2121

22-
import { getRootUrl } from '@nextcloud/router'
22+
import { getCurrentUser } from '@nextcloud/auth'
23+
import { generateUrl, getRootUrl } from '@nextcloud/router'
2324

2425
/**
2526
*
@@ -42,6 +43,41 @@ const isNextcloudUrl = (url) => {
4243
|| (isRelativeUrl(url) && url.startsWith(getRootUrl()))
4344
}
4445

46+
/**
47+
* Check if a user was logged in but is now logged-out.
48+
* If this is the case then the user will be forwarded to the login page.
49+
* @returns {Promise<void>}
50+
*/
51+
async function checkLoginStatus() {
52+
// skip if no logged in user
53+
if (getCurrentUser() === null) {
54+
return
55+
}
56+
57+
// skip if already running
58+
if (checkLoginStatus.running === true) {
59+
return
60+
}
61+
62+
// only run one request in parallel
63+
checkLoginStatus.running = true
64+
65+
try {
66+
// We need to check this as a 401 in the first place could also come from other reasons
67+
const { status } = await window.fetch(generateUrl('/apps/files'))
68+
if (status === 401) {
69+
console.warn('User session was terminated, forwarding to login page.')
70+
window.location = generateUrl('/login?redirect_url={url}', {
71+
url: window.location.pathname + window.location.search + window.location.hash,
72+
})
73+
}
74+
} catch (error) {
75+
console.warn('Could not check login-state')
76+
} finally {
77+
delete checkLoginStatus.running
78+
}
79+
}
80+
4581
/**
4682
* Intercept XMLHttpRequest and fetch API calls to add X-Requested-With header
4783
*
@@ -51,17 +87,24 @@ export const interceptRequests = () => {
5187
XMLHttpRequest.prototype.open = (function(open) {
5288
return function(method, url, async) {
5389
open.apply(this, arguments)
54-
if (isNextcloudUrl(url) && !this.getResponseHeader('X-Requested-With')) {
55-
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
90+
if (isNextcloudUrl(url)) {
91+
if (!this.getResponseHeader('X-Requested-With')) {
92+
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
93+
}
94+
this.addEventListener('loadend', function() {
95+
if (this.status === 401) {
96+
checkLoginStatus()
97+
}
98+
})
5699
}
57100
}
58101
})(XMLHttpRequest.prototype.open)
59102

60103
window.fetch = (function(fetch) {
61-
return (resource, options) => {
104+
return async (resource, options) => {
62105
// fetch allows the `input` to be either a Request object or any stringifyable value
63106
if (!isNextcloudUrl(resource.url ?? resource.toString())) {
64-
return fetch(resource, options)
107+
return await fetch(resource, options)
65108
}
66109
if (!options) {
67110
options = {}
@@ -76,7 +119,11 @@ export const interceptRequests = () => {
76119
options.headers['X-Requested-With'] = 'XMLHttpRequest'
77120
}
78121

79-
return fetch(resource, options)
122+
const response = await fetch(resource, options)
123+
if (response.status === 401) {
124+
checkLoginStatus()
125+
}
126+
return response
80127
}
81128
})(window.fetch)
82129
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
/**
7+
* Test that when a session expires / the user logged out in another tab,
8+
* the user gets redirected to the login on the next request.
9+
*/
10+
describe('Logout redirect ', { testIsolation: true }, () => {
11+
12+
let user
13+
14+
before(() => {
15+
cy.createRandomUser()
16+
.then(($user) => {
17+
user = $user
18+
})
19+
})
20+
21+
it('Redirects to login if session timed out', () => {
22+
// Login and see settings
23+
cy.login(user)
24+
cy.visit('/settings/user#profile')
25+
cy.findByRole('checkbox', { name: /Enable profile/i })
26+
.should('exist')
27+
28+
// clear session
29+
cy.clearAllCookies()
30+
31+
// trigger an request
32+
cy.findByRole('checkbox', { name: /Enable profile/i })
33+
.click({ force: true })
34+
35+
// See that we are redirected
36+
cy.url()
37+
.should('match', /\/login/i)
38+
.and('include', `?redirect_url=${encodeURIComponent('/index.php/settings/user#profile')}`)
39+
40+
cy.get('form[name="login"]').should('be.visible')
41+
})
42+
43+
it('Redirect from login works', () => {
44+
cy.logout()
45+
// visit the login
46+
cy.visit(`/login?redirect_url=${encodeURIComponent('/index.php/settings/user#profile')}`)
47+
48+
// see login
49+
cy.get('form[name="login"]').should('be.visible')
50+
cy.get('form[name="login"]').within(() => {
51+
cy.get('input[name="user"]').type(user.userId)
52+
cy.get('input[name="password"]').type(user.password)
53+
cy.contains('button[data-login-form-submit]', 'Log in').click()
54+
})
55+
56+
// see that we are correctly redirected
57+
cy.url().should('include', '/index.php/settings/user#profile')
58+
cy.findByRole('checkbox', { name: /Enable profile/i })
59+
.should('exist')
60+
})
61+
62+
})

dist/core-main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/core-main.js.LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
/*
2+
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
3+
*
4+
* @author Julius Härtl <jus@bitgrid.net>
5+
*
6+
* @license GNU AGPL version 3 or any later version
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of the
11+
* License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*/
21+
122
/*!
223
* clipboard.js v2.0.11
324
* https://clipboardjs.com/

dist/core-main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)