# Azaion Admin API — System Flows > **Cycle 1 (2026-05-13) note** — F4 (Hardware Check) was deleted by AZ-197; F3 no longer depends on hardware. Two new flows were added: F8 Detection Classes CRUD (AZ-513), F9 Device Auto-Provisioning (AZ-196). F10 OTA Update Check & Publish (AZ-183) was reverted later the same day after the security audit (finding F-1) — the OTA delivery model itself was deemed obsolete; see `_docs/05_security/security_report.md` for context. F3's narrative was updated to drop the hardware-check step. > > **Cycle 2 (2026-05-14) note** — F3 (Encrypted Resource Download) and F6 (Installer Download) were removed entirely as obsolete. The encrypted-download support stack (`Security.GetApiEncryptionKey`, `EncryptTo`, `DecryptTo`, `ResourcesService.GetEncryptedResource`, `ResourcesService.GetInstaller`, `GetResourceRequest`, `WrongResourceName` (50)) and the installer config (`SuiteInstallerFolder`, `SuiteStageInstallerFolder`) all went with them. See `_docs/02_document/architecture.md` ADR-003 (retired). ## Flow Inventory | # | Flow Name | Trigger | Primary Components | Criticality | |---|-----------|---------|-------------------|-------------| | F1 | User Login | POST /login | Admin API, User Mgmt, Auth & Security | High | | F2 | User Registration | POST /users | Admin API, User Mgmt | High | | ~~F3~~ | ~~Encrypted Resource Download~~ | ~~POST /resources/get~~ | — | **REMOVED — cycle 2 (obsolete)** | | ~~F4~~ | ~~Hardware Check~~ | ~~POST /resources/check~~ | — | **REMOVED — AZ-197** | | F5 | Resource Upload | POST /resources | Admin API, Resource Mgmt | Medium | | ~~F6~~ | ~~Installer Download~~ | ~~GET /resources/get-installer~~ | — | **REMOVED — cycle 2 (obsolete)** | | F7 | User Management (CRUD) | Various /users/* | Admin API, User Mgmt | Medium | | F8 | Detection Classes CRUD *(AZ-513)* | POST/PATCH/DELETE /classes | Admin API, DetectionClassService | High | | F9 | Device Auto-Provisioning *(AZ-196)* | POST /devices | Admin API, User Mgmt | High | | ~~F10~~ | ~~OTA Update Check & Publish~~ | ~~POST /get-update + POST /resources/publish~~ | — | **REMOVED — post-cycle-1 (AZ-183 reverted, see security audit F-1)** | ## Flow Dependencies | Flow | Depends On | Shares Data With | |------|-----------|-----------------| | F1 | — | All other flows (produces JWT token) | | F2 | — | F1, F9 (creates user records — including device users via F9) | | F5 | F1 (requires JWT) | — | | F7 | F1 (requires JWT, ApiAdmin role) | — | | F8 | F1 (requires JWT, ApiAdmin role) | UI Detection Classes table | | F9 | F1 (requires JWT, ApiAdmin role) | F2 (writes a user row, but reuses `RegisterUser` end-to-end), F1 (provisioned devices later log in) | --- ## Flow F1: User Login ### Description A user submits email/password credentials. The system validates them against the database and returns a signed JWT token for subsequent authenticated requests. ### Preconditions - User account exists in the database - User knows correct password ### Sequence Diagram ```mermaid sequenceDiagram participant Client participant API as Admin API participant US as UserService participant DB as PostgreSQL participant Auth as AuthService Client->>API: POST /login {email, password} API->>US: ValidateUser(request) US->>DB: SELECT user WHERE email = ? DB-->>US: User record US->>US: Compare password hash US-->>API: User entity API->>Auth: CreateToken(user) Auth-->>API: JWT string API-->>Client: 200 OK {token} ``` ### Error Scenarios | Error | Where | Detection | Recovery | |-------|-------|-----------|----------| | Email not found | UserService.ValidateUser | No DB record | 409: NoEmailFound (code 10) | | Wrong password | UserService.ValidateUser | Hash mismatch | 409: WrongPassword (code 30) | --- ## Flow F2: User Registration ### Description An admin creates a new user account with email, password, and role. ### Preconditions - Caller has ApiAdmin role - Email is not already registered ### Sequence Diagram ```mermaid sequenceDiagram participant Admin participant API as Admin API participant VAL as FluentValidation participant US as UserService participant DB as PostgreSQL Admin->>API: POST /users {email, password, role} API->>VAL: Validate RegisterUserRequest VAL-->>API: OK API->>US: RegisterUser(request) US->>DB: SELECT user WHERE email = ? DB-->>US: null (no duplicate) US->>US: Hash password (SHA-384) US->>DB: INSERT user (admin connection) DB-->>US: OK US-->>API: void API-->>Admin: 200 OK ``` ### Error Scenarios | Error | Where | Detection | Recovery | |-------|-------|-----------|----------| | Validation failure | FluentValidation | Email < 8 chars, bad format, password < 8 chars | 400 Bad Request | | Duplicate email | UserService.RegisterUser | Existing user found | 409: EmailExists (code 20) | --- ## Flow F3: Encrypted Resource Download — REMOVED (cycle 2, 2026-05-14) The `POST /resources/get/{dataFolder?}` endpoint and its supporting stack (`Security.GetApiEncryptionKey`, `Security.EncryptTo`, `Security.DecryptTo`, `ResourcesService.GetEncryptedResource`, `GetResourceRequest` DTO + validator, `ExceptionEnum.WrongResourceName` (50)) were removed as obsolete. Per-user file encryption is no longer part of the system; resource files are now stored as plain bytes and only ever leave the server through the upload (F5) and admin clear paths. ADR-003 in `architecture.md` was retired in the same change. --- ## Flow F4: Hardware Check (REMOVED by AZ-197) The hardware-fingerprint binding flow (`POST /resources/check`, `UserService.CheckHardwareHash`, `Security.GetHWHash`, error code 40 `HardwareIdMismatch`, error code 45 `BadHardware`) was removed entirely in cycle 1. Reason: the threat the binding mitigated (credential reuse via desktop installers) was eliminated by the architectural shift to fTPM-secured Jetsons + browser-only SaaS access. See `_docs/03_implementation/batch_06_report.md` and the obsolete diagram `diagrams/flows/flow_hardware_check.md`. --- ## Flow F5: Resource Upload ### Description An authenticated user uploads a file to a specified resource folder on the server. ### Preconditions - User is authenticated (JWT) - File size <= 200 MB ### Sequence Diagram ```mermaid sequenceDiagram participant User participant API as Admin API participant RS as ResourcesService participant FS as Filesystem User->>API: POST /resources/{folder} (multipart/form-data) API->>RS: SaveResource(folder, file) RS->>FS: Create directory (if needed) RS->>FS: Delete existing file (same name) RS->>FS: Write file FS-->>RS: OK RS-->>API: void API-->>User: 200 OK ``` --- ## Flow F6: Installer Download — REMOVED (cycle 2, 2026-05-14) The `GET /resources/get-installer` and `GET /resources/get-installer/stage` endpoints, the `ResourcesService.GetInstaller` method, the `ResourcesConfig.SuiteInstallerFolder` / `SuiteStageInstallerFolder` configuration properties, and their environment-variable rows in every config artifact (`appsettings.json`, `.env.example`, `secrets/*.public.env`, `docker-compose.test.yml`) were removed. The installer-shipping era is over in the target architecture (browser SaaS + fTPM Jetsons); installer artefacts are no longer served from the Admin API. --- ## Flow F7: User Management (CRUD) ### Description Admin operations: list users, change role, enable/disable, update queue offsets, delete user. (The "set hardware" operation was removed by AZ-197 — see F4.) ### Preconditions - Caller has ApiAdmin role (for most operations) All operations follow the same pattern: API endpoint → UserService method → DbFactory.RunAdmin → PostgreSQL UPDATE/DELETE. Cache is invalidated for affected user keys after writes (the `UpdateQueueOffsets` path is the only remaining cache-invalidation site post-AZ-197). --- ## Flow F8: Detection Classes CRUD *(AZ-513, 2026-05-13)* ### Description ApiAdmin manages the detection-class catalogue exposed to operators in the UI: create new entries, partial-merge edits, delete entries. The UI's existing add/delete affordances start working end-to-end once this flow exists; the in-place edit affordance arrives via UI cycle AZ-512. ### Preconditions - Caller has ApiAdmin role (`apiAdminPolicy`) - `detection_classes` table exists in the admin DB ### Sequence Diagram ```mermaid sequenceDiagram participant Client participant API as Admin API participant VAL as FluentValidation participant DCS as DetectionClassService participant DB as PostgreSQL Client->>API: POST /classes {name, shortName, color, maxSizeM, photoMode?} API->>VAL: Validate CreateDetectionClassRequest VAL-->>API: OK / 400 API->>DCS: Create(request) DCS->>DB: InsertWithInt32IdentityAsync (admin conn) DB-->>DCS: new id DCS-->>API: DetectionClass {id, …} API-->>Client: 200 OK {DetectionClass} Client->>API: PATCH /classes/{id} {…partial fields} API->>VAL: Validate UpdateDetectionClassRequest VAL-->>API: OK / 400 API->>DCS: Update(id, request) alt id exists DCS->>DB: UPDATE row applying non-null fields (admin conn) DCS-->>API: DetectionClass API-->>Client: 200 OK {DetectionClass} else id missing DCS-->>API: null API-->>Client: 404 Not Found end Client->>API: DELETE /classes/{id} API->>DCS: Delete(id) DCS->>DB: DELETE WHERE id = ? (admin conn) alt deleted > 0 DCS-->>API: true API-->>Client: 204 No Content else DCS-->>API: false API-->>Client: 404 Not Found end ``` ### Error Scenarios | Error | Where | Detection | Recovery | |-------|-------|-----------|----------| | Not authenticated | API | No JWT | 401 Unauthorized | | Wrong role | API | Non-ApiAdmin JWT | 403 Forbidden | | Validation failure | FluentValidation | Field bounds violated | 400 Bad Request | | Missing id (PATCH/DELETE) | DetectionClassService | Row not found | 404 Not Found | --- ## Flow F9: Device Auto-Provisioning *(AZ-196, 2026-05-13)* ### Description ApiAdmin requests a fresh CompanionPC device user. The server allocates the next sequential serial (`azj-NNNN`), generates a 32-char hex password, persists the user with the SHA-384 hash, and returns the plaintext credentials exactly once. The provisioning script (out-of-tree) embeds the values into the device's `device.conf`. ### Preconditions - Caller has ApiAdmin role (`apiAdminPolicy`) ### Sequence Diagram ```mermaid sequenceDiagram participant Admin participant API as Admin API participant US as UserService participant DB as PostgreSQL Admin->>API: POST /devices (no body) API->>US: RegisterDevice() US->>DB: SELECT TOP 1 email FROM users WHERE role = 'CompanionPC' ORDER BY created_at DESC DB-->>US: lastEmail (or null) US->>US: nextNumber = parse(lastEmail.suffix) + 1 (or 0) US->>US: serial = "azj-" + nextNumber.PadLeft(4) US->>US: password = ToHex(RandomBytes(16)) // 32 hex chars US->>DB: INSERT user {Email=serial@domain, PasswordHash=SHA384(password), Role=CompanionPC, IsEnabled=true} (admin conn) DB-->>US: OK US-->>API: RegisterDeviceResponse {Serial, Email, Password} API-->>Admin: 200 OK {Serial, Email, Password} ``` ### Error Scenarios | Error | Where | Detection | Recovery | |-------|-------|-----------|----------| | Not authenticated / wrong role | API | JWT missing or non-ApiAdmin | 401 / 403 | | Email already exists | UserService.RegisterUser (called by RegisterDevice) | DB UNIQUE INDEX `users_email_uidx` violation translated to `EmailExists` (5) | 409 — caller retries (the next call recomputes a fresh `azj-NNNN`) | > **Implementation note** — `RegisterDevice` reuses `UserService.RegisterUser` for the row insert (post-security-audit consolidation, finding F-3). The `users.email` column has a UNIQUE INDEX (`env/db/06_users_email_unique.sql`); concurrent provisioning calls that race on the same serial surface the violation atomically. --- ## Flow F10: OTA Update Check & Publish *(REMOVED — post-cycle-1 revert)* The `POST /get-update` and `POST /resources/publish` endpoints, the `IResourceUpdateService` / `ResourceUpdateService` / `ResourceColumnEncryption` types, the `Resource` entity, the `resources` table, the `apiUploaderPolicy`, and the `ResourcesConfig.EncryptionMasterKey` field were all removed shortly after AZ-183 shipped. Reasons: 1. Security audit finding F-1 — `/get-update` was registered with `.RequireAuthorization()` (any authenticated caller) and returned the per-resource decrypted `EncryptionKey` in the response body, defeating the at-rest column encryption. 2. The OTA delivery model is itself a leftover from the installer-shipping era; the target architecture (browser-only SaaS + fTPM-secured Jetsons) does not need it. The `apiUploaderPolicy` definition was removed from `Program.cs`; the `RoleEnum.ResourceUploader` enum value remains as data (the seed `uploader@azaion.com` user still uses it for negative-auth tests) but is no longer wired to any endpoint.