Public Access
101 lines
4.5 KiB
C#
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);
|