using DodoSSH.Api.Authorization; using DodoSSH.Api.Features.Events; using DodoSSH.Api.Features.Identity; using DodoSSH.Api.Features.Sync; using DodoSSH.Api.Features.Teams; using DodoSSH.Api.Setup; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; var builder = WebApplication.CreateBuilder(args); // Docker secrets, if mounted. Optional so the same image works with plain environment variables. builder.Configuration.AddKeyPerFile("/run/secrets", optional: true); builder.Configuration.AddEnvironmentVariables(prefix: "DODOSSH_"); builder.Services.AddDodoJson(); builder.Services.AddDodoOptions(); builder.Services.AddDodoPersistence(builder.Configuration); builder.Services.AddDodoAuthentication(); builder.Services.AddDodoEndpoints(); builder.Services.AddDodoOpenApi(); builder.Services.AddDodoHealthChecks(); // DateTime.UtcNow is banned repo-wide (see BannedSymbols.txt); everything takes // TimeProvider so time can be faked in tests. builder.Services.AddSingleton(TimeProvider.System); // FastEndpoints registers this too. Kept because CurrentUserContext needs it on its own merits, and // the registration would be silently lost the day the endpoint framework changed again. builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); // A singleton, because the sockets it holds outlive the requests that opened them. The endpoint needs the // whole hub — admit, remove, count — while the write paths that announce a change need only the two methods // that announce one, and get them from a fan-out that also tells the relay: a user who loses access to a // vault loses their relay sessions into it too. See VaultEventFanOut and RelayAccessWatch. builder.Services.AddSingleton(); builder.Services.AddSingleton(); // ADR 0003's tombstone collection, and the compaction of the change log that goes with it. builder.Services.AddScoped(); builder.Services.AddHostedService(); // The relay's services, its ticket key ring, and a shutdown long enough for its drain. See ADR 0004. builder.Services.AddDodoRelay(); // Scoped rather than the AddAuthorization default of singleton: the handler reads the request's // DbContext, and a singleton would capture one for the lifetime of the process. builder.Services.AddScoped(); builder.Services.AddScoped(); // Replaces the framework default, which is also registered as a singleton. Last registration wins. builder.Services.AddSingleton(); builder.Services.AddProblemDetails(); var app = builder.Build(); // Deliberately no UseHttpsRedirection: the API is always fronted by a reverse proxy // (Caddy in the reference compose stack) which terminates TLS. Redirecting here // produces redirect loops behind a proxy. HTTPS in development comes from the // launch profile instead. app.BlockFastEndpointsRouteTable(); // Before the authentication middleware, because the upgrade handshake has to survive it: the events // endpoint answers an ordinary authenticated request that happens to become a socket, and without // this the upgrade is never offered and the handler sees a plain GET. No allow-list of origins is // configured, deliberately — every client here is a native application sending a bearer token, so // there is no browser origin to trust and nothing a cross-site request could reach without one. app.UseWebSockets(); app.UseAuthentication(); app.UseAuthorization(); app.MapDodoHealthChecks(); app.MapDodoEndpoints(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.WarnOnRiskyConfiguration(); // Before the port opens, not after. See Persistence.MigrateDodoDatabaseAsync. await app.MigrateDodoDatabaseAsync().ConfigureAwait(false); await app.RunAsync().ConfigureAwait(false);