diff --git a/Core/Config/AppDomainOptions.cs b/Core/Config/AppDomainOptions.cs index 57d1e95..d140333 100644 --- a/Core/Config/AppDomainOptions.cs +++ b/Core/Config/AppDomainOptions.cs @@ -26,4 +26,11 @@ public class AppDomainOptions return domain + "/" + path.TrimStart('/'); } + + /// + /// The configured domain as an absolute base (scheme + host [+ port]), or + /// null when no domain is set or it can't be parsed. + /// + public Uri? GetBaseUri() => + BuildUrl("/") is { } url && Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri : null; } diff --git a/Program.cs b/Program.cs index b2dcfcf..fe10c37 100644 --- a/Program.cs +++ b/Program.cs @@ -309,6 +309,23 @@ var app = builder.Build(); // Must run before anything that inspects the request scheme/IP (auth, OIDC, cookies). app.UseForwardedHeaders(); +// When App__Domain is set, rewrite every request's scheme + host to the public domain. The +// framework builds absolute URLs (the cookie login redirect, the OIDC redirect_uri, …) from +// Request.Scheme/Host; behind a proxy that doesn't forward the Host header these are the +// internal host (server IP:port), so loading https:/// would 302 to http://:8080. +// Forcing the host here keeps every generated URL on the public domain. Must run before auth. +var publicBaseUri = appDomain.GetBaseUri(); +if (publicBaseUri is not null) +{ + var publicHost = HostString.FromUriComponent(publicBaseUri); + app.Use((context, next) => + { + context.Request.Scheme = publicBaseUri.Scheme; + context.Request.Host = publicHost; + return next(context); + }); +} + // ── First-run bootstrap ─────────────────────────────────────────────────────── // Seed a local admin when no users exist yet, so a plain-HTTP / LAN deployment that // can't use Microsoft OIDC (which requires HTTPS + a matching Entra redirect URI) can diff --git a/README.md b/README.md index 91000e9..80642e8 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ These are separate and registered on **different** Entra apps. Don't conflate th > **HTTPS note.** The sign-in app is a confidential (Web) client, so Entra requires its `/signin-oidc` redirect URI to be **HTTPS** — plain HTTP is allowed only for `http://localhost`, not a LAN host/IP. To run OIDC on a plain-HTTP LAN deployment, put the app behind an HTTPS-terminating reverse proxy: register `https://your-host/signin-oidc`, and the app honours `X-Forwarded-Proto` (see `UseForwardedHeaders`) to build the correct `https` redirect. Without a proxy, OIDC sign-in won't work over a non-localhost HTTP host — use the local email/password login instead. +> **Reverse-proxy host.** Set `App__Domain` so the app builds every redirect (cookie login, OIDC) against the public domain regardless of what host the proxy forwards. Without it, a proxy that doesn't forward the `Host` header makes the app 302 to the internal `IP:port` it actually received. + Persistent state (profiles, settings, templates, logs, exports, certs) lives in `DataFolder`. ## Installation — Docker (prebuilt image)