Files
JiriUhlir 09db30a3ca first
2026-06-18 11:05:46 +02:00

117 lines
5.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text.Json.Serialization;
using Microsoft.OpenApi.Models;
using Csob.Client;
using Csob.Configuration;
using Csob.Credentials;
using Csob.Infrastructure;
using Csob.Services;
var builder = WebApplication.CreateBuilder(args);
// Configuration resolved from environment variables (non-secret infra config only).
var settings = new CsobSettings();
builder.Services.AddSingleton(settings);
// The eIDAS client certificate is supplied per request as a Base64 PFX header, which is large.
// Raise Kestrel's header size limit so the certificate header is not rejected.
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestHeadersTotalSize = 1024 * 1024; // 1 MiB
});
builder.Services.AddHttpContextAccessor();
// Builds/caches mutual-TLS HttpClients by certificate thumbprint; shared across requests.
builder.Services.AddSingleton<CsobHttpClientProvider>();
// Per-request credential resolution and API access.
builder.Services.AddScoped<RequestCredentialsProvider>();
builder.Services.AddScoped<CsobApiAccessor>();
// Agenda services.
builder.Services.AddScoped<AccountsService>();
builder.Services.AddScoped<PaymentsService>();
builder.Services.AddScoped<StandingOrdersService>();
builder.Services.AddScoped<DirectDebitsService>();
builder.Services.AddScoped<ConsentsService>();
builder.Services.AddScoped<OAuthService>();
builder.Services
.AddControllers()
.AddJsonOptions(options =>
{
// camelCase (web defaults) matches the COBS JSON contract; omit null properties on write.
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = settings.AppName,
Version = settings.AppVersion,
Description =
"Multi-tenant REST integration with the ČSOB PSD2 (Open Banking) API, following the " +
"Czech Open Banking Standard (COBS). Covers AISP (account information), PISP (payment, " +
"standing-order and direct-debit initiation with the sign/SCA flow), consents and the " +
"OAuth2 Authorization Code helper.\n\n" +
"**Credentials.** This service stores no secrets — every sensitive value is supplied per " +
"request in a header and forwarded to ČSOB over TLS (never the query string or body):\n\n" +
"- `X-CSOB-Certificate` — eIDAS client certificate (QWAC) as a Base64 PFX (mutual TLS). Required.\n" +
"- `X-CSOB-Certificate-Password` — PFX passphrase (optional).\n" +
"- `X-Access-Token` — OAuth2 Bearer access token for the PSU (AISP/PISP/consents). Required.\n" +
"- `X-API-Key` — ČSOB application API key (sent upstream as `APIKEY`). Required.\n" +
"- `X-TPP-Name` — TPP organisation name (sent upstream as `TPP-Name`). Required.\n" +
"- `X-CSOB-Client-Id` / `X-CSOB-Client-Secret` — OAuth2 app credentials (OAuth endpoints only).\n\n" +
"Obtain the access token via the `/oauth/*` endpoints (Authorization Code flow with PSU redirect).",
});
options.OperationFilter<CredentialHeadersOperationFilter>();
var xmlPath = Path.Combine(AppContext.BaseDirectory, $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml");
if (File.Exists(xmlPath))
{
options.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
}
});
var app = builder.Build();
// ROOT_PATH carries the public reverse-proxy prefix (e.g. /apps/csob). AppFactory's Caddy uses
// handle_path, which strips that prefix before the request reaches the container, so PathBase ends
// up empty and the OpenAPI server URL must come from ROOT_PATH directly (see the filter below).
var publicPrefix = string.IsNullOrWhiteSpace(settings.RootPath) ? null : "/" + settings.RootPath.Trim('/');
if (publicPrefix is not null)
{
app.UsePathBase(publicPrefix);
}
app.UseMiddleware<ExceptionHandlingMiddleware>();
// Serve the OpenAPI document under the same /docs prefix as the UI so a relative endpoint resolves
// correctly both locally and behind a reverse-proxy prefix (ROOT_PATH).
app.UseSwagger(options =>
{
options.RouteTemplate = "docs/{documentName}/swagger.json";
// Advertise the public prefix (e.g. /apps/csob) as the OpenAPI server so Swagger UI "Try it out"
// targets {prefix}/accounts, not the host root. The prefix comes from ROOT_PATH because
// handle_path has already stripped it from the request, leaving PathBase empty. Fall back to
// PathBase (a proxy that keeps the prefix) and finally "/".
options.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
var serverUrl = publicPrefix ?? (httpReq.PathBase.HasValue ? httpReq.PathBase.Value : "/");
swaggerDoc.Servers = new List<OpenApiServer> { new() { Url = serverUrl } };
});
});
app.UseSwaggerUI(options =>
{
options.RoutePrefix = "docs";
options.SwaggerEndpoint("v1/swagger.json", $"{settings.AppName} v1");
options.DocumentTitle = $"{settings.AppName} API docs";
});
app.MapControllers();
app.Run();