refactor: enhance JWT authentication and CORS configuration

Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 19:48:25 +03:00
parent 2fe394d732
commit 7025f4d075
74 changed files with 8494 additions and 19 deletions
@@ -0,0 +1,241 @@
# Input Data Parameters — Azaion.Missions
> **Status**: derived-from-code (autodev `/document` Step 6, 2026-05-14).
> Schemas below match the actual `Database/Entities/*.cs` LinqToDB mappings and `DTOs/*.cs` request shapes (post-B6 names). Today's source still uses pre-rename names; the doc-vs-code mapping is in `_docs/02_document/04_verification_log.md` § 0.
---
## 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 |
**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).
## 2. HTTP request DTOs (post-B6 shapes)
### 2.1 Vehicle (`/vehicles`)
```csharp
public class CreateVehicleRequest {
public VehicleType Type { get; set; } // enum int: Plane=0, Copter=1, UGV=2, GuidedMissile=3
public string Model { get; set; } = "";
public string Name { get; set; } = "";
public FuelType FuelType { get; set; } // enum int: Electric=0, Gasoline=1, Diesel=2
public decimal BatteryCapacity { get; set; }
public decimal EngineConsumption { get; set; }
public decimal EngineConsumptionIdle { get; set; }
public bool IsDefault { get; set; }
}
public class UpdateVehicleRequest { // all properties nullable -- partial update
public VehicleType? Type;
public string? Model;
public string? Name;
public FuelType? FuelType;
public decimal? BatteryCapacity;
public decimal? EngineConsumption;
public decimal? EngineConsumptionIdle;
public bool? IsDefault;
}
public class GetVehiclesQuery {
public string? Name { get; set; } // case-sensitive contains
public bool? IsDefault { get; set; } // exact match
}
public class SetDefaultRequest {
public bool IsDefault { get; set; }
}
```
**Validation**: NONE today. No `[Required]`, no `[Range]`, no min-length. Empty `Name`, negative `BatteryCapacity`, out-of-range enum int values are accepted. Carry-forward improvement.
### 2.2 Mission (`/missions`)
```csharp
public class CreateMissionRequest {
public Guid VehicleId { get; set; }
public string Name { get; set; } = "";
public DateTime? CreatedDate { get; set; } // defaults to UtcNow if null
}
public class UpdateMissionRequest { // partial update
public string? Name { get; set; }
public Guid? VehicleId { get; set; }
}
public class GetMissionsQuery {
public string? Name { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
```
**Validation**: existence check on `VehicleId` (returns 400 today via `ArgumentException`; spec wants 404 — carry-forward divergence). No bounds on `Page` / `PageSize` (negative or huge values accepted by binding).
### 2.3 Waypoint (`/missions/{id}/waypoints`)
```csharp
public class GeoPoint { // shared value object; all fields nullable
public decimal? Lat { get; set; }
public decimal? Lon { get; set; }
public string? Mgrs { get; set; } // Military Grid Reference System
}
public class CreateWaypointRequest {
public GeoPoint? GeoPoint { get; set; } // nullable: all-null is accepted today (no invariant)
public WaypointSource WaypointSource { get; set; } // enum int
public WaypointObjective WaypointObjective { get; set; } // enum int
public int OrderNum { get; set; }
public decimal Height { get; set; }
}
public class UpdateWaypointRequest { // identical SHAPE to Create -- non-nullable enums/numerics
public GeoPoint? GeoPoint { get; set; }
public WaypointSource WaypointSource { get; set; }
public WaypointObjective WaypointObjective { get; set; }
public int OrderNum { get; set; }
public decimal Height { get; set; }
}
```
**Validation**: NONE. No min-length, no enum range check, no `Lat`/`Lon` bounds, no MGRS format validation. `GeoPoint` may be all-null. **`UpdateWaypoint` is structurally NOT partial** — every field gets overwritten on PUT (inconsistent with vehicle's partial-update pattern).
**Spec divergence (Geopoint)**: spec stores `Waypoints.GPS` as a single `string GPS` field with `Lat <-> MGRS` auto-conversion (`../../suite/_docs/02_missions.md`, `../../suite/_docs/00_database_schema.md`). Code stores 3 separate columns with NO conversion. Carry-forward.
## 3. Persisted data — owned tables (post-B7+B9)
### 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) |
### 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 |
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 |
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 | |
Index: `ix_map_objects_mission_id` on `mission_id`.
`autopilot` is the writer (per `../../suite/_docs/06_autopilot_design.md`); this service owns the schema and cascade-deletes only.
## 4. Persisted data — borrowed read-only stubs
| Table | Schema owner | This service uses for |
|-------|--------------|------------------------|
| `media` | `annotations` (per `../../suite/_docs/01_annotations.md`) | id resolution + cascade-delete walk on mission/waypoint delete |
| `annotations` | `annotations` | id resolution + cascade-delete walk |
| `detection` (singular by upstream owner) | Detection pipeline | cascade-delete walk |
Stub schemas (just enough to query / delete by id):
```csharp
[Table("media")] public class Media { [PrimaryKey, Column("id")] public string Id = ""; [Column("waypoint_id")] public Guid? WaypointId; }
[Table("annotations")] public class Annotation { [PrimaryKey, Column("id")] public string Id = ""; [Column("media_id")] public string MediaId = ""; }
[Table("detection")] public class Detection { [PrimaryKey, Column("id")] public Guid Id; [Column("annotation_id")] public string AnnotationId = ""; }
```
Migrations for these tables are owned by the respective sibling services. If they have not migrated on a given device, this service's cascade-delete walk fails on `relation does not exist` (abnormal deployment).
## 5. Removed in B7 (post-B7+B9 schema)
These tables and entities are **out of this repo**; cleanup happens once on legacy devices via the B9 `DROP TABLE IF EXISTS` block in `DatabaseMigrator`:
| Table | Pre-B7 owner | Post-B7 owner |
|-------|--------------|---------------|
| `orthophotos` | this repo (`Orthophoto` entity, 03_gps_denied component) | `gps-denied` service (separate repo) |
| `gps_corrections` | this repo (`GpsCorrection` entity, 03_gps_denied component) | `gps-denied` service |
`gps-denied` references `mission_id` / `waypoint_id` as plain GUIDs in its OWN tables — no runtime coupling, no FK declaration, no cascade by this service.
## 6. Enum values
| Enum | Values | Persisted as | Defined in |
|------|--------|--------------|------------|
| `VehicleType` | `Plane=0`, `Copter=1`, `UGV=2`, `GuidedMissile=3` | INTEGER | `Enums/VehicleType.cs` (post-B6) |
| `FuelType` | `Electric=0`, `Gasoline=1`, `Diesel=2` | INTEGER | `Enums/FuelType.cs` |
| `WaypointSource` | `Operator=0`, `Mission=1`, ... | INTEGER | `Enums/WaypointSource.cs` |
| `WaypointObjective` | `Surveillance=0`, `Strike=1`, ... | INTEGER | `Enums/WaypointObjective.cs` |
| `ObjectStatus` | `Active=0`, `Lost=1`, ... | INTEGER | `Enums/ObjectStatus.cs` (used only by `MapObject`) |
Per `_docs/02_document/modules/enums.md`, integer values are NOT range-validated on input — model binding accepts any int.
## 7. Inbound data shapes (HTTP)
| Endpoint | Method | Body / Query | Returns |
|----------|--------|--------------|---------|
| `/vehicles` | GET | `?name=&isDefault=` | `List<Vehicle>` (PascalCase JSON; not paginated) |
| `/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/{id}` | GET | — | `Mission` |
| `/missions` | POST | `CreateMissionRequest` | `Mission` (created) |
| `/missions/{id}` | PUT | `UpdateMissionRequest` (partial) | `Mission` (updated) |
| `/missions/{id}` | DELETE | — | 204 / 404; runs F3 cascade |
| `/missions/{id}/waypoints` | GET | — | `List<Waypoint>` (unpaginated, ordered by `OrderNum`) |
| `/missions/{id}/waypoints` | POST | `CreateWaypointRequest` | `Waypoint` (created) |
| `/missions/{id}/waypoints/{wpId}` | PUT | `UpdateWaypointRequest` (full overwrite) | `Waypoint` |
| `/missions/{id}/waypoints/{wpId}` | DELETE | — | 204; runs F4 scoped cascade |
| `/health` | GET | — anonymous | `200 { "status": "healthy" }` |
All routes except `/health` require JWT bearer with `permissions=FL` claim.