aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_auth_race.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-17 10:26:09 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-17 10:26:09 +0200
commit1fd284dbe33d05fd5037b172fe652a8a98f7b68d (patch)
tree52465b6d438fc7d7bd96f1cf83571069eb0fa3d3 /packages/meshbay-hub/tests/test_auth_race.py
parenta98445ce246509b0d2600df14907f72b448a6d10 (diff)
downloadmeshbay-1fd284dbe33d05fd5037b172fe652a8a98f7b68d.tar.gz
auth: a sign-out during a renewal must not write half a session
`{ ..._auth }` after the await spreads a null _auth to {}, so the renewal stored a token with no username and no userId. The app renders the signed-in interface from that and throws on user.username[0] — a blank page on every load, in localStorage, until the site's data is cleared. The sign-out wins the race now, and loadAuth treats an identity-less object as signed out so a browser already holding one heals itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_auth_race.py')
-rw-r--r--packages/meshbay-hub/tests/test_auth_race.py62
1 files changed, 62 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_auth_race.py b/packages/meshbay-hub/tests/test_auth_race.py
new file mode 100644
index 0000000..32e7e49
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_auth_race.py
@@ -0,0 +1,62 @@
+"""
+A sign-out during a token renewal must not leave half a session behind.
+
+A tab left open overnight is exactly this race: the idle watch signs the browser
+out at the same moment the renewal fires — the hub's log has the two requests
+one second apart. `refreshAccessToken` came back to an `_auth` that was already
+null and wrote `{ ..._auth, token, refreshToken }`. Spreading null is silent, so
+what landed in localStorage was a token with **no identity at all**.
+
+That object is worse than no session. The app believes it is one, renders the
+signed-in interface from it, and throws on the first field it reads —
+`user.username[0]`, which is "Cannot read properties of undefined (reading
+'0')". Every load afterwards is blank, and because it is stored it survives
+reloads, clearing the cache and restarting the device. Reported from a phone,
+diagnosed from a screenshot of `boot-guard.js` naming the error on screen.
+
+Two fixes, and both are needed: the sign-out now wins the race, and `loadAuth`
+treats an identity-less object as signed out, so a browser already holding one
+heals on its next load instead of needing someone to find the reset button.
+"""
+
+import json
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+HARNESS = Path(__file__).parent / "harness" / "auth_race_probe.py"
+
+
+@pytest.fixture(scope="module")
+def out():
+ if shutil.which("google-chrome") is None:
+ pytest.skip("Chrome is not available")
+ proc = subprocess.run([sys.executable, str(HARNESS)],
+ capture_output=True, text=True, timeout=120)
+ data = json.loads(proc.stdout)
+ assert "error" not in data, f"probe failed: {proc.stdout}{proc.stderr}"
+ return data
+
+
+def test_a_sign_out_during_a_renewal_wins(out):
+ # Not "writes something harmless": writes nothing. The sign-out is the
+ # later intention and there is nothing in a renewal worth keeping over it.
+ assert out["afterTheRace"] is None, (
+ f"a session survived the sign-out: {out['afterTheRace']}")
+
+
+def test_what_used_to_be_written_is_recognised_as_not_a_session(out):
+ # The healing half. Without it every browser already holding one stays
+ # blank for a year, and the only way out is a menu nobody finds.
+ assert out["poisonedLoads"] is None, out["poisonedLoads"]
+ assert out["poisonedLeftBehind"] is None, (
+ "the identity-less object was left in storage to be read again")
+
+
+def test_a_real_session_still_loads_untouched(out):
+ # The guard above must not sign anybody out. This is the case that says so.
+ assert out["goodLoads"] == "someone", out["goodLoads"]
+ assert out["goodLeftAlone"], "a valid session was cleared"