diff --git a/core/src/utils/xhr-request.js b/core/src/utils/xhr-request.js
index ff8b7641b07..75f99e3f671 100644
--- a/core/src/utils/xhr-request.js
+++ b/core/src/utils/xhr-request.js
@@ -19,7 +19,8 @@
* along with this program. If not, see .
*/
-import { getRootUrl } from '@nextcloud/router'
+import { getCurrentUser } from '@nextcloud/auth'
+import { generateUrl, getRootUrl } from '@nextcloud/router'
/**
*
@@ -42,6 +43,41 @@ const isNextcloudUrl = (url) => {
|| (isRelativeUrl(url) && url.startsWith(getRootUrl()))
}
+/**
+ * Check if a user was logged in but is now logged-out.
+ * If this is the case then the user will be forwarded to the login page.
+ * @returns {Promise}
+ */
+async function checkLoginStatus() {
+ // skip if no logged in user
+ if (getCurrentUser() === null) {
+ return
+ }
+
+ // skip if already running
+ if (checkLoginStatus.running === true) {
+ return
+ }
+
+ // only run one request in parallel
+ checkLoginStatus.running = true
+
+ try {
+ // We need to check this as a 401 in the first place could also come from other reasons
+ const { status } = await window.fetch(generateUrl('/apps/files'))
+ if (status === 401) {
+ console.warn('User session was terminated, forwarding to login page.')
+ window.location = generateUrl('/login?redirect_url={url}', {
+ url: window.location.pathname + window.location.search + window.location.hash,
+ })
+ }
+ } catch (error) {
+ console.warn('Could not check login-state')
+ } finally {
+ delete checkLoginStatus.running
+ }
+}
+
/**
* Intercept XMLHttpRequest and fetch API calls to add X-Requested-With header
*
@@ -51,17 +87,24 @@ export const interceptRequests = () => {
XMLHttpRequest.prototype.open = (function(open) {
return function(method, url, async) {
open.apply(this, arguments)
- if (isNextcloudUrl(url) && !this.getResponseHeader('X-Requested-With')) {
- this.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
+ if (isNextcloudUrl(url)) {
+ if (!this.getResponseHeader('X-Requested-With')) {
+ this.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
+ }
+ this.addEventListener('loadend', function() {
+ if (this.status === 401) {
+ checkLoginStatus()
+ }
+ })
}
}
})(XMLHttpRequest.prototype.open)
window.fetch = (function(fetch) {
- return (resource, options) => {
+ return async (resource, options) => {
// fetch allows the `input` to be either a Request object or any stringifyable value
if (!isNextcloudUrl(resource.url ?? resource.toString())) {
- return fetch(resource, options)
+ return await fetch(resource, options)
}
if (!options) {
options = {}
@@ -76,7 +119,11 @@ export const interceptRequests = () => {
options.headers['X-Requested-With'] = 'XMLHttpRequest'
}
- return fetch(resource, options)
+ const response = await fetch(resource, options)
+ if (response.status === 401) {
+ checkLoginStatus()
+ }
+ return response
}
})(window.fetch)
}
diff --git a/cypress/e2e/login/login-redirect.cy.ts b/cypress/e2e/login/login-redirect.cy.ts
new file mode 100644
index 00000000000..eb0710dcbcc
--- /dev/null
+++ b/cypress/e2e/login/login-redirect.cy.ts
@@ -0,0 +1,62 @@
+/*!
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/**
+ * Test that when a session expires / the user logged out in another tab,
+ * the user gets redirected to the login on the next request.
+ */
+describe('Logout redirect ', { testIsolation: true }, () => {
+
+ let user
+
+ before(() => {
+ cy.createRandomUser()
+ .then(($user) => {
+ user = $user
+ })
+ })
+
+ it('Redirects to login if session timed out', () => {
+ // Login and see settings
+ cy.login(user)
+ cy.visit('/settings/user#profile')
+ cy.findByRole('checkbox', { name: /Enable profile/i })
+ .should('exist')
+
+ // clear session
+ cy.clearAllCookies()
+
+ // trigger an request
+ cy.findByRole('checkbox', { name: /Enable profile/i })
+ .click({ force: true })
+
+ // See that we are redirected
+ cy.url()
+ .should('match', /\/login/i)
+ .and('include', `?redirect_url=${encodeURIComponent('/index.php/settings/user#profile')}`)
+
+ cy.get('form[name="login"]').should('be.visible')
+ })
+
+ it('Redirect from login works', () => {
+ cy.logout()
+ // visit the login
+ cy.visit(`/login?redirect_url=${encodeURIComponent('/index.php/settings/user#profile')}`)
+
+ // see login
+ cy.get('form[name="login"]').should('be.visible')
+ cy.get('form[name="login"]').within(() => {
+ cy.get('input[name="user"]').type(user.userId)
+ cy.get('input[name="password"]').type(user.password)
+ cy.contains('button[data-login-form-submit]', 'Log in').click()
+ })
+
+ // see that we are correctly redirected
+ cy.url().should('include', '/index.php/settings/user#profile')
+ cy.findByRole('checkbox', { name: /Enable profile/i })
+ .should('exist')
+ })
+
+})