Files
DodoSSH/src/DodoSSH.Api/Program.cs
T

101 lines
4.5 KiB
C#

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<ICurrentUserContext, CurrentUserContext>();
builder.Services.AddScoped<IVaultAccessService, VaultAccessService>();
builder.Services.AddScoped<SyncService>();
builder.Services.AddScoped<IdentityService>();
builder.Services.AddScoped<EnrollmentService>();
builder.Services.AddScoped<DeviceService>();
builder.Services.AddScoped<DirectoryService>();
builder.Services.AddScoped<KeyLogService>();
builder.Services.AddScoped<TeamService>();
builder.Services.AddScoped<VaultGrantService>();
builder.Services.AddScoped<IIdentityBindingVerifier, IdentityBindingVerifier>();
builder.Services.AddSingleton<ICursorKeyProvider, CursorKeyProvider>();
// 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<VaultEventHub>();
builder.Services.AddSingleton<IVaultEventPublisher, VaultEventFanOut>();
// ADR 0003's tombstone collection, and the compaction of the change log that goes with it.
builder.Services.AddScoped<TombstoneCollector>();
builder.Services.AddHostedService<TombstoneCollectionService>();
// 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<IAuthorizationHandler, EnrolledHandler>();
builder.Services.AddScoped<IAuthorizationHandler, ActiveAccountHandler>();
// Replaces the framework default, which is also registered as a singleton. Last registration wins.
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, DodoAuthorizationResultHandler>();
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);