From 57b988efb24f8575ba0f382812256fc7e2ce901e Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 30 Jan 2026 11:01:34 +0100 Subject: [PATCH 1/5] feat: add singin popup callback --- packages/web-runtime/src/pages/oidcCallback.vue | 2 ++ packages/web-runtime/src/router/index.ts | 6 ++++++ .../web-runtime/src/services/auth/authService.ts | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/web-runtime/src/pages/oidcCallback.vue b/packages/web-runtime/src/pages/oidcCallback.vue index b6ecc5d0b89..7cf18c940bc 100644 --- a/packages/web-runtime/src/pages/oidcCallback.vue +++ b/packages/web-runtime/src/pages/oidcCallback.vue @@ -72,6 +72,8 @@ export default defineComponent({ if (unref(route).path === '/web-oidc-silent-redirect') { authService.signInSilentCallback() + } else if (unref(route).path === '/web-oidc-popup-callback') { + authService.signInPopupCallback() } else { authService.signInCallback() } diff --git a/packages/web-runtime/src/router/index.ts b/packages/web-runtime/src/router/index.ts index 46c66ef63c1..4e6a2bb4a72 100644 --- a/packages/web-runtime/src/router/index.ts +++ b/packages/web-runtime/src/router/index.ts @@ -52,6 +52,12 @@ const routes = [ component: OidcCallbackPage, meta: { title: $gettext('Oidc redirect'), authContext: 'anonymous' } }, + { + path: '/web-oidc-popup-callback', + name: 'oidcPopupCallback', + component: OidcCallbackPage, + meta: { title: $gettext('Oidc popup callback'), authContext: 'anonymous' } + }, { path: '/f/:fileId', name: 'resolvePrivateLink', diff --git a/packages/web-runtime/src/services/auth/authService.ts b/packages/web-runtime/src/services/auth/authService.ts index 7d9ea9342a8..2109b9b54c6 100644 --- a/packages/web-runtime/src/services/auth/authService.ts +++ b/packages/web-runtime/src/services/auth/authService.ts @@ -7,7 +7,8 @@ import { CapabilityStore, ConfigStore, useTokenTimerWorker, - AuthServiceInterface + AuthServiceInterface, + useEmbedMode } from '@ownclouders/web-pkg' import { RouteLocation, Router } from 'vue-router' import { @@ -18,7 +19,7 @@ import { isUserContextRequired } from '../../router' import { unref } from 'vue' -import { Ability } from '@ownclouders/web-client' +import { Ability, urlJoin } from '@ownclouders/web-client' import { Language } from 'vue3-gettext' import { PublicLinkType } from '@ownclouders/web-client' import { WebWorkersStore } from '@ownclouders/web-pkg' @@ -235,6 +236,13 @@ export class AuthService implements AuthServiceInterface { } public loginUser(redirectUrl?: string) { + const { isEnabled: isEmbedModeEnable } = useEmbedMode() + // if embed mode is enabled, use popup login instead of a redirect + if (unref(isEmbedModeEnable)) { + return this.userManager.signinPopup({ + redirect_uri: urlJoin(unref(this.configStore.serverUrl), 'web-oidc-popup-callback') + }) + } this.userManager.setPostLoginRedirectUrl(redirectUrl) return this.userManager.signinRedirect() } @@ -285,6 +293,10 @@ export class AuthService implements AuthServiceInterface { await this.userManager.signinSilentCallback(this.buildSignInCallbackUrl()) } + public async signInPopupCallback() { + await this.userManager.signinPopupCallback(this.buildSignInCallbackUrl()) + } + /** * craft a url that the parser in oidc-client-ts can handle… */ From 4260243a3c0a79f78f23088455e2f03c4ba5b7f4 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Wed, 13 May 2026 15:26:53 +0200 Subject: [PATCH 2/5] Refactor auth flow Do not redirect on failure to renew session, instead show a modal. Allow login via pop-up. Fix flow also for embed mode. --- dev/docker/ocis.idp.config.yaml | 4 + .../composables/authContext/useAuthService.ts | 4 +- .../src/composables/piniaStores/auth.ts | 6 + .../tokenTimerWorker/useTokenTimerWorker.ts | 4 +- packages/web-runtime/src/App.vue | 5 +- .../src/components/SessionExpiredModal.vue | 108 ++++++++++++++++++ .../src/composables/layout/useLayout.ts | 1 + .../web-runtime/src/helpers/silentRedirect.ts | 1 + packages/web-runtime/src/pages/login.vue | 72 ++++++++++-- .../web-runtime/src/pages/oidcCallback.vue | 30 ++++- .../web-runtime/src/router/setupAuthGuard.ts | 14 +-- .../src/services/auth/authService.ts | 67 ++++++----- 12 files changed, 259 insertions(+), 57 deletions(-) create mode 100644 packages/web-runtime/src/components/SessionExpiredModal.vue diff --git a/dev/docker/ocis.idp.config.yaml b/dev/docker/ocis.idp.config.yaml index ae4faf852f9..506383a7cf5 100644 --- a/dev/docker/ocis.idp.config.yaml +++ b/dev/docker/ocis.idp.config.yaml @@ -8,15 +8,19 @@ clients: - https://host.docker.internal:9200/ - https://host.docker.internal:9200/oidc-callback.html - https://host.docker.internal:9200/oidc-silent-redirect.html + - https://host.docker.internal:9200/web-oidc-popup-callback - https://host.docker.internal:9201/ - https://host.docker.internal:9201/oidc-callback.html - https://host.docker.internal:9201/oidc-silent-redirect.html + - https://host.docker.internal:9201/web-oidc-popup-callback - https://ocis.owncloud.test:10200/ - https://ocis.owncloud.test:10200/oidc-callback.html - https://ocis.owncloud.test:10200/oidc-silent-redirect.html + - https://ocis.owncloud.test:10200/web-oidc-popup-callback - https://ocis.owncloud.test:10201/ - https://ocis.owncloud.test:10201/oidc-callback.html - https://ocis.owncloud.test:10201/oidc-silent-redirect.html + - https://ocis.owncloud.test:10201/web-oidc-popup-callback origins: - https://host.docker.internal:9200 - https://host.docker.internal:9201 diff --git a/packages/web-pkg/src/composables/authContext/useAuthService.ts b/packages/web-pkg/src/composables/authContext/useAuthService.ts index 224533ce49d..7c0745234ba 100644 --- a/packages/web-pkg/src/composables/authContext/useAuthService.ts +++ b/packages/web-pkg/src/composables/authContext/useAuthService.ts @@ -2,10 +2,12 @@ import { useService } from '../service' import { NavigationFailure } from 'vue-router' export interface AuthServiceInterface { - handleAuthError(route: any, options?: { forceLogout?: boolean }): any + handleAuthError(route: any): any signinSilent(): Promise logoutUser(): Promise getRefreshToken(): Promise + showSessionExpiredModal(): void + loginUserPopup(): Promise } export const useAuthService = (): AuthServiceInterface => { diff --git a/packages/web-pkg/src/composables/piniaStores/auth.ts b/packages/web-pkg/src/composables/piniaStores/auth.ts index 7ab0efe0144..9bf683fc426 100644 --- a/packages/web-pkg/src/composables/piniaStores/auth.ts +++ b/packages/web-pkg/src/composables/piniaStores/auth.ts @@ -5,6 +5,7 @@ export const useAuthStore = defineStore('auth', () => { const accessToken = ref() const idpContextReady = ref(false) const userContextReady = ref(false) + const sessionExpired = ref(false) const publicLinkToken = ref() const publicLinkPassword = ref() const publicLinkType = ref() @@ -19,6 +20,9 @@ export const useAuthStore = defineStore('auth', () => { const setUserContextReady = (value: boolean) => { userContextReady.value = value } + const setSessionExpired = (value: boolean) => { + sessionExpired.value = value + } const setPublicLinkContext = (context: { publicLinkToken: string publicLinkPassword: string @@ -50,6 +54,7 @@ export const useAuthStore = defineStore('auth', () => { accessToken, idpContextReady, userContextReady, + sessionExpired, publicLinkToken, publicLinkPassword, publicLinkType, @@ -58,6 +63,7 @@ export const useAuthStore = defineStore('auth', () => { setAccessToken, setIdpContextReady, setUserContextReady, + setSessionExpired, setPublicLinkContext, clearUserContext, clearPublicLinkContext diff --git a/packages/web-pkg/src/composables/webWorkers/tokenTimerWorker/useTokenTimerWorker.ts b/packages/web-pkg/src/composables/webWorkers/tokenTimerWorker/useTokenTimerWorker.ts index 9788ad20d61..c326e0f1f09 100644 --- a/packages/web-pkg/src/composables/webWorkers/tokenTimerWorker/useTokenTimerWorker.ts +++ b/packages/web-pkg/src/composables/webWorkers/tokenTimerWorker/useTokenTimerWorker.ts @@ -24,10 +24,10 @@ export const useTokenTimerWorker = ({ authService }: { authService: AuthServiceI console.error('token renewal error:', error) - // log out user if they don't have a refresh token + // show session expired modal if there's no refresh token to renew silently const refreshToken = await authService.getRefreshToken() if (!refreshToken) { - return authService.logoutUser() + return authService.showSessionExpiredModal() } }) } diff --git a/packages/web-runtime/src/App.vue b/packages/web-runtime/src/App.vue index d6be50d9934..2a89ea1d517 100644 --- a/packages/web-runtime/src/App.vue +++ b/packages/web-runtime/src/App.vue @@ -7,11 +7,13 @@ + + + diff --git a/packages/web-runtime/src/composables/layout/useLayout.ts b/packages/web-runtime/src/composables/layout/useLayout.ts index 55e0ded3547..1777281a13b 100644 --- a/packages/web-runtime/src/composables/layout/useLayout.ts +++ b/packages/web-runtime/src/composables/layout/useLayout.ts @@ -21,6 +21,7 @@ export const useLayout = (options?: LayoutOptions) => { 'logout', 'oidcCallback', 'oidcSilentRedirect', + 'oidcPopupCallback', 'resolvePublicLink', 'accessDenied' ] diff --git a/packages/web-runtime/src/helpers/silentRedirect.ts b/packages/web-runtime/src/helpers/silentRedirect.ts index a923cf97814..9e8627827b8 100644 --- a/packages/web-runtime/src/helpers/silentRedirect.ts +++ b/packages/web-runtime/src/helpers/silentRedirect.ts @@ -1 +1,2 @@ export const isSilentRedirectRoute = () => window.location.pathname === '/web-oidc-silent-redirect' +export const isPopupCallbackRoute = () => window.location.pathname === '/web-oidc-popup-callback' diff --git a/packages/web-runtime/src/pages/login.vue b/packages/web-runtime/src/pages/login.vue index c1ef74eaf38..58b145cc424 100644 --- a/packages/web-runtime/src/pages/login.vue +++ b/packages/web-runtime/src/pages/login.vue @@ -1,25 +1,79 @@ + + diff --git a/packages/web-runtime/src/pages/oidcCallback.vue b/packages/web-runtime/src/pages/oidcCallback.vue index 7cf18c940bc..3f6b8e157a9 100644 --- a/packages/web-runtime/src/pages/oidcCallback.vue +++ b/packages/web-runtime/src/pages/oidcCallback.vue @@ -1,5 +1,12 @@