Files
admin/_docs/02_document/architecture.md
T
Oleksandr Bezdieniezhnykh 3a925b9b0f
ci/woodpecker/push/01-test Pipeline failed
ci/woodpecker/push/02-build-push unknown status
refactor: remove obsolete resource download and installer endpoints
- Deleted the `POST /resources/get/{dataFolder?}` and `GET /resources/get-installer` endpoints as part of the architectural shift towards simplified resource management.
- Removed associated methods and configurations, including `ResourcesService.GetEncryptedResource`, `ResourcesService.GetInstaller`, and related properties in `ResourcesConfig`.
- Cleaned up environment variables and configuration files to reflect the removal of installer-related settings.
- Eliminated the `GetResourceRequest` DTO and its validator, along with the `WrongResourceName` error code.
- Updated documentation to clarify the changes in resource handling and the retirement of per-user file encryption.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 04:17:55 +03:00

12 KiB

Azaion Admin API — Architecture

1. System Context

Problem being solved: Azaion Suite requires a centralized admin API to manage users, assign roles, and securely distribute encrypted software resources (DLLs, AI models, installers) to authorized devices and SaaS users.

System boundaries:

  • Inside: User management, authentication (JWT), role-based authorization, file-based resource storage (upload / list / clear).
  • Outside: Client applications (admin web panel at admin.azaion.com, fTPM-secured Jetson edge devices), PostgreSQL database, server filesystem for resource storage.

Note (AZ-197, 2026-05-13): hardware-fingerprint binding (User.Hardware, CheckHardwareHash, PUT /users/hardware/set, POST /resources/check, HardwareIdMismatch/BadHardware error codes) was removed. Edge devices now ship as fTPM-secured Jetsons; server/desktop access is SaaS-only. The User.Hardware DB column remains as a nullable tombstone (no migration in AZ-197).

Note (cycle 2, 2026-05-14): the encrypted resource download (POST /resources/get/{dataFolder?}) and both installer endpoints (GET /resources/get-installer, GET /resources/get-installer/stage) were removed as obsolete. Their orphaned support code went with them: ResourcesService.GetEncryptedResource / GetInstaller, Security.GetApiEncryptionKey / EncryptTo / DecryptTo, the GetResourceRequest DTO (+ WrongResourceName error code 50, gap kept), and the ResourcesConfig.SuiteInstallerFolder / SuiteStageInstallerFolder properties + their env var rows in every config artifact. The Azaion.Test unit-test project became empty and was removed from the solution. Per-user file encryption is no longer part of the system; resource delivery is now upload + list + clear only. ADR-003 below is retired as a result.

External systems:

System Integration Type Direction Purpose
PostgreSQL Database (linq2db) Both User data persistence
Server filesystem File I/O Both Resource file storage and retrieval
Azaion Suite client REST API Inbound Resource download, login
Admin web panel (admin.azaion.com) REST API Inbound User management, resource upload

2. Technology Stack

Layer Technology Version Rationale
Language C# .NET 10.0 Modern, cross-platform, strong typing
Framework ASP.NET Core Minimal API 10.0 Lightweight, minimal boilerplate
Database PostgreSQL (server-side) Open-source, robust relational DB
ORM linq2db 5.4.1 Lightweight, LINQ-native, no migrations overhead
Cache LazyCache (in-memory) 2.4.0 Simple async caching for user lookups
Auth JWT Bearer 10.0.3 Stateless token authentication
Validation FluentValidation 11.3.0 / 11.10.0 Declarative request validation
Logging Serilog 4.1.0 Structured logging (console + file)
API Docs Swashbuckle (Swagger) 10.1.4 OpenAPI specification
Serialization Newtonsoft.Json 13.0.1 JSON for DB field mapping and responses
Container Docker .NET 10.0 images Multi-stage build, ARM64 support
CI/CD Woodpecker CI Branch-based ARM64 builds
Registry docker.azaion.com Private container registry

