- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine suspend; the reload client otherwise destroys the session under test. - `dev-peer.sh` plus a separate userdata dir runs a second local peer. - `tools/voice-probe.js` samples peer state and RTP counters from a live window, persisting to localStorage so a renderer reload cannot erase it. - e2e helpers for voice pairs, peer-role election, and a TURN relay. - Electron single-instance and dev-client-load decisions move into rules files with colocated specs.
5.0 KiB
04 — Auth & login bugs
User theme: “I’m logged in but it keeps asking me to log in / authorize.”
Severity: P0 — blocks multi-server chat, voice, presence.
Existing research:agents-docs/user-stories/silent-cross-signal-server-auth.md(do not redo; implement after interview).
How auth is supposed to work (user view)
- Register or log in once on a home signal server.
- Stay signed in across app restarts (desktop).
- When touching another signal server (join, invite, create room, activate endpoint): the app silently creates or reuses an account there.
- You only see a login form again if:
- true home session expired / missing token, or
- last-resort: username collision exhaustion / user clicked Sign in in Network settings.
- Offline / dead endpoints never bounce you to authorize login.
Settings → Network may show Authorized / Needs sign-in as diagnostics — not as the default path for normal joins.
How auth is supposed to work (system)
| Concept | Role |
|---|---|
| Home credential | Token + user id for homeSignalServerUrl |
| Foreign credential | Separate user id + token per URL in SignalServerCredentialStore |
| Provision secret | Password used only for auto register/login on foreign hosts |
ensureProvisioned |
Register-or-login with secret; suffix username on collision |
ensureCredentialForServerUrl |
Gate before foreign room connect; navigate authorize only for collision or no-provision-secret when endpoint online |
authorize mode |
Manual login that upserts foreign credential without resetting home profile |
auth_required vs auth_error |
Race vs rejected token — must not falsely expire home on foreign races |
Authorize nav rule: shouldNavigateToAuthorizeSignalServer in signal-server-authorize.rules.ts.
Failure modes (code-backed)
A — Missing provision secret → authorize UI (primary)
Symptom: Logged-in user opens foreign room/invite → /login?mode=authorize.
Mechanism
ensureProvisioned→{ kind: 'skipped', reason: 'no-provision-secret' }.ensureCredentialForServerUrlnavigates to authorize.- Authorize mode does not auto-leave when
currentUserexists → stuck prompt.
Why secret missing
- Created only in
prepareAuthenticatedUserStoragewhenhomeSignalServerUrl+loginResponsepresent. - Session restore calls
ensureProvisionedbut does not ensure secret exists first. - Web: sessionStorage secret dies with tab.
- Old installs / wiped Electron
userData/provision-secrets/.
Files
signal-server-auth.service.ts,signal-server-authorize.service.tsprovision-secret-store.service.ts,electron/api/provision-secret-store.tsusers.effects.ts
B — Username collision exhaustion
All register candidates 409 + login 401 → collision → authorize. Rare but real on crowded foreign servers.
C — False home session expiry
signalServerAuthFailed$ may SESSION_EXPIRED → full /login when home classification / retry budget wrong. Distinguish auth_required (re-identify) vs auth_error (clear credential; foreign re-provision vs home expire).
D — Credential missing → unauthenticated join → invisible user
Without token, socket never identifies; join_server dropped; user alone; later gates open authorize. Cascades into “messages not seen” and “voice empty”.
E — Opportunistic provision swallows errors
ensureProvisioned(...).catch(() => undefined) on health/startup → failure deferred until mid-join authorize popup.
F — Others?
Doc / README lies in this area
| Claim | Reality |
|---|---|
Auth domain README sequence: POST /api/auth/login |
Real paths: /api/users/login, /api/users/register (AuthenticationService) |
| Feature doc: offline must not open authorize | Code path exists; still fails open on missing secret when “online” |
Fix directions (interview choices — not approved)
From user story (recommend A):
| Option | Idea |
|---|---|
| A | On restore / before foreign provision, always ensureHomeProvisionSecret; keep authorize only for collision / manual |
| B | Never navigate on no-provision-secret; toast + Network badge |
| C | Durable web secret (not sessionStorage) |
| D | Stronger unique usernames before collision UI |
Proof of done
- Two live signal servers: home register on A → join room on B → no
/loginnavigation. - Full Electron restart → foreign rejoin still silent.
- Offline foreign URL → no authorize navigation.
- Focused tests: missing secret on restore → secret created → provision →
Router.navigate(['/login'])never called. - Foreign
auth_errorwith valid home → re-provision, notSESSION_EXPIRED.
Agent scope
- Default:
toju-app/domains/authentication,store/users,store/rooms/room-signaling-connection.ts, server-directory call sites. - Electron: provision-secret store/IPC only if persistence fix needs it.
- Ask before deep
server/auth changes.