From 946287365cac80c34280be183153ad8c95e55cfc Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:23:18 +0200 Subject: [PATCH 01/12] feat: partition rate limiting by forwarded client IP behind the proxy --- api/Varde.Api/Program.cs | 18 ++++++ .../Integration/ForwardedHeadersTests.cs | 58 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 api/Varde.Tests/Integration/ForwardedHeadersTests.cs diff --git a/api/Varde.Api/Program.cs b/api/Varde.Api/Program.cs index 14d6ff6..d26fef7 100644 --- a/api/Varde.Api/Program.cs +++ b/api/Varde.Api/Program.cs @@ -1,4 +1,5 @@ using System.Threading.RateLimiting; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.RateLimiting; using Microsoft.EntityFrameworkCore; using Varde.Core.Interfaces; @@ -53,6 +54,23 @@ var app = builder.Build(); +// First in the pipeline, in every environment. App Service terminates TLS and proxies plain +// HTTP to Kestrel, so X-Forwarded-Proto must be applied before UseHttpsRedirection (else +// production redirect-loops) and X-Forwarded-For before the rate limiter (else every visitor +// shares one bucket). KnownNetworks/KnownProxies are cleared because App Service's proxy +// addresses are not enumerable. ForwardLimit stays at 1: App Service APPENDS the real client +// IP, so the right-most entry is the trustworthy one — reading deeper into the chain would +// let clients choose their own rate-limit bucket. Enabled in dev too: there is no proxy +// there, so a spoofed header only mis-partitions a local limiter, and unconditional +// enablement keeps WebApplicationFactory tests in their default Development environment. +var forwardedHeaders = new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, +}; +forwardedHeaders.KnownNetworks.Clear(); +forwardedHeaders.KnownProxies.Clear(); +app.UseForwardedHeaders(forwardedHeaders); + app.UseExceptionHandler(); app.UseCors(CorsPolicy); diff --git a/api/Varde.Tests/Integration/ForwardedHeadersTests.cs b/api/Varde.Tests/Integration/ForwardedHeadersTests.cs new file mode 100644 index 0000000..3d2f122 --- /dev/null +++ b/api/Varde.Tests/Integration/ForwardedHeadersTests.cs @@ -0,0 +1,58 @@ +using System.Net; +using Varde.Tests.Infrastructure; + +namespace Varde.Tests.Integration; + +public class ForwardedHeadersTests +{ + private static HttpRequestMessage Get(string forwardedFor) + { + var request = new HttpRequestMessage(HttpMethod.Get, "/api/resources"); + request.Headers.Add("X-Forwarded-For", forwardedFor); + return request; + } + + [Fact] + public async Task Rate_limit_buckets_partition_by_forwarded_client_ip() + { + using var factory = new VardeApiFactory { RateLimitPermitLimit = 3 }; + var client = factory.CreateClient(); + + for (var i = 0; i < 3; i++) + { + var allowed = await client.SendAsync(Get("203.0.113.10")); + Assert.Equal(HttpStatusCode.OK, allowed.StatusCode); + } + + var exhausted = await client.SendAsync(Get("203.0.113.10")); + Assert.Equal(HttpStatusCode.TooManyRequests, exhausted.StatusCode); + + // A different forwarded identity gets its own bucket — this is the assert that fails + // today, because without the middleware every request shares the "unknown" partition. + var otherIdentity = await client.SendAsync(Get("203.0.113.99")); + Assert.Equal(HttpStatusCode.OK, otherIdentity.StatusCode); + } + + [Fact] + public async Task Only_the_rightmost_forwarded_entry_names_the_bucket() + { + // App Service APPENDS the real client IP to any client-supplied X-Forwarded-For, so + // with ForwardLimit = 1 the right-most entry wins and spoofed prefixes are ignored. + using var factory = new VardeApiFactory { RateLimitPermitLimit = 3 }; + var client = factory.CreateClient(); + + for (var i = 0; i < 3; i++) + { + var allowed = await client.SendAsync(Get($"198.51.100.{i}, 203.0.113.10")); + Assert.Equal(HttpStatusCode.OK, allowed.StatusCode); + } + + // Same spoofed prefix style, different right-most hop: different bucket, still 200. + var realOther = await client.SendAsync(Get("203.0.113.10, 198.51.100.77")); + Assert.Equal(HttpStatusCode.OK, realOther.StatusCode); + + // Right-most hop 203.0.113.10 again: that bucket is exhausted regardless of prefix. + var exhausted = await client.SendAsync(Get("198.51.100.200, 203.0.113.10")); + Assert.Equal(HttpStatusCode.TooManyRequests, exhausted.StatusCode); + } +} From 9b0ad77ae85b190515a56919f99fd3aeec0cef63 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:26:53 +0200 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20use=20KnownIPNetworks=20=E2=80=94?= =?UTF-8?q?=20KnownNetworks=20is=20obsolete=20in=20.NET=2010?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Varde.Api/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/Varde.Api/Program.cs b/api/Varde.Api/Program.cs index d26fef7..d20c49c 100644 --- a/api/Varde.Api/Program.cs +++ b/api/Varde.Api/Program.cs @@ -57,7 +57,7 @@ // First in the pipeline, in every environment. App Service terminates TLS and proxies plain // HTTP to Kestrel, so X-Forwarded-Proto must be applied before UseHttpsRedirection (else // production redirect-loops) and X-Forwarded-For before the rate limiter (else every visitor -// shares one bucket). KnownNetworks/KnownProxies are cleared because App Service's proxy +// shares one bucket). KnownIPNetworks/KnownProxies are cleared because App Service's proxy // addresses are not enumerable. ForwardLimit stays at 1: App Service APPENDS the real client // IP, so the right-most entry is the trustworthy one — reading deeper into the chain would // let clients choose their own rate-limit bucket. Enabled in dev too: there is no proxy @@ -67,7 +67,7 @@ { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, }; -forwardedHeaders.KnownNetworks.Clear(); +forwardedHeaders.KnownIPNetworks.Clear(); forwardedHeaders.KnownProxies.Clear(); app.UseForwardedHeaders(forwardedHeaders); From 8c7da984916f119d345da5cbc3cdbc2e432c83e3 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:30:00 +0200 Subject: [PATCH 03/12] feat: apply migrations at startup in every environment --- api/Varde.Api/Program.cs | 14 +++++---- .../Infrastructure/VardeApiFactory.cs | 13 ++++++-- .../Integration/ProductionStartupTests.cs | 31 +++++++++++++++++++ 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 api/Varde.Tests/Integration/ProductionStartupTests.cs diff --git a/api/Varde.Api/Program.cs b/api/Varde.Api/Program.cs index d20c49c..a800051 100644 --- a/api/Varde.Api/Program.cs +++ b/api/Varde.Api/Program.cs @@ -76,14 +76,16 @@ app.UseCors(CorsPolicy); app.UseRateLimiter(); -if (app.Environment.IsDevelopment()) +// Schema comes from migrations, always — never EnsureCreated. Runs in every environment: +// production Neon fills itself at deploy (schema + seed rows live in the migrations), and +// a failed migration blocks startup, which is the safe failure. +using (var scope = app.Services.CreateScope()) { - // Schema comes from migrations, always — never EnsureCreated. - using (var scope = app.Services.CreateScope()) - { - scope.ServiceProvider.GetRequiredService().Database.Migrate(); - } + scope.ServiceProvider.GetRequiredService().Database.Migrate(); +} +if (app.Environment.IsDevelopment()) +{ app.MapOpenApi(); // JSON spec at /openapi/v1.json — dev only } else diff --git a/api/Varde.Tests/Infrastructure/VardeApiFactory.cs b/api/Varde.Tests/Infrastructure/VardeApiFactory.cs index d5fe112..2120545 100644 --- a/api/Varde.Tests/Infrastructure/VardeApiFactory.cs +++ b/api/Varde.Tests/Infrastructure/VardeApiFactory.cs @@ -12,7 +12,7 @@ namespace Varde.Tests.Infrastructure; /// /// Boots the real app against a throwaway PostgreSQL database on the local/CI server. Each factory /// instance creates its OWN empty database and drops it on dispose, so every test that news up a -/// factory gets full isolation. The app applies migrations on startup in Development. +/// factory gets full isolation. The app applies migrations on startup in every environment. /// Create one per test — `using var factory = new VardeApiFactory();` — rather than sharing a /// class fixture, or data from one test leaks into the next. /// @@ -34,13 +34,20 @@ public sealed class VardeApiFactory : WebApplicationFactory /// public bool KeepSeedData { get; init; } + /// + /// Host environment for this test's app instance. Production hides OpenAPI and enables + /// HTTPS redirection (inert under TestServer — no https port is configured, so the + /// middleware skips redirecting); migrations run in every environment. + /// + public string Environment { get; init; } = "Development"; + /// Every log message the app wrote during this test. public CapturingLoggerProvider Logs { get; } = new(); protected override void ConfigureWebHost(IWebHostBuilder builder) { - // Program.cs's Development branch applies migrations and maps OpenAPI; tests need the former. - builder.UseEnvironment("Development"); + // Program.cs applies migrations at startup in every environment; OpenAPI stays dev-only. + builder.UseEnvironment(Environment); // Touching TestDatabase runs its static constructor (stale-database cleanup) exactly once. using (var admin = new NpgsqlConnection(TestDatabase.AdminConnectionString)) diff --git a/api/Varde.Tests/Integration/ProductionStartupTests.cs b/api/Varde.Tests/Integration/ProductionStartupTests.cs new file mode 100644 index 0000000..9b2ec17 --- /dev/null +++ b/api/Varde.Tests/Integration/ProductionStartupTests.cs @@ -0,0 +1,31 @@ +using System.Net; +using Varde.Tests.Infrastructure; + +namespace Varde.Tests.Integration; + +public class ProductionStartupTests +{ + [Fact] + public async Task Production_startup_applies_migrations_and_seed() + { + // KeepSeedData: this test asserts the migrated seed is queryable, so don't truncate. + using var factory = new VardeApiFactory { Environment = "Production", KeepSeedData = true }; + var client = factory.CreateClient(); + + var response = await client.GetAsync("/api/categories"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task Production_does_not_expose_openapi() + { + // Guard, not new behavior: MapOpenApi stays inside the Development branch. + using var factory = new VardeApiFactory { Environment = "Production", KeepSeedData = true }; + var client = factory.CreateClient(); + + var response = await client.GetAsync("/openapi/v1.json"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} From ef6efc5372157ad8f74943651e9e031bcc445cc1 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:34:44 +0200 Subject: [PATCH 04/12] feat: add Static Web Apps config and extract theme-init for a hash-free CSP --- web/index.html | 11 +++-------- web/public/staticwebapp.config.json | 11 +++++++++++ web/public/theme-init.js | 5 +++++ 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 web/public/staticwebapp.config.json create mode 100644 web/public/theme-init.js diff --git a/web/index.html b/web/index.html index 788ce45..6ee3dd7 100644 --- a/web/index.html +++ b/web/index.html @@ -10,14 +10,9 @@ Varde - - + + diff --git a/web/public/staticwebapp.config.json b/web/public/staticwebapp.config.json new file mode 100644 index 0000000..0476f17 --- /dev/null +++ b/web/public/staticwebapp.config.json @@ -0,0 +1,11 @@ +{ + "navigationFallback": { + "rewrite": "/index.html", + "exclude": ["/assets/*", "/theme-init.js", "/favicon.ico"] + }, + "globalHeaders": { + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + "Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self' __API_ORIGIN__; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" + } +} diff --git a/web/public/theme-init.js b/web/public/theme-init.js new file mode 100644 index 0000000..a6a53d5 --- /dev/null +++ b/web/public/theme-init.js @@ -0,0 +1,5 @@ +(() => { + var root = document.documentElement + root.dataset.theme = localStorage.getItem("theme") || "dark" + root.dataset.palette = localStorage.getItem("palette") || "default" +})() From 517bdaab66ba8fff61d2466af88cafb61097d4b2 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:38:38 +0200 Subject: [PATCH 05/12] ci: run both test suites on every pull request --- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..04537e3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + pull_request: + +jobs: + # Job keys are the required-check contexts in the protect-main ruleset — do not rename. + api-tests: + runs-on: ubuntu-latest + services: + # Matches TestDatabase.cs's default connection string, so no VARDE_TEST_PG is needed. + # Throwaway credentials for a job-local container; they secure nothing. + postgres: + image: postgres:17 + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + - name: Test + run: dotnet test api/Varde.slnx + + web-tests: + runs-on: ubuntu-latest + defaults: + run: + working-directory: web + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: web/package-lock.json + - name: Install + run: npm ci + - name: Biome + run: npx biome ci . + - name: Test + run: npm test + - name: Build + run: npm run build From 90baa85157a897c4f04d9f023089da76bb302958 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:38:39 +0200 Subject: [PATCH 06/12] ci: deploy API to App Service on merge to main (OIDC) --- .github/workflows/deploy-api.yml | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/deploy-api.yml diff --git a/.github/workflows/deploy-api.yml b/.github/workflows/deploy-api.yml new file mode 100644 index 0000000..01f5d7e --- /dev/null +++ b/.github/workflows/deploy-api.yml @@ -0,0 +1,61 @@ +name: Deploy API + +on: + push: + branches: [main] + paths: + - "api/**" + - ".github/workflows/deploy-api.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:17 + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + - name: Test + run: dotnet test api/Varde.slnx + + deploy: + needs: test + runs-on: ubuntu-latest + environment: production + permissions: + # OIDC: the job requests a GitHub-signed token; Azure trusts it via the federated + # credential on the app registration. No stored Azure secret exists. + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + - name: Publish + run: dotnet publish api/Varde.Api/Varde.Api.csproj -c Release -o publish + - uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - uses: azure/webapps-deploy@v3 + with: + app-name: ${{ vars.API_APP_NAME }} + package: publish From 5e5bc3a88d1ed4eff419ed5a60d9dc75588139fe Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:38:41 +0200 Subject: [PATCH 07/12] ci: deploy web to Static Web Apps on merge to main --- .github/workflows/deploy-web.yml | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/deploy-web.yml diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml new file mode 100644 index 0000000..5c2191a --- /dev/null +++ b/.github/workflows/deploy-web.yml @@ -0,0 +1,62 @@ +name: Deploy Web + +on: + push: + branches: [main] + paths: + - "web/**" + - ".github/workflows/deploy-web.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: web + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: web/package-lock.json + - name: Install + run: npm ci + - name: Biome + run: npx biome ci . + - name: Test + run: npm test + + deploy: + needs: test + runs-on: ubuntu-latest + environment: production + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: web/package-lock.json + - name: Install + working-directory: web + run: npm ci + - name: Build + working-directory: web + env: + VITE_API_URL: ${{ vars.API_URL }} + run: npm run build + - name: Inject API origin into the CSP + # The repo holds only the __API_ORIGIN__ placeholder; the real hostname lives in + # the API_URL environment variable and lands in dist/ only, never in git. + run: sed -i "s|__API_ORIGIN__|${{ vars.API_URL }}|g" web/dist/staticwebapp.config.json + - uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} + action: upload + app_location: web/dist + skip_app_build: true From 3c8b8214f321615825be0fd9387edd7a21322177 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Wed, 19 Aug 2026 23:42:45 +0200 Subject: [PATCH 08/12] docs: document the deployment pipeline in the README --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 6cea272..1fb9e8e 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,29 @@ Tests create disposable `varde_test_` databases. The connection defaults t standard local development setup (`localhost`, `postgres`/`postgres`); override it with the `VARDE_TEST_PG` environment variable. The web dev server expects the API at `http://localhost:5005` by default (`VITE_API_URL` to override). + +## Deployment + +Varde deploys automatically on merge to `main`: the frontend to **Azure Static Web Apps** +(Free), the API to **Azure App Service** (F1, Linux, Germany West Central), the database on +**Neon** (PostgreSQL 17, Frankfurt, `nb-NO` ICU collation). Schema and seed data arrive via +EF Core migrations at API startup — nothing is hand-built in the database. + +Three GitHub Actions workflows drive it: + +| Workflow | Trigger | Does | +|---|---|---| +| `ci.yml` | every pull request | both test suites + web build — the required merge checks | +| `deploy-api.yml` | push to `main` touching `api/**` | re-test, then deploy to App Service via OIDC | +| `deploy-web.yml` | push to `main` touching `web/**` | re-test, build with the real API origin, deploy to SWA | + +Deploy credentials live in the GitHub `production` environment: secrets `AZURE_CLIENT_ID`, +`AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID` (OIDC federated login — no stored password), +`AZURE_STATIC_WEB_APPS_API_TOKEN`, and variables `API_APP_NAME` and `API_URL`. The repo +itself contains no hostnames or secrets; `staticwebapp.config.json` carries an +`__API_ORIGIN__` placeholder replaced at deploy time. + +By design there is no Application Insights and HTTP logging is off — see the privacy posture +in `docs/superpowers/specs/2026-08-12-varde-design.md`. The full deployment design, including +the first-deploy runbook and verification checklist, is +`docs/superpowers/specs/2026-08-19-varde-deploy-design.md`. From 9d987e323d761a5e57b3cab4bfa7fec8c8f0a06f Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Thu, 20 Aug 2026 07:18:18 +0200 Subject: [PATCH 09/12] docs: add HTTPS Only step to the deploy runbook --- docs/superpowers/specs/2026-08-19-varde-deploy-design.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/superpowers/specs/2026-08-19-varde-deploy-design.md b/docs/superpowers/specs/2026-08-19-varde-deploy-design.md index 88d0eac..d8aa603 100644 --- a/docs/superpowers/specs/2026-08-19-varde-deploy-design.md +++ b/docs/superpowers/specs/2026-08-19-varde-deploy-design.md @@ -203,7 +203,10 @@ One-time sequence; portal/psql steps are Malin's, repo steps land via PR: Pass connection string's **database name changes from the default `neondb` to `varde`** — update the stored entry, or the app would migrate the wrong, mis-collated database. 4. Create `rg-varde`, App Service plan + Web App; set the `ConnectionStrings__VardeDb` app - setting from Proton Pass. + setting from Proton Pass. Enable **HTTPS Only** on the Web App: on App Service Linux the + container listens on HTTP only, so `UseHttpsRedirection` cannot determine the redirect + port and silently serves plain-HTTP requests — the platform-level setting is what + actually enforces HTTPS. 5. Create the Static Web App; store its deployment token as a `production` environment secret; put its hostname into the `Cors__AllowedOrigins__0` app setting. Everything Azure now exists **before** any workflow fires — the first deploys cannot fail on missing @@ -226,6 +229,7 @@ All checks run against the **live** site: - [ ] Rate limiter live: a curl burst past the window limit returns 429. - [ ] Collation: psql spot-check that seeded names order æ/ø/å correctly. - [ ] Deep link to a sub-path loads the app (SWA fallback working). +- [ ] `http://` API URL redirects to `https://` (HTTPS Only enforced at the platform). - [ ] Cold-start behavior observed once and noted in the README if user-visible. - [ ] Frontend error state confirmed against an unreachable API (F1 quota exhaustion returns a platform 403; cold starts take 3–10 s) — a user in crisis must see the app's error From c1383b6ac82ae06f7528fd54a23cf3e21ee25d30 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Thu, 20 Aug 2026 07:18:22 +0200 Subject: [PATCH 10/12] ci: fail web deploy fast when API_URL is unset --- .github/workflows/deploy-web.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index 5c2191a..971ac16 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -45,6 +45,10 @@ jobs: - name: Install working-directory: web run: npm ci + - name: Require API_URL + env: + API_URL: ${{ vars.API_URL }} + run: test -n "$API_URL" || { echo "vars.API_URL is not set - refusing to build a broken bundle"; exit 1; } - name: Build working-directory: web env: @@ -53,7 +57,9 @@ jobs: - name: Inject API origin into the CSP # The repo holds only the __API_ORIGIN__ placeholder; the real hostname lives in # the API_URL environment variable and lands in dist/ only, never in git. - run: sed -i "s|__API_ORIGIN__|${{ vars.API_URL }}|g" web/dist/staticwebapp.config.json + env: + API_URL: ${{ vars.API_URL }} + run: sed -i "s|__API_ORIGIN__|$API_URL|g" web/dist/staticwebapp.config.json - uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} From 70a2ba0b90c8858668b3cf78f354dbb4eed7c76f Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Thu, 20 Aug 2026 07:38:54 +0200 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20never=20log=20SQL=20command=20text?= =?UTF-8?q?=20at=20Information=20=E2=80=94=20privacy=20posture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Varde.Api/appsettings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/Varde.Api/appsettings.json b/api/Varde.Api/appsettings.json index ae3abf7..bcf7abd 100644 --- a/api/Varde.Api/appsettings.json +++ b/api/Varde.Api/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore.Database.Command": "Warning" } }, "AllowedHosts": "*", From e6dee47d17b72d5d1405666c3f9975503ffbc082 Mon Sep 17 00:00:00 2001 From: Malin Fossum Date: Thu, 20 Aug 2026 07:39:36 +0200 Subject: [PATCH 12/12] fix: format theme-init.js to Biome style --- web/public/theme-init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/public/theme-init.js b/web/public/theme-init.js index a6a53d5..0ab1c02 100644 --- a/web/public/theme-init.js +++ b/web/public/theme-init.js @@ -1,4 +1,4 @@ -(() => { +;(() => { var root = document.documentElement root.dataset.theme = localStorage.getItem("theme") || "dark" root.dataset.palette = localStorage.getItem("palette") || "default"