From d8c270859bb1961365cf4fc7691534c0e0c66268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 12 Nov 2026 14:15:55 +0100 Subject: [PATCH] refactor(auth): leave for single sign-on through one helper The sign-in page and the profile take-over page each cleared the offline copies with the same capped wait before leaving for the identity provider, with the limit written down twice. leaveForSingleSignOn does it once for both. --- src/app/auth/login/page.tsx | 15 ++------------- src/components/auth/claim-profile.tsx | 16 ++-------------- src/lib/auth/leave-for-single-sign-on.ts | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 27 deletions(-) create mode 100644 src/lib/auth/leave-for-single-sign-on.ts diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 3d5b3e9ed..89eae5a58 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -24,7 +24,7 @@ import { OIDC_MFA_COOKIE, OIDC_MFA_COOKIE_PATH } from "@/lib/auth/oidc-cookie"; import { isDashboardSnapshotEnabled } from "@/lib/dashboard/snapshot-flag"; import { prefetchDashboardSnapshot } from "@/lib/queries/use-dashboard-snapshot"; import { clearOfflineCachesForSessionEnd } from "@/lib/pwa/query-persister"; -import { settleWithin } from "@/lib/async/settle-within"; +import { leaveForSingleSignOn } from "@/lib/auth/leave-for-single-sign-on"; import { sanitizeSameOriginPath } from "@/lib/url-safety"; import { localizedSignInError } from "@/lib/api/localized-error"; import { formatDate } from "@/lib/format"; @@ -52,9 +52,6 @@ function deletionPendingMessage(t: Translate, purgeAt: unknown): string { : t("auth.deletionPendingNoDate"); } -/** How long the single-sign-on start waits for the offline copies to go. */ -const SSO_CLEANUP_WAIT_MS = 1_500; - /** The message for the `?error=` a sign-in redirect (the SSO callback) set. */ function signInRedirectError( searchParams: URLSearchParams, @@ -187,15 +184,7 @@ export default function LoginPage() { params.set("client_proto", clientTransport.protocol); params.set("client_host", clientTransport.host); } - // The session starts on the way back; the last account's copies go - // first. Bounded: WebKit can leave an IndexedDB open hanging after the - // app returns from the background, and the button must still work. - await settleWithin( - clearOfflineCachesForSessionEnd(), - SSO_CLEANUP_WAIT_MS, - undefined, - ); - loadDocument(`/api/auth/oidc/login?${params}`); + await leaveForSingleSignOn(`/api/auth/oidc/login?${params}`); } function getRedirectTarget(): string { diff --git a/src/components/auth/claim-profile.tsx b/src/components/auth/claim-profile.tsx index 3ea40d5e6..695bf2ef5 100644 --- a/src/components/auth/claim-profile.tsx +++ b/src/components/auth/claim-profile.tsx @@ -21,18 +21,13 @@ import { import { ApiError } from "@/lib/api/api-fetch"; import { showErrorToast } from "@/lib/errors/error-toast"; import { useFormatters, useTranslations } from "@/lib/i18n/context"; -import { loadDocument } from "@/lib/navigation/load-document"; -import { clearOfflineCachesForSessionEnd } from "@/lib/pwa/query-persister"; -import { settleWithin } from "@/lib/async/settle-within"; +import { leaveForSingleSignOn } from "@/lib/auth/leave-for-single-sign-on"; import { useClaimProfile, useClaimProfileSso, useProfileClaimPreview, } from "@/lib/queries/use-profile-claim"; -/** How long the single-sign-on claim waits for the offline copies to go. */ -const SSO_CLEANUP_WAIT_MS = 1_500; - /** * v1.42 (#959) — the person a managed profile describes takes it over as * their own account (`/auth/claim?token=hlp_…`). @@ -115,14 +110,7 @@ export function ClaimProfile({ next.searchParams.set("client_proto", clientTransport.protocol); next.searchParams.set("client_host", clientTransport.host); } - // The session starts on the way back; the last account's copies - // go first, waited on no longer than on the sign-in page. - await settleWithin( - clearOfflineCachesForSessionEnd(), - SSO_CLEANUP_WAIT_MS, - undefined, - ); - loadDocument(`${next.pathname}${next.search}`); + await leaveForSingleSignOn(`${next.pathname}${next.search}`); }, }, ); diff --git a/src/lib/auth/leave-for-single-sign-on.ts b/src/lib/auth/leave-for-single-sign-on.ts new file mode 100644 index 000000000..07b2bf363 --- /dev/null +++ b/src/lib/auth/leave-for-single-sign-on.ts @@ -0,0 +1,14 @@ +import { settleWithin } from "@/lib/async/settle-within"; +import { loadDocument } from "@/lib/navigation/load-document"; +import { clearOfflineCachesForSessionEnd } from "@/lib/pwa/query-persister"; + +/** + * Leave for the identity provider. The session starts on the way back, so + * the last account's offline copies go first. The wait for them is capped: + * WebKit can leave an IndexedDB open hanging after the installed app returns + * from the background, and the button must still work then. + */ +export async function leaveForSingleSignOn(url: string): Promise { + await settleWithin(clearOfflineCachesForSessionEnd(), 1_500, undefined); + loadDocument(url); +}