Files
missions/_docs/02_document/modules/program.md
T
Oleksandr Bezdieniezhnykh 7025f4d075 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.
2026-05-14 19:48:25 +03:00

6.1 KiB

Module: Program (composition root) + GlobalUsings

Files (2): Program.cs, GlobalUsings.cs

NOTE (forward-looking): post-rename. Today's source has Azaion.Flights namespace and dotnet Azaion.Flights.dll entrypoint. Renames + DLL/image changes tracked under Jira AZ-EPIC children B5 (namespace), B7 (drop GPS policy), B10 (Dockerfile + docker image rename).

Purpose

Top-level statements that build the ASP.NET Core web host: read environment, register DI services (DB connection, services, auth, CORS, MVC, Swagger), run the migrator once, then app.Run().

GlobalUsings.cs adds three project-wide global using directives so individual files don't need to repeat them:

global using LinqToDB;
global using LinqToDB.Async;
global using LinqToDB.Data;

Public Interface

Program.cs is a top-level program -- it is not a class with a public surface. Its observable contract is the resulting HTTP server:

  • Listens on the Kestrel-default URL (typically http://0.0.0.0:8080 in container per Dockerfile EXPOSE 8080).
  • Exposes routes mapped by MapControllers (see controller_vehicles.md, controller_missions.md) plus GET /health.
  • Serves Swagger UI at the default /swagger route (not gated on environment).

Internal Logic

1. WebApplicationBuilder = WebApplication.CreateBuilder(args)
2. Resolve DATABASE_URL (Configuration -> Env -> fallback)
   If it begins with "postgresql://" -> ConvertPostgresUrl() to Npgsql key=value form.
3. Resolve JWT_SECRET (Configuration -> Env -> fallback)
4. Register services (scoped where applicable):
   - AppDataConnection <- scoped, built via new DataOptions().UsePostgreSQL(connectionString)
   - MissionService, WaypointService, VehicleService <- scoped
   - AddJwtAuth(jwtSecret)  -> JWT bearer + "FL" policy
   - AddCors with default policy = AllowAnyOrigin/Method/Header
   - AddControllers, AddEndpointsApiExplorer, AddSwaggerGen
5. Build the WebApplication.
6. Open a temp scope, resolve AppDataConnection, call DatabaseMigrator.Migrate(db).
7. Configure pipeline (order matters):
   a. UseMiddleware<ErrorHandlingMiddleware>
   b. UseCors
   c. UseAuthentication
   d. UseAuthorization
   e. UseSwagger, UseSwaggerUI
   f. MapControllers
   g. MapGet("/health", () => Results.Ok({status:"healthy"}))
8. app.Run()

ConvertPostgresUrl(url):
   parses postgresql://user[:pass]@host[:port]/db into
   "Host={host};Port={port};Database={db};Username={user};Password={pass}"
   (defaults port to 5432; absent password becomes empty)

Dependencies

  • All internal namespaces: Azaion.Missions.{Auth, Database, Middleware, Services}.
  • ASP.NET Core, LinqToDB, Npgsql, Swashbuckle, JWT bearer (NuGet).

Consumers

  • The container runtime (ENTRYPOINT ["dotnet", "Azaion.Missions.dll"] in Dockerfile after B10).
  • dotnet run for local development.

Configuration

Env / Config Key Required? Default
DATABASE_URL No (has dev fallback) Host=localhost;Database=azaion;Username=postgres;Password=changeme
JWT_SECRET No (has dev fallback) development-secret-key-min-32-chars!!
AZAION_REVISION Set by Dockerfile from CI_COMMIT_SHA unknown (build arg default)

There is no appsettings.json in this repo (per discovery) -- config comes from env / process variables only. Suite-wide env conventions live in ../../suite/_docs/00_top_level_architecture.md (Edge compose excerpt).

External Integrations

  • PostgreSQL (read/write) via Npgsql.
  • Identity provider: the suite's admin service mints JWTs against the central user PostgreSQL; JWT_SECRET is the shared HMAC secret. Local validation only -- no network round-trip per request.

Security

  • Hardcoded fallbacks for both DATABASE_URL and JWT_SECRET are dev-only. Production deployments MUST override them; failure to do so silently runs with weak/known credentials.
  • CORS is permissive in all environments (AllowAnyOrigin/Method/Header). Combined with JWT auth this is not catastrophic (browser will send the bearer token only if the front-end opts in), but exposes the API to opportunistic browser-based scraping.
  • Swagger is unconditionally enabled -- both the JSON document and the UI are served regardless of environment. Anyone reaching the host can enumerate the API surface.
  • No HTTPS redirection middleware (UseHttpsRedirection) -- TLS is assumed to terminate at an upstream reverse proxy.
  • app.UseMiddleware<ErrorHandlingMiddleware> runs before UseAuthentication/UseAuthorization -- auth failures still emit the framework's stock 401/403 (which is fine), but any auth-stage exceptions ALSO run through the global handler (which converts KeyNotFoundException -> 404, etc.; auth pipeline doesn't typically throw those).

Tests

None present.

Notes / Smells

  1. DATABASE_URL URL parsing: ConvertPostgresUrl is a small ad-hoc parser. Fine for typical cases but does not URL-decode the user/password. A password containing @, :, /, % would break parsing or be interpreted wrong. Carry to verification log.
  2. No IsDevelopment() checks anywhere in Program.cs. Dev/prod behaviors (Swagger, fallback secrets) are not gated.
  3. AddSwaggerGen() with no JWT bearer security definition -- Swagger UI's "Authorize" button won't appear; users must supply tokens via curl -H "Authorization: Bearer ...". Not a bug, but a usability issue.
  4. DatabaseMigrator.Migrate is fire-and-forget -- if it throws (DB down at startup), the host process crashes. Acceptable for container orchestration that restarts on failure.
  5. GlobalUsings.cs imports LinqToDB.Async but most async LINQ extensions used by the project (AnyAsync, FirstOrDefaultAsync, ToListAsync, etc.) actually live in the LinqToDB namespace already. Harmless redundancy.
  6. Service lifetime: AppDataConnection is scoped (per-HTTP-request) -- correct, because DataConnection holds a backing Npgsql connection that should not be shared across requests. The three domain services share this scope, so all DB calls within one request go through the same physical connection (good for correctness, no implicit transactions though).