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:
@@ -223,10 +223,11 @@ function reportFailure(err: HttpError, quiet: boolean): void {
|
||||
if (RETRIABLE_GATEWAY.has(err.status)) {
|
||||
markConnectivityLost();
|
||||
} else if (err.status === 401) {
|
||||
// Session ended server-side (e.g. the account was suspended or deleted mid-visit). Let the app
|
||||
// shell decide what to do (drop to the login page if we were signed in). Not a modal — but
|
||||
// Session ended server-side (e.g. the account was suspended or deleted mid-visit), or this tab
|
||||
// shows another account than the one signed in now. Let the app shell decide what to do (drop to
|
||||
// the login page, or reload as the account signed in, if we were signed in). Not a modal — but
|
||||
// handled all the same, so not the caller's to toast either.
|
||||
onUnauthorized?.();
|
||||
onUnauthorized?.(err);
|
||||
reported.add(err);
|
||||
} else if (quiet) {
|
||||
/* caller handles the error inline — no global modal */
|
||||
@@ -256,8 +257,8 @@ export interface ReqConfig {
|
||||
// page. The handler itself guards against firing when we were never signed in, so it's safe to
|
||||
// call for every 401. (The bootstrap /api/me probe no longer 401s — it returns 200 with a null
|
||||
// user when logged out — but other protected endpoints still do.)
|
||||
let onUnauthorized: (() => void) | null = null;
|
||||
export function setUnauthorizedHandler(fn: (() => void) | null): void {
|
||||
let onUnauthorized: ((err: HttpError) => void) | null = null;
|
||||
export function setUnauthorizedHandler(fn: ((err: HttpError) => void) | null): void {
|
||||
onUnauthorized = fn;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ import { describe, expect, it } from "vitest";
|
||||
// one without a word.
|
||||
const read = (path: string) => readFileSync(new URL(path, import.meta.url), "utf-8");
|
||||
const AUTH = read("../../../../backend/app/auth.py");
|
||||
const ROUTES = read("../../../../backend/app/routes/auth.py");
|
||||
const CORE = read("./core.ts");
|
||||
const CONNECT = read("../googleConnect.ts");
|
||||
const ENTRY = read("../entryUrl.ts");
|
||||
|
||||
describe("the account a tab was built for", () => {
|
||||
it("is the header the server checks", () => {
|
||||
@@ -16,4 +19,17 @@ describe("the account a tab was built for", () => {
|
||||
expect(backend).toBeTruthy();
|
||||
expect(backend).toBe(frontend?.toLowerCase());
|
||||
});
|
||||
|
||||
it("rides a Google connect as the query parameter the route reads", () => {
|
||||
// A navigation carries no headers; a rename on one side would leave the connect unchecked.
|
||||
expect(CONNECT).toMatch(/params\.set\("expect", account\)/);
|
||||
expect(ROUTES).toMatch(/^ {4}expect: str \| None = None,$/m);
|
||||
});
|
||||
|
||||
it("lands a refused navigation where the reloaded tab says why", () => {
|
||||
const landing = /^ACCOUNT_CHANGED_LANDING = "\/\?([^"]+)"$/m.exec(ROUTES)?.[1];
|
||||
const flag = /^export const ACCOUNT_CHANGED_FLAG = "([^"]+)";$/m.exec(ENTRY)?.[1];
|
||||
expect(landing).toBeTruthy();
|
||||
expect(landing).toBe(flag);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user