Lifts role_templates / role_template_fields / user_role_assignments from
fewo-webapp into oatpp-authkit, exposed via the declarative SchemaContract
introduced in PR 0.
New files (all in oatpp-authkit):
- dto/RoleTemplateDto.hpp — RoleTemplateDto, RoleTemplateFieldDto,
UserRoleAssignmentDto. UserWithPermissionsDto stays in fewo (fewo-
specific /api/auth/me response shape).
- db/RoleTemplateDb.hpp — DbClient with all queries (CRUD + cascade
soft-delete + getEffectiveFieldPermissions). RoleTemplateSchema struct
declares the three tables' columns/indexes/sidecar tables in the new
declarative form. TemporalRepository overlays valid_until + the
composite UNIQUE(entity_id, valid_until) index.
- repo/ConcreteRoleTemplateRepository.hpp — Repository<RoleTemplateDto>
inner adapter; makeRoleTemplateRepository helper composes the stack.
- docs/MIGRATIONS.md — Atlas workflow for consumers (atlasgo.io as the
diff-driven migration tool; SchemaBuilder produces desired state, Atlas
generates versioned SQL, SchemaContract::verify asserts at runtime).
- test/test_role_template_schema.cpp — verifies SchemaBuilder<
RoleTemplateSchema, TemporalRepository<RoleTemplateDto>> emits the
expected 5 DDL statements (2 sidecars + entity table + 2 indexes) with
composite-FK + ON UPDATE CASCADE on both sidecars.
11 of 11 tests pass. RoleTemplateDto is registered as temporal via
OATPP_AUTHKIT_REGISTER_TEMPORAL so TemporalRepository compiles cleanly.
Atlas binary integration in CI is documented but not yet wired — owner
deferred to a follow-up after the first concrete consumer migration. The
shipped role_templates stack itself is fully consumable today; fewo-
webapp's switch from local copies to oatpp-authkit-shipped headers is
the natural next PR.
Bumped 0.9.0 → 0.10.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
D-replace per #14: rip out PREREQ + RESHAPE_STEPS + applyDecoratorMigrations
and replace with declarative DecoratorSchema (entity columns + indexes +
sidecar tables). SchemaBuilder<Decorators...>::create composes the stack
into a single CREATE TABLE per entity table; SchemaContract::verify
introspects-and-asserts at runtime so code can never run against an
under-migrated DB. Atlas (atlasgo.io) becomes the authority for schema
evolution between deploys — decorator code never runs ALTER at runtime.
- TemporalRepository contributes valid_from/valid_until + UNIQUE composite index
- AuditLogRepository contributes the audit_log sidecar table
- ScopeGuardRepository declares empty contributions for clean stacking
- 8 new tests in test_schema_contract.cpp covering compose / dedup / verify
- README updated; bumped 0.8.0 → 0.9.0
fewo-webapp does not yet call applyDecoratorMigrations, so this is a
clean cut — no consumer-side breakage. PRs 1-4 (role_templates,
user_property_permissions, user_group_permissions, users) follow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The decorator's save() flow now preserves the live row's id PK across
updates and captures each prior version as a fresh row with a new id.
This unblocks fewo-webapp#459: the consumer's composite-FK schema needs
stable child references to the live row (UNIQUE(entity_id, valid_until)
with ON UPDATE CASCADE on every child FK), which the previous
close-then-insert flow couldn't provide.
New flow on update (when a live row exists for entity_id):
1. Clone the live row in memory (cloneDto via oatpp reflection),
assign a fresh id and set valid_until=now, save → INSERT historical.
2. Set the new dto's id=live.id (preserve PK), valid_from=now,
valid_until=SENTINEL, save → inner UPDATEs the live row in place by
PK.
Inner adapter contract changes from "upsert keyed by (entity_id,
valid_from)" to "upsert keyed by id (per-row PK)". TemporalFieldTraits
gains an id() accessor; OATPP_AUTHKIT_REGISTER_TEMPORAL grows from 4 to
5 args (Dto + IdMember + EntityIdMember + FromMember + UntilMember).
Tests: test_repository_decorators asserts livePk stability across saves
and fresh historicalPk per version; remaining decorator tests updated to
the 5-arg macro form. README's TemporalRepository.hpp row rewritten to
describe the new write semantics.
Bumped CMake version 0.7.0 → 0.8.0 (semantic break — save() no longer
reallocates the live PK; consumers depending on the old contract need
audit).
Closes#13
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each decorator now bundles its schema prereqs alongside its code via
DecoratorPrereq (additive CREATE-IF-NOT-EXISTS) and ReshapeStep
(non-idempotent reshape gated on a detectSql probe).
applyDecoratorMigrations<Decorators...>(table, probe, exec, recorder)
walks the listed decorators at startup, runs every PREREQ, runs every
reshape step whose probe returns false. Database-agnostic — consumer
wires probe/exec to their DbClient. SCHEMA_MIGRATIONS_TABLE_SQL is
provided for observability; the detect-probe is the source of truth.
TemporalRepository ships add_valid_from / add_valid_until /
drop_unique_entity_id / composite_unique (UNIQUE(entity_id, valid_until)
so close-then-insert can run in a deferred-FK transaction).
AuditLogRepository ships the audit_log CREATE TABLE.
ScopeGuardRepository ships nothing — exposes empty PREREQ + zero-length
RESHAPE_STEPS so it can be listed in applyDecoratorMigrations alongside
the schema-touching decorators without SFINAE.
Closes#12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace hard-coded dto->entity_id/valid_from/valid_until accesses in
TemporalRepository with trait calls (F::entityId/validFrom/validUntil).
DTOs register canonical→actual member name mapping via
OATPP_AUTHKIT_REGISTER_TEMPORAL. Forgetting to register is a hard
compile error. ITemporalEntity marker is gone; the trait specialisation
carries the contract. Bumps version 0.4.0 → 0.5.0.
New test verifies the full save/close/history/softDelete flow against a
DTO whose columns are id/effective_from/effective_until rather than the
canonical names — exercises the renaming the trait enables.
Closes#10
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
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>
New virtual hook on IRuntimeConfig, defaulting to isLoopback() so existing
consumers keep their current behaviour. AuthInterceptor now consults
certAuthTrusted() (instead of isLoopback() directly) to decide whether to
honour an inbound X-SSL-Client-DN header.
Operators with an SSH tunnel to a loopback bind, or a non-TLS proxy that
forwards X-SSL-Client-DN from untrusted clients, can now override the
hook to require additional gating (e.g. an env var, a TLS-only port).
Bump to 0.3.5 (additive — no consumer break).
Closes#5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Pure libcurl SMTP + MIME transport, DTO-free so it drops into any
consumer that can cough up host/port/from/user/pass. Callers adapt
their own settings row/DTO to `oatpp_authkit::mail::SmtpConfig`.
Closes the email-service half of #447 (tracked under fewo-webapp #454).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the generic audit-log helper (timestamp + actor + action + entity
+ changed_fields JSON) into the shared library so every consumer picks
up the same shape without reimplementing it. The table is now named
`audit_log` (was `command_log` in fewo-webapp); consumers copy
`AuditLog::CREATE_TABLE_SQL` into their schema.sql so class name and
table name stay in one source of truth.
Legacy data on fewo-webapp migrates via a one-shot
`INSERT INTO audit_log SELECT … FROM command_log; DROP TABLE command_log;`
statement in that project's schema.sql.
Closes#449 (fewo-webapp half follows in separate commits).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The four clean-lift headers (SecurityHeadersInterceptor,
BodySizeLimitInterceptor, JsonErrorHandler, RateLimiter) were copied
verbatim in v0.1.0 and left in the global namespace — consumers that
adopt the library alongside existing same-named classes (e.g. fewo-webapp
during the #417 swap) would hit ODR clashes.
Wrap them in the same namespace the v0.2 auth seams use. Patch bump; no
API surface change beyond the qualifier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports the fewo-webapp AuthInterceptor + requireAdmin onto three abstract
interfaces so consumer apps plug in their own user store, public paths,
and runtime config without forking:
auth/AuthPrincipal.hpp library-owned {id, username, role} value
auth/IAuthBackend.hpp resolveBy{Session,ApiKey,Cert}, hasActiveUsers,
deleteExpiredSessions
auth/IAuthPolicy.hpp isPublicPath, adminRoles, readonlyRoles,
setupModeActive (defaults: admin/readonly,
no public paths, setup off)
auth/IRuntimeConfig.hpp bindAddress, isLoopback
auth/AuthInterceptor.hpp intercept() running the same 6-step ladder as
fewo's original (public → setup → cert DN →
session/API key → CSRF → readonly)
auth/RequireRole.hpp requireUser + requireAdmin helpers reading
bundle data (config-driven role sets, not
hard-coded 'admin')
TokenHasher is passed in so the library doesn't prescribe SHA-256 vs.
whatever. Bundle keys match fewo's existing controllers so the consumer
migration in #418 is a straightforward adapter swap.
Smoke-compiled against oatpp 1.3.0 headers.
Closes fewo-webapp#413
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>