From e5ccdf0b0ade64337fd0e1ad11426ce0cb6ea4b5 Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Wed, 30 Sep 2026 08:26:01 +0530 Subject: [PATCH] docs(changelog): audit retention per tenant, Production SMTP binding, supported base images (#1413, #1414) --- src/content/docs/changelog/index.mdx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx index fa55834c..c3c9bbce 100644 --- a/src/content/docs/changelog/index.mdx +++ b/src/content/docs/changelog/index.mdx @@ -1,6 +1,6 @@ --- title: Overview -lastUpdated: 2026-09-28 +lastUpdated: 2026-09-30 description: Release notes and version history for fullstackhero. sidebar: order: 1 @@ -11,6 +11,12 @@ seo: Notable changes to the kit, newest first. +## 2026-09-30 + +- **Auditing: the retention job now actually purges audit records, in every tenant (fix).** The `auditing-retention` Hangfire job is registered without a tenant, and `AuditRecords` carries the default-on tenant filter, so every run threw a `NullReferenceException` inside the filter and deleted nothing: with `Auditing:Retention:Enabled` on, audit tables still grew without limit. The job now loads every tenant from the tenant store and runs the sweep inside each tenant's context, which also reaches tenants with a dedicated database. A failure in one tenant is logged with its `TenantId` and the other tenants still run. Retention options and batching are unchanged. See [#1413](https://github.com/fullstackhero/dotnet-starter-kit/pull/1413). +- **Production mail settings now bind (fix).** `appsettings.Production.json` put `Host`, `Port`, `UserName` and `Password` directly under `MailOptions` instead of `MailOptions:Smtp`, so they bound to nothing and Production silently inherited the base `smtp.ethereal.email` host. They now sit under `MailOptions:Smtp`, with the port defaulting to `587`. Set `MailOptions__Smtp__Host` (and credentials) for Production; with the blank default, sending fails at send time. See [#1414](https://github.com/fullstackhero/dotnet-starter-kit/pull/1414). +- **Docker: the API and DbMigrator images build on supported base images.** Both Dockerfiles moved from `mcr.microsoft.com/dotnet/nightly/aspnet:10.0-noble-chiseled` to the supported `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`. The SDK container publish (`ContainerFamily`, used by the AWS deploy) stays on full `noble` on purpose. See [#1414](https://github.com/fullstackhero/dotnet-starter-kit/pull/1414). + ## 2026-09-28 - **Identity: a root operator's cross-tenant request no longer fails with a random `401` (fix).** A root operator scopes a request to another tenant with the `tenant` header, and every permission-gated endpoint then checks the operator's permission set through `UserPermissionService`. That set is cached under `perm:u:{userId}`, a key with no tenant, and on a cache miss it was loaded under the tenant the request had just been moved to, where the root user does not exist, so the check threw `UnauthorizedException` and the request answered `401 Authentication failed`. Whether it failed depended only on whether the entry happened to be warm: it expires from the in-process cache every 2 minutes when no Redis is configured, and after an hour, a startup permission sync or any role or group change otherwise. The set is now loaded under the operator's own tenant (from the token's tenant claim), so the request succeeds whether the entry is cold or warm; nothing changes for requests that stay in the caller's tenant. The intermittent `401` seen in `TenantHeaderOverrideTests` in CI matches this failure. The docs and code comments that called Finbuckle's claim strategy a no-op are corrected too: it runs the authentication handler itself, so an authenticated caller always resolves to its own tenant claim and the `tenant` header only decides for anonymous requests. See [#1404](https://github.com/fullstackhero/dotnet-starter-kit/pull/1404).