mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
fix(auth): replace the sign-in page in the history after signing in
Back after signing in, registering or taking over a profile showed the form that had just been submitted, because each pushed the next page on top of it. Password, passkey and second-factor sign-in, registration and the password take-over now replace the page instead.
This commit is contained in:
1 parent
6c5a6eacbb
commit
8fdc238981
6 files changed
+47
-10
No files matched your search
@@ -3,12 +3,19 @@
|
||||
*
|
||||
* Single sign-on leaves the page for the identity provider and the session
|
||||
* starts on the way back, so the previous account's offline copies have to
|
||||
* be gone before the page is left.
|
||||
* be gone before the page is left. A sign-in that stays in the app replaces
|
||||
* the sign-in page in the history, so Back does not show the form again.
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { stubFetch } from "../../../../../tests/browser/fetch-stub";
|
||||
import { page } from "vitest/browser";
|
||||
|
||||
import {
|
||||
stubFetch,
|
||||
type FetchStub,
|
||||
} from "../../../../../tests/browser/fetch-stub";
|
||||
import { renderWithApp } from "../../../../../tests/browser/render";
|
||||
import { recordHistory } from "../../../../../tests/browser/url";
|
||||
|
||||
const clearOffline = vi.hoisted(() => vi.fn(async () => {}));
|
||||
const loadDocument = vi.hoisted(() => vi.fn());
|
||||
@@ -20,8 +27,10 @@ vi.mock("@/lib/navigation/load-document", () => ({ loadDocument }));
|
||||
|
||||
import LoginPage from "../page";
|
||||
|
||||
let api: FetchStub;
|
||||
|
||||
beforeEach(() => {
|
||||
stubFetch()
|
||||
api = stubFetch()
|
||||
.on("GET", "/api/auth/registration-status", {
|
||||
data: { registrationEnabled: false, sessionCookieSecure: false },
|
||||
})
|
||||
@@ -50,3 +59,23 @@ describe("sign-in page, single sign-on", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sign-in page, password", () => {
|
||||
it("replaces itself with the page asked for", async () => {
|
||||
api.on("POST", "/api/auth/login", { data: { id: "signed-in" } });
|
||||
window.history.replaceState(null, "", "/auth/login?next=%2Fmood%3Fadd%3D1");
|
||||
const history = recordHistory();
|
||||
const { screen } = await renderWithApp(<LoginPage />, { user: null });
|
||||
|
||||
await screen.getByTestId("login-use-password").click();
|
||||
await screen.getByTestId("login-email").fill("someone");
|
||||
await page
|
||||
.elementLocator(document.querySelector("#password")!)
|
||||
.fill("a long enough passphrase");
|
||||
await screen.getByTestId("login-password-submit").click();
|
||||
|
||||
await expect.poll(() => window.location.pathname).toBe("/mood");
|
||||
expect(window.location.search).toBe("?add=1");
|
||||
expect(history.pushes()).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -236,7 +236,7 @@ export default function LoginPage() {
|
||||
if (target === "/" && isDashboardSnapshotEnabled()) {
|
||||
prefetchDashboardSnapshot(queryClient);
|
||||
}
|
||||
router.push(target);
|
||||
router.replace(target);
|
||||
}
|
||||
|
||||
async function handlePasskeyLogin() {
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
*
|
||||
* A new account starts from an empty cache, like every other sign-in: the
|
||||
* client cache outlives the navigation, and nothing read before the account
|
||||
* existed belongs to it.
|
||||
* existed belongs to it. The app replaces the page in the history, so Back
|
||||
* does not show the form again.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { page } from "vitest/browser";
|
||||
@@ -12,6 +13,7 @@ import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
import { stubFetch } from "../../../../../tests/browser/fetch-stub";
|
||||
import { renderWithApp } from "../../../../../tests/browser/render";
|
||||
import { recordHistory } from "../../../../../tests/browser/url";
|
||||
import RegisterPage from "../page";
|
||||
|
||||
async function fill(selector: string, value: string): Promise<void> {
|
||||
@@ -24,6 +26,7 @@ describe("registration page", () => {
|
||||
.on("GET", "/api/auth/me", { status: 401, error: "Unauthorized" })
|
||||
.on("POST", "/api/auth/register", { data: { id: "new-account" } });
|
||||
window.history.replaceState(null, "", "/auth/register");
|
||||
const history = recordHistory();
|
||||
const { screen, queryClient } = await renderWithApp(<RegisterPage />, {
|
||||
user: null,
|
||||
});
|
||||
@@ -37,6 +40,7 @@ describe("registration page", () => {
|
||||
|
||||
await expect.poll(() => window.location.pathname).toBe("/");
|
||||
expect(api.calls("POST", "/api/auth/register")).toHaveLength(1);
|
||||
expect(history.pushes()).toEqual([]);
|
||||
expect(queryClient.getQueryData(queryKeys.dashboardSnapshot())).toBe(
|
||||
undefined,
|
||||
);
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function RegisterPage() {
|
||||
});
|
||||
|
||||
queryClient.clear(); // nothing cached before belongs to the new account
|
||||
router.push("/");
|
||||
router.replace("/");
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof ApiError && err.message
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* Taking over a managed profile, driven by the claim page's buttons.
|
||||
*
|
||||
* Both ways in start a session for a different account than whatever this
|
||||
* browser held before: the password claim stays in the single-page app and
|
||||
* starts from an empty cache, the single-sign-on claim leaves for the
|
||||
* identity provider and clears the offline copies before it goes.
|
||||
* browser held before: the password claim stays in the single-page app,
|
||||
* starts from an empty cache and replaces the claim page in the history; the
|
||||
* single-sign-on claim leaves for the identity provider and clears the
|
||||
* offline copies before it goes.
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { page } from "vitest/browser";
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
type FetchStub,
|
||||
} from "../../../../tests/browser/fetch-stub";
|
||||
import { renderWithApp } from "../../../../tests/browser/render";
|
||||
import { recordHistory } from "../../../../tests/browser/url";
|
||||
|
||||
const clearOffline = vi.hoisted(() => vi.fn(async () => {}));
|
||||
const loadDocument = vi.hoisted(() => vi.fn());
|
||||
@@ -70,6 +72,7 @@ async function click(slot: string): Promise<void> {
|
||||
|
||||
describe("ClaimProfile", () => {
|
||||
it("starts the claimed account from an empty cache", async () => {
|
||||
const history = recordHistory();
|
||||
const { queryClient } = await renderWithApp(
|
||||
<ClaimProfile token={TOKEN} />,
|
||||
{
|
||||
@@ -87,6 +90,7 @@ describe("ClaimProfile", () => {
|
||||
.poll(() => window.location.pathname)
|
||||
.toBe("/onboarding/handover");
|
||||
expect(api.calls("POST", "/api/auth/claim")).toHaveLength(1);
|
||||
expect(history.pushes()).toEqual([]);
|
||||
expect(queryClient.getQueryData(queryKeys.dashboardSnapshot())).toBe(
|
||||
undefined,
|
||||
);
|
||||
|
||||
@@ -122,7 +122,7 @@ export function ClaimProfile({
|
||||
onSuccess: () => {
|
||||
// As after any sign-in: nothing cached before belongs to this account.
|
||||
queryClient.clear();
|
||||
router.push("/onboarding/handover");
|
||||
router.replace("/onboarding/handover");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user