refactor(auth): navigate for single sign-on at the call sites again

Moving the navigation into the shared helper took both sign-in hand-offs
out of what the headerless-transport guard walks, which only reads the
component and page trees. The helper now only clears the offline copies
with the capped wait, and each page navigates itself. The device
registration test checks the end of the no-device rest inside the test
that already pairs an iPhone instead of a copy of it.
This commit is contained in:
Marc-André Bombeck committed 2026-11-12 14:19:47 +01:00
1 parent f78c9b0b78
commit ae005f020b
5 files changed
+22 -32

No files matched your search

+1 -14
View File
@@ -221,21 +221,8 @@ describe("POST /api/devices", () => {
};
expect(args.data.apnsToken).toBe("deadbeef".repeat(8));
expect(args.data.apnsEnvironment).toBe("sandbox");
});
it("ends a pause that was waiting for a paired iPhone", async () => {
// A reminder with no iPhone paired rests the APNs channel for an hour;
// pairing one must not leave the next reminder waiting it out.
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
const res = await POST(
req({
token: "abcd1234efgh5678",
bundleId: "io.healthlog.app",
apnsToken: "deadbeef".repeat(8),
apnsEnvironment: "sandbox",
}),
);
expect(res.status).toBe(201);
// pairing one ends that rest, so the next reminder does not wait it out.
expect(prisma.notificationChannel.updateMany).toHaveBeenCalledWith({
where: {
userId: "user-1",
+4 -2
View File
@@ -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 { leaveForSingleSignOn } from "@/lib/auth/leave-for-single-sign-on";
import { clearOfflineCachesBeforeLeaving } from "@/lib/pwa/clear-before-leaving";
import { sanitizeSameOriginPath } from "@/lib/url-safety";
import { localizedSignInError } from "@/lib/api/localized-error";
import { formatDate } from "@/lib/format";
@@ -184,7 +184,9 @@ export default function LoginPage() {
params.set("client_proto", clientTransport.protocol);
params.set("client_host", clientTransport.host);
}
await leaveForSingleSignOn(`/api/auth/oidc/login?${params}`);
// The session starts on the way back; the last account's copies go first.
await clearOfflineCachesBeforeLeaving();
loadDocument(`/api/auth/oidc/login?${params}`);
}
function getRedirectTarget(): string {
+5 -2
View File
@@ -21,7 +21,8 @@ import {
import { ApiError } from "@/lib/api/api-fetch";
import { showErrorToast } from "@/lib/errors/error-toast";
import { useFormatters, useTranslations } from "@/lib/i18n/context";
import { leaveForSingleSignOn } from "@/lib/auth/leave-for-single-sign-on";
import { loadDocument } from "@/lib/navigation/load-document";
import { clearOfflineCachesBeforeLeaving } from "@/lib/pwa/clear-before-leaving";
import {
useClaimProfile,
useClaimProfileSso,
@@ -110,7 +111,9 @@ export function ClaimProfile({
next.searchParams.set("client_proto", clientTransport.protocol);
next.searchParams.set("client_host", clientTransport.host);
}
await leaveForSingleSignOn(`${next.pathname}${next.search}`);
// The session starts on the way back; the last account's copies go first.
await clearOfflineCachesBeforeLeaving();
loadDocument(`${next.pathname}${next.search}`);
},
},
);
-14
View File
@@ -1,14 +0,0 @@
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<void> {
await settleWithin(clearOfflineCachesForSessionEnd(), 1_500, undefined);
loadDocument(url);
}
+12
View File
@@ -0,0 +1,12 @@
import { settleWithin } from "@/lib/async/settle-within";
import { clearOfflineCachesForSessionEnd } from "@/lib/pwa/query-persister";
/**
* The session-end wipe before the page leaves for the identity provider,
* where the next session starts. The wait 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 function clearOfflineCachesBeforeLeaving(): Promise<void> {
return settleWithin(clearOfflineCachesForSessionEnd(), 1_500, undefined);
}