From 22c33c5388ba33c4338e1906ff65a256260bae00 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 21 Sep 2026 14:38:49 +0100 Subject: [PATCH 1/3] Hide inserted estimates from client list; redirect anon to /signin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Estimates with status Inserted (provider hasn't worked on them yet) were showing up in the client's "Mes devis à valider" list. The asclient endpoint now requires ProviderValidationDate to be set, so only estimates the provider has actually established and submitted are visible to the client. The asprovider endpoint is unchanged (providers still need to see incoming unprocessed requests). Also fix the anonymous-access redirect on Yavsc.Org: the application cookie's LoginPath defaulted to /Account/Login, which has no controller. Anonymous hits to a restricted page now redirect to the real /signin endpoint (with /signout and /Account/AccessDenied set to match the existing controller routes). Co-Authored-By: Claude Code --- .../EstimateApiControllerTests.cs | 28 +++++++++++++++++-- .../Business/EstimateApiController.cs | 8 +++++- .../Smoke/AccountSmokeTests.cs | 25 +++++++++++++++++ src/Yavsc.Org/Extensions/HostingExtensions.cs | 12 ++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/Yavsc.Api.Test/EstimateApiControllerTests.cs b/src/Yavsc.Api.Test/EstimateApiControllerTests.cs index cb945f51a..d8cae0265 100644 --- a/src/Yavsc.Api.Test/EstimateApiControllerTests.cs +++ b/src/Yavsc.Api.Test/EstimateApiControllerTests.cs @@ -209,9 +209,12 @@ public sealed class EstimateApiControllerTests : IClassFixture /// Seeds an estimate owned by alice (the provider) for the given - /// client, optionally already validated by the client. + /// client, optionally already validated by the client. By default the + /// estimate is also provider-validated (i.e. the provider has submitted + /// it); pass = false to seed a + /// freshly inserted request the provider hasn't worked on yet. /// - private long SeedEstimate(string clientId, bool clientValidated) + private long SeedEstimate(string clientId, bool clientValidated, bool providerValidated = true) { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); @@ -229,6 +232,7 @@ public sealed class EstimateApiControllerTests : IClassFixture e.Bill) - .Where(e => e.ClientId == uid && e.ClientValidationDate == default)); + .Where(e => e.ClientId == uid + && e.ProviderValidationDate != default + && e.ClientValidationDate == default)); } // GET: api/estimate/asprovider diff --git a/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs index 61d774c57..5e7ab7776 100644 --- a/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs +++ b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs @@ -1,4 +1,7 @@ +using System.Net; +using System.Net.Http; using IdentityServer8.Stores; +using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -40,4 +43,26 @@ public class AccountSmokeTests : IClassFixture Assert.Null(exception); } + + // Regression: anonymous access to a restricted page used to redirect to + // the ASP.NET Identity default LoginPath "/Account/Login", which has no + // controller here. The application cookie must redirect to the real + // sign-in endpoint at /signin instead. + [Fact] + public async Task Anonymous_access_to_protected_page_redirects_to_signin() + { + var client = _factory.CreateClient(new WebApplicationFactoryClientOptions + { + AllowAutoRedirect = false, + }); + + // EstimateController is [Authorize]; GET /Estimate hits Index(). + var response = await client.GetAsync("/Estimate", TestContext.Current.CancellationToken); + + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + var location = response.Headers.Location; + Assert.NotNull(location); + Assert.Equal("/signin", location.AbsolutePath); + Assert.Contains("ReturnUrl=%2FEstimate", location.Query); + } } diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs index 8c1db2a73..ea035001c 100644 --- a/src/Yavsc.Org/Extensions/HostingExtensions.cs +++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs @@ -215,6 +215,18 @@ public static class HostingExtensions services.AddScoped, UserClaimsPrincipalFactory>(); + // The default application cookie LoginPath is "/Account/Login", which + // has no controller here — anonymous access to a restricted page must + // redirect to the actual sign-in endpoint instead. The paths mirror + // the route attributes on AccountController (Constants.SigninPath etc., + // which carry a "~/" prefix that the cookie middleware does not want). + services.ConfigureApplicationCookie(options => + { + options.LoginPath = "/signin"; + options.LogoutPath = "/signout"; + options.AccessDeniedPath = "/Account/AccessDenied"; + }); + // Dev-only: Chromium rejects SameSite=None without Secure on http:// // (e.g. http://localhost:5000). The default Identity cookie policy // sets SameSite=None, which is invalid without Secure. Force Lax in From e28959b068f7457ac2cf1c98966bcfcba0897397 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 21 Sep 2026 14:39:25 +0100 Subject: [PATCH 2/3] asciidoc => markdown --- src/Yavsc.Org/Views/Home/Privacy.cshtml | 16 ++++++++-------- src/Yavsc.Server/Helpers/ServiceExtensions.cs | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Yavsc.Org/Views/Home/Privacy.cshtml b/src/Yavsc.Org/Views/Home/Privacy.cshtml index e5b285eb2..a9a6e7cf4 100644 --- a/src/Yavsc.Org/Views/Home/Privacy.cshtml +++ b/src/Yavsc.Org/Views/Home/Privacy.cshtml @@ -3,14 +3,14 @@ } -= La confidentialité -:hide-uri-scheme: +# La confidentialité -xref:Contact[Get Report] - -À aucun moment, aucune adresse postale, aucune adresse e-mail ni aucun numéro de téléphone -ne sont transmis à personne. Seul le système et son link:Contact[possesseur] ont accès à ces informations. - -De plus, le droit de retrait est permanent et sa mise en oeuvre link:/Account/Delete[immédiate]. +À aucun moment, aucune adresse postale, +aucune adresse e-mail ni aucun numéro de téléphone +ne sont transmis à personne. Seul le système + et son link:Contact[possesseur] ont accès + à ces informations. +De plus, le droit de retrait est permanent +et sa [mise en oeuvre est immédiate](/Account/Delete). diff --git a/src/Yavsc.Server/Helpers/ServiceExtensions.cs b/src/Yavsc.Server/Helpers/ServiceExtensions.cs index f20d50476..297084c28 100644 --- a/src/Yavsc.Server/Helpers/ServiceExtensions.cs +++ b/src/Yavsc.Server/Helpers/ServiceExtensions.cs @@ -113,6 +113,7 @@ public static class ServiceExtensions options.ClaimsIssuer = authority; options.Audience = audiences[0]; + // Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs, // Yavsc.Org itself) validates JWTs against the OP that runs // on https://localhost:5001 with a self-signed dev cert. From 9aef10f281f7e56b360b18c9289dc7b2ef7cffd5 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 21 Sep 2026 14:58:40 +0100 Subject: [PATCH 3/3] make reinstall, better --- contrib/Makefile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contrib/Makefile b/contrib/Makefile index 4c7e0e6d6..4ba6059df 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -9,12 +9,12 @@ include .$(APP_INSTALL_ENV).env reinstall: copy-binaries @for project in $(APP_PROJECT_NAMES); do \ - echo "🚀 Starting $(APP_INSTALL_PREFIX)yavsc$${project}" \ + echo "🚀 Starting $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; done @echo "💯 backend started" reinstall-prod: - make APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production + make reinstall APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production generated/$(APP_INSTALL_PREFIX)yavscOrg.service: generated/$(APP_INSTALL_PREFIX)yavscBlogs.service: @@ -82,14 +82,16 @@ install: copy-binaries copy-services @sudo systemctl daemon-reload @for project in $(APP_PROJECT_NAMES); \ do \ - sudo systemctl enable yavsc$${project} ; \ - sudo systemctl start yavsc$${project} ; \ + echo "🖒 enable $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ + sudo systemctl enable $(APP_INSTALL_PREFIX)yavsc$${project} ; \ + echo "🚀 start $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ + sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; \ done - stop-services: @for project in $(APP_PROJECT_NAMES); \ do \ + echo "🔥 Stopping $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ sudo systemctl stop $(APP_INSTALL_PREFIX)yavsc$${project} ; \ done