Commit graph

5 commits

Author SHA1 Message Date
a0c61b3d94 #7: Repository<T> interface set + ITemporalEntity + IHistoryRepository<T>
Header-only foundation for the structural refactor that moves fewo-webapp
from per-entity *Db clients to a shared Repository<TDto> abstraction. This
ships interfaces only — no concrete implementations, no callers updated.

Decisions baked in (all settled in the issue body):
- Mixed entity_id allocation: caller may supply, otherwise the concrete
  repo generates a UUID inside save().
- UnitOfWork / cross-repo transactions: explicitly out of scope.
- Repository<T> is a virtual-method interface, not a C++20 concept.
- History queries live on a separate IHistoryRepository<T> so non-temporal
  repos don't have to implement a stub.

Decorators (TemporalRepository<T>, ScopeGuardRepository<T>) follow in #8;
the optional IQueryable<T> capability for typed filtering follows in #9.
The fewo-webapp Person pilot (uwe.admin/fewo-webapp#457) and the wider
26-entity rollout (uwe.admin/fewo-webapp#458) build on this.

Closes #7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:42:47 +02:00
f43f5f0633 #6: route ad-hoc JSON through ObjectMapper (Option A — DI everywhere, all-in-one)
- New dto/InternalDto.hpp with JsonErrorDto, WsEntityEventDto,
  WsPresenceUpdateDto, WsClientMsgDto.

- JsonErrorHandler: now takes a shared ObjectMapper (DI). Body built
  via writeToString on JsonErrorDto. Closes the audit's concrete bug
  where status.description was embedded raw — a Status with a `"`/`\\`
  in the description previously emitted invalid JSON.

- AuthInterceptor: takes an optional ObjectMapper ctor arg (defaults to
  a fresh mapper). makeForbidden's `msg` is now serialised via
  JsonErrorDto + ObjectMapper, so a `"` in a forbidden-reason no longer
  breaks the response envelope.

- Hub: process-wide sharedMapper() with optional setObjectMapper()
  override. buildPresenceMsg / notifyBooking / notifyPerson all go
  through ObjectMapper-emitted DTOs. User-supplied IDs / property IDs
  / usernames containing `"`/`\\`/control chars are now escaped.

- Listener: jsonStr/jsonInt regex parsers gone. handleMessage parses
  inbound frames via ObjectMapper::readFromString into WsClientMsgDto.
  Malformed JSON / nested objects / escaped quotes — previously silent
  corruption — now produce a clean drop of the frame.

- test/test_json_serialization.cpp: 4 cases pinning the round-trip
  behaviour (special chars in usernames, IDs, status.description, and
  malformed-input rejection).

Bump to 0.4.0 — ctor signatures changed (additive defaults, but the
behaviour of the JSON envelopes is now governed by ObjectMapper).

Closes #6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:56:05 +02:00
0d2312499e #3: SecurityHeadersInterceptor — strict baseline + CspOverride ctor (Option B)
Aligns the default CSP, X-Frame-Options, HSTS and Permissions-Policy with
docs/security-baseline.md:
  - script-src/style-src drop 'unsafe-inline' and the unpkg.com allowance
  - img-src narrows from 'self' data: https: → 'self' data:
  - connect-src narrows from 'self' wss: ws: → 'self'
  - frame-ancestors flips from 'self' → 'none'
  - X-Frame-Options flips from SAMEORIGIN → DENY
  - HSTS keeps max-age=63072000 but drops includeSubDomains by default
    (apex-clobbering hazard noted in audit #1)
  - Permissions-Policy header added with the baseline sensor allowlist

Adds a CspOverride struct + ctor so consumers that genuinely need a
relaxation (Swagger UI subtree, cross-origin connect, …) can flip
individual directives without forking the interceptor. Empty fields
inherit the strict baseline.

Bumps to 0.3.6 (alongside owner's pending #4 + #5 + #6 work).

Closes #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:54:58 +02:00
950012d946 #4: BodySizeLimitInterceptor — fail-closed on missing/malformed Content-Length
Body-bearing methods (POST/PUT/PATCH) now reject:
- missing Content-Length → 411
- malformed Content-Length → 400
- non-identity Transfer-Encoding (chunked, etc.) → 411
- declared length > maxBytes → 413 (unchanged)

GET/HEAD/DELETE/OPTIONS/TRACE pass through unchanged. Consumers needing
the legacy fail-open behaviour pass `requireContentLength = false`.

Bump to 0.3.3 (behaviour tightening — consumers on default ctor see new
411/400 responses on requests that previously sailed through).

Closes #4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:36:50 +02:00
abf6153439 #2: Browser-friendly 401/403 — content-negotiate JSON vs HTML/redirect
AuthInterceptor previously returned application/json for every rejection,
which is wrong for browser navigation: the user followed a /set-password
link and saw a raw {"status":"Unauthorized"} blob.

Add wantsJson() negotiation (path /api/* OR X-Requested-With OR Accept
prefers application/json over text/html) and an IAuthPolicy hook
unauthenticatedRedirect(path) that lets consumers bounce browser
navigations to a landing/login page. JSON callers (fetch/axios) still
get JSON 401/403. Default policy returns nullopt → minimal HTML error
page, never raw JSON to a browser.

Same hook covers both 401 and 403 (decision Option A on the issue) so
consumers wire one redirect target for both unauth and forbidden cases.

Bootstrap a minimal test harness (decision Option T2): CMake option
OATPP_AUTHKIT_BUILD_TESTS gates enable_testing() + a tests subdir.
Adds test_negotiation covering wantsJson + urlEncode. No third-party
test framework — assertions use <cassert> + a tiny REQUIRE macro so the
suite stays dependency-free for future tests.

Closes #2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:23:08 +02:00