3. Deployment Model

Environments: Development (local), Production (Linux server)

Infrastructure:

  • Self-hosted Linux server (evidenced by env/ provisioning scripts for Debian/Ubuntu)
  • Docker containerization with private registry (docker.azaion.com, localhost:5000)
  • No orchestration (single container deployment via deploy.cmd)

Environment-specific configuration:

Config Development Production
Database Local PostgreSQL (port 4312) Remote PostgreSQL (same custom port)
Secrets Environment variables (ASPNETCORE_*) Environment variables
Logging Console + file Console + rolling file (logs/log.txt)
Swagger Enabled Disabled
CORS Same as prod admin.azaion.com

4. Data Model Overview

Core entities:

Entity Description Owned By Component
User System user with email (UNIQUE-indexed via users_email_uidx), password hash, role, config (legacy Hardware column tombstoned per AZ-197). Subset of users have Role = CompanionPC and are auto-provisioned via POST /devices (AZ-196), which delegates the insert to UserService.RegisterUser (post-security-audit consolidation, finding F-3). 01 Data Layer
UserConfig JSON-serialized per-user configuration (queue offsets) 01 Data Layer
RoleEnum Authorization role hierarchy (None → ApiAdmin); ResourceUploader retained as data only after the OTA endpoints were retired 01 Data Layer
DetectionClass (AZ-513, cycle 1) Operator-managed detection-class catalogue (Name, ShortName, Color, MaxSizeM, PhotoMode?) backing the UI Detection Classes table 01 Data Layer
ExceptionEnum Business error code catalog (HW-related codes 40/45 removed by AZ-197) Common Helpers

Removed in cycle 1 / post-cycle-1: the Resource entity, the resources table, and the OTA delivery flow (AZ-183 — F10) were reverted after the security audit (finding F-1). The data model no longer carries an OTA-artifact entity.

Key relationships:

  • User → RoleEnum: each user has exactly one role
  • User → UserConfig: optional 1:1 JSON field containing queue offsets

Data flow summary:

  • Client → API → UserService → PostgreSQL: user CRUD operations
  • Client → API → ResourcesService → Filesystem: resource upload / list / clear (encrypted download + installer delivery were retired in cycle 2)

5. Integration Points

Internal Communication

From To Protocol Pattern Notes
Admin API User Management Direct DI call Request-Response Scoped service injection
Admin API Auth & Security Direct DI call Request-Response Scoped service injection
Admin API Resource Management Direct DI call Request-Response Scoped service injection
User Management Data Layer Direct DI call Request-Response Singleton DbFactory
Auth & Security User Management Direct DI call Request-Response IUserService.GetByEmail

External Integrations

External System Protocol Auth Rate Limits Failure Mode
PostgreSQL TCP (Npgsql) Username/password None configured Exception propagation
Filesystem OS I/O OS-level permissions None Exception propagation

6. Non-Functional Requirements

Requirement Target Measurement Priority
Max upload size 200 MB Kestrel MaxRequestBodySize High
Password hashing SHA-384 Per-user Medium
Cache TTL 4 hours User entity cache Low

The "File encryption / AES-256-CBC" NFR was retired in cycle 2 along with the encrypted-download endpoint. See ADR-003.

No explicit availability, latency, throughput, or recovery targets found in the codebase.

7. Security Architecture

Authentication: JWT Bearer tokens (HMAC-SHA256 signed, validated for issuer/audience/lifetime/signing key).

Authorization: Role-based (RBAC) via ASP.NET Core authorization policies:

  • apiAdminPolicy — requires ApiAdmin role
  • General [Authorize] — any authenticated user

The apiUploaderPolicy was added by AZ-183 and removed in the post-cycle-1 revert along with the OTA endpoints it guarded. RoleEnum.ResourceUploader remains as data only.

