Files
Toju/emergency-fix/04-auth-login-bugs.md
T
myxelium e49b3ec112 chore: dev-stack switches, shared e2e harness, and desktop shell rules
- `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.
2026-08-14 03:19:29 +02:00

120 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 — Auth & login bugs
> **User theme:** “Im 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)
1. Register or log in **once** on a home signal server.
2. Stay signed in across app restarts (desktop).
3. When touching another signal server (join, invite, create room, activate endpoint): the app **silently** creates or reuses an account there.
4. 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.
5. 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**
1. `ensureProvisioned``{ kind: 'skipped', reason: 'no-provision-secret' }`.
2. `ensureCredentialForServerUrl` navigates to authorize.
3. Authorize mode does not auto-leave when `currentUser` exists → stuck prompt.
**Why secret missing**
- Created only in `prepareAuthenticatedUserStorage` when `homeSignalServerUrl` + `loginResponse` present.
- Session restore calls `ensureProvisioned` but **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.ts`
- `provision-secret-store.service.ts`, `electron/api/provision-secret-store.ts`
- `users.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
1. Two live signal servers: home register on A → join room on B → **no** `/login` navigation.
2. Full Electron restart → foreign rejoin still silent.
3. Offline foreign URL → no authorize navigation.
4. Focused tests: missing secret on restore → secret created → provision → `Router.navigate(['/login'])` never called.
5. Foreign `auth_error` with valid home → re-provision, not `SESSION_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.