Commit Graph
26 Commits
Author SHA1 Message Date
Sluijsens 9f4ae475e7 U5 — the gate everything else has to pass
continuous_integration.yaml: six blocking checks, then a separate
publish per environment so a Vite build never gets tagged for the
wrong one, then a call into last commit's deploy workflow. Along the
way: the lint list had drifted (two problems not in the requirement,
one already fixed), and the Umami-origin gate needed a variable pair
of its own since the backend's side of that comparison lives on the
host, not in CI. Pinned the two vulnerable packages while at it.
2026-07-28 16:07:03 +02:00
SluijsensandClaude Opus 5 8e79a72340 Makes the application say what it is doing and when it fails
U4. Console logging plus Sentry, a same-origin tunnel so ad blockers cannot
silence browser errors, Umami on the admin SPA, and six security events that
alert rules can actually be built on.

The correlation id is the W3C trace id from the ambient Activity, enabled by
one line of ActivityTrackingOptions so every entry from every category carries
it without touching a call site. It propagates across the master/slave
boundary via traceparent, which TraceIdentifier cannot do at all, and it is
the same value ProblemDetails already returns to the browser.

The security events use source-generated LoggerMessage with constant
templates. Sentry groups log events by message, so interpolating an email
address would give every address its own issue and "more than 20 failed
logins in five minutes" could never fire — the events would arrive, be
visible, be tagged, and the alerting would silently be impossible. A test
asserts the rendered message is identical across argument values.

Scrubbing happens in-process, before transmission, and covers Set-Cookie as
well as Cookie: the login response issues the refreshToken there, so
scrubbing only the request side would protect nothing. Transactions are
scrubbed too, because they carry request data and are the channel nobody
thinks of.

The tunnel derives its destination from the DSN once at startup and reads
nothing from the request, which is what separates a tunnel from a
server-side request forgery primitive. Size is capped by a bounded read
rather than by trusting Content-Length, and the endpoint is rate limited.

Two things found along the way. Zod 4's url() hands the value to the URL
constructor, which accepts any scheme — so the existing frontend validation
would have accepted the exact "htp://" typo BR-U4-24 names, and the SPA
would have called a nonexistent origin. Now constrained to http(s). And the
new appsettings comments are verified against the real configuration
provider, because the failure mode if it rejected them is both hosts
refusing to start after a release switch.

One deviation. IAdminTokenValidator was meant to gain a reason-reporting
overload; implemented that way, a substitute returning false by default
silently inverted the access decision while both methods compiled. Two
methods whose difference is invisible at the call site is the defect, so it
is now a single Validate returning AdminTokenResult.

Touches two files from already-committed units: DatabaseMigrationExtensions
(U2) gains a flush before the rethrow, or the one Critical event in the
system dies with the process; AdminTokenValidator (U1) classifies why a
bypass was refused.

Build 0 errors; 366 backend tests pass, up from 315, and 237 frontend tests,
up from 213. tsc clean, eslint clean on every changed file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
2026-07-28 11:25:54 +02:00
SluijsensandClaude Sonnet 5 38857038a0 Serves public website, admin SPA, and API from a single host
Shared hosting (e.g. mijnhostingpartner.nl) typically allows only one
site/app-pool, so SlpModularCms.Api now serves everything itself:
'/' for the customer's public website (deployed separately, not part
of this repo), '/admin' for the CMS admin SPA, and '/api/v1' for the
API as before.

- Program.cs: static files from wwwroot + SPA fallbacks per path so
  client-side routing works for both frontends.
- frontend/: builds with base '/admin/' in production (dev unchanged),
  router basepath follows suit.
- SlpModularCms.Api.csproj: publish now builds the admin frontend and
  copies its output into wwwroot/admin automatically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wi5qHAuq8UbzN4NLUFeKkJ
2026-07-26 21:01:28 +02:00
SluijsensandClaude Sonnet 5 0447993181 Completes local-dev-master-slave-setup: dual-instance frontend tooling, module-capability gating, and master/slave protocol self-healing fixes
Frontend (Unit 2 completion): dual dev-server tooling (pnpm dev:slave,
pnpm dev:all), per-instance browser tab titles, and a backend
capability check (SystemController + useSystemCapabilities +
ModuleGuard) so a Master-only page is hidden on a slave instance
instead of assuming every backend has every module.

Master/slave protocol fixes surfaced by actually running master and
slave side by side locally:
- Deactivating a CMS instance (Inactive) now releases the slave's
  master gate instead of leaving it stuck on its last pushed status.
- The periodic integrity check now also re-pushes status to every
  reachable slave (previously URL-verification only) and runs once
  immediately on startup.
- Added the originally-specified (but never implemented) slave-pull
  path: a slave now periodically polls its own status from the master
  (GET /api/v1/SlaveStatus) and fails open to Available if the master
  is unreachable for too long, complementing the existing push.
- The slave's own Settings page can no longer "successfully" change
  local availability while the master controls it; it's now locked
  with an explanatory banner and the backend rejects the write with
  409 instead of silently no-op'ing it.
