L-1 RequireRole: guard std::stoi on the bundle id — a non-numeric/out-of-range
value now yields a clean 401 instead of an uncaught exception → 500.
AuthPrincipal::id documented as numeric-only (carry UUIDs in username).
L-2 SmtpTransport: require TLS (CURLUSESSL_ALL) for non-loopback relays so a
stripped STARTTLS can't downgrade credentials/body to cleartext; localhost
relay stays opportunistic.
L-3 AuditLog: escapeJson now escapes all control chars (RFC 8259) so a newline
in a field can't forge/corrupt the audit JSON; SKIP_FIELDS gains credential
names (password/passwordHash/tlsCertDn/apiKey/token/secret) so secrets never
land in changed_fields.
L-4 ws/Hub: consume the thread_local auth handoff once, up front, and clear it
unconditionally — a stale value can't attach to a later connection on a
reused worker thread.
L-5 TemporalRepository: default id generator draws 128 bits from the platform
CSPRNG (std::random_device) per call instead of a once-seeded mt19937_64,
so entity_ids aren't predictable from observed output.
L-6 AuthInterceptor: expired-session sweep is now a lock-free atomic timer and
exception-isolated; documented that resolveBySessionHash() must enforce
expiry at query time (the sweep is GC only).
L-7 new util/ConstantTime.hpp (constantTimeEquals) + TokenHasher doc requiring a
>=256-bit cryptographic hash.
L-8 IQueryable: likeEscape + Field::likeContains/likePrefix emit
`LIKE ? ESCAPE '\'` with %/_/\ escaped for untrusted terms; documented the
compile-time identifier-source invariant.
Tests: new test_constant_time; likeEscape/likeContains/likePrefix cases added to
test_queryable. All 20 ctest targets pass. README + header docs updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M-1 TokenExtract: exact-name cookie parse (new pure cookieValue helper) —
a substring find("session=") could be shadowed by a sibling xsession=,
defeating __Host-/__Secure- prefix guarantees.
M-2 AuthInterceptor: gate setup-mode pseudo-admin on a loopback bind and log
the grant; document that IAuthBackend::hasActiveUsers() must fail closed.
M-3 ws/Hub: empty propertyIds now means NO access for non-admins (was "all") —
a non-admin whose scope set failed to populate no longer gets every
property's notifications. Admins still get all via role.
M-4 new util/OriginCheck.hpp (originHostname/sameOrigin/originAllowed) +
Hub doc: WSController must validate Origin at the handshake (CSWSH).
M-6 RedactedFieldRepository: ctor throws on an unknown redaction field name
(a typo would silently redact nothing, leaving credentials in history).
M-7 RateLimiter: ctor validates capacity (finite >=1) / refillRate (finite >0),
throws std::invalid_argument — zero/negative/NaN silently disabled it.
M-8 TokenExtract: document that clientIpTrusted's "unknown"/"invalid" sentinels
collapse to one shared rate-limit bucket off-proxy.
M-9 new util/SessionCookie.hpp: safe-by-default Set-Cookie builder
(HttpOnly+Secure+SameSite=Strict+Path=/), rejects control chars / ';'.
M-10 AuthInterceptor: Origin/Referer-vs-Host check on session mutations
(defence in depth atop X-Requested-With); cert path documented as
non-browser / not CSRF-gated.
M-11 AuthInterceptor: optional injected RateLimiter throttles invalid-token
attempts per client IP → 429.
M-12 AuthInterceptor: sanitize request method/path (strip control chars, cap
length) before logging — closes log-line forging (CWE-117).
(M-5 — temporal non-atomic save — was already resolved by the H-4 fix.)
Tests: new test_token_extract / test_rate_limiter / test_origin_check /
test_session_cookie; extended test_redacted_field_repository. All 19 ctest
targets pass. README + header docs updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- H-1 cert-DN spoofing: IRuntimeConfig::certAuthTrusted() now defaults to
false (fail-closed). X-SSL-Client-DN is an ordinary request header; a
loopback bind does not prove it came from a TLS-terminating proxy.
Consumers must opt in explicitly behind a header-stripping proxy.
- H-3 scope reparenting: ScopeGuardRepository::save() now also checks the
EXISTING row's scope (via a new required entity-id accessor), so an actor
can't claim an out-of-scope row by relabelling it in the request body.
- H-2 IQueryable bypass: add ScopeGuardQueryable<T> — filters query()
results through the same predicate so the queryable surface can't escape
the scope guard.
- H-4 TemporalRepository TOCTOU: serialise the read-modify-write with a
per-instance mutex (no more duplicate-live / lost-update under concurrent
same-entity saves) and add an optional TxRunner so the close-then-insert
pair can commit/rollback atomically.
- H-5 SMTP header injection: reject CR/LF/NUL in `to`/`fromAddress` before
building the envelope and From:/To: header lines.
Tests: expand test_repository_decorators (reparenting + queryable filtering),
add curl-guarded test_smtp_transport (base64 vectors + CRLF guard). All 15
ctest targets pass. README updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a decorator that sits below TemporalRepository and redacts
configured fields whenever it sees a save with valid_until != SENTINEL
(i.e., a historical row being closed by the temporal close-then-update
flow). The live row keeps its values intact.
Per Option B from the issue thread: by default the user-repo factory
redacts both passwordHash and tlsCertDn. Empty redaction list passes
everything through unchanged, so non-user temporal stacks compose the
decorator without surprise behaviour.
Files:
- repo/RedactedFieldRepository.hpp — new decorator. Schema contribution
is empty (purely a save-time transform). Field-name matching uses
oatpp's reflective property dispatcher and matches against the C++
identifier name (first DTO_FIELD argument).
- repo/ConcreteUserRepository.hpp — makeUserRepository now wraps the
concrete repo in RedactedFieldRepository<UserDto>{"passwordHash",
"tlsCertDn"} before passing to TemporalRepository. Optional second
argument lets consumers override the redaction list.
- test/test_redacted_field_repository.cpp — five tests cover live-row
pass-through, historical-row redaction (both fields), partial
redaction list, empty list, and null-valid_until treated as live.
- README.md — adds RedactedFieldRepository to the header inventory.
14 of 14 tests pass. Bumped 0.12.0 → 0.13.0.
Closes#15
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts the auth-essential users table from fewo-webapp into oatpp-authkit
in temporal form per Option B from the issue body. The previous shape
(id INTEGER autoinc + is_active flag) is replaced with the entity_id +
valid_from/valid_until triple; soft-delete via valid_until = now()
instead of toggling is_active.
New files (all in oatpp-authkit):
- dto/UserDto.hpp — auth-essential columns only: id, entity_id, username,
password_hash, role, tls_cert_dn, valid_from, valid_until. Registered
as temporal so TemporalRepository composes cleanly. Application-
specific columns (email, profile data) belong on a consumer-side DTO
+ parallel SchemaContract that contributes additional columns to the
same users table.
- db/UserDb.hpp — DbClient with login-path queries (findLiveByUsername,
findLiveByTlsCertDn) plus generic CRUD. UserSchema declares the
schema: TEXT id, entity_id, username, password_hash, role, tls_cert_dn,
with natural-key UNIQUE on (username, valid_until) so no two live rows
can share a username while historical rows for the same username are
allowed.
- repo/ConcreteUserRepository.hpp — Repository<UserDto> adapter +
makeUserRepository factory wrapping in TemporalRepository.
- test/test_user_schema.cpp — verifies SchemaBuilder<UserSchema,
TemporalRepository<UserDto>>::create produces the expected 5 DDL
statements; specifically asserts is_active and created_at are NOT
present in the temporal shape (Option B replacement).
13 of 13 tests pass. Bumped 0.11.0 → 0.12.0.
Per owner directive on authkit#14: password_hash rides the temporal row.
A separate security follow-up issue tracks the redaction policy for
historical password hashes (likely blank the hash but keep the row so
change-history is auditable).
The migration of an existing non-temporal users table to this shape is
documented in db/UserDb.hpp: Atlas-generated migration handles the
structural conversion + backfill (each existing row becomes its own
entity with entity_id = CAST(id AS TEXT)). Sessions/certificates FKs
that referenced users.id (INTEGER) need rewiring to reference
users.entity_id — that's a consumer-side rewire, separate PR.
Closes#14 — the four migration sub-PRs (PR 1 role_templates, PRs 2+3
permissions, PR 4 users) are now landed; the umbrella issue can close.
Follow-ups (security hash redaction, fewo-webapp consumer migration,
Atlas CI integration) get their own issues.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts both per-property and per-property-set RBAC tables from fewo-webapp
into oatpp-authkit. Combined into one commit because they share a
DbClient and the cross-table effective-permission resolver — the resolver
itself stays in fewo since it joins property_set_members (a fewo-side
concept).
New files (all in oatpp-authkit):
- dto/UserPermissionDto.hpp — UserPropertyPermissionDto +
UserGroupPermissionDto, both registered as temporal.
EffectivePermissionDto stays in fewo (it's the result shape of fewo's
property_set_members JOIN).
- db/UserPermissionDb.hpp — DbClient with CRUD for both tables. Each
table also has a *Schema struct exposing kSchema for SchemaBuilder
composition. Natural-key UNIQUE indexes carried explicitly:
ux_..._user_property_until, ux_..._user_set_until.
- repo/ConcreteUserPermissionRepository.hpp — two concrete repos +
makeUserPropertyPermissionRepository / makeUserGroupPermissionRepository
factories that wrap each in TemporalRepository.
- test/test_user_permission_schema.cpp — verifies both schemas compose
with TemporalRepository to produce the expected 5 DDL statements each
(entity table + 3 schema indexes + 1 temporal composite index).
12 of 12 tests pass. Bumped 0.10.0 → 0.11.0.
Per-row natural-key UNIQUE prevents duplicate live grants for the same
(user_id, property_id) or (user_id, set_id) pair while still allowing
historical rows for the same key (their valid_until differs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
sharedMapper was made private in #6 but Listener::handleMessage (defined
later in the same header) still calls Hub::sharedMapper(). Any consumer
that actually instantiates the WebSocket Listener hit a private-access
compile error. Add `friend class Listener;` so Listener can reach the
shared ObjectMapper without exposing it publicly.
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>
Two cross-cutting decorators that wrap any Repository<TDto> from #7.
TemporalRepository<TDto>:
- Requires TDto : ITemporalEntity (compile-time static_assert).
- save() finds the existing live version, closes its valid_until, and
inserts a new row at valid_until = '9999-12-31T23:59:59Z' sentinel.
- findByEntityId() returns the live row; findByEntityIdAt(id, at) does
the [valid_from, valid_until) point-in-time read.
- list() returns live rows only; history(id) returns all versions
ordered by valid_from. Implements IHistoryRepository<TDto>.
- softDelete closes the live row without inserting a new version.
- Clock and id-generator are constructor-injected (defaults: system_clock
+ 32-char hex from mt19937_64) so the unit tests are deterministic.
The decorator's contract on the inner repository: list() must expose all
rows including historical, and save() must be upsert keyed by
(entity_id, valid_from). Documented on the class.
ScopeGuardRepository<TDto>:
- Generic; knows nothing about "property"/"tenant"/etc. Constructor
takes a std::function<bool(ActorContext, TDto)> predicate plus a
std::function<ActorContext()> accessor (so a single instance can
serve many requests with different actors).
- list() filters; findByEntityId/save/softDelete throw
ScopeDeniedException on deny.
Tests cover the five acceptance criteria from the issue body:
- Temporal save closes the prior version
- Live read returns only the row with valid_until = sentinel
- Point-in-time read returns the version live at that time
- History returns all versions in order
- Scope guard short-circuits when the predicate returns false
ctest: 6/6 green (4 prior + repository_interface + repository_decorators).
Closes#8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- 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>
Request-target from getStartingLine().path includes the query string
(e.g. "/set-password?token=abc"), causing exact-match public-path
checks like `path == "/set-password"` in IAuthPolicy::isPublicPath
to fail and the request to be rejected with 401.
Strip the query string once at the top of intercept() so policies
and access logs see clean paths.
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>
Lifted from fewo-webapp src/ws/ — zero fewo-webapp domain coupling in
the public surface. Classes renamed WSHub→Hub, WSListener→Listener and
namespaced under oatpp_authkit::ws.
Features:
- 64 KB per-message cap (rejects fragmented frames exceeding the buffer)
- 500-socket cap
- Detached housekeeper thread pinging idle sockets >90 s, closing >180 s
- Per-socket SocketInfo (userId, role, property scopes) populated via
thread_local handoff from the HTTP controller that served the upgrade
Consumers construct a Hub and pass it to oatpp's
HttpConnectionHandler::setSocketInstanceListener. No other integration
required.
Unblocks fewo-webapp #452.
Lifted from fewo-webapp (src/App.cpp). 15-line helper that speaks the
systemd notification protocol directly — no libsystemd link — for
Type=notify services.
Silent no-op when NOTIFY_SOCKET is unset so the same binary runs
unchanged under systemd or as a plain background process.
Supports Linux abstract-namespace sockets.
Unblocks fewo-webapp #451 and its twin extractions for derived projects.
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>