fix(auth): a Google connect names its tab's account too, and a refused tab says why
- /auth/google/connect is a navigation and carries no headers: the tab names the account it was built for in `expect`, checked by the same comparison as the request header (built_for_another_account); a mismatch lands on /?account=changed - the link marker names the OAuth state of its own flow: an older flow coming back no longer spends the marker of the connect still open (which then returned as a plain sign-in) - a 401 account_changed reloads the tab with a one-time flag, and the reloaded tab says it now shows the account signed in - a refused switch to a suspended or not-yet-approved account says so in the signed-in app's words, naming the address a request was recorded for - the prefs merge locks FOR NO KEY UPDATE, so a transaction that only added a row referring to the account does not hold the save up; a test pins the lock - giving up a lost page closes a channel page open over it - e2e README names the chromium-only specs
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// "Sign in as someone else" with a Google account the server will not sign in (not approved, or
|
||||
// suspended) comes back to the account still signed in here, with the flags the sign-in page reads;
|
||||
// a tab the server refused because another account signed in under it reloads with a flag of its
|
||||
// own. Each says so in the signed-in app's words, not the sign-in page's.
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
ACCOUNT_CHANGED_FLAG,
|
||||
accountChangedUrl,
|
||||
accountSwitchNotice,
|
||||
clearEntryUrl,
|
||||
reloadAfterRefusal,
|
||||
} from "./entryUrl";
|
||||
|
||||
const notice = (search: string, hash = "") =>
|
||||
accountSwitchNotice(new URLSearchParams(search), new URLSearchParams(hash));
|
||||
|
||||
afterEach(() => {
|
||||
window.history.replaceState(null, "", "/");
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("a switch of account, told to the account signed in", () => {
|
||||
it("names the address a request was recorded for", () => {
|
||||
expect(notice("", "access=requested&email=b%2Btag%40example.com")).toEqual({
|
||||
level: "info",
|
||||
key: "header.account.switchRequested",
|
||||
values: { email: "b+tag@example.com" },
|
||||
});
|
||||
});
|
||||
|
||||
it("names the address the request lives under when that is another spelling", () => {
|
||||
expect(notice("", "access=requested&email=bob%40x.com&linked=b.ob%40x.com")?.values).toEqual({
|
||||
email: "b.ob@x.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("says the account is not approved, and nothing changed here", () => {
|
||||
expect(notice("", "access=denied")).toEqual({
|
||||
level: "warning",
|
||||
key: "header.account.switchNotApproved",
|
||||
});
|
||||
});
|
||||
|
||||
it("says that account is suspended, not this one", () => {
|
||||
expect(notice("?login=suspended")).toEqual({
|
||||
level: "warning",
|
||||
key: "header.account.switchSuspended",
|
||||
});
|
||||
});
|
||||
|
||||
it("says the tab reloaded as the account signed in now", () => {
|
||||
expect(notice(`?page=stats&${ACCOUNT_CHANGED_FLAG}`)).toEqual({
|
||||
level: "info",
|
||||
key: "header.account.tabSwitched",
|
||||
});
|
||||
});
|
||||
|
||||
it("says nothing on an ordinary load", () => {
|
||||
expect(notice("?page=stats", "")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the reload of a tab another account signed in under", () => {
|
||||
it("stays on its page and carries the flag", () => {
|
||||
window.history.replaceState(null, "", "/?page=stats&chan=UC1#x=1");
|
||||
const url = new URL(accountChangedUrl(), "http://h");
|
||||
expect(url.pathname).toBe("/");
|
||||
expect(Object.fromEntries(url.searchParams)).toEqual({
|
||||
page: "stats",
|
||||
chan: "UC1",
|
||||
account: "changed",
|
||||
});
|
||||
expect(url.hash).toBe("#x=1");
|
||||
});
|
||||
|
||||
it("drops the flag once read, so a later reload does not say it again", () => {
|
||||
window.history.replaceState(null, "", `/?page=stats&${ACCOUNT_CHANGED_FLAG}`);
|
||||
clearEntryUrl();
|
||||
expect(window.location.search).toBe("?page=stats");
|
||||
});
|
||||
});
|
||||
|
||||
describe("a tab whose session the server refused", () => {
|
||||
const stubLocation = () => {
|
||||
const loc = {
|
||||
pathname: "/",
|
||||
search: "?page=stats",
|
||||
hash: "",
|
||||
replace: vi.fn(),
|
||||
reload: vi.fn(),
|
||||
};
|
||||
vi.stubGlobal("location", loc);
|
||||
return loc;
|
||||
};
|
||||
|
||||
it("reloads as the account signed in now, saying why, when it showed another", () => {
|
||||
const loc = stubLocation();
|
||||
reloadAfterRefusal({ detailData: { code: "refused", reason: "account_changed" } });
|
||||
expect(loc.replace).toHaveBeenCalledWith(`/?page=stats&${ACCOUNT_CHANGED_FLAG}`);
|
||||
expect(loc.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reloads plainly when the session ended", () => {
|
||||
const loc = stubLocation();
|
||||
reloadAfterRefusal({ detailData: { code: "refused", reason: "not_authenticated" } });
|
||||
expect(loc.reload).toHaveBeenCalled();
|
||||
expect(loc.replace).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user