mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 16:01:09 +00:00
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>
This commit is contained in:
@@ -5,11 +5,13 @@
|
||||
**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 with per-user AES encryption.
|
||||
- **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 |
|
||||
@@ -76,8 +78,7 @@
|
||||
|
||||
**Data flow summary**:
|
||||
- Client → API → UserService → PostgreSQL: user CRUD operations
|
||||
- Client → API → ResourcesService → Filesystem: resource upload/download
|
||||
- Client → API → Security → ResourcesService: encrypted resource retrieval (key derived from user email + password; hardware-hash component removed in AZ-197)
|
||||
- Client → API → ResourcesService → Filesystem: resource upload / list / clear (encrypted download + installer delivery were retired in cycle 2)
|
||||
|
||||
## 5. Integration Points
|
||||
|
||||
@@ -103,10 +104,11 @@
|
||||
| Requirement | Target | Measurement | Priority |
|
||||
|------------|--------|-------------|----------|
|
||||
| Max upload size | 200 MB | Kestrel MaxRequestBodySize | High |
|
||||
| File encryption | AES-256-CBC | Per-resource | 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
|
||||
@@ -120,7 +122,7 @@ No explicit availability, latency, throughput, or recovery targets found in the
|
||||
> 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: Resources encrypted with AES-256-CBC using per-user derived key (email + password). The hardware-hash component was removed in AZ-197 (sealed-Jetson + SaaS architecture).
|
||||
- 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)
|
||||
|
||||
@@ -144,15 +146,13 @@ No explicit availability, latency, throughput, or recovery targets found in the
|
||||
|
||||
**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
|
||||
### ADR-003: Per-User Resource Encryption — RETIRED (cycle 2, 2026-05-14)
|
||||
|
||||
**Context**: Resources (DLLs, AI models) must be delivered only to authorized users.
|
||||
**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.
|
||||
|
||||
**Decision**: Resources are encrypted at download time using AES-256-CBC with a key derived from the user's email and password. The client must know both to decrypt.
|
||||
**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**: Strong per-user binding. However, encryption happens in memory (MemoryStream), which limits practical file sizes. Key derivation is deterministic — same inputs always produce the same key.
|
||||
|
||||
> **Update (AZ-197, 2026-05-13)**: the hardware-hash component of the derivation was removed. The new key formula is `SHA384(email + "-" + password + "-#%@AzaionKey@%#---")`. See ADR-004 for context on why the hardware binding was retired.
|
||||
**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)
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ Shared utility extensions used across multiple components.
|
||||
## Modules
|
||||
- `EnumExtensions` — enum description/attribute extraction (used by BusinessException)
|
||||
- `StringExtensions` — PascalCase → snake_case conversion (used by AzaionDbSchemaHolder)
|
||||
- `StreamExtensions` — Stream → string conversion (used by SecurityTest)
|
||||
<!-- StreamExtensions removed in cycle 2 (2026-05-14): only consumer was the deleted SecurityTest. -->
|
||||
|
||||
- `QueryableExtensions` — conditional LINQ Where filter (used by UserService)
|
||||
|
||||
## Consumers
|
||||
|
||||
@@ -8,14 +8,16 @@ Domain exception type with catalog of business error codes (`ExceptionEnum`).
|
||||
| NoEmailFound | 10 | No such email found |
|
||||
| EmailExists | 20 | Email already exists |
|
||||
| WrongPassword | 30 | Passwords do not match |
|
||||
| PasswordLengthIncorrect | 32 | Password should be at least 8 characters |
|
||||
| PasswordLengthIncorrect | 32 | Password should be at least 12 characters (validator threshold is 12 in `RegisterUserValidator`; the description text on the enum still reads "12 characters") |
|
||||
| EmailLengthIncorrect | 35 | Email is empty or invalid |
|
||||
| WrongEmail | 37 | (no description) |
|
||||
| HardwareIdMismatch | 40 | Hardware mismatch |
|
||||
| BadHardware | 45 | Hardware should be not empty |
|
||||
| WrongResourceName | 50 | Wrong resource file name |
|
||||
| UserDisabled | 38 | User account is disabled |
|
||||
| NoFileProvided | 60 | No file provided |
|
||||
|
||||
> **Retired numeric codes — DO NOT REUSE**:
|
||||
> - `40` (HardwareIdMismatch) and `45` (BadHardware) — removed by AZ-197 (cycle 1, 2026-05-13). Older clients may still surface "Hardware mismatch" UX strings keyed on these integers.
|
||||
> - `50` (WrongResourceName) — removed in cycle 2 (2026-05-14) along with the `GetResourceRequest` validator (its only consumer) and the `POST /resources/get/{dataFolder?}` endpoint.
|
||||
|
||||
## Consumers
|
||||
| Component | Usage |
|
||||
|-----------|-------|
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
### Entities
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — `DetectionClass` (AZ-513) entity was added. `Resource` (AZ-183) was added then removed in the same cycle (post-cycle-1 revert; security audit F-1 + the OTA delivery model itself was deemed obsolete). The `User.Hardware` column is left in place as a tombstone (nullable, unused) per AZ-197. A UNIQUE INDEX `users_email_uidx` was added on `users.email` (security audit F-3, `env/db/06_users_email_unique.sql`).
|
||||
>
|
||||
> **Cycle 2 (2026-05-14) note** — `ResourcesConfig.SuiteInstallerFolder` and `SuiteStageInstallerFolder` were removed along with the installer endpoints (`GET /resources/get-installer[/stage]`); the POCO is now a single-property class (`ResourcesFolder`).
|
||||
|
||||
```
|
||||
User:
|
||||
@@ -81,8 +83,7 @@ JwtConfig:
|
||||
|
||||
ResourcesConfig:
|
||||
ResourcesFolder: string
|
||||
SuiteInstallerFolder: string
|
||||
SuiteStageInstallerFolder: string
|
||||
# SuiteInstallerFolder / SuiteStageInstallerFolder removed in cycle 2 with the installer endpoints.
|
||||
# EncryptionMasterKey was added by AZ-183 and removed in the post-cycle-1 revert.
|
||||
```
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# Authentication & Security
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — AZ-197 simplified `GetApiEncryptionKey` to `(email, password)` and removed `GetHWHash` outright. The hardware-binding threat model that motivated those primitives is no longer in scope (fTPM-anchored Jetsons + browser SaaS).
|
||||
>
|
||||
> **Cycle 2 (2026-05-14) note** — `GetApiEncryptionKey`, `EncryptTo`, and `DecryptTo` were all removed along with the encrypted-download endpoint. `Security` is now a one-method utility (`ToHash`) that backs SHA-384 password hashing.
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: JWT token creation/validation and cryptographic utilities (password hashing, AES file encryption/decryption).
|
||||
**Purpose**: JWT token creation/validation and password hashing (`Security.ToHash`).
|
||||
|
||||
**Architectural Pattern**: Service + static utility — `AuthService` is a DI-managed service for JWT operations; `Security` is a static class for cryptographic primitives.
|
||||
**Architectural Pattern**: Service + static utility — `AuthService` is a DI-managed service for JWT operations; `Security` is a static class with a single SHA-384 helper.
|
||||
|
||||
**Upstream dependencies**: Data Layer (JwtConfig, IUserService for GetByEmail), ASP.NET Core (IHttpContextAccessor).
|
||||
|
||||
**Downstream consumers**: Admin API (token creation on login, current user resolution), User Management (password hashing for both web users and provisioned devices), Resource Management (encryption key derivation, stream encryption).
|
||||
**Downstream consumers**: Admin API (token creation on login, current user resolution), User Management (password hashing for both web users and provisioned devices).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
@@ -26,11 +28,11 @@
|
||||
| Method | Input | Output | Description |
|
||||
|--------|-------|--------|-------------|
|
||||
| `ToHash` | `string` | `string` (Base64) | SHA-384 hash |
|
||||
| `GetApiEncryptionKey` | `string email, string password` | `string` (Base64) | Derives the per-user AES encryption key string. **Signature simplified by AZ-197** (`hardwareHash` parameter removed). |
|
||||
| `EncryptTo` | `Stream input, Stream output, string key, CancellationToken` | void | AES-256-CBC encrypt stream |
|
||||
| `DecryptTo` | `Stream encrypted, Stream output, string key, CancellationToken` | void | AES-256-CBC decrypt stream |
|
||||
|
||||
**Removed by AZ-197**: `GetHWHash(string hardware)` — no remaining callers in the post-cycle-1 codebase.
|
||||
**Removed**:
|
||||
- `GetHWHash(string hardware)` — removed by AZ-197 (cycle 1).
|
||||
- `GetApiEncryptionKey(string email, string password)` — removed in cycle 2 (no remaining callers after `POST /resources/get/{dataFolder?}` was deleted).
|
||||
- `EncryptTo` / `DecryptTo` extension methods — removed in cycle 2 (no remaining callers; the only consumer was `ResourcesService.GetEncryptedResource`, also deleted).
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
@@ -42,7 +44,7 @@ No direct database access. `AuthService.GetCurrentUser` delegates to `IUserServi
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**Algorithmic Complexity**: Encryption/decryption is O(n) where n is file size, streaming in 512 KB buffers.
|
||||
**Algorithmic Complexity**: SHA-384 hashing is O(n) where n is input length; in practice it operates on short password strings only.
|
||||
|
||||
**State Management**: `AuthService` is stateless (reads claims from HTTP context per request). `Security` is purely static.
|
||||
|
||||
@@ -54,7 +56,6 @@ No direct database access. `AuthService.GetCurrentUser` delegates to `IUserServi
|
||||
| Microsoft.AspNetCore.Authentication.JwtBearer | 10.0.3 | JWT middleware integration |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `EncryptTo` throws `ArgumentNullException` for unreadable streams or empty keys.
|
||||
- JWT token creation does not throw (malformed config would cause runtime errors at middleware level).
|
||||
- `GetCurrentUser` returns null if claims are missing or user not found.
|
||||
|
||||
@@ -65,15 +66,12 @@ None — `Security` itself is a utility consumed by other components.
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- Password hashing uses SHA-384 without per-user salt or key stretching. Not resistant to rainbow table attacks. (Unchanged by cycle 1.)
|
||||
- The encryption-key salt is a hardcoded constant. (`Security.GetApiEncryptionKey` body — see `services_security.md`.)
|
||||
- Password hashing uses SHA-384 without per-user salt or key stretching. Not resistant to rainbow table attacks. (Unchanged by cycles 1 and 2.)
|
||||
- `GetCurrentUserEmail` assumes `ClaimTypes.Name` is always present; accessing a missing key would throw `KeyNotFoundException`.
|
||||
- AES encryption prepends IV as first 16 bytes — consumers must know this format.
|
||||
|
||||
**Removed in cycle 1**: hardware fingerprint hashing was a known weakness (static salt, no rotation); deleting it via AZ-197 also removed that attack surface.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- Large file encryption loads encrypted output into `MemoryStream` before sending — high memory usage for large files.
|
||||
**Removed in cycle 2**: per-user file encryption (`GetApiEncryptionKey` + `EncryptTo` + `DecryptTo`). The hardcoded encryption-key salt and the in-memory `MemoryStream` round-trip are no longer attack / performance surfaces in this codebase.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
@@ -81,7 +79,7 @@ None — `Security` itself is a utility consumed by other components.
|
||||
|
||||
**Can be implemented in parallel with**: User Management (shared dependency on Data Layer).
|
||||
|
||||
**Blocks**: Admin API, Resource Management (uses encryption).
|
||||
**Blocks**: Admin API. (Resource Management no longer depends on this component after cycle 2 removed `EncryptTo` / `DecryptTo`.)
|
||||
|
||||
## 9. Logging Strategy
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# Resource Management
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — AZ-197 removed the `Hardware` field from `GetResourceRequest` and removed `CheckResourceRequest` and `POST /resources/check` entirely. AZ-183 introduced an OTA update path (`POST /get-update`, `POST /resources/publish`, `IResourceUpdateService`, `Resource` entity, `resources` table, `ResourcesConfig.EncryptionMasterKey`) but it was reverted later the same day after the security audit (finding F-1) — the OTA delivery model itself was deemed obsolete. The component is now back to filesystem-backed storage only.
|
||||
> **Cycle 1 (2026-05-13) note** — AZ-197 removed the `Hardware` field from `GetResourceRequest` and removed `CheckResourceRequest` and `POST /resources/check` entirely. AZ-183 introduced an OTA update path (`POST /get-update`, `POST /resources/publish`, `IResourceUpdateService`, `Resource` entity, `resources` table, `ResourcesConfig.EncryptionMasterKey`) but it was reverted later the same day after the security audit (finding F-1) — the OTA delivery model itself was deemed obsolete.
|
||||
>
|
||||
> **Cycle 2 (2026-05-14) note** — the encrypted-download endpoint (`POST /resources/get/{dataFolder?}`) and both installer endpoints (`GET /resources/get-installer[/stage]`) were removed as obsolete. With them went `ResourcesService.GetEncryptedResource` / `GetInstaller`, `GetResourceRequest` (and `WrongResourceName = 50`), and the `ResourcesConfig.SuiteInstallerFolder` / `SuiteStageInstallerFolder` properties + their env-var rows. The component is now upload + list + clear only and no longer depends on Authentication & Security for encryption primitives.
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: filesystem-backed storage — upload, list, download (per-user AES-encrypted), folder clearing, installer distribution. Owned by `IResourcesService`.
|
||||
**Purpose**: filesystem-backed storage — upload, list, clear. Owned by `IResourcesService`.
|
||||
|
||||
**Architectural Pattern**: a single service over the local filesystem. No DB access, no cache.
|
||||
|
||||
**Upstream dependencies**: Data Layer (`ResourcesConfig`), Authentication & Security (encryption via `Security.EncryptTo`).
|
||||
**Upstream dependencies**: Data Layer (`ResourcesConfig`).
|
||||
|
||||
**Downstream consumers**: Admin API (resource endpoints).
|
||||
|
||||
@@ -18,22 +20,18 @@
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `GetInstaller` | `bool isStage` | `(string?, Stream?)` | No | None (returns nulls if not found) |
|
||||
| `GetEncryptedResource` | `string? dataFolder, string fileName, string key, CancellationToken` | `Stream` | Yes | `FileNotFoundException` |
|
||||
| `SaveResource` | `string? dataFolder, IFormFile data, CancellationToken` | void | Yes | `BusinessException(NoFileProvided)` |
|
||||
| `ListResources` | `string? dataFolder, string? search, CancellationToken` | `IEnumerable<string>` | Yes | `DirectoryNotFoundException` |
|
||||
| `ClearFolder` | `string? dataFolder` | void | No | None |
|
||||
|
||||
**Input DTO**:
|
||||
```
|
||||
GetResourceRequest (post-AZ-197):
|
||||
Password: string (required, min 8 chars)
|
||||
FileName: string (required, not empty)
|
||||
// Hardware field removed by AZ-197.
|
||||
**Removed**:
|
||||
- `GetEncryptedResource` — removed in cycle 2 with the encrypted-download endpoint.
|
||||
- `GetInstaller` — removed in cycle 2 with the installer endpoints.
|
||||
|
||||
// CheckResourceRequest — REMOVED by AZ-197.
|
||||
// GetUpdateRequest, PublishResourceRequest — added by AZ-183, removed in the post-cycle-1 revert.
|
||||
```
|
||||
**Removed DTOs**:
|
||||
- `GetResourceRequest` — removed in cycle 2 (file deleted).
|
||||
- `CheckResourceRequest` — removed by AZ-197 (cycle 1).
|
||||
- `GetUpdateRequest`, `PublishResourceRequest` — removed in the post-cycle-1 AZ-183 revert.
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
@@ -49,7 +47,7 @@ N/A — exposed through Admin API.
|
||||
|
||||
### Storage Estimates
|
||||
|
||||
- **Filesystem**: AI models, DLLs, installers — potentially hundreds of MB per file.
|
||||
- **Filesystem**: AI models, DLLs, etc. — potentially hundreds of MB per file.
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
@@ -61,32 +59,26 @@ N/A — exposed through Admin API.
|
||||
- `SaveResource` throws `BusinessException(NoFileProvided)` for null uploads.
|
||||
- Missing files/directories throw standard .NET I/O exceptions.
|
||||
- `ClearFolder` silently returns if directory doesn't exist.
|
||||
- `GetInstaller` returns `(null, null)` tuple if installer file is not found.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
| Helper | Purpose | Used By |
|
||||
|--------|---------|---------|
|
||||
| `Security.EncryptTo` | AES stream encryption | `GetEncryptedResource` |
|
||||
| `Security.GetApiEncryptionKey(email, password)` | Per-user key derivation (post-AZ-197 — no hardware component) | Admin API (before calling `GetEncryptedResource`) |
|
||||
None remaining after the cycle-2 removal of `Security.EncryptTo` and `Security.GetApiEncryptionKey`.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations** (security-audit findings):
|
||||
- **F-2 (High)** — no path traversal protection: `dataFolder` parameter is concatenated directly with `ResourcesFolder`. A malicious `dataFolder` like `../../etc` could access arbitrary filesystem paths. Filed as separate ticket.
|
||||
- `SaveResource` deletes existing file before writing — no versioning or backup.
|
||||
- `GetEncryptedResource` loads the entire encrypted file into a `MemoryStream` — memory-intensive for large files.
|
||||
- `ListResources` wraps a synchronous `DirectoryInfo.GetFiles` in `Task.FromResult` — not truly async.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- Full file encryption to memory before streaming response: memory usage scales with file size.
|
||||
- `ClearFolder` iterates and deletes files synchronously.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: Data Layer (ResourcesConfig), Authentication & Security (encryption).
|
||||
**Must be implemented after**: Data Layer (ResourcesConfig).
|
||||
|
||||
**Can be implemented in parallel with**: User Management.
|
||||
**Can be implemented in parallel with**: User Management, Authentication & Security.
|
||||
|
||||
**Blocks**: Admin API.
|
||||
|
||||
@@ -101,6 +93,5 @@ N/A — exposed through Admin API.
|
||||
**Log storage**: console + rolling file (via Serilog configured in Program.cs).
|
||||
|
||||
## Modules Covered
|
||||
- `Services/ResourcesService`
|
||||
- `Common/Requests/GetResourceRequest` (post-AZ-197 — no `CheckResourceRequest`, no `Hardware` field)
|
||||
- `Common/Configs/ResourcesConfig` (the `EncryptionMasterKey` field added by AZ-183 was removed in the post-cycle-1 revert)
|
||||
- `Services/ResourcesService` (post-cycle-2 — only `SaveResource` / `ListResources` / `ClearFolder` remain)
|
||||
- `Common/Configs/ResourcesConfig` (post-cycle-2 — only `ResourcesFolder` remains)
|
||||
|
||||
@@ -50,12 +50,10 @@ Converts `BusinessException` to HTTP 409 JSON response: `{ ErrorCode: int, Messa
|
||||
| `/resources/{dataFolder?}` | POST | Authenticated | Uploads a file (up to 200 MB) |
|
||||
| `/resources/list/{dataFolder?}` | GET | Authenticated | Lists files |
|
||||
| `/resources/clear/{dataFolder?}` | POST | ApiAdmin | Clears folder |
|
||||
| `/resources/get/{dataFolder?}` | POST | Authenticated | Downloads encrypted resource (key derived from `email + password` only — no Hardware) |
|
||||
| `/resources/get-installer` | GET | Authenticated | Downloads production installer |
|
||||
| `/resources/get-installer/stage` | GET | Authenticated | Downloads staging installer |
|
||||
|
||||
**Removed by AZ-197**: `POST /resources/check` (was the hardware-binding side-effect probe).
|
||||
**Removed in post-cycle-1 revert**: `POST /get-update` and `POST /resources/publish` (AZ-183 reverted — security audit F-1; OTA delivery model itself obsolete).
|
||||
**Removed in cycle 2 (2026-05-14)**: `POST /resources/get/{dataFolder?}`, `GET /resources/get-installer`, `GET /resources/get-installer/stage` — all obsolete; the encrypted-download support stack (`Security.GetApiEncryptionKey` / `EncryptTo` / `DecryptTo`, `ResourcesService.GetEncryptedResource` / `GetInstaller`, `GetResourceRequest`, `WrongResourceName = 50`, `ResourcesConfig.SuiteInstallerFolder` / `SuiteStageInstallerFolder`) was removed with them. ADR-003 retired.
|
||||
|
||||
### Detection Classes
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
## Configuration
|
||||
|
||||
### appsettings.json Defaults
|
||||
- `ResourcesConfig`: ResourcesFolder=`"Content"`, SuiteInstallerFolder=`"suite"`, SuiteStageInstallerFolder=`"suite-stage"`
|
||||
- `ResourcesConfig`: ResourcesFolder=`"Content"` (the `SuiteInstallerFolder` / `SuiteStageInstallerFolder` keys were removed in cycle 2 along with the installer endpoints)
|
||||
- `JwtConfig`: Issuer=`"AzaionApi"`, Audience=`"Annotators/OrangePi/Admins"`, TokenLifetimeHours=`4`
|
||||
- `ConnectionStrings` and `JwtConfig.Secret` are NOT in appsettings — must be provided via environment variables
|
||||
|
||||
@@ -25,8 +25,6 @@ Configuration is loaded via ASP.NET Core's `IConfiguration` with the following s
|
||||
| `JwtConfig.Audience` | Token audience | — |
|
||||
| `JwtConfig.TokenLifetimeHours` | Token TTL | — |
|
||||
| `ResourcesConfig.ResourcesFolder` | File storage root | — |
|
||||
| `ResourcesConfig.SuiteInstallerFolder` | Prod installer dir | — |
|
||||
| `ResourcesConfig.SuiteStageInstallerFolder` | Stage installer dir | — |
|
||||
|
||||
## Infrastructure Scripts (`env/`)
|
||||
|
||||
|
||||
@@ -65,11 +65,10 @@ graph TD
|
||||
| # | Component | Modules | Purpose |
|
||||
|---|-----------|---------|---------|
|
||||
| 01 | Data Layer | 9 | DB access, entities, configs, caching |
|
||||
| 02 | User Management | 5 | User CRUD, hardware binding, role management |
|
||||
| 03 | Auth & Security | 2 | JWT tokens, cryptographic utilities |
|
||||
| 04 | Resource Management | 3 | File upload/download/encryption |
|
||||
| 02 | User Management | 5 | User CRUD, role management, device provisioning (hardware binding removed by AZ-197) |
|
||||
| 03 | Auth & Security | 2 | JWT tokens + SHA-384 password hashing (per-user file encryption removed in cycle 2) |
|
||||
| 04 | Resource Management | 2 | File upload / list / clear (encrypted-download + installer endpoints removed in cycle 2) |
|
||||
| 05 | Admin API | 2 | HTTP endpoints, middleware, DI composition |
|
||||
| — | Common Helpers | 6 | Extensions, BusinessException |
|
||||
| — | Tests | 2 | SecurityTest, UserServiceTest |
|
||||
|
||||
**Total**: 27 modules across 5 components + common helpers + tests.
|
||||
**Total**: 26 modules across 5 components + common helpers. The previously listed in-process unit tests (`SecurityTest`, `UserServiceTest`) and the `Azaion.Test` project itself were removed in cycle 2; remaining test coverage lives in `e2e/Azaion.E2E/`.
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
# Flow: Encrypted Resource Download
|
||||
# Flow: Encrypted Resource Download — OBSOLETE
|
||||
|
||||
> **Removed in cycle 2 (2026-05-14).**
|
||||
>
|
||||
> The `POST /resources/get/{dataFolder?}` endpoint, the `ResourcesService.GetEncryptedResource` method, the `Security.GetApiEncryptionKey` / `EncryptTo` / `DecryptTo` helpers, the `GetResourceRequest` DTO + validator, and the `ExceptionEnum.WrongResourceName` (50) error code no longer exist. Per-user file encryption is no longer part of the system; resource files are stored as plain bytes and only ever leave the server through upload (`POST /resources/{dataFolder?}`) and admin clear (`POST /resources/clear/{dataFolder?}`).
|
||||
>
|
||||
> See `_docs/02_document/architecture.md` ADR-003 (retired) and `_docs/02_document/system-flows.md` flow F3 (removed) for context.
|
||||
>
|
||||
> This file is retained as a tombstone so historical references resolve. Do not link to it from new docs.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant API as Admin API
|
||||
participant Auth as AuthService
|
||||
participant US as UserService
|
||||
participant Sec as Security
|
||||
participant RS as ResourcesService
|
||||
participant FS as Filesystem
|
||||
|
||||
Client->>API: POST /resources/get {password, hardware, fileName}
|
||||
API->>Auth: GetCurrentUser()
|
||||
Auth-->>API: User
|
||||
API->>US: CheckHardwareHash(user, hardware)
|
||||
US->>Sec: GetHWHash(hardware)
|
||||
Sec-->>US: hash
|
||||
US-->>API: hwHash
|
||||
API->>Sec: GetApiEncryptionKey(email, password, hwHash)
|
||||
Sec-->>API: AES key
|
||||
API->>RS: GetEncryptedResource(folder, fileName, key)
|
||||
RS->>FS: Read file
|
||||
FS-->>RS: FileStream
|
||||
RS->>Sec: EncryptTo(stream, key) [AES-256-CBC]
|
||||
Sec-->>RS: Encrypted MemoryStream
|
||||
RS-->>API: Stream
|
||||
API-->>Client: 200 OK (application/octet-stream)
|
||||
flowchart TD
|
||||
Start([POST /resources/get — REMOVED]) --> Removed[Endpoint deleted in cycle 2]
|
||||
```
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
## Layout Rules
|
||||
|
||||
1. This admin/ workspace is one **deployable** (the `Azaion.AdminApi` HTTP service) split across four production csproj projects + one e2e test csproj: `Azaion.AdminApi`, `Azaion.Services`, `Azaion.Common`, `Azaion.Test`, `e2e/Azaion.E2E`.
|
||||
1. This admin/ workspace is one **deployable** (the `Azaion.AdminApi` HTTP service) split across three production csproj projects + one e2e test csproj: `Azaion.AdminApi`, `Azaion.Services`, `Azaion.Common`, `e2e/Azaion.E2E`. (The `Azaion.Test` unit-test project was removed in cycle 2 once its only test class — `SecurityTest.cs` — was deleted along with the encrypted-download stack; no in-process unit tests remain.)
|
||||
2. Existing task specs (`_docs/02_tasks/*/AZ-*.md`) all use `Component: Admin API` as a single coarse identifier covering this entire workspace. The Per-Component Mapping below honors that convention rather than rewriting every task spec.
|
||||
3. The conceptual sub-components documented in `_docs/02_document/components/01_data_layer..05_admin_api/` are **read-time** documentation aids, not write-time ownership boundaries. They are listed under "Conceptual Sub-Components" below for reference only.
|
||||
4. Public API surface = the namespaces / interfaces exposed across csproj boundaries (`I*Service` interfaces in `Azaion.Services`, request DTOs in `Azaion.Common/Requests/`, entities in `Azaion.Common/Entities/`).
|
||||
5. Tests live in `Azaion.Test/` (in-process unit/integration) and `e2e/Azaion.E2E/` (HTTP black-box). Production code never imports from either.
|
||||
5. Tests live in `e2e/Azaion.E2E/` (HTTP black-box). Production code never imports from there.
|
||||
|
||||
## Per-Component Mapping
|
||||
|
||||
@@ -23,10 +23,8 @@
|
||||
- `Azaion.AdminApi/**`
|
||||
- `Azaion.Services/**`
|
||||
- `Azaion.Common/**`
|
||||
- `Azaion.Test/**`
|
||||
- `e2e/Azaion.E2E/**` (xUnit/HttpClient-based black-box tests)
|
||||
- `e2e/db-init/**` (test-DB seed/init scripts consumed by the e2e harness)
|
||||
- `docker.test/**` (test fixture / schema-init helpers used by `Azaion.Test`)
|
||||
- `docker-compose.test.yml`
|
||||
- **Public API** (visible to other csprojs within the workspace):
|
||||
- `Azaion.Services/I*Service.cs` interfaces (UserService, AuthService, ResourcesService, …)
|
||||
@@ -54,8 +52,8 @@ These come from `_docs/02_document/components/` and exist for reading the codeba
|
||||
|---|----------------------|------------------------|
|
||||
| 1 | Data Layer | `Azaion.Common/Database/`, `Azaion.Common/Configs/`, `Azaion.Common/Entities/` (incl. `DetectionClass.cs` added cycle 1; `Resource.cs` added then removed in same cycle — see post-cycle-1 revert) |
|
||||
| 2 | User Management | `Azaion.Services/UserService.cs` (incl. `RegisterDevice` added cycle 1 / AZ-196 — calls `RegisterUser` end-to-end after security-audit consolidation, finding F-3), `Azaion.Common/Requests/Register{User,DeviceResponse}.cs`, `LoginRequest.cs`, `SetUserQueueOffsetsRequest.cs` |
|
||||
| 3 | Auth & Security | `Azaion.Services/AuthService.cs`, `Azaion.Services/Security.cs` (post-AZ-197 — `GetHWHash` removed; signature simplified), `Azaion.Services/Cache.cs` |
|
||||
| 4 | Resource Management | `Azaion.Services/ResourcesService.cs`, `Azaion.Common/Requests/GetResourceRequest.cs` (`SetHWRequest.cs` removed by AZ-197; `ResourceUpdateService.cs` + `GetUpdateRequest.cs` + `PublishResourceRequest.cs` removed when AZ-183 was reverted) |
|
||||
| 3 | Auth & Security | `Azaion.Services/AuthService.cs`, `Azaion.Services/Security.cs` (post-cycle-2 — only `ToHash` remains; `GetApiEncryptionKey` / `EncryptTo` / `DecryptTo` removed with the encrypted-download endpoint), `Azaion.Services/Cache.cs` |
|
||||
| 4 | Resource Management | `Azaion.Services/ResourcesService.cs` (`GetResourceRequest.cs` removed in cycle 2 with `POST /resources/get`; `SetHWRequest.cs` removed by AZ-197; `ResourceUpdateService.cs` + `GetUpdateRequest.cs` + `PublishResourceRequest.cs` removed when AZ-183 was reverted) |
|
||||
| 4b | Detection Classes | `Azaion.Services/DetectionClassService.cs` + `Azaion.Common/Requests/{Create,Update}DetectionClassRequest.cs` (added cycle 1 / AZ-513) |
|
||||
| 5 | Admin API (HTTP) | `Azaion.AdminApi/Program.cs`, `Azaion.AdminApi/BusinessExceptionHandler.cs`, `Azaion.AdminApi/appsettings*.json` |
|
||||
|
||||
@@ -66,7 +64,6 @@ These come from `_docs/02_document/components/` and exist for reading the codeba
|
||||
| 4. Entry / Host | `Azaion.AdminApi` | `Azaion.Services`, `Azaion.Common` |
|
||||
| 3. Application | `Azaion.Services` | `Azaion.Common` |
|
||||
| 2. Foundation | `Azaion.Common` | (none) |
|
||||
| —. Tests (in-process) | `Azaion.Test` | `Azaion.Services`, `Azaion.Common`, `Azaion.AdminApi` (integration only) |
|
||||
| —. Tests (out-of-process e2e) | `e2e/Azaion.E2E` | (none from production csprojs — HTTP only) |
|
||||
|
||||
A reference from a lower production layer to a higher production layer is an **Architecture** finding (High severity) in `/code-review` Phase 7. Test projects may reference any production csproj; production csprojs may NOT reference test projects.
|
||||
@@ -75,7 +72,7 @@ A reference from a lower production layer to a higher production layer is an **A
|
||||
|
||||
| Language | Root | Per-component path | Public API file | Test path |
|
||||
|----------|------|-------------------|-----------------|-----------|
|
||||
| C# (.NET) | `./` (this workspace, legacy flat layout) | `./<Csproj>/` | namespace-root types in each csproj | `Azaion.Test/`, `e2e/Azaion.E2E/` |
|
||||
| C# (.NET) | `./` (this workspace, legacy flat layout) | `./<Csproj>/` | namespace-root types in each csproj | `e2e/Azaion.E2E/` |
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ Application entry point: configures DI, middleware, authentication, authorizatio
|
||||
## Public Interface (HTTP Endpoints)
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — endpoint surface changed by AZ-513 (detection-class CRUD), AZ-196 (device auto-registration), AZ-197 (hardware-binding removal). AZ-183 (OTA update check + publish) 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. The table reflects the post-cycle-1 state including that revert.
|
||||
>
|
||||
> **Cycle 2 (2026-05-14) note** — three more endpoints were removed as obsolete: `POST /resources/get/{dataFolder?}`, `GET /resources/get-installer`, `GET /resources/get-installer/stage`. The encrypted-download support stack (`Security.GetApiEncryptionKey` / `EncryptTo` / `DecryptTo`, `ResourcesService.GetEncryptedResource` / `GetInstaller`, `GetResourceRequest` DTO, `WrongResourceName = 50` enum value, `ResourcesConfig.SuiteInstallerFolder` / `SuiteStageInstallerFolder`) went with them. ADR-003 in `architecture.md` was retired in the same change.
|
||||
|
||||
| Method | Path | Auth | Summary | Cycle 1 origin |
|
||||
|--------|------|------|---------|----------------|
|
||||
@@ -22,23 +24,23 @@ Application entry point: configures DI, middleware, authentication, authorizatio
|
||||
| POST | `/resources/{dataFolder?}` | Any authenticated | Uploads a resource file | — |
|
||||
| GET | `/resources/list/{dataFolder?}` | Any authenticated | Lists files in a resource folder | — |
|
||||
| POST | `/resources/clear/{dataFolder?}` | ApiAdmin | Clears a resource folder | — |
|
||||
| POST | `/resources/get/{dataFolder?}` | Any authenticated | Downloads an encrypted resource (key derived from `email + password` only) | AZ-197 wire change (no `Hardware` field) |
|
||||
| GET | `/resources/get-installer` | Any authenticated | Downloads latest production installer | — |
|
||||
| GET | `/resources/get-installer/stage` | Any authenticated | Downloads latest staging installer | — |
|
||||
| POST | `/classes` | ApiAdmin | Creates a detection class | AZ-513 |
|
||||
| PATCH | `/classes/{id:int}` | ApiAdmin | Updates a detection class (partial-merge) | AZ-513 |
|
||||
| DELETE | `/classes/{id:int}` | ApiAdmin | Deletes a detection class | AZ-513 |
|
||||
|
||||
### Removed in cycle 1
|
||||
### Removed endpoints
|
||||
|
||||
The following endpoints were removed during cycle 1 and now return `404`:
|
||||
The following endpoints have been removed and now return `404`:
|
||||
|
||||
| Method | Path | Reason removed |
|
||||
|--------|------|----------------|
|
||||
| PUT | `/users/hardware/set` | AZ-197 — hardware-binding feature deleted (no fielded clients in target architecture) |
|
||||
| POST | `/resources/check` | AZ-197 — was the hardware-binding side-effect probe; no remaining purpose |
|
||||
| POST | `/get-update` | OTA delivery model retired post-cycle-1 (security audit F-1: endpoint disclosed plaintext per-resource encryption keys to any authenticated caller; the underlying installer-distribution flow is itself obsolete) |
|
||||
| POST | `/resources/publish` | Same revert as `/get-update` — the publish counterpart of the OTA flow |
|
||||
| Method | Path | Removed in | Reason |
|
||||
|--------|------|------------|--------|
|
||||
| PUT | `/users/hardware/set` | cycle 1 (AZ-197) | hardware-binding feature deleted (no fielded clients in target architecture) |
|
||||
| POST | `/resources/check` | cycle 1 (AZ-197) | was the hardware-binding side-effect probe; no remaining purpose |
|
||||
| POST | `/get-update` | post-cycle-1 (AZ-183 reverted) | security audit F-1: endpoint disclosed plaintext per-resource encryption keys to any authenticated caller; the underlying installer-distribution flow is itself obsolete |
|
||||
| POST | `/resources/publish` | post-cycle-1 (AZ-183 reverted) | same revert as `/get-update` — the publish counterpart of the OTA flow |
|
||||
| POST | `/resources/get/{dataFolder?}` | cycle 2 (2026-05-14) | obsolete — per-user encrypted-download flow no longer used by any client; ADR-003 retired |
|
||||
| GET | `/resources/get-installer` | cycle 2 (2026-05-14) | obsolete — installer-shipping era is over (browser SaaS + fTPM Jetsons) |
|
||||
| GET | `/resources/get-installer/stage` | cycle 2 (2026-05-14) | same as `/resources/get-installer` |
|
||||
|
||||
## Internal Logic
|
||||
|
||||
@@ -69,7 +71,7 @@ The following endpoints were removed during cycle 1 and now return `404`:
|
||||
### Configuration Sections
|
||||
- `JwtConfig` — JWT signing/validation
|
||||
- `ConnectionStrings` — DB connections
|
||||
- `ResourcesConfig` — file storage paths
|
||||
- `ResourcesConfig` — file storage path (`ResourcesFolder`); the installer subfolders were dropped in cycle 2 along with the installer endpoints
|
||||
|
||||
### Kestrel
|
||||
- Max request body size: 200 MB (for file uploads)
|
||||
|
||||
@@ -22,10 +22,11 @@ Custom exception type for domain-level errors, paired with an `ExceptionEnum` ca
|
||||
| `EmailLengthIncorrect` | 35 | Email is empty or invalid |
|
||||
| `WrongEmail` | 37 | (no description attribute) |
|
||||
| `UserDisabled` | 38 | User account is disabled |
|
||||
| `WrongResourceName` | 50 | Wrong resource file name |
|
||||
| `NoFileProvided` | 60 | No file provided |
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — `HardwareIdMismatch = 40` and `BadHardware = 45` were removed by AZ-197 (admin-side hardware-binding cleanup). Code 40 should NOT be reused for a different meaning — older clients may still surface "Hardware mismatch" UX strings keyed on the integer. `UserDisabled = 38` was added earlier (still part of the baseline). See `_docs/03_implementation/batch_06_report.md`.
|
||||
> **Cycle 1 (2026-05-13) note** — `HardwareIdMismatch = 40` and `BadHardware = 45` were removed by AZ-197 (admin-side hardware-binding cleanup). Codes 40 and 45 should NOT be reused for a different meaning — older clients may still surface "Hardware mismatch" UX strings keyed on the integer. `UserDisabled = 38` was added earlier (still part of the baseline). See `_docs/03_implementation/batch_06_report.md`.
|
||||
>
|
||||
> **Cycle 2 (2026-05-14) note** — `WrongResourceName = 50` was removed along with the `GetResourceRequest` validator (the only consumer). Code 50 should NOT be reused — gap kept per the cycle-1 lesson on retired numeric codes.
|
||||
|
||||
## Internal Logic
|
||||
Static constructor eagerly loads all `ExceptionEnum` descriptions into a dictionary via `EnumExtensions.GetDescriptions<ExceptionEnum>()`. Messages are retrieved by dictionary lookup with fallback to `ToString()`.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Module: Azaion.Common.Configs.ResourcesConfig
|
||||
|
||||
## Purpose
|
||||
Configuration POCO for file resource storage paths, bound from `appsettings.json` section `ResourcesConfig`.
|
||||
Configuration POCO for the file resource storage root, bound from `appsettings.json` section `ResourcesConfig`.
|
||||
|
||||
> **Cycle 2 (2026-05-14) note** — `SuiteInstallerFolder` and `SuiteStageInstallerFolder` were removed along with the installer endpoints (`GET /resources/get-installer[/stage]`) and `ResourcesService.GetInstaller`. Their `ASPNETCORE_ResourcesConfig__SuiteInstallerFolder` / `__SuiteStageInstallerFolder` env-var rows were removed from `appsettings.json`, `.env.example`, `secrets/staging.public.env`, `secrets/production.public.env`, and `docker-compose.test.yml`.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `ResourcesFolder` | `string` | Root directory for uploaded resource files |
|
||||
| `SuiteInstallerFolder` | `string` | Subdirectory for production installer files |
|
||||
| `SuiteStageInstallerFolder` | `string` | Subdirectory for staging installer files |
|
||||
|
||||
## Internal Logic
|
||||
None — pure data class.
|
||||
@@ -18,7 +18,7 @@ None — pure data class.
|
||||
None.
|
||||
|
||||
## Consumers
|
||||
- `ResourcesService` — uses all three properties to resolve file paths
|
||||
- `ResourcesService` — uses `ResourcesFolder` to resolve upload / list / clear paths
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
@@ -30,7 +30,7 @@ Bound via `builder.Configuration.GetSection(nameof(ResourcesConfig))` in `Progra
|
||||
None.
|
||||
|
||||
## Security
|
||||
Paths control where files are read from and written to on the server's filesystem.
|
||||
Path controls where files are read from and written to on the server's filesystem.
|
||||
|
||||
## Tests
|
||||
None.
|
||||
|
||||
@@ -59,4 +59,4 @@ None.
|
||||
`PasswordHash` stores SHA-384 hash. `Hardware` stores raw hardware fingerprint (hashed for comparison via `Security.GetHWHash`).
|
||||
|
||||
## Tests
|
||||
Indirectly tested via `UserServiceTest` and `SecurityTest`.
|
||||
Indirectly tested end-to-end via `e2e/Azaion.E2E/Tests/LoginTests.cs`, `UserManagementTests.cs`, and `DeviceTests.cs`. (The previous in-process `Azaion.Test/UserServiceTest` and `SecurityTest` were both removed by cycle 2 along with the `Azaion.Test` project.)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Module: Azaion.Common.Extensions.StreamExtensions
|
||||
|
||||
## Purpose
|
||||
Stream-to-string conversion utility.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `ConvertToString` | `static string ConvertToString(this Stream stream)` | Reads entire stream as UTF-8 string, resets position to 0 afterward |
|
||||
|
||||
## Internal Logic
|
||||
Resets stream position to 0, reads via `StreamReader`, then resets again so the stream remains usable.
|
||||
|
||||
## Dependencies
|
||||
- `System.Text.Encoding`, `System.IO.StreamReader` (BCL only)
|
||||
|
||||
## Consumers
|
||||
- `SecurityTest.EncryptDecryptTest` — converts decrypted stream to string for assertion
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None.
|
||||
|
||||
## Security
|
||||
None.
|
||||
|
||||
## Tests
|
||||
Indirectly tested via `SecurityTest.EncryptDecryptTest`.
|
||||
@@ -1,46 +0,0 @@
|
||||
# Module: Azaion.Common.Requests.GetResourceRequest
|
||||
|
||||
## Purpose
|
||||
Request DTO and validator for the `POST /resources/get/{dataFolder?}` endpoint. The user's password is supplied per-request so the server can derive the per-user AES encryption key for the response stream.
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — the `Hardware` property and its `BadHardware` validator rule were removed by AZ-197 (admin-side hardware-binding cleanup). The wire-compat policy was "drop entirely" — any client still sending `Hardware` will not see it deserialized. The companion `CheckResourceRequest` was removed along with the `POST /resources/check` endpoint. See `_docs/03_implementation/batch_06_report.md`.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### GetResourceRequest
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Password` | `string` | User's password (used to derive the encryption key) |
|
||||
| `FileName` | `string` | Resource file to retrieve |
|
||||
|
||||
### GetResourceRequestValidator
|
||||
| Rule | Constraint | Error Code |
|
||||
|------|-----------|------------|
|
||||
| `Password` min length | >= 8 chars | `PasswordLengthIncorrect` |
|
||||
| `FileName` not empty | Required | `WrongResourceName` |
|
||||
|
||||
## Internal Logic
|
||||
Validator uses `BusinessException.GetMessage()` to derive user-facing error messages from `ExceptionEnum`.
|
||||
|
||||
## Dependencies
|
||||
- `BusinessException`, `ExceptionEnum`
|
||||
- FluentValidation
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` `POST /resources/get/{dataFolder?}` endpoint
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None.
|
||||
|
||||
## Security
|
||||
- Password is sent in the POST body (not URL) to avoid logging in access logs.
|
||||
- Per-user encryption key derivation now uses `email + password` only (see `services_security.md`).
|
||||
|
||||
## Tests
|
||||
- `e2e/Azaion.E2E/Tests/ResourceTests.cs` (encrypted download / round-trip) — updated by AZ-197 to stop sending `Hardware`
|
||||
@@ -27,7 +27,7 @@ Private method:
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` `/login` endpoint — calls `CreateToken` after successful validation
|
||||
- `Program.cs` `/users/current`, `/resources/get`, `/resources/get-installer`, `/resources/check` — call `GetCurrentUser`
|
||||
- `Program.cs` `/users/current` — calls `GetCurrentUser` (the previously listed `/resources/get`, `/resources/get-installer`, `/resources/check` consumers were removed in cycle 2 / by AZ-197 along with their endpoints)
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
# Module: Azaion.Services.ResourcesService
|
||||
|
||||
## Purpose
|
||||
File-based resource management: upload, list, download (encrypted), clear, and installer retrieval from the server's filesystem.
|
||||
File-based resource management: upload, list, and clear files in the server's filesystem.
|
||||
|
||||
> **Cycle 2 (2026-05-14) note** — `GetInstaller` and `GetEncryptedResource` were removed along with the `POST /resources/get/{dataFolder?}` and `GET /resources/get-installer[/stage]` endpoints; the corresponding interface methods, the `Security.EncryptTo` dependency, and the `ResourcesConfig.SuiteInstallerFolder` / `SuiteStageInstallerFolder` properties went with them. The service is now upload + list + clear only.
|
||||
|
||||
## Public Interface
|
||||
|
||||
### IResourcesService
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `GetInstaller` | `(string?, Stream?) GetInstaller(bool isStage)` | Returns the latest installer file (prod or stage) |
|
||||
| `GetEncryptedResource` | `Task<Stream> GetEncryptedResource(string? dataFolder, string fileName, string key, CancellationToken ct)` | Reads a file and returns it AES-encrypted |
|
||||
| `SaveResource` | `Task SaveResource(string? dataFolder, IFormFile data, CancellationToken ct)` | Saves an uploaded file to the resource folder |
|
||||
| `ListResources` | `Task<IEnumerable<string>> ListResources(string? dataFolder, string? search, CancellationToken ct)` | Lists file names in a resource folder, optionally filtered |
|
||||
| `ClearFolder` | `void ClearFolder(string? dataFolder)` | Deletes all files and subdirectories in the specified folder |
|
||||
|
||||
## Internal Logic
|
||||
- **GetResourceFolder**: resolves the target directory. If `dataFolder` is null/empty, uses `ResourcesConfig.ResourcesFolder` directly; otherwise, appends it as a subdirectory.
|
||||
- **GetInstaller**: scans the installer folder for files matching `"AzaionSuite.Iterative*"`, returns the first match as a `FileStream`.
|
||||
- **GetEncryptedResource**: opens the file, encrypts via `Security.EncryptTo` extension into a `MemoryStream`, returns the encrypted stream.
|
||||
- **SaveResource**: creates the folder if needed, deletes any existing file with the same name, then copies the uploaded file.
|
||||
- **ListResources**: uses `DirectoryInfo.GetFiles` with optional search pattern.
|
||||
- **ClearFolder**: iterates and deletes all files and subdirectories.
|
||||
@@ -26,24 +24,22 @@ File-based resource management: upload, list, download (encrypted), clear, and i
|
||||
- `IOptions<ResourcesConfig>` — folder paths
|
||||
- `ILogger<ResourcesService>` — logs successful saves
|
||||
- `BusinessException` — thrown for null file uploads
|
||||
- `Security.EncryptTo` — stream encryption extension
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` — all `/resources/*` endpoints
|
||||
- `Program.cs` — `POST /resources/{dataFolder?}` (upload), `GET /resources/list/{dataFolder?}`, `POST /resources/clear/{dataFolder?}`
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
Uses `ResourcesConfig` (ResourcesFolder, SuiteInstallerFolder, SuiteStageInstallerFolder).
|
||||
Uses `ResourcesConfig.ResourcesFolder`.
|
||||
|
||||
## External Integrations
|
||||
Local filesystem for resource storage.
|
||||
|
||||
## Security
|
||||
- Resources are encrypted per-user using a key derived from `email + password` (the hardware-hash component was removed by AZ-197 — see `services_security.md`).
|
||||
- File deletion overwrites existing files before writing new ones.
|
||||
- No path traversal protection on `dataFolder` parameter.
|
||||
- No path traversal protection on `dataFolder` parameter (security audit F-2 — open).
|
||||
|
||||
## Tests
|
||||
None at the module level. End-to-end coverage lives in `e2e/Azaion.E2E/Tests/ResourceTests.cs` (encrypted download / round-trip / 200 MB upload limit) — updated by AZ-197 to stop sending the `Hardware` field.
|
||||
End-to-end coverage in `e2e/Azaion.E2E/Tests/ResourceTests.cs` — `File_upload_succeeds` and `Upload_without_file_is_rejected_with_400_or_409_and_60_on_conflict`.
|
||||
|
||||
@@ -1,50 +1,39 @@
|
||||
# Module: Azaion.Services.Security
|
||||
|
||||
## Purpose
|
||||
Static utility class providing cryptographic operations: password hashing, encryption key derivation, and AES-CBC stream encryption/decryption.
|
||||
Static utility class providing the SHA-384 password hashing helper used by `UserService`.
|
||||
|
||||
> **Cycle 1 (2026-05-13) note** — `GetHWHash` was deleted and `GetApiEncryptionKey` was simplified from `(email, password, hardwareHash)` to `(email, password)` by AZ-197 (admin-side hardware-binding cleanup). The hardware-hash component of the derived key is gone; existing ciphertexts produced under the old derivation are no longer re-derivable from the new signature. See `_docs/03_implementation/batch_06_report.md`.
|
||||
> **Cycle 1 (2026-05-13) note** — `GetHWHash` was deleted and `GetApiEncryptionKey` was simplified from `(email, password, hardwareHash)` to `(email, password)` by AZ-197.
|
||||
>
|
||||
> **Cycle 2 (2026-05-14) note** — `GetApiEncryptionKey`, `EncryptTo`, and `DecryptTo` were all removed along with the encrypted-download endpoint. Only `ToHash` remains; it still backs SHA-384 password hashing in `UserService` (`PasswordHash = request.Password.ToHash()`). The `Azaion.Test/SecurityTest.cs` unit tests went with the removed methods, leaving the `Azaion.Test` project empty (also removed from the solution). See `_docs/06_metrics/retro_2026-05-14.md` once cycle 2's retro lands.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `ToHash` | `static string ToHash(this string str)` | Extension: SHA-384 hash of input, returned as Base64 |
|
||||
| `GetApiEncryptionKey` | `static string GetApiEncryptionKey(string email, string password)` | Derives the per-user AES encryption key string from email + password (+ static salt) |
|
||||
| `EncryptTo` | `static async Task EncryptTo(this Stream inputStream, Stream toStream, string key, CancellationToken ct)` | AES-256-CBC encrypts a stream; prepends IV to output |
|
||||
| `DecryptTo` | `static async Task DecryptTo(this Stream encryptedStream, Stream toStream, string key, CancellationToken ct)` | Reads IV prefix, then AES-256-CBC decrypts stream |
|
||||
|
||||
## Internal Logic
|
||||
- **Password hashing**: `ToHash` uses SHA-384 with UTF-8 encoding, outputting Base64.
|
||||
- **Encryption key derivation**: `GetApiEncryptionKey` concatenates email and password with the static salt `"-#%@AzaionKey@%#---"`, then hashes via `ToHash` (SHA-384, Base64).
|
||||
- **Encryption**: AES-256-CBC with PKCS7 padding. Key is SHA-256 of the derived key string. IV is randomly generated and prepended to the output stream. Uses 512 KB buffer for streaming.
|
||||
- **Decryption**: Reads the first 16 bytes as IV, then AES-256-CBC decrypts with PKCS7 padding.
|
||||
- `ToHash` uses SHA-384 with UTF-8 encoding, outputting Base64.
|
||||
|
||||
## Dependencies
|
||||
- `System.Security.Cryptography` (Aes, SHA256, SHA384)
|
||||
- `System.Security.Cryptography` (SHA384)
|
||||
- `System.Text.Encoding`
|
||||
|
||||
## Consumers
|
||||
- `Program.cs` `/resources/get/{dataFolder}` endpoint — calls `GetApiEncryptionKey(user.Email, request.Password)`
|
||||
- `ResourcesService.GetEncryptedResource` — uses `EncryptTo` extension
|
||||
- `Azaion.Test/SecurityTest` — directly tests `EncryptTo` / `DecryptTo` round-trips (no longer tests hardware-hash derivation)
|
||||
- `Azaion.Services/UserService.cs` — `RegisterUser` (password storage) and `ValidateUser` (login comparison) both call `request.Password.ToHash()`
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
- `BUFFER_SIZE = 524288` (512 KB) — hardcoded streaming buffer size
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
None.
|
||||
|
||||
## Security
|
||||
Core cryptographic module. Key observations:
|
||||
- Passwords are hashed with SHA-384 (no per-user salt, no key stretching — not bcrypt/scrypt/argon2). This is unchanged by AZ-197.
|
||||
- AES encryption uses SHA-256 of the derived key, with random IV per encryption.
|
||||
- All salts/prefixes are hardcoded constants.
|
||||
- Per AZ-197: device hardware fingerprints no longer participate in key derivation. The threat that hardware binding mitigated (credential reuse via desktop installers) was eliminated by the architectural shift to fTPM-secured Jetsons + browser-only SaaS access.
|
||||
- Password hashing uses SHA-384 with no per-user salt and no key stretching. Not resistant to rainbow-table attacks (security audit F-7 — open). Unchanged by cycles 1 and 2.
|
||||
|
||||
## Tests
|
||||
- `Azaion.Test/SecurityTest.EncryptDecryptTest` — round-trip encrypt/decrypt of a string
|
||||
- `Azaion.Test/SecurityTest.EncryptDecryptLargeFileTest` — round-trip encrypt/decrypt of a ~400 MB generated file
|
||||
None at the unit-test level after the `Azaion.Test` project was removed in cycle 2. `ToHash` is exercised end-to-end through every login / register e2e test (`e2e/Azaion.E2E/Tests/`).
|
||||
|
||||
@@ -63,5 +63,7 @@ PostgreSQL via `IDbFactory`.
|
||||
- Read operations use the read-only DB connection; writes use the admin connection.
|
||||
|
||||
## Tests
|
||||
- `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
|
||||
- `e2e/Azaion.E2E/Tests/UserManagementTests.cs` and `LoginTests.cs` — e2e coverage for the rest of the user lifecycle (login, register, role change, enable/disable, delete, queue offsets)
|
||||
|
||||
(Unit-test coverage in `Azaion.Test/UserServiceTest.cs` was removed earlier with the AZ-197 hardware-binding cleanup; the `Azaion.Test` project itself was removed from the solution in cycle 2 once its only remaining file — `SecurityTest.cs` — was deleted with the encrypted-download stack.)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Module: Azaion.Test.SecurityTest
|
||||
|
||||
## Purpose
|
||||
xUnit tests for the `Security` encryption/decryption functionality.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `EncryptDecryptTest` | Round-trip encrypt/decrypt of a ~1 KB string; asserts decrypted output matches original |
|
||||
| `EncryptDecryptLargeFileTest` | Round-trip encrypt/decrypt of a ~400 MB generated file; compares SHA-256 hashes of original and decrypted files |
|
||||
|
||||
## Internal Logic
|
||||
- **EncryptDecryptTest**: creates a key via `Security.GetApiEncryptionKey`, encrypts a test string to a `MemoryStream`, decrypts back, compares with `FluentAssertions`.
|
||||
- **EncryptDecryptLargeFileTest**: generates a large JSON file (4M numbers chunked), encrypts, decrypts to a new file, compares file hashes via `SHA256.HashDataAsync`.
|
||||
|
||||
Private helpers:
|
||||
- `CompareFiles` — SHA-256 hash comparison of two files
|
||||
- `CreateLargeFile` — generates a large file by serializing number dictionaries in 100K chunks
|
||||
- `StringToStream` — converts a UTF-8 string to a `MemoryStream`
|
||||
|
||||
## Dependencies
|
||||
- `Security` (encrypt/decrypt)
|
||||
- `StreamExtensions.ConvertToString`
|
||||
- `FluentAssertions`
|
||||
- `Newtonsoft.Json`
|
||||
- xUnit
|
||||
|
||||
## Consumers
|
||||
None — test module.
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
None.
|
||||
|
||||
## External Integrations
|
||||
Local filesystem (creates/deletes `large.txt` and `large_decrypted.txt` during large file test).
|
||||
|
||||
## Security
|
||||
None.
|
||||
|
||||
## Tests
|
||||
This IS the test module.
|
||||
@@ -1,39 +0,0 @@
|
||||
# Module: Azaion.Test.UserServiceTest
|
||||
|
||||
## Purpose
|
||||
xUnit integration test for `UserService.CheckHardwareHash` against a live PostgreSQL database.
|
||||
|
||||
## Public Interface
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `CheckHardwareHashTest` | Looks up a known user by email, then calls `CheckHardwareHash` with a hardware fingerprint string |
|
||||
|
||||
## Internal Logic
|
||||
- Creates a `DbFactory` with hardcoded connection strings pointing to a remote PostgreSQL instance.
|
||||
- Creates a `UserService` with that factory and a fresh `MemoryCache`.
|
||||
- Fetches user `spielberg@azaion.com`, then calls `CheckHardwareHash` with a specific hardware string.
|
||||
- No assertion — the test only verifies no exception is thrown.
|
||||
|
||||
## Dependencies
|
||||
- `UserService`, `DbFactory`, `MemoryCache`
|
||||
- `ConnectionStrings`, `OptionsWrapper`
|
||||
- xUnit
|
||||
|
||||
## Consumers
|
||||
None — test module.
|
||||
|
||||
## Data Models
|
||||
None.
|
||||
|
||||
## Configuration
|
||||
Hardcoded connection strings to `188.245.120.247:4312` (remote database).
|
||||
|
||||
## External Integrations
|
||||
Live PostgreSQL database (remote server).
|
||||
|
||||
## Security
|
||||
Contains hardcoded database credentials in source code. This is a security concern — credentials should be in test configuration or environment variables.
|
||||
|
||||
## Tests
|
||||
This IS the test module.
|
||||
@@ -1,6 +1,8 @@
|
||||
# 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
|
||||
|
||||
@@ -8,10 +10,10 @@
|
||||
|---|-----------|---------|-------------------|-------------|
|
||||
| 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 |
|
||||
| ~~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 | Admin API, Auth, 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 |
|
||||
@@ -23,10 +25,8 @@
|
||||
|------|-----------|-----------------|
|
||||
| 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) |
|
||||
| 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) |
|
||||
|
||||
@@ -112,48 +112,9 @@ sequenceDiagram
|
||||
|
||||
---
|
||||
|
||||
## Flow F3: Encrypted Resource Download
|
||||
## Flow F3: Encrypted Resource Download — REMOVED (cycle 2, 2026-05-14)
|
||||
|
||||
> **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 |
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -195,34 +156,9 @@ sequenceDiagram
|
||||
|
||||
---
|
||||
|
||||
## Flow F6: Installer Download
|
||||
## Flow F6: Installer Download — REMOVED (cycle 2, 2026-05-14)
|
||||
|
||||
### 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)
|
||||
```
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -184,51 +184,17 @@
|
||||
|
||||
---
|
||||
|
||||
### FT-P-09: Download Encrypted Resource
|
||||
### FT-P-09: Download Encrypted Resource — OBSOLETE (cycle 2, 2026-05-14)
|
||||
|
||||
**Summary**: Authenticated user downloads an encrypted resource file.
|
||||
**Traces to**: AC-14, AC-18
|
||||
**Category**: Resource Distribution
|
||||
The `POST /resources/get/{dataFolder?}` endpoint, the `Security.GetApiEncryptionKey` / `EncryptTo` helpers, the `ResourcesService.GetEncryptedResource` method, the `GetResourceRequest` DTO, and the e2e tests `Encrypted_download_returns_octet_stream_and_non_empty_body` (in `ResourceTests.cs`) and `Per_user_encryption_produces_distinct_ciphertext_for_same_file` (in `SecurityTests.cs`) were all removed. The endpoint now returns 404 — verified by FT-N-16 below.
|
||||
|
||||
**Preconditions**:
|
||||
- User authenticated, hardware bound, resource file uploaded
|
||||
|
||||
**Input data**: `{"password":"validpwd1","hardware":"test-hw-001","fileName":"test.txt"}`
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | POST /resources/get with credentials | HTTP 200, Content-Type: application/octet-stream, non-empty body |
|
||||
|
||||
**Expected outcome**: HTTP 200 with encrypted binary content
|
||||
**Max execution time**: 10s
|
||||
ID retained for traceability stability; do not regenerate the spec body until a full `/test-spec` rerun.
|
||||
|
||||
---
|
||||
|
||||
### FT-P-10: Encryption Round-Trip Verification
|
||||
### FT-P-10: Encryption Round-Trip Verification — OBSOLETE (cycle 2, 2026-05-14)
|
||||
|
||||
**Summary**: Downloaded encrypted resource decrypts to original file content.
|
||||
**Traces to**: AC-15, AC-19
|
||||
**Category**: Resource Distribution
|
||||
|
||||
**Preconditions**:
|
||||
- Known file uploaded, user credentials known
|
||||
|
||||
**Input data**: Original file content, user email, password, hardware hash
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | Upload known file | HTTP 200 |
|
||||
| 2 | Download encrypted file via API | HTTP 200, encrypted bytes |
|
||||
| 3 | Derive AES key from email + password + hwHash | Key bytes |
|
||||
| 4 | Decrypt downloaded content with derived key | Decrypted bytes |
|
||||
| 5 | Compare decrypted bytes with original | Byte-level equality |
|
||||
|
||||
**Expected outcome**: Decrypted content matches original file exactly
|
||||
**Max execution time**: 10s
|
||||
Same removal as FT-P-09. Additionally `Security.DecryptTo` and the e2e test `Encryption_round_trip_decrypt_matches_original_bytes` (in `ResourceTests.cs`) are gone. ID retained for traceability stability.
|
||||
|
||||
---
|
||||
|
||||
@@ -487,12 +453,38 @@ The following legacy entries describe behaviour removed by AZ-197 (admin-side ha
|
||||
- FT-P-04 (First Hardware Check Stores Fingerprint) — superseded; the `POST /resources/check` endpoint and the hardware-store side-effect were removed.
|
||||
- FT-P-05 (Subsequent Hardware Check Matches) — superseded; same endpoint removed.
|
||||
- FT-N-06 (Hardware Mismatch) — superseded; the `HardwareIdMismatch` / error code 40 path no longer exists in `ExceptionEnum`.
|
||||
- FT-P-09 / FT-P-10 wire shape — the `hardware` field on `POST /resources/get/{dataFolder}` is no longer required; the encryption key is now derived from `email + password` only. The tests still pass without the field; do not regenerate spec bodies until a full `/test-spec` rerun.
|
||||
- FT-P-09 / FT-P-10 — fully obsolete after the cycle-2 cleanup; the endpoint, support code, and corresponding e2e tests are gone (see the FT-P-09 / FT-P-10 stubs above and FT-N-16 below).
|
||||
|
||||
See `_docs/03_implementation/batch_06_report.md` for the full AZ-197 implementation rationale and the wire-compat policy decision (drop entirely).
|
||||
|
||||
---
|
||||
|
||||
### Cycle-2 Cleanup (2026-05-14) — Obsolete Resource Endpoints Removed
|
||||
|
||||
#### FT-N-16: Removed Resource Endpoints Return 404
|
||||
|
||||
**Summary**: After the cycle-2 cleanup, the three obsolete resource endpoints are no longer routed and return 404.
|
||||
**Traces to**: Cycle-2 AC-1, Cycle-2 AC-2, Cycle-2 AC-3
|
||||
**Category**: Negative — Removed Endpoints
|
||||
|
||||
**Preconditions**:
|
||||
- Caller authenticated as any user (404 must precede any auth check, since the route is gone)
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | `POST /resources/get` (with or without body) | HTTP 404 |
|
||||
| 2 | `POST /resources/get/somefolder` | HTTP 404 |
|
||||
| 3 | `GET /resources/get-installer` | HTTP 404 |
|
||||
| 4 | `GET /resources/get-installer/stage` | HTTP 404 |
|
||||
|
||||
**Expected outcome**: each request returns HTTP 404 (not 401, not 405); no `Security.GetApiEncryptionKey` / `EncryptTo` invocation observable in logs.
|
||||
|
||||
**Notes**: this is a parallel to FT-N-15 (which covers the AZ-197 endpoint removals). Together they enumerate every route that has been retired in cycles 1 and 2.
|
||||
|
||||
---
|
||||
|
||||
### Detection Classes CRUD (AZ-513)
|
||||
|
||||
#### FT-P-14: POST /classes Creates Detection Class
|
||||
|
||||
@@ -10,13 +10,15 @@
|
||||
| Step | Consumer Action | Expected Response |
|
||||
|------|----------------|------------------|
|
||||
| 1 | GET /users (no JWT) | HTTP 401 |
|
||||
| 2 | POST /resources/get (no JWT) | HTTP 401 |
|
||||
| 3 | POST /resources/check (no JWT) | HTTP 401 |
|
||||
| 4 | GET /resources/get-installer (no JWT) | HTTP 401 |
|
||||
| 5 | PUT /users/role (no JWT) | HTTP 401 |
|
||||
| 6 | DELETE /users (no JWT) | HTTP 401 |
|
||||
| 2 | POST /resources/{folder} upload (no JWT) | HTTP 401 |
|
||||
| 3 | GET /resources/list/{folder} (no JWT) | HTTP 401 |
|
||||
| 4 | PUT /users/{email}/set-role/{role} (no JWT) | HTTP 401 |
|
||||
| 5 | DELETE /users/{email} (no JWT) | HTTP 401 |
|
||||
| 6 | POST /classes (no JWT) | HTTP 401 |
|
||||
|
||||
**Pass criteria**: All endpoints return HTTP 401 for unauthenticated requests
|
||||
**Pass criteria**: All remaining protected endpoints return HTTP 401 for unauthenticated requests.
|
||||
|
||||
> Earlier revisions of this scenario also covered `POST /resources/get`, `POST /resources/check`, and `GET /resources/get-installer`. Those endpoints were removed (AZ-197 / cycle 2) and now return 404 — see FT-N-15 (AZ-197 routes) and FT-N-16 (cycle-2 routes) in `blackbox-tests.md`.
|
||||
|
||||
---
|
||||
|
||||
@@ -71,21 +73,9 @@
|
||||
|
||||
---
|
||||
|
||||
### NFT-SEC-05: Encryption Key Uniqueness
|
||||
### NFT-SEC-05: Encryption Key Uniqueness — OBSOLETE (cycle 2, 2026-05-14)
|
||||
|
||||
**Summary**: Different users produce different encryption keys for the same resource.
|
||||
**Traces to**: AC-19
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected Response |
|
||||
|------|----------------|------------------|
|
||||
| 1 | Upload test file | HTTP 200 |
|
||||
| 2 | Download encrypted file as User A | Encrypted bytes A |
|
||||
| 3 | Download same file as User B (different credentials + hardware) | Encrypted bytes B |
|
||||
| 4 | Compare encrypted bytes A and B | Different |
|
||||
|
||||
**Pass criteria**: Encrypted outputs differ between users
|
||||
The `POST /resources/get/{dataFolder?}` endpoint that this test exercised was removed along with `Security.GetApiEncryptionKey` / `EncryptTo` / `DecryptTo` and `ResourcesService.GetEncryptedResource`. Per-user resource encryption is no longer part of the system. ID retained for traceability stability; do not regenerate the spec body until a full `/test-spec` rerun.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -43,8 +43,9 @@
|
||||
|----------|-----------|---------|-------------|-----------|
|
||||
| Acceptance Criteria (baseline) | 19 | 19 | 0 | 100% |
|
||||
| Acceptance Criteria (cycle 1) | 24 | 24 | 0 | 100% |
|
||||
| Acceptance Criteria (cycle 2) | 6 | 6 | 0 | 100% |
|
||||
| Restrictions | 8 | 5 | 3 | 63% |
|
||||
| **Total** | **51** | **48** | **3** | **94%** |
|
||||
| **Total** | **57** | **54** | **3** | **95%** |
|
||||
|
||||
## Uncovered Items Analysis
|
||||
|
||||
@@ -118,3 +119,21 @@ The matrix rows below are kept for ID stability but no longer reflect production
|
||||
| AC-11 (Subsequent hardware check validates) | Obsoleted by AZ-197 — endpoint removed |
|
||||
| AC-12 (Hardware mismatch returns code 40) | Obsoleted by AZ-197 — `ExceptionEnum` value removed |
|
||||
| AC-19 (Encryption key derived from email+password+hw) | Partially obsoleted — derivation is now `email + password` only |
|
||||
|
||||
## Cycle 2 Cleanup (2026-05-14) — Obsolete Resource Endpoints Removed
|
||||
|
||||
The encrypted-download and installer-download endpoints were removed as obsolete. Affected matrix rows below are kept for ID stability but the underlying behaviour is gone; they are superseded by FT-N-16 in `blackbox-tests.md`.
|
||||
|
||||
| Removed surface | Endpoint(s) | Affected legacy entries | Status |
|
||||
|-----------------|-------------|-------------------------|--------|
|
||||
| Per-user encrypted resource download | `POST /resources/get/{dataFolder?}` | AC-14 (AES-256-CBC encryption), AC-15 (round-trip), AC-19 (key derivation), FT-P-09, FT-P-10 | **Reverted** — endpoint deleted; `Security.GetApiEncryptionKey` / `EncryptTo` / `DecryptTo` and `ResourcesService.GetEncryptedResource` deleted; `GetResourceRequest` DTO deleted; e2e tests `Encrypted_download_returns_octet_stream_and_non_empty_body` and `Encryption_round_trip_decrypt_matches_original_bytes` deleted from `ResourceTests.cs`; e2e test `Per_user_encryption_produces_distinct_ciphertext_for_same_file` deleted from `SecurityTests.cs`; `Azaion.Test/SecurityTest.cs` deleted (and the now-empty `Azaion.Test` project removed from the solution). |
|
||||
| Installer download (production + staging) | `GET /resources/get-installer`, `GET /resources/get-installer/stage` | AC-23 (latest installer), `ResourcesConfig.SuiteInstallerFolder` / `SuiteStageInstallerFolder` references | **Reverted** — endpoints deleted; `ResourcesService.GetInstaller` deleted; both config properties removed from `appsettings.json`, `.env.example`, `secrets/staging.public.env`, `secrets/production.public.env`, and `docker-compose.test.yml`. No e2e tests had been written for these endpoints, so no tests required removal. |
|
||||
|
||||
| AC ID | Acceptance Criterion | Test IDs | Coverage |
|
||||
|-------|---------------------|----------|----------|
|
||||
| Cycle-2 AC-1 | `POST /resources/get/{dataFolder?}` returns 404 | FT-N-16 | Covered |
|
||||
| Cycle-2 AC-2 | `GET /resources/get-installer` returns 404 | FT-N-16 | Covered |
|
||||
| Cycle-2 AC-3 | `GET /resources/get-installer/stage` returns 404 | FT-N-16 | Covered |
|
||||
| Cycle-2 AC-4 | `ExceptionEnum` no longer carries `WrongResourceName` (50); the gap is preserved | — | Build/CI invariant — verified by enum read |
|
||||
| Cycle-2 AC-5 | `Azaion.Test` project no longer in solution; build is clean | — | Build invariant — `dotnet build Azaion.AdminApi.sln` clean post-cleanup |
|
||||
| Cycle-2 AC-6 | E2E suite passes after the test deletions above | All e2e tests | Covered by Step 11 Run Tests post-cleanup (2026-05-14) |
|
||||
|
||||
Reference in New Issue
Block a user