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.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# 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)
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user