# Authentication Session-token authentication for the signaling server and product client. ## Trust boundaries | Surface | Identity proof | Notes | |---|---|---| | Signaling server REST (mutations) | `Authorization: Bearer ` | Actor user IDs in request bodies are ignored; server derives `authUserId` from the token | | Signaling server REST (discovery) | None | `GET /api/servers`, featured/trending/search remain public | | Signaling server WebSocket | `identify.token` | Connections must identify before any other message type | | Electron Local API | Separate in-memory bearer tokens | Proxies login to allowed signaling servers only | | Product client local DB | OS user account | SQLite and attachments are plaintext at rest | ## Login / register response ```json { "id": "", "username": "alice", "displayName": "Alice", "token": "", "expiresAt": 1710000000000 } ``` - Tokens are opaque 64-character hex strings stored in server SQLite (`session_tokens`). - Default TTL: 10 years (`SESSION_TOKEN_TTL_MS` env override supported on the signaling server). - Passwords are stored with bcrypt; legacy SHA-256 hashes are upgraded transparently on successful login. ## Protected REST routes Require `Authorization: Bearer`: - `PUT/POST/DELETE` under `/api/servers/*` (except public `GET`) - `PUT /api/requests/:id` - Plugin-support mutations under `/api/servers/:serverId/plugins/*` - `/api/users/device-tokens/*` - `POST /api/users/logout` ## WebSocket identify contract ```json { "type": "identify", "token": "", "oderId": "", "displayName": "Alice", "connectionScope": "ws://host:3001", "clientInstanceId": "" } ``` - `oderId` must match the token's user id when provided. - `clientInstanceId` is a stable per-install UUID generated by the product client (`metoyou.clientInstanceId` in `localStorage`). The signaling server uses it to distinguish multiple WebSocket connections for the same user and to route voice ownership. - Server responds with `auth_error` or `auth_required` when authentication fails. ## Multi-device sessions - Each login/register issues a **new** session token; prior tokens remain valid until they expire or the client calls `POST /api/users/logout` with that token. - The same user may keep multiple WebSocket connections open (different devices or browser profiles). Server broadcasts (chat, typing, voice state, status) exclude only the **sending connection**, so other connections for that identity still receive updates. - Voice/WebRTC is exclusive per user: only one `clientInstanceId` may own active voice at a time. Other connections show passive UI and can send `voice_client_takeover` to move voice to the local device. - Stale reconnect hygiene: when a client re-identifies with the same `(oderId, connectionScope, clientInstanceId)` tuple, the server closes the older socket for that tuple. ## Client storage The product client stores tokens per signaling-server base URL in `localStorage` (`metoyou.authTokens`). An HTTP interceptor attaches the bearer token to `/api/*` requests targeting that server. Per-server credentials (`metoyou.signalServerCredentials`) map each normalized signal-server URL to the authenticated user id, username, display name, session token, expiry, and whether the account was auto-provisioned. The home user profile in SQLite/NgRx remains the device-local identity (`homeSignalServerUrl`); foreign-server credentials are a side map used for REST and WebSocket identify on that URL. A per-install **provision secret** enables silent account creation on newly added or encountered signal servers. It is generated on home register/login, stored in Electron `safeStorage` when available (sessionStorage fallback on web), and never persisted as the user's visible login password. ### Multi-signal-server auth flows | Flow | Action | Effect | |---|---|---| | Home login/register | `authenticateUser` | Resets local state, stores home credential + provision secret | | Foreign login/register | `authorizeSignalServer` | Upserts credential for that URL only; home session unchanged | | Auto-provision | `SignalServerProvisionerService` | Registers or logs in on foreign server using provision secret; on username collision tries suffixed username (`alice-`) | | Foreign auth failure | `signalServerAuthFailed` | Clears that URL's credential and re-provisions when home token is still valid; global logout only when home server rejects auth | Authorize UI: `/login?mode=authorize&serverId=…&returnUrl=…` (also supported on `/register`). Settings → Network shows per-endpoint `Authorized` / `Needs sign-in` badges. Persisted local user state (`metoyou_currentUserId` + IndexedDB/SQLite profile) is **not** sufficient to use chat or presence. On startup, `loadCurrentUser$` requires a non-expired session token for the user's home signaling server (or any stored token as a fallback). Missing or rejected **home** tokens dispatch `SESSION_EXPIRED` and redirect to `/login`. Foreign-server `auth_required` / `auth_error` responses clear only that server's credential and attempt re-provision. ## Security considerations - Rate limits: login/register (100 / 15 min), server join (30 / min). - CORS allowlist: optional `corsAllowlist` in `server/data/variables.json` or `CORS_ALLOWLIST` env (comma-separated). Empty allowlist keeps permissive CORS for local development. - Push-token routes require bearer auth and user-id match. - RTC relay: direct-message/direct-call types always relay; server-icon types require shared server membership; WebRTC offer/answer/ice remain open for cross-server DM WebRTC.