7d42c85345
Restore the __Host- browser guarantees for the default secure deployment without breaking plaintext/dev: - DashboardServiceCollectionExtensions PostConfigure now resolves the cookie name as: explicit MxGateway:Dashboard:CookieName override wins; else __Host-MxGatewayDashboard when SecurePolicy==Always (RequireHttpsCookie true); else the plain MxGatewayDashboard default. Guard: never apply the __Host- prefix without Secure (browsers silently drop it). - DashboardAuthenticationDefaults: add SecureCookieName const; keep the plain CookieName as the non-secure fallback. - Docs corrected to the actual conditional contract (five stale claims): gateway.md, GatewayProcessDesign.md, ImplementationPlanGateway.md, GatewayDashboardDesign.md, CLAUDE.md; GatewayConfiguration.md phrasing tightened. - Test: DashboardCookieOptionsTests asserts the name flips with RequireHttpsCookie and that an explicit override wins. Server build clean (0 warnings); Dashboard tests 149/149. Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
85 lines
4.1 KiB
C#
85 lines
4.1 KiB
C#
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.MxGateway.Server;
|
|
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
|
|
|
public sealed class DashboardCookieOptionsTests
|
|
{
|
|
/// <summary>
|
|
/// Verifies that the application configures secure dashboard authentication cookies.
|
|
/// With <c>RequireHttpsCookie</c> defaulting to <see langword="true"/> and no explicit
|
|
/// <c>CookieName</c> override, the cookie is named with the <c>__Host-</c> prefix so
|
|
/// browsers enforce the Secure/no-Domain/Path=/ guarantees the prefix promises.
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task Build_ConfiguresSecureDashboardCookie()
|
|
{
|
|
await using WebApplication app = GatewayApplication.Build([]);
|
|
IOptionsMonitor<CookieAuthenticationOptions> optionsMonitor = app.Services
|
|
.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>();
|
|
|
|
CookieAuthenticationOptions options = optionsMonitor.Get(
|
|
DashboardAuthenticationDefaults.AuthenticationScheme);
|
|
|
|
Assert.Equal(DashboardAuthenticationDefaults.SecureCookieName, options.Cookie.Name);
|
|
Assert.True(options.Cookie.HttpOnly);
|
|
Assert.Equal(CookieSecurePolicy.Always, options.Cookie.SecurePolicy);
|
|
Assert.Equal(SameSiteMode.Strict, options.Cookie.SameSite);
|
|
Assert.Equal("/", options.Cookie.Path);
|
|
Assert.Equal("/login", options.LoginPath);
|
|
Assert.Equal("/logout", options.LogoutPath);
|
|
Assert.Equal("/denied", options.AccessDeniedPath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that setting <c>MxGateway:Dashboard:RequireHttpsCookie=false</c>
|
|
/// relaxes the cookie to <see cref="CookieSecurePolicy.SameAsRequest"/> so
|
|
/// the dashboard can be reached over plain HTTP in dev, and that the plain
|
|
/// <see cref="DashboardAuthenticationDefaults.CookieName"/> is used rather than the
|
|
/// <c>__Host-</c> name — a <c>__Host-</c> cookie without a guaranteed Secure flag is
|
|
/// silently dropped by browsers.
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task Build_WithRequireHttpsCookieFalse_UsesSameAsRequestAndPlainName()
|
|
{
|
|
await using WebApplication app = GatewayApplication.Build(
|
|
["--MxGateway:Dashboard:RequireHttpsCookie=false"]);
|
|
IOptionsMonitor<CookieAuthenticationOptions> optionsMonitor = app.Services
|
|
.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>();
|
|
|
|
CookieAuthenticationOptions options = optionsMonitor.Get(
|
|
DashboardAuthenticationDefaults.AuthenticationScheme);
|
|
|
|
Assert.Equal(CookieSecurePolicy.SameAsRequest, options.Cookie.SecurePolicy);
|
|
Assert.Equal(DashboardAuthenticationDefaults.CookieName, options.Cookie.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that an explicit <c>MxGateway:Dashboard:CookieName</c> override wins over the
|
|
/// secure <c>__Host-</c> default (this build leaves <c>RequireHttpsCookie</c> at its
|
|
/// <see langword="true"/> default), so a gateway instance sharing a hostname with another
|
|
/// can be given a distinct name (browser cookies are scoped by host+path, not port).
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task Build_WithCookieNameOverride_UsesConfiguredName()
|
|
{
|
|
await using WebApplication app = GatewayApplication.Build(
|
|
["--MxGateway:Dashboard:CookieName=MxGatewayDashboard.env2"]);
|
|
IOptionsMonitor<CookieAuthenticationOptions> optionsMonitor = app.Services
|
|
.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>();
|
|
|
|
CookieAuthenticationOptions options = optionsMonitor.Get(
|
|
DashboardAuthenticationDefaults.AuthenticationScheme);
|
|
|
|
Assert.Equal("MxGatewayDashboard.env2", options.Cookie.Name);
|
|
}
|
|
}
|