# Authentication Domain Handles the durable home session plus per-signal-server credentials used for cross-server identity. Provides login, registration, silent foreign provisioning, contextual recovery, and user-bar UI. ## Module map ``` authentication/ ├── application/ │ └── services/ │ ├── authentication.service.ts HTTP login/register against the active endpoint │ ├── signal-server-auth.service.ts Home migration and silent foreign provisioning │ ├── signal-server-authorize.service.ts Explicit authorization and credential checks │ ├── signal-server-auth-recovery.service.ts Contextual per-server recovery state │ └── home-provision-secret.service.ts Account-wide secret issued by the home server │ ├── domain/ │ └── models/ │ └── authentication.model.ts LoginResponse interface │ ├── feature/ │ ├── login/ Login form (`
`; autofocus + select-on-focus via shared directives) │ ├── register/ Registration form (same form-field UX as login) │ └── user-bar/ Displays current user or login/register links │ └── index.ts Barrel exports ``` ## Service overview `AuthenticationService` resolves the API base URL from `ServerDirectoryFacade`, then makes POST requests for login and registration. It does not hold session state itself; after a successful login the calling component dispatches `UsersActions.authenticateUser`, and the users effects prepare the local persistence boundary before exposing the new user in the NgRx store. `SignalServerAuthService` keeps one credential per normalized signal-server URL. Provision failures never expire the valid home session or automatically redirect to generic login. They publish a contextual issue rendered on server/join surfaces with Retry. ```mermaid graph TD Login[LoginComponent] Register[RegisterComponent] UserBar[UserBarComponent] Auth[AuthenticationService] SD[ServerDirectoryFacade] Store[NgRx Store] Login --> Auth Register --> Auth UserBar --> Store Auth --> SD Login --> Store click Auth "application/services/authentication.service.ts" "HTTP login/register" _blank click Login "feature/login/" "Login form" _blank click Register "feature/register/" "Registration form" _blank click UserBar "feature/user-bar/" "Current user display" _blank click SD "../server-directory/application/server-directory.facade.ts" "Resolves API URL" _blank ``` ## Login flow ```mermaid sequenceDiagram participant User participant Login as LoginComponent participant Auth as AuthenticationService participant SD as ServerDirectoryFacade participant API as Server API participant Store as NgRx Store participant Effects as UsersEffects User->>Login: Submit credentials Login->>Auth: login(username, password) Auth->>SD: getApiBaseUrl() SD-->>Auth: https://server/api Auth->>API: POST /api/users/login API-->>Auth: { userId, displayName } Auth-->>Login: success Login->>Store: UsersActions.authenticateUser Store->>Effects: prepare persisted user scope Effects->>Store: reset stale room/user/message state Effects->>Store: UsersActions.setCurrentUser ``` ## Registration flow Registration follows the same pattern but posts to `/api/users/register` with an additional `displayName` field. On success the user is treated as logged in and the same authenticated-user transition runs, switching the browser persistence layer to that user's local scope before the app reloads rooms and user state. ## One human, one account per signal server A linked account on a foreign signal server is an ordinary account whose password is the user's **provision secret**. That secret is issued and stored by the **home** signal server (`GET /api/users/me/provision-secret`, created on first use), so it is identical on every device the person signs in from. `HomeProvisionSecretService` fetches it with the home session token and caches it in memory only. This matters because the secret decides identity. Older builds generated a secret per device; the second device could not sign in to the account the first one had created, fell through to the `username-` candidate, and registered a **second account with the same display name**. Everyone else then saw that person twice, DM threads forked, and a 1:1 call looked like a group call. `buildProvisionPlan` therefore orders attempts so a duplicate cannot happen by accident: 1. register the preferred username; 2. on conflict, sign in with the canonical secret — this is another device of ours; 3. then sign in with the legacy device-local secret — an account this device made before canonical secrets existed, which is immediately rotated onto the canonical secret via `POST /api/users/me/password`; 4. only once the preferred name is proven to belong to somebody else, repeat for `username-`. Registering the suffixed name requires a canonical secret. Without one the client cannot distinguish "another human owns this name" from "our own account whose secret this device never had", so it raises a contextual recovery issue instead of guessing. ## Restore and foreign-server recovery 1. Restore validates the home session token and migrates the home credential. 2. Active foreign endpoints call `ensureProvisioned`. 3. Provisioning resolves the canonical secret from the home server, then follows the plan above. 4. Successful provisioning stores the foreign actor credential; room connection then identifies and joins with that actor id. 5. Rejected credentials or an unavailable endpoint publish per-server recovery state. The home session remains active; Retry re-runs provisioning and reconnects the current room after success. Diagnostics record only home user id, foreign actor id, normalized server URL, and outcome. Tokens, passwords, provision secrets, SDP, and message contents must never be logged. ## User bar `UserBarComponent` reads the current user from the NgRx store. When logged in it shows the user's display name; when not logged in it shows links to the login and register views.