- CMS instance status badges now match the dashboard's color/icon
  styling instead of a plain grey badge.

Also corrected the master-cms-module design docs to match this
as-built behavior, and flagged (without a full rewrite) a larger,
pre-existing divergence between its inception-stage application
design and what construction actually built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 19:53:52 +02:00
Sluijsens 8072025e0b Completes master-cms-module: Build & Test, docs, and appsettings
Finishes the master-cms-module feature (Units 1-4): runs Build and
Test across master-backend, slave-availability-extension and
frontend-cms-page, fixes a missing Availability EF migration for
MasterRegistration and a TanStack Query v5 mutation-callback type
break, adds the missing MasterModule appsettings section, and
documents the module in README.md. Also seeds a tech-debt-backlog
feature to track dead config and pre-existing/introduced frontend
lint findings for later cleanup.
2026-07-01 23:09:15 +02:00
Sluijsens 488ab821a7 Adds frontend for cms page 2026-06-30 23:23:50 +02:00
Sluijsens 0e01ca1e1c adds unit tests to cover 80% 2026-06-26 00:27:27 +02:00
Sluijsens b84e2f8e3e Changes back-end unavailable to redirect to login instead of setup page 2026-06-23 22:13:01 +02:00
Sluijsens 2544e20b3c Adds profile and settings pages 2026-06-22 23:59:04 +02:00
Sluijsens 6976eb4337 Adds user management 2026-06-22 21:14:22 +02:00
SluijsensandClaude Haiku 4.5 5331be4279 fix(auth): configurable SameSite cookie for cross-origin dev setup
Chrome 89+ schemeful same-site treats http://localhost and https://localhost
as different sites, blocking SameSite=Strict cookies on cross-origin fetch
(e.g. Vite on port 5173, API on port 7221).

Fix: make CookieSameSite configurable per environment in JwtSettings.
- Default: Strict (production)
- appsettings.Development.json: None (allows cross-origin cookie in dev)
- When SameSite=None, Secure is always forced (browser requirement)

Revert the earlier Vite proxy approach in favour of this backend config.
VITE_API_BASE_URL remains a freely configurable URL in .env.local.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 15:42:56 +02:00
SluijsensandClaude Haiku 4.5 546b773781 fix(auth): add Vite dev proxy to fix SameSite=Strict cookie on page reload
Chrome 89+ treats http://localhost and https://localhost as different sites
(schemeful same-site). The refreshToken cookie (SameSite=Strict) was not
sent when the Vite dev server (HTTP) made cross-origin fetch calls to the
.NET backend (HTTPS), causing a 401 on every page reload.