Data protection:

  • At rest: resource files are stored as plain bytes on the server filesystem (per-user AES-256-CBC encryption was retired in cycle 2 — see ADR-003).
  • In transit: HTTPS (assumed, not enforced in code)
  • Secrets management: Environment variables (ASPNETCORE_* prefix)

Audit logging: No explicit audit trail. Serilog logs business exceptions (WARN) and general events (INFO).

8. Key Architectural Decisions

ADR-001: Minimal API over Controllers

Context: API has ~17 endpoints with simple request/response patterns.

Decision: Use ASP.NET Core Minimal API with top-level statements instead of MVC controllers.

Consequences: All endpoints in a single Program.cs. Simple for small APIs but could become unwieldy as endpoints grow.

ADR-002: Read/Write Database Connection Separation

Context: Needed different privilege levels for read vs. write operations.

Decision: DbFactory maintains two connection strings — a read-only one (AzaionDb) and an admin one (AzaionDbAdmin) — with separate Run and RunAdmin methods.

Consequences: Write operations are explicitly gated through RunAdmin. Prevents accidental writes through the reader connection. Requires maintaining two DB users with different privileges.

ADR-003: Per-User Resource Encryption — RETIRED (cycle 2, 2026-05-14)

Original context: Resources (DLLs, AI models) had to be delivered only to authorized users via a per-download AES-256-CBC stream keyed off the user's email + password.

Retirement decision: With the OTA delivery flow (AZ-183) and the hardware-binding flow (AZ-197) both gone, the only remaining consumer of the encrypted-download path was a now-vestigial POST /resources/get/{dataFolder?} endpoint and the two installer endpoints. None of them are part of the target architecture (browser SaaS + fTPM Jetsons), so the entire encrypt-on-download stack — POST /resources/get, GET /resources/get-installer, GET /resources/get-installer/stage, ResourcesService.GetEncryptedResource, ResourcesService.GetInstaller, Security.GetApiEncryptionKey, Security.EncryptTo, Security.DecryptTo, GetResourceRequest, WrongResourceName (50), ResourcesConfig.SuiteInstallerFolder / SuiteStageInstallerFolder — was removed. Security.ToHash is retained because it still backs SHA-384 password hashing in UserService.

Consequences: resource files now live on disk as plain bytes; any future at-rest encryption must come from filesystem or storage-layer features (LUKS, object-store SSE), not from application code.

ADR-004: Hardware Fingerprint Binding — RETIRED (AZ-197)

Original context: Resources should only be usable on a specific physical machine.

Original decision: On first resource access, the user's hardware fingerprint string was stored. Subsequent accesses compared the hash of the provided hardware against the stored value.

Retirement decision (2026-05-13, AZ-197): The threat model that motivated this binding (credential reuse across machines via desktop installers) no longer applies:

  • Edge devices ship as fTPM-secured Jetsons (secure boot, fTPM-protected key storage, no user filesystem access, no installer redistribution). Hardware identity is anchored in the fTPM, not in a SHA-384 of CPU/GPU/Memory/DriveSerial strings.
  • Server / desktop access is SaaS-only (browser → admin API). There is no installer to copy and no hardware fingerprint to take.

The binding's only remaining effect was a real production failure mode (HardwareIdMismatch, error code 40) on legitimate hardware events. AZ-197 removed CheckHardwareHash, UpdateHardware, Security.GetHWHash, the PUT /users/hardware/set and POST /resources/check endpoints, and the Hardware field from GetResourceRequest. The User.Hardware DB column is a nullable tombstone (no migration in AZ-197; separate ticket if/when the column is dropped).

ADR-005: linq2db over Entity Framework

Context: Needed a lightweight ORM for PostgreSQL.

Decision: Use linq2db instead of Entity Framework Core.

Consequences: No migration framework — schema managed via SQL scripts (env/db/). Lighter runtime footprint. Manual mapping configuration in AzaionDbSchemaHolder.