- 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>
14 KiB
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.mdfor context. F3's narrative was updated to drop the hardware-check step.
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 | Admin API, Auth, User Mgmt, Resource Mgmt | High |
| — | REMOVED — AZ-197 | |||
| F5 | Resource Upload | POST /resources | Admin API, Resource Mgmt | Medium |
| F6 | Installer Download | GET /resources/get-installer | Admin API, Auth, Resource Mgmt | Medium |
| 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 |
| — | 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) |
| F3 | F1 (requires JWT) | — (post-AZ-197: no hardware-binding dependency) |
| F5 | F1 (requires JWT) | F3 (uploaded resources are later downloaded) |
| F6 | F1 (requires JWT) | — |
| F7 | F1 (requires JWT, ApiAdmin role) | F3 (user data) |
| 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
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
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
Updated by AZ-197 (2026-05-13) — the hardware-binding precondition and the
CheckHardwareHash/GetHWHashsteps were removed; the encryption key is now derived fromemail + passwordonly. The diagram below reflects the post-cycle-1 path.
Description
An authenticated user requests a resource file. The system derives a per-user encryption key from email + password, encrypts the file with AES-256-CBC, and streams the encrypted content.
Preconditions
- User is authenticated (JWT)
- Resource file exists on server
Sequence Diagram
sequenceDiagram
participant Client
participant API as Admin API
participant Auth as AuthService
participant Sec as Security
participant RS as ResourcesService
participant FS as Filesystem
Client->>API: POST /resources/get {password, fileName}
API->>Auth: GetCurrentUser()
Auth-->>API: User
API->>Sec: GetApiEncryptionKey(email, password)
Sec-->>API: AES key string
API->>RS: GetEncryptedResource(folder, fileName, key)
RS->>FS: Read file
FS-->>RS: FileStream
RS->>Sec: EncryptTo(stream, key)
Sec-->>RS: Encrypted MemoryStream
RS-->>API: Stream
API-->>Client: 200 OK (application/octet-stream)
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Not authenticated | API | No/invalid JWT | 401 Unauthorized |
| File not found | ResourcesService | FileStream throws | 500 Internal Server Error |
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
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
Description
An authenticated user downloads the latest Azaion Suite installer (production or staging).
Preconditions
- User is authenticated (JWT)
- Installer file exists on server
Sequence Diagram
sequenceDiagram
participant Client
participant API as Admin API
participant Auth as AuthService
participant RS as ResourcesService
participant FS as Filesystem
Client->>API: GET /resources/get-installer
API->>Auth: GetCurrentUser()
Auth-->>API: User (not null)
API->>RS: GetInstaller(isStage: false)
RS->>FS: Scan for AzaionSuite.Iterative*
FS-->>RS: FileInfo
RS-->>API: (name, FileStream)
API-->>Client: 200 OK (application/octet-stream)
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_classestable exists in the admin DB
Sequence Diagram
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
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 —
RegisterDevicereusesUserService.RegisterUserfor the row insert (post-security-audit consolidation, finding F-3). Theusers.emailcolumn 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:
- Security audit finding F-1 —
/get-updatewas registered with.RequireAuthorization()(any authenticated caller) and returned the per-resource decryptedEncryptionKeyin the response body, defeating the at-rest column encryption. - 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.