diff --git a/Dockerfile b/Dockerfile index f58ae1c..296618d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,15 @@ -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base +# Base images pinned to exact patch for reproducible builds. Floating `:10.0` tags +# drift; a stale/pre-GA SDK base silently drops the Blazor framework static assets +# (blazor.web.js) from the publish manifest → 404 in production. Bump deliberately. +FROM mcr.microsoft.com/dotnet/aspnet:10.0.8 AS base WORKDIR /app EXPOSE 8080 +# curl for the compose healthcheck (aspnet image ships no wget/curl). +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0.300 AS build WORKDIR /src COPY ["SharepointToolbox.Web.csproj", "."] RUN dotnet restore diff --git a/Program.cs b/Program.cs index 9019fb8..5b45224 100644 --- a/Program.cs +++ b/Program.cs @@ -43,6 +43,15 @@ builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddHttpContextAccessor(); +// ── Data Protection ─────────────────────────────────────────────────────────── +// Keys MUST persist across container recreates: they encrypt the auth cookie AND +// the app-only certs on disk (/data/appcerts). Default storage is the container's +// ephemeral home dir, so a redeploy would log everyone out and make stored certs +// undecryptable. Pin keys + app name to the data volume. +builder.Services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(dataFolder, "dpkeys"))) + .SetApplicationName("SharepointToolbox.Web"); + // Localization string source — Scoped: one per circuit, with its own explicit culture. builder.Services.AddScoped(); @@ -65,8 +74,13 @@ else { builder.Services.AddAuthentication(options => { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + // Challenge the cookie scheme (→ redirect to /account/login, the combined + // local + Microsoft page). OIDC is triggered explicitly from the "Sign in + // with Microsoft" button (/account/login/entra), never as the implicit + // challenge — otherwise logged-out hits on protected pages force OIDC and + // 404 when it is unconfigured/unreachable. + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(options => { diff --git a/docker-compose.yml b/docker-compose.yml index 1fe0b40..6b69790 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,10 @@ services: - DataFolder=/data restart: unless-stopped healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/"] + # /account/login is anonymous and returns 200 (the app root now 302-redirects + # unauthenticated users, which would read as unhealthy). curl is installed in + # the image; -f fails on >=400. + test: ["CMD", "curl", "-fsS", "http://localhost:8080/account/login"] interval: 30s timeout: 10s retries: 3