summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_auth_race.py
blob: 32e7e491677bdb96bdd59360ff92444c3c2f7cca (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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"