# User Management > **Cycle 1 (2026-05-13) note** — hardware-binding methods (`UpdateHardware`, `CheckHardwareHash`) and `SetHWRequest` were removed by AZ-197; the `ValidateUser` error set now includes `UserDisabled`; `RegisterDevice` was added by AZ-196 to back the new `POST /devices` endpoint. Post-cycle-1 (security audit F-3): `RegisterDevice` now reuses `RegisterUser` for the row insert; the duplicate-row race was closed by adding a UNIQUE INDEX on `users.email` (`env/db/06_users_email_unique.sql`) and translating `Npgsql.PostgresException(SqlState=23505)` to `BusinessException(EmailExists)` inside `RegisterUser`. ## 1. High-Level Overview **Purpose**: Full user lifecycle management — web-user registration, credential validation, role changes, account enable/disable, deletion, plus auto-provisioning of CompanionPC device users. **Architectural Pattern**: Service layer — stateless business logic operating on the Data Layer through `IDbFactory`. **Upstream dependencies**: Data Layer (IDbFactory, ICache, User entity), Security & Cryptography (hashing), `System.Security.Cryptography.RandomNumberGenerator` (device password entropy). **Downstream consumers**: Admin API (endpoint handlers), Authentication (GetByEmail). ## 2. Internal Interfaces ### Interface: IUserService | Method | Input | Output | Async | Error Types | |--------|-------|--------|-------|-------------| | `RegisterUser` | `RegisterUserRequest, CancellationToken` | void | Yes | `BusinessException(EmailExists)` — translated from `PostgresException(23505)` after the F-3 hardening | | `RegisterDevice` | `CancellationToken` | `RegisterDeviceResponse` | Yes | `BusinessException(EmailExists)` (propagated from `RegisterUser`) — added by AZ-196, refactored post-audit to call `RegisterUser` end-to-end | | `ValidateUser` | `LoginRequest, CancellationToken` | `User` | Yes | `BusinessException(NoEmailFound, WrongPassword, UserDisabled)` | | `GetByEmail` | `string? email, CancellationToken` | `User?` | Yes | `ArgumentNullException` | | `UpdateQueueOffsets` | `string email, UserQueueOffsets, CancellationToken` | void | Yes | None | | `GetUsers` | `string? searchEmail, RoleEnum? searchRole, CancellationToken` | `IEnumerable` | Yes | None | | `ChangeRole` | `string email, RoleEnum, CancellationToken` | void | Yes | None | | `SetEnableStatus` | `string email, bool, CancellationToken` | void | Yes | None | | `RemoveUser` | `string email, CancellationToken` | void | Yes | None | **Removed by AZ-197**: `UpdateHardware`, `CheckHardwareHash`, and the private `UpdateLastLoginDate` helper. **Input / Output DTOs**: ``` RegisterUserRequest: Email: string (required) — validated: min 8 chars, valid email format Password: string (required) — validated: min 8 chars Role: RoleEnum (required) LoginRequest: Email: string (required) Password: string (required) RegisterDeviceResponse (AZ-196): Serial: string ("azj-NNNN", zero-padded) Email: string ("azj-NNNN@azaion.com") Password: string (32-char hex, plaintext, exposed exactly once) SetUserQueueOffsetsRequest: Email: string (required) Offsets: UserQueueOffsets (required) ``` ## 3. External API Specification N/A — exposed through Admin API component. ## 4. Data Access Patterns ### Queries | Query | Frequency | Hot Path | Index Needed | |-------|-----------|----------|--------------| | User by email (cached) | High | Yes | Yes | | User list with filters | Medium | No | No | | User insert (registration) | Low | No | No | | User update (hardware, role, config, status) | Medium | No | No | | User delete | Low | No | No | ### Caching Strategy | Data | Cache Type | TTL | Invalidation | |------|-----------|-----|-------------| | User by email | In-memory (via ICache) | 4 hours | After `UpdateQueueOffsets` (only — `UpdateHardware` / `CheckHardwareHash` invalidations are gone with AZ-197) | ## 5. Implementation Details **State Management**: Stateless — all state in PostgreSQL + in-memory cache. **Key Dependencies**: | Library | Version | Purpose | |---------|---------|---------| | FluentValidation | 11.10.0 | Request validation (auto-discovered) | **Error Handling Strategy**: - Domain errors thrown as `BusinessException` with specific `ExceptionEnum` codes. - `GetByEmail` throws `ArgumentNullException` for null/whitespace email. - Database errors propagate from `IDbFactory`. - Write operations use `RunAdmin` (admin connection); reads use `Run` (reader connection). ## 6. Extensions and Helpers | Helper | Purpose | Used By | |--------|---------|---------| | `Security.ToHash` | Password hashing (SHA-384) | RegisterUser, RegisterDevice, ValidateUser | | `RandomNumberGenerator.GetBytes(16)` + `Convert.ToHexString` | 32-char hex device password | RegisterDevice | | `QueryableExtensions.WhereIf` | Conditional LINQ filters | GetUsers | ## 7. Caveats & Edge Cases **Known limitations**: - No pagination on `GetUsers` — returns all matching users. - `RemoveUser` is a hard delete, not soft delete. - `RegisterDevice` returns the plaintext password to the caller exactly once; if the provisioning script loses it, the device must be re-registered. - The `User.Hardware` column is left in place but unused (AZ-197 chose to leave the column nullable rather than ship a migration). **Potential race conditions**: - Concurrent `RegisterUser` calls with the same email: both could pass the existence check before insert. Mitigated by database unique constraint on email (if one exists). - Concurrent `RegisterDevice` calls: both could read the same "most recent CompanionPC" row and try to claim the same `azj-NNNN` serial. Mitigated by the `users.email` unique constraint — the loser will fail the insert. (Out of cycle-1 scope: a sequence-based serial allocator would eliminate the retry.) **Performance bottlenecks**: - `GetUsers` loads full user objects including `UserConfig` JSON; for large user bases, projection would be more efficient. ## 8. Dependency Graph **Must be implemented after**: Data Layer, Security & Cryptography. **Can be implemented in parallel with**: Resource Management. **Blocks**: Authentication (uses `GetByEmail`), Admin API. ## 9. Logging Strategy No explicit logging in UserService. ## Modules Covered - `Services/UserService` - `Common/Requests/LoginRequest` - `Common/Requests/RegisterUserRequest` - `Common/Requests/RegisterDeviceResponse` *(added cycle 1, AZ-196)* - `Common/Requests/SetUserQueueOffsetsRequest` **Removed cycle 1 (AZ-197)**: `Common/Requests/SetHWRequest`