mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 12:11:09 +00:00
refactor: remove deploy.cmd and update Dockerfile for health checks
- Deleted the deploy.cmd script as it was no longer needed. - Updated Dockerfile to include curl for health checks and added a non-root user for improved security. - Modified health check command to use curl for better reliability. - Adjusted docker-compose.test.yml to reflect changes in health check configuration. - Cleaned up appsettings.json and removed unused configuration properties. - Removed Resource entity and related requests from the codebase as part of the architectural shift. - Updated documentation to reflect the removal of hardware binding and related endpoints. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
# Module: Azaion.Services.UserService
|
||||
|
||||
## Purpose
|
||||
Core business logic for user management: registration, authentication, hardware binding, role management, and account lifecycle.
|
||||
Core business logic for user management: registration (web users + provisioned devices), authentication, role management, and account lifecycle.
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — hardware-binding methods (`UpdateHardware`, `CheckHardwareHash`, private `UpdateLastLoginDate`) and the bound `IUserService` declarations were removed by AZ-197 (admin-side hardware-binding cleanup). Device auto-provisioning (`RegisterDevice`) was added by AZ-196. **Post-cycle-1 (security audit F-3)**: `RegisterDevice` was refactored to delegate the row insert to `RegisterUser`, and `RegisterUser` itself now relies on the new `users_email_uidx` UNIQUE INDEX (`env/db/06_users_email_unique.sql`) — the check-then-insert race is gone; `Npgsql.PostgresException(SqlState=23505)` is translated to `BusinessException(EmailExists)`. See `_docs/03_implementation/batch_05_report.md` and `batch_06_report.md`.
|
||||
|
||||
## Public Interface
|
||||
|
||||
@@ -9,43 +11,45 @@ Core business logic for user management: registration, authentication, hardware
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `RegisterUser` | `Task RegisterUser(RegisterUserRequest request, CancellationToken ct)` | Creates a new user with hashed password |
|
||||
| `ValidateUser` | `Task<User> ValidateUser(LoginRequest request, CancellationToken ct)` | Validates email + password, returns user |
|
||||
| `RegisterDevice` | `Task<RegisterDeviceResponse> RegisterDevice(CancellationToken ct)` | Creates a new `CompanionPC` user with auto-assigned `azj-NNNN` serial / email and a 32-char hex password (returned plaintext exactly once) |
|
||||
| `ValidateUser` | `Task<User> ValidateUser(LoginRequest request, CancellationToken ct)` | Validates email + password, returns user. Throws `NoEmailFound`, `WrongPassword`, or `UserDisabled` |
|
||||
| `GetByEmail` | `Task<User?> GetByEmail(string? email, CancellationToken ct)` | Cached user lookup by email |
|
||||
| `UpdateHardware` | `Task UpdateHardware(string email, string? hardware, CancellationToken ct)` | Sets/clears user's hardware fingerprint |
|
||||
| `UpdateQueueOffsets` | `Task UpdateQueueOffsets(string email, UserQueueOffsets offsets, CancellationToken ct)` | Updates user's annotation queue offsets |
|
||||
| `GetUsers` | `Task<IEnumerable<User>> GetUsers(string? searchEmail, RoleEnum? searchRole, CancellationToken ct)` | Lists users with optional email/role filters |
|
||||
| `CheckHardwareHash` | `Task<string> CheckHardwareHash(User user, string hardware, CancellationToken ct)` | Validates or initializes hardware binding |
|
||||
| `ChangeRole` | `Task ChangeRole(string email, RoleEnum newRole, CancellationToken ct)` | Changes a user's role |
|
||||
| `SetEnableStatus` | `Task SetEnableStatus(string email, bool isEnabled, CancellationToken ct)` | Enables or disables a user account |
|
||||
| `RemoveUser` | `Task RemoveUser(string email, CancellationToken ct)` | Permanently deletes a user |
|
||||
|
||||
## Internal Logic
|
||||
- **RegisterUser**: checks for duplicate email, hashes password via `Security.ToHash`, inserts via `RunAdmin`.
|
||||
- **ValidateUser**: finds user by email, compares password hash. Throws `NoEmailFound` or `WrongPassword`.
|
||||
- **RegisterUser**: hashes password via `Security.ToHash`, inserts via `RunAdmin`. Catches `Npgsql.PostgresException` with `SqlState == PostgresErrorCodes.UniqueViolation` (23505) on the `users_email_uidx` UNIQUE INDEX and rethrows as `BusinessException(EmailExists)`. The previous check-then-insert pattern was removed (race-prone before the index existed; redundant after).
|
||||
- **RegisterDevice**: calls private `NextDeviceIdentity` (read-only) to compute the next `azj-NNNN` serial + matching email, generates a 32-char hex password from `RandomNumberGenerator.GetBytes(16)`, then delegates the row insert to `RegisterUser` (so any future change to user-creation policy applies here too). Returns `{Serial, Email, Password}` (plaintext password exposed exactly once at provisioning time). On a serial-allocation race, the second caller's insert hits the UNIQUE INDEX and surfaces `BusinessException(EmailExists)`; the caller can retry.
|
||||
- **NextDeviceIdentity** (private): queries the most recent `RoleEnum.CompanionPC` user via `dbFactory.Run` (read connection), parses the `azj-NNNN` suffix (chars `[SerialNumberStart, SerialNumberLength)` of the email, constants on the class), increments by 1, returns `(serial, email)`.
|
||||
- **ValidateUser**: finds user by email, compares password hash. Throws `NoEmailFound`, `WrongPassword`, or `UserDisabled`.
|
||||
- **GetByEmail**: uses `ICache.GetFromCacheAsync` with key `User.{email}`.
|
||||
- **CheckHardwareHash**: on first access (null hardware), stores the raw hardware string and returns the hash. On subsequent access, compares hashes. Throws `HardwareIdMismatch` on mismatch. Also updates `LastLogin` timestamp.
|
||||
- **UpdateHardware/UpdateQueueOffsets**: use `RunAdmin` for writes, then invalidate cache.
|
||||
- **UpdateQueueOffsets**: writes via `RunAdmin`, then invalidates the user cache.
|
||||
- **GetUsers**: uses `WhereIf` for optional filter predicates.
|
||||
|
||||
Private method:
|
||||
- `UpdateLastLoginDate` — updates `LastLogin` to `DateTime.UtcNow`.
|
||||
Private constants (device provisioning):
|
||||
- `DeviceEmailPrefix = "azj-"`, `DeviceEmailDomain = "@azaion.com"`, `SerialNumberStart = 4`, `SerialNumberLength = 4`, `DevicePasswordBytes = 16`.
|
||||
|
||||
## Dependencies
|
||||
- `IDbFactory` (database access)
|
||||
- `ICache` (user caching)
|
||||
- `Security` (hashing)
|
||||
- `Security` (hashing — `ToHash`)
|
||||
- `System.Security.Cryptography.RandomNumberGenerator` (device password entropy)
|
||||
- `Npgsql` (`PostgresException`, `PostgresErrorCodes.UniqueViolation` — used to translate UNIQUE-INDEX violations to `BusinessException(EmailExists)`)
|
||||
- `BusinessException` (domain errors)
|
||||
- `QueryableExtensions.WhereIf`
|
||||
- `User`, `UserConfig`, `UserQueueOffsets`, `RoleEnum`
|
||||
- `RegisterUserRequest`, `LoginRequest`
|
||||
- `RegisterUserRequest`, `LoginRequest`, `RegisterDeviceResponse`
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` — all `/users/*` endpoints delegate to `IUserService`
|
||||
- `Program.cs` — `/users/*` endpoints delegate to `IUserService`
|
||||
- `Program.cs` — `POST /devices` calls `RegisterDevice` (added by AZ-196)
|
||||
- `AuthService.GetCurrentUser` — calls `GetByEmail`
|
||||
- `Program.cs` `/resources/get` — calls `CheckHardwareHash`
|
||||
|
||||
## Data Models
|
||||
Operates on `User` entity via `AzaionDb.Users` table.
|
||||
Operates on `User` entity via `AzaionDb.Users` table. The `User.Hardware` column is left in place (nullable, unused) per AZ-197 — see the entity doc.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
@@ -54,9 +58,10 @@ None.
|
||||
PostgreSQL via `IDbFactory`.
|
||||
|
||||
## Security
|
||||
- Passwords hashed with SHA-384 (via `Security.ToHash`) before storage
|
||||
- Hardware binding prevents resource access from unauthorized devices
|
||||
- Read operations use read-only DB connection; writes use admin connection
|
||||
- Passwords hashed with SHA-384 (via `Security.ToHash`) before storage.
|
||||
- Device passwords are returned plaintext to the caller exactly once at provisioning; the persisted form is the SHA-384 hash. The plaintext is never re-derivable.
|
||||
- Read operations use the read-only DB connection; writes use the admin connection.
|
||||
|
||||
## Tests
|
||||
- `UserServiceTest.CheckHardwareHashTest` — integration test against live database
|
||||
- `Azaion.Test/UserServiceTest.cs` — unit/integration tests against the live test database (hardware-binding tests removed by AZ-197)
|
||||
- `e2e/Azaion.E2E/Tests/DeviceTests.cs` — e2e for AZ-196 device-provisioning ACs
|
||||
|
||||
Reference in New Issue
Block a user