using System.Net; using System.Net.Http.Json; using Xunit; namespace VolcanicCore.Tests; public class ForwardedHeadersTests : IClassFixture { private readonly WebFactory _f; public ForwardedHeadersTests(WebFactory f) { _f = f; } [Fact] public async Task Login_with_XForwardedProto_https_returns_Secure_cookie() { // AllowAutoRedirect false so we can inspect the raw Set-Cookie header. var client = _f.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false, }); // Simulate Traefik terminating TLS: it forwards over plain HTTP with the // scheme and client IP in headers. client.DefaultRequestHeaders.Add("X-Forwarded-Proto", "https"); client.DefaultRequestHeaders.Add("X-Forwarded-For", "203.0.113.7"); var req = new HttpRequestMessage(HttpMethod.Post, "/api/session") { Content = JsonContent.Create(new { password = _f.AdminPassword }), }; var res = await client.SendAsync(req); Assert.Equal(HttpStatusCode.NoContent, res.StatusCode); // Cookie should be marked Secure because the middleware saw https. var setCookies = res.Headers.GetValues("Set-Cookie").ToList(); var sid = setCookies.FirstOrDefault(c => c.StartsWith("volcaniccore.sid=", StringComparison.Ordinal)); Assert.NotNull(sid); Assert.Contains("secure", sid!, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task Login_without_XForwardedProto_returns_cookie_without_Secure() { // Same request WITHOUT the forwarded header — Request.Scheme stays http, // so CookieSecurePolicy.SameAsRequest omits the Secure flag. This proves // the middleware is doing the work; without it the "https" test above // would still pass because both requests would look like http. var client = _f.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false, }); var req = new HttpRequestMessage(HttpMethod.Post, "/api/session") { Content = JsonContent.Create(new { password = _f.AdminPassword }), }; var res = await client.SendAsync(req); Assert.Equal(HttpStatusCode.NoContent, res.StatusCode); var setCookies = res.Headers.GetValues("Set-Cookie").ToList(); var sid = setCookies.FirstOrDefault(c => c.StartsWith("volcaniccore.sid=", StringComparison.Ordinal)); Assert.NotNull(sid); Assert.DoesNotContain("secure", sid!, StringComparison.OrdinalIgnoreCase); } }