diff --git a/sites/mainweb/lib/safe-callback.test.ts b/sites/mainweb/lib/safe-callback.test.ts index d83ce33f..2b26b803 100644 --- a/sites/mainweb/lib/safe-callback.test.ts +++ b/sites/mainweb/lib/safe-callback.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { safeCallback } from "./safe-callback"; +import { loginHref, safeCallback } from "./safe-callback"; describe("safeCallback", () => { it("keeps a same-origin path", () => { @@ -29,3 +29,21 @@ describe("safeCallback", () => { expect(safeCallback("/\\evil.example")).toBeNull(); }); }); + +describe("loginHref", () => { + it("returns to the full current location, hash included", () => { + const previous = (globalThis as { window?: unknown }).window; + (globalThis as { window?: unknown }).window = { + location: { pathname: "/club", search: "?x=1", hash: "#history" }, + }; + try { + expect(loginHref()).toBe( + `/login?callbackUrl=${encodeURIComponent("/club?x=1#history")}`, + ); + // And what comes back through the login page is still accepted. + expect(safeCallback("/club?x=1#history")).toBe("/club?x=1#history"); + } finally { + (globalThis as { window?: unknown }).window = previous; + } + }); +}); diff --git a/sites/mainweb/lib/safe-callback.ts b/sites/mainweb/lib/safe-callback.ts index 65af3eb9..6639e9d0 100644 --- a/sites/mainweb/lib/safe-callback.ts +++ b/sites/mainweb/lib/safe-callback.ts @@ -43,6 +43,9 @@ export function safeCallback(raw: string | null | undefined): string | null { * Browser-only: call it from an effect or a handler. */ export function loginHref(): string { - const here = window.location.pathname + window.location.search; + // The hash too: /club and /hackathons pick their tab from it, so a shared + // #history link otherwise came back to the default tab. + const here = + window.location.pathname + window.location.search + window.location.hash; return `/login?callbackUrl=${encodeURIComponent(here)}`; }