using System.Net; using Customer.Orders.Bff.Application.Adapters; using Customer.Orders.Bff.Application.Handlers; using Customer.Orders.Bff.Contracts.Requests; using Microsoft.Extensions.Primitives; const string CorrelationHeaderName = "x-correlation-id"; const string SessionAccessCookieName = "thalos_session"; const string SessionRefreshCookieName = "thalos_refresh"; var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHttpClient("ThalosAuth"); var app = builder.Build(); app.Use(async (context, next) => { var correlationId = ResolveCorrelationId(context); context.Items[CorrelationHeaderName] = correlationId; context.Request.Headers[CorrelationHeaderName] = correlationId; context.Response.Headers[CorrelationHeaderName] = correlationId; await next(); }); app.MapGet("/api/customer/orders/status", async ( string contextId, HttpContext context, IHttpClientFactory httpClientFactory, IConfiguration configuration, IGetCustomerOrderStatusHandler handler, CancellationToken ct) => { var authError = await EnforceSessionAsync(context, httpClientFactory, configuration, ct); if (authError is not null) { return authError; } var request = new GetCustomerOrderStatusRequest(contextId); return Results.Ok(await handler.HandleAsync(request, ct)); }); app.MapPost("/api/customer/orders", async ( SubmitCustomerOrderRequest request, HttpContext context, IHttpClientFactory httpClientFactory, IConfiguration configuration, ISubmitCustomerOrderHandler handler, CancellationToken ct) => { var authError = await EnforceSessionAsync(context, httpClientFactory, configuration, ct); if (authError is not null) { return authError; } return Results.Ok(await handler.HandleAsync(request, ct)); }); app.MapGet("/health", () => Results.Ok(new { status = "ok", service = "customer-orders-bff" })); app.MapGet("/healthz", () => Results.Ok(new { status = "ok", service = "customer-orders-bff" })); app.Run(); string ResolveCorrelationId(HttpContext context) { if (context.Items.TryGetValue(CorrelationHeaderName, out var itemValue) && itemValue is string itemCorrelationId && !string.IsNullOrWhiteSpace(itemCorrelationId)) { return itemCorrelationId; } if (context.Request.Headers.TryGetValue(CorrelationHeaderName, out var headerValue) && !StringValues.IsNullOrEmpty(headerValue)) { return headerValue.ToString(); } return context.TraceIdentifier; } async Task EnforceSessionAsync( HttpContext context, IHttpClientFactory httpClientFactory, IConfiguration configuration, CancellationToken ct) { var correlationId = ResolveCorrelationId(context); if (!context.Request.Cookies.ContainsKey(SessionAccessCookieName) && !context.Request.Cookies.ContainsKey(SessionRefreshCookieName)) { return ErrorResponse(StatusCodes.Status401Unauthorized, "session_missing", "No active session.", correlationId); } var thalosBaseAddress = configuration["ThalosAuth:BaseAddress"] ?? "http://thalos-bff:8080"; using var request = new HttpRequestMessage( HttpMethod.Get, $"{thalosBaseAddress.TrimEnd('/')}/api/identity/session/me"); request.Headers.TryAddWithoutValidation(CorrelationHeaderName, correlationId); var cookieHeader = BuildForwardCookieHeader(context); if (!string.IsNullOrWhiteSpace(cookieHeader)) { request.Headers.TryAddWithoutValidation("Cookie", cookieHeader); } using var response = await httpClientFactory.CreateClient("ThalosAuth").SendAsync(request, ct); if (response.StatusCode == HttpStatusCode.Forbidden) { return ErrorResponse(StatusCodes.Status403Forbidden, "forbidden", "Permission denied.", correlationId); } if (response.StatusCode == HttpStatusCode.Unauthorized) { return ErrorResponse(StatusCodes.Status401Unauthorized, "unauthorized", "Unauthorized request.", correlationId); } if (!response.IsSuccessStatusCode) { return ErrorResponse(StatusCodes.Status401Unauthorized, "session_invalid", "Session validation failed.", correlationId); } return null; } static string BuildForwardCookieHeader(HttpContext context) { var cookies = new List(); if (context.Request.Cookies.TryGetValue(SessionAccessCookieName, out var accessCookie) && !string.IsNullOrWhiteSpace(accessCookie)) { cookies.Add($"{SessionAccessCookieName}={accessCookie}"); } if (context.Request.Cookies.TryGetValue(SessionRefreshCookieName, out var refreshCookie) && !string.IsNullOrWhiteSpace(refreshCookie)) { cookies.Add($"{SessionRefreshCookieName}={refreshCookie}"); } return string.Join("; ", cookies); } static IResult ErrorResponse(int statusCode, string code, string message, string correlationId) { return Results.Json(new AuthErrorResponse(code, message, correlationId), statusCode: statusCode); } sealed record AuthErrorResponse(string Code, string Message, string CorrelationId);