Files
admin/_docs/02_document/system-flows.md
T
Oleksandr Bezdieniezhnykh c7b297de83
ci/woodpecker/push/01-test Pipeline failed
ci/woodpecker/push/02-build-push unknown status
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>
2026-05-13 08:47:21 +03:00

355 lines
14 KiB
Markdown

# 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.
## 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 |
| ~~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 | 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 |
| ~~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) |
| 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
```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
> **Updated by AZ-197 (2026-05-13)** — the hardware-binding precondition and the `CheckHardwareHash` / `GetHWHash` steps were removed; the encryption key is now derived from `email + password` only. 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
```mermaid
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
```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
### 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
```mermaid
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_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.