Fix: route /api/* through the Vite dev server proxy so all requests stay
same-origin. The cookie is now always sent and auth sessions survive reloads.

- vite.config.ts: proxy /api → https://localhost:7221 (secure:false for dev cert)
- .env.example: document proxy vs direct mode, set default VITE_API_BASE_URL to empty
- config.ts: accept empty string as valid apiBaseUrl alongside full URLs

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 15:23:19 +02:00
SluijsensandClaude Haiku 4.5 d8b0484803 fix(availability): translate status messages, return stored admin reason from API
- Add AvailabilityStatusDetails record and GetStatusDetailsAsync() to
  IAvailabilityService; implement in PersistentAvailabilityService so the
  admin-set reason stored in the database is returned alongside the status
- AvailabilityController.GetStatus() now returns the stored message instead
  of hardcoded English strings
- Add messageAvailable / messageMaintenance / messageUnavailable translation
  keys in en + nl so default messages are fully translatable
- AvailabilityStatusBadge: Available always shows the translated default;
  Maintenance and Unavailable show the custom admin reason when set,
  otherwise fall back to the translated default

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 14:53:11 +02:00
SluijsensandClaude Haiku 4.5 d69512a449 fix(i18n): eager-load nl bundle, fix language selector, remove hardcoded backend messages
- Eager-load both en and nl translation bundles at i18n init to eliminate
  the async gap that caused English flash when Dutch was the detected language
- LanguageSwitcher: use i18n.language (synchronous) instead of resolvedLanguage
  (asynchronous) so the visual selection is always correct after switching
- AvailabilityController: remove hardcoded English messages ("System is running
  normally.") from GET /availability/status; return empty string so the frontend
  translations control the display text

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 14:16:27 +02:00
SluijsensandClaude Haiku 4.5 3c6a06028e feat(unit-4): Dashboard — availability widget with TanStack Query
- Add @tanstack/react-query 5.101.0; wrap app with QueryClientProvider
- Add AvailabilityStatus type and AvailabilityResponse to api/types.ts
- Implement useAvailabilityStatus (staleTime 30s, stale-on-error preserved)
- Add AvailabilityStatusBadge with green/amber/red states and stale indicator
- Replace DashboardPage placeholder card with live availability widget
- Add MSW availability handler; update test/utils with QueryClientProvider
- 55/55 tests pass (FR-05)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 13:39:29 +02:00
SluijsensandClaude Haiku 4.5 f476e06691 fix(unit-3): Add settings/profile routes, move profile to UserMenu
- Add /settings (Owner only) and /profile routes under authenticatedRoute
  so sidebar stays visible and layout is preserved
- Create SettingsPage and ProfilePage placeholder components (coming soon)
- Remove Profile nav item from Sidebar — profile now accessible via UserMenu
- Add Profile link to UserMenu dropdown above logout
- Update Sidebar tests to reflect profile-free nav

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 12:56:16 +02:00
SluijsensandClaude Haiku 4.5 76b7bfc09b feat(unit-3): Layout & Navigation — sidebar, mobile overlay, theme toggle
- Remove Topbar; move UserMenu + LanguageSwitcher to sidebar footer
- Add ThemeToggle (dark/light) with localStorage persistence
- Add useTheme hook; no-flash inline script in index.html
- Add MobileBar (hamburger + app name, mobile-only)
- Add SidebarOverlay (slide-in from left, backdrop closes it)
- Sidebar: role-filtered nav (BR-U3-01–06), onClose prop for mobile
- AppLayout: desktop sidebar-only layout, mobile bar + overlay
- i18n: theme.* and nav.openMenu/closeMenu keys (NL + EN)
- Tests: 43/43 passing (14 new — useTheme, Sidebar roles, AppLayout)

Stories: US-08, US-18, US-19

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 12:03:10 +02:00
SluijsensandClaude Haiku 4.5 7237669b14 fix(setup): Apply language choice and fix post-setup redirect
- Call changeLanguage(values.language) after successful owner creation so
  the UI immediately switches to the language the owner chose
- Call _markSystemInitialized() to update the router cache before navigating
  to /login; without this the guard saw initialized=false and redirected
  back to /setup, blocking the redirect

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 11:25:40 +02:00
SluijsensandClaude Haiku 4.5 27490356cb fix(ui): Add type=button to FormErrorBanner dismiss to prevent form resubmit
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 11:20:48 +02:00
SluijsensandClaude Haiku 4.5 2f381d8407 fix(frontend): Strip confirmPassword before API calls; add pointer cursor
- Destructure confirmPassword out of form values in SetupPage and
  InviteCompletePage so it is never sent to the backend
- Add global cursor:pointer rule for buttons, selects, labels and links

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 11:17:20 +02:00
SluijsensandClaude Haiku 4.5 f221d7cee5 fix(unit-2-auth): Redirect to /setup on API failure; store owner display name
- Router catch block now falls back to { initialized: false } instead of
  silently continuing, preventing unwanted redirect to /login when the
  API call fails at startup
- Add Name field to CreateOwnerRequest and propagate to ApplicationUser.DisplayName
  so the owner's display name is stored during initial setup

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 11:06:34 +02:00
SluijsensandClaude Haiku 4.5 8d95754ece fix(unit-2-auth): Fix API endpoint paths and JSON casing for real backend
- Add /api/v1/ prefix to all Unit 2 API calls (Setup/status, Setup/owner,
  Invitation/validate, Invitation/complete) to match backend ApiPrefixConvention
- Fix setup status endpoint to POST /api/v1/Setup/owner (not /Setup)
- Handle PascalCase 'Initialized' response from .NET backend without camelCase policy
- Update all MSW mock handlers to match corrected /api/v1/ URL patterns
- Remove unused imports from RouteGuard.test.tsx

Root cause: backend uses ApiPrefixConvention('api/v1') but frontend calls
were missing the prefix, and .NET defaults to PascalCase JSON serialization.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 10:55:34 +02:00
SluijsensandClaude Haiku 4.5 c9cd0ff457 fix(unit-2-auth): Fix all 4 failing unit tests to 29/29 passing
- Fix invitation MSW handler: use 'valid-token' to match test fixtures
- Add data-testid='setup-success' to SetupPage success state
- Change invite email input from readOnly to disabled (matches test assertion)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 10:20:00 +02:00
SluijsensandClaude Haiku 4.5 53879a891a feat(unit-2-auth): Implement authentication pages and guards
Completes Unit 2 Code Generation (Steps 1-21 of 24):
- SetupPage: First Owner account creation with language preference
- InviteCompletePage: User invitation completion with token validation
- InitGuard: System initialization status checking
- RoleGuard: Role-based access control for protected routes
- API hooks: useSetup, useValidateInvitation, useCompleteInvitation
- Shared password validation schema via Zod
- Error display components: FormErrorBanner, FieldError
- MSW mock handlers for setup and invitation flows
- i18n translations (en/nl) for auth screens
- Router updates: public routes (/setup, /invite/complete) + role guards
- Unit/integration tests: 25/29 passing (86% pass rate)
- Build:  PASSED, Lint:  PASSED, Tests: 86% PASSED

Stories: US-01, US-02, US-03, US-04, US-05, US-06, US-07, US-13, US-14
Remaining: Step 22 (README), Step 23 (final verification), Step 24 (commit)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 09:58:28 +02:00
Sluijsens ab93a5c7d1 Adds auth pages 2026-06-21 00:15:28 +02:00
Sluijsens 7dfc3a9692 Adds front-end set-up 2026-06-20 17:04:17 +02:00