Files
Toju/agents-docs/features/authentication.md
Myx eb51f043ac
All checks were successful
Queue Release Build / prepare (push) Successful in 19s
Deploy Web Apps / deploy (push) Successful in 8m12s
Queue Release Build / build-windows (push) Successful in 27m44s
Queue Release Build / build-linux (push) Successful in 48m1s
Queue Release Build / build-android (push) Successful in 22m7s
Queue Release Build / finalize (push) Successful in 2m42s
fix: Major bug cleanup pass 1
2026-06-09 17:59:54 +02:00

5.6 KiB

Authentication

Session-token authentication for the signaling server and product client.

Trust boundaries

Surface Identity proof Notes
Signaling server REST (mutations) Authorization: Bearer <token> 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

{
  "id": "<uuid>",
  "username": "alice",
  "displayName": "Alice",
  "token": "<opaque-hex>",
  "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

{
  "type": "identify",
  "token": "<session-token>",
  "oderId": "<user-id>",
  "displayName": "Alice",
  "connectionScope": "ws://host:3001",
  "clientInstanceId": "<per-install-uuid>"
}
  • 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-<homeUserIdPrefix>)
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.