Files
missions/_docs/02_document/modules/middleware.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

4.3 KiB

Module: Azaion.Missions.Middleware

NOTE (forward-looking): post-rename namespace. Today's source still lives under Azaion.Flights.Middleware. Renames tracked under Jira AZ-EPIC child B5.

Files (1): Middleware/ErrorHandlingMiddleware.cs

Purpose

Global exception → JSON error response mapper. Wraps the rest of the request pipeline and converts a fixed set of exception types to specific HTTP status codes; everything else becomes a 500.

Public Interface

public class ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger) {
    public Task Invoke(HttpContext context);
}

Standard ASP.NET Core middleware shape (primary-constructor variant).

Internal Logic

try { await next(context); }
catch (KeyNotFoundException     ex) → 404 NotFound,            body: { statusCode, message = ex.Message }
catch (ArgumentException        ex) → 400 BadRequest,          body: { statusCode, message = ex.Message }
catch (InvalidOperationException ex) → 409 Conflict,           body: { statusCode, message = ex.Message }
catch (Exception                 ex) → 500 InternalServerError, body: { statusCode, message = "Internal server error" }
                                       PLUS: logger.LogError(ex, "Unhandled exception");

Body is serialized via JsonSerializer.Serialize(new { statusCode, message }) — an anonymous object literal, NOT the DTOs.ErrorResponse type. The anonymous-type property names are written lowercase-first in code (statusCode, message); System.Text.Json preserves them as-is when no JsonNamingPolicy is configured, so the wire shape is {"statusCode":..., "message":"..."}camelCase by accidental match with the suite spec. (The unused DTOs.ErrorResponse type, by contrast, declares PascalCase properties and would serialize PascalCase if it were ever used directly.)

Content-Type is set to application/json. Response body is written via WriteAsync (string).

Dependencies

  • System.Net (HttpStatusCode)
  • System.Text.Json (JsonSerializer)
  • ASP.NET Core middleware abstractions (transitive via Microsoft.NET.Sdk.Web)
  • Microsoft.Extensions.Logging.ILogger<T> (transitive)

No internal dependencies.

Consumers

  • Program.csapp.UseMiddleware<ErrorHandlingMiddleware>(); is called BEFORE UseCors, UseAuthentication, UseAuthorization, UseSwagger, UseSwaggerUI, MapControllers.

Configuration / External Integrations / Security

None.

Tests

None present.

Notes / Smells

  1. DTOs.ErrorResponse is unused here — middleware writes an anonymous object instead. Partial spec divergence: ../../suite/_docs/00_top_level_architecture.md § Error Response Format mandates { "statusCode", "message", "errors": object? } (camelCase, errors is an object of per-field arrays). The middleware's anonymous-object output IS already camelCase on case ({"statusCode":..., "message":"..."}) but missing the errors field; the static ErrorResponse DTO has the wrong shape (List<string>? Errors instead of object?) and is dead code.
  2. Entity / DTO body case-style is PascalCase across the rest of the API (controller responses serialize entities and PaginatedResponse<T> via System.Text.Json defaults with no naming policy override). The error envelope's accidental camelCase match documented in point 1 does NOT extend to those responses — see architecture.md ADR-002.
  3. InvalidOperationException → 409 Conflict is a non-standard mapping. The .NET BCL throws this for many "wrong state at the moment" conditions; in this codebase it is used by VehicleService.DeleteVehicle to report "vehicle is referenced by missions" (a true 409). But any third-party library throwing InvalidOperationException for an unrelated reason would also surface as 409, masking the real cause.
  4. Generic 500 swallows the message (good for security — no internal detail leaked) and logs the exception (good for diagnostics). No correlation ID is included in the response, so production support has to grep logs by timestamp.
  5. Order of catches matters: ArgumentException is base of ArgumentNullException / ArgumentOutOfRangeException, so those also become 400 (correct). InvalidOperationException ahead of generic Exception is correct.