chore: update configuration and Docker setup for JWT and test results
ci/woodpecker/push/build-arm Pipeline was successful

Enhanced the .gitignore to exclude test results and updated the Dockerfile to include a new entrypoint script for improved container initialization. Refactored JWT configuration to support additional parameters for automatic refresh intervals, ensuring better control over token management. Updated the ConfigurationResolver to enforce required environment variables without hardcoded fallbacks, enhancing security and flexibility.
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-15 03:23:23 +03:00
parent 7025f4d075
commit 78dea8ebab
40 changed files with 1990 additions and 510 deletions
+65 -55
View File
@@ -7,14 +7,21 @@
## 1. Configuration input (env vars)
| Variable | Type | Required | Default (dev fallback) | Source order | Format / constraints | Used by |
|----------|------|----------|------------------------|--------------|----------------------|---------|
| `DATABASE_URL` | string | yes (production) | `Host=localhost;Database=azaion;Username=postgres;Password=changeme` | `IConfiguration``Environment.GetEnvironmentVariable` → fallback | Either `postgresql://user:pass@host:port/db` (converted via local helper `ConvertPostgresUrl`) OR a raw Npgsql connection string | `Program.cs` (DI registration of `AppDataConnection`) |
| `JWT_SECRET` | string | yes (production) | `development-secret-key-min-32-chars!!` | same as above | UTF-8 string, ≥32 chars (`SymmetricSecurityKey` accepts shorter but `JwtBearer` HS256 requires ≥32 bytes) | `Program.cs` `AddJwtAuth` |
| `AZAION_REVISION` | string | no | (build-time) | Dockerfile `ARG` baked from `CI_COMMIT_SHA` | git SHA | Dockerfile only; surfaced via `docker inspect` |
| `ASPNETCORE_URLS` | string | no | `http://+:8080` | ASP.NET Core convention | URL list | ASP.NET Core host |
All four required values are resolved through `Infrastructure/ConfigurationResolver.cs``ResolveRequiredOrThrow(envName, configKey)`. Resolution order is **env var first**, then `IConfiguration` config key, else **throw `InvalidOperationException` at startup**. There are NO hardcoded dev fallbacks anymore.
**Important**: ADR-005 carry-forward — neither Swagger UI mounting nor the dev fallbacks for `JWT_SECRET` / `DATABASE_URL` are gated on `IsDevelopment()`. A production deploy without the env vars set will silently boot with the well-known dev secret; tracked at suite level (CMMC L2 row 3, AZ-487 / AZ-494).
| Variable | Config key | Type | Required | Resolution | Format / constraints | Used by |
|----------|------------|------|----------|------------|----------------------|---------|
| `DATABASE_URL` | `Database:Url` | string | yes (always) | `ResolveRequiredOrThrow` | Either `postgresql://user:pass@host:port/db` (converted via local helper `ConvertPostgresUrl`; NO URL-decoding of user/password — credentials with `@`, `:`, `/`, `%` need raw Npgsql form) OR a raw Npgsql connection string | `Program.cs` (DI registration of `AppDataConnection`) |
| `JWT_ISSUER` | `Jwt:Issuer` | string | yes (always) | `ResolveRequiredOrThrow` | The expected `iss` claim value on every accepted JWT; usually the `admin` service's stable identifier | `Program.cs`, `Auth/JwtExtensions.cs``ValidIssuer` |
| `JWT_AUDIENCE` | `Jwt:Audience` | string | yes (always) | `ResolveRequiredOrThrow` | The expected `aud` claim value on every accepted JWT; usually the suite-wide audience identifier shared by all backend validators | `Program.cs`, `Auth/JwtExtensions.cs``ValidAudience` |
| `JWT_JWKS_URL` | `Jwt:JwksUrl` | string | yes (always) | `ResolveRequiredOrThrow` | **HTTPS URL** to `admin`'s JWKS endpoint. `HttpDocumentRetriever { RequireHttps = true }` rejects `http://` at fetch time (not at startup config resolution). Cached via `ConfigurationManager<JsonWebKeySet>`, refreshed on the default schedule | `Program.cs`, `Auth/JwtExtensions.cs` |
| `ASPNETCORE_ENVIRONMENT` | (built-in) | string | no | ASP.NET Core convention | Case-insensitive match on `Production` triggers the CORS strict gate in `CorsConfigurationValidator` | `Program.cs`, `Infrastructure/CorsConfigurationValidator.cs` |
| `CorsConfig:AllowedOrigins` | (config) | string list | conditionally required | `IConfiguration.GetSection("CorsConfig").Get<CorsConfig>()` | List of allowed origins. In `Production`, MUST be non-empty OR `AllowAnyOrigin=true`, else startup throws | `Program.cs`, `Infrastructure/CorsConfigurationValidator.cs` |
| `CorsConfig:AllowAnyOrigin` | (config) | bool | no | same | Opt-in to permissive CORS in production explicitly (use sparingly) | same |
| `AZAION_REVISION` | — | string | no | Dockerfile `ARG` baked from `CI_COMMIT_SHA` | git SHA | Dockerfile only; surfaced via `docker inspect` |
| `ASPNETCORE_URLS` | — | string | no | ASP.NET Core convention | URL list (default `http://+:8080`) | ASP.NET Core host |
**Important**: The legacy `JWT_SECRET` env var is no longer consulted. The ADR-005 "dev fallback secret silently accepted in production" failure mode is structurally eliminated; only the unconditional-Swagger branch of ADR-005 survives.
## 2. HTTP request DTOs (post-B6 shapes)
@@ -44,7 +51,7 @@ public class UpdateVehicleRequest { // all properties nullable
}
public class GetVehiclesQuery {
public string? Name { get; set; } // case-sensitive contains
public string? Name { get; set; } // case-INSENSITIVE contains (LOWER(name) LIKE %lower(input)%)
public bool? IsDefault { get; set; } // exact match
}
@@ -70,11 +77,12 @@ public class UpdateMissionRequest { // partial update
}
public class GetMissionsQuery {
public string? Name { get; set; }
public string? Name { get; set; } // case-INSENSITIVE contains
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
// Results ordered by CreatedDate DESC (newest first).
}
```
@@ -112,65 +120,67 @@ public class UpdateWaypointRequest { // identical SHAPE to Creat
## 3. Persisted data — owned tables (post-B7+B9)
FKs in this section are **declared as DB-level `REFERENCES` constraints** in `DatabaseMigrator.cs`, not just logical. Date columns use PostgreSQL `TIMESTAMP` (no timezone, NOT `TIMESTAMPTZ`) — `DateTime.Kind` is normalized to `Unspecified` on read.
### 3.1 `vehicles` (owned)
| Column | Type | Nullable | Notes |
|--------|------|----------|-------|
| `id` | UUID | NO | primary key |
| `type` | INTEGER | NO | `VehicleType` enum int (Plane / Copter / UGV / GuidedMissile) |
| `model` | TEXT | NO | |
| `name` | TEXT | NO | |
| `fuel_type` | INTEGER | NO | `FuelType` enum int |
| `battery_capacity` | NUMERIC | NO | |
| `engine_consumption` | NUMERIC | NO | |
| `engine_consumption_idle` | NUMERIC | NO | |
| `is_default` | BOOLEAN | NO | "exactly one default" enforced by `VehicleService` (stricter than spec — B12 decision) |
| Column | Type | Nullable | Default | Notes |
|--------|------|----------|---------|-------|
| `id` | UUID | NO | — | primary key |
| `type` | INTEGER | NO | `0` | `VehicleType` enum int (Plane / Copter / UGV / GuidedMissile) |
| `model` | TEXT | NO | — | |
| `name` | TEXT | NO | — | |
| `fuel_type` | INTEGER | NO | `0` | `FuelType` enum int |
| `battery_capacity` | NUMERIC | NO | `0` | |
| `engine_consumption` | NUMERIC | NO | `0` | |
| `engine_consumption_idle` | NUMERIC | NO | `0` | |
| `is_default` | BOOLEAN | NO | `FALSE` | "exactly one default" enforced by `VehicleService` (stricter than spec — B12 decision) |
### 3.2 `missions` (owned)
| Column | Type | Nullable | Notes |
|--------|------|----------|-------|
| `id` | UUID | NO | primary key |
| `created_date` | TIMESTAMPTZ | NO | server-assigned `UtcNow` if not supplied |
| `name` | TEXT | NO | |
| `vehicle_id` | UUID | NO | logical FK to `vehicles.id`; existence-checked in service, no DB-level FK constraint declared in migrator |
| Column | Type | Nullable | Default | Notes |
|--------|------|----------|---------|-------|
| `id` | UUID | NO | — | primary key |
| `created_date` | TIMESTAMP | NO | `NOW()` | server-assigned `UtcNow` if not supplied; `TIMESTAMP` (no timezone) |
| `name` | TEXT | NO | — | |
| `vehicle_id` | UUID | NO | — | `REFERENCES vehicles(id)` — DB-level FK; PostgreSQL error `23503` raised if parent vehicle was deleted between service-layer existence check and insert |
Index: `ix_missions_vehicle_id` on `vehicle_id`.
### 3.3 `waypoints` (owned)
| Column | Type | Nullable | Notes |
|--------|------|----------|-------|
| `id` | UUID | NO | primary key |
| `mission_id` | UUID | NO | logical FK to `missions.id` |
| `lat` | NUMERIC | YES | spec divergence — see § 2.3 |
| `lon` | NUMERIC | YES | spec divergence |
| `mgrs` | TEXT | YES | spec divergence |
| `waypoint_source` | INTEGER | NO | `WaypointSource` enum int |
| `waypoint_objective` | INTEGER | NO | `WaypointObjective` enum int |
| `order_num` | INTEGER | NO | listing order |
| `height` | NUMERIC | NO | metres |
| Column | Type | Nullable | Default | Notes |
|--------|------|----------|---------|-------|
| `id` | UUID | NO | — | primary key |
| `mission_id` | UUID | NO | — | `REFERENCES missions(id)` — DB-level FK |
| `lat` | NUMERIC | YES | — | spec divergence — see § 2.3 |
| `lon` | NUMERIC | YES | — | spec divergence |
| `mgrs` | TEXT | YES | — | spec divergence |
| `waypoint_source` | INTEGER | NO | `0` | `WaypointSource` enum int |
| `waypoint_objective` | INTEGER | NO | `0` | `WaypointObjective` enum int |
| `order_num` | INTEGER | NO | `0` | listing order |
| `height` | NUMERIC | NO | `0` | metres |
Index: `ix_waypoints_mission_id` on `mission_id`.
### 3.4 `map_objects` (owned schema; written by `autopilot`)
| Column | Type | Nullable | Notes |
|--------|------|----------|-------|
| `id` | UUID | NO | primary key |
| `mission_id` | UUID | NO | logical FK to `missions.id` |
| `h3_index` | TEXT | NO | Uber H3 hex grid cell |
| `mgrs` | TEXT | NO | |
| `lat` | NUMERIC | YES | |
| `lon` | NUMERIC | YES | |
| `class_num` | INTEGER | NO | detection class id |
| `label` | TEXT | NO | |
| `size_width_m` | NUMERIC | NO | |
| `size_length_m` | NUMERIC | NO | |
| `confidence` | NUMERIC | NO | 0..1 |
| `object_status` | INTEGER | NO | `ObjectStatus` enum int |
| `first_seen_at` | TIMESTAMPTZ | NO | |
| `last_seen_at` | TIMESTAMPTZ | NO | |
| Column | Type | Nullable | Default | Notes |
|--------|------|----------|---------|-------|
| `id` | UUID | NO | — | primary key |
| `mission_id` | UUID | NO | — | `REFERENCES missions(id)` — DB-level FK |
| `h3_index` | TEXT | NO | — | Uber H3 hex grid cell |
| `mgrs` | TEXT | NO | — | |
| `lat` | NUMERIC | YES | — | |
| `lon` | NUMERIC | YES | — | |
| `class_num` | INTEGER | NO | `0` | detection class id |
| `label` | TEXT | NO | `''` | |
| `size_width_m` | NUMERIC | NO | `0` | |
| `size_length_m` | NUMERIC | NO | `0` | |
| `confidence` | NUMERIC | NO | `0` | 0..1 |
| `object_status` | INTEGER | NO | `0` | `ObjectStatus` enum int |
| `first_seen_at` | TIMESTAMP | NO | `NOW()` | `TIMESTAMP` (no timezone) |
| `last_seen_at` | TIMESTAMP | NO | `NOW()` | `TIMESTAMP` (no timezone) |
Index: `ix_map_objects_mission_id` on `mission_id`.
@@ -221,13 +231,13 @@ Per `_docs/02_document/modules/enums.md`, integer values are NOT range-validated
| Endpoint | Method | Body / Query | Returns |
|----------|--------|--------------|---------|
| `/vehicles` | GET | `?name=&isDefault=` | `List<Vehicle>` (PascalCase JSON; not paginated) |
| `/vehicles` | GET | `?name=&isDefault=` | `List<Vehicle>` (PascalCase JSON; not paginated; ordered by `Name` ASC; `name` filter is case-INSENSITIVE) |
| `/vehicles/{id}` | GET | — | `Vehicle` |
| `/vehicles` | POST | `CreateVehicleRequest` | `Vehicle` (created) |
| `/vehicles/{id}` | PUT | `UpdateVehicleRequest` (partial) | `Vehicle` (updated) |
| `/vehicles/{id}/setDefault` | POST | `SetDefaultRequest` | `Vehicle` |
| `/vehicles/{id}` | DELETE | — | 204 / 409 if referenced |
| `/missions` | GET | `?name=&fromDate=&toDate=&page=&pageSize=` | `PaginatedResponse<Mission>` |
| `/missions` | GET | `?name=&fromDate=&toDate=&page=&pageSize=` | `PaginatedResponse<Mission>` (ordered by `CreatedDate` DESC; `name` filter case-INSENSITIVE) |
| `/missions/{id}` | GET | — | `Mission` |
| `/missions` | POST | `CreateMissionRequest` | `Mission` (created) |
| `/missions/{id}` | PUT | `UpdateMissionRequest` (partial) | `Mission` (updated) |