# oatpp-authkit Header-only C++ library distilled from [fewo-webapp](https://git.uwe-schuster.info/uwe.admin/fewo-webapp)'s hardened auth / security stack. Header-only, oatpp 1.3+, C++17. ## What's in v0.1 (the clean-lift set) | Header | Purpose | |--------|---------| | `interceptor/SecurityHeadersInterceptor.hpp` | CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. Strict defaults. | | `interceptor/BodySizeLimitInterceptor.hpp` | Reject request bodies above a configurable limit with 413 before they hit your handlers. | | `handler/JsonErrorHandler.hpp` | Normalises thrown exceptions into `{status, message}` JSON so controllers never leak raw HTML error pages. | | `util/RateLimiter.hpp` | In-memory token-bucket keyed on an arbitrary string (typically the client IP from `clientIpTrusted`). | | `util/TokenExtract.hpp` | `extractToken` (Cookie/Bearer), `isValidIp` (IPv4/IPv6 via `inet_pton`), `clientIpTrusted` (loopback-gated XFF). | | `startup/RequireEncryptionKey.hpp` | `requireEncryptionKey(envVarName, encryptionEnabled, allowPlaintext)` — refuse startup without a symmetric key unless a dev flag overrides. | | `repo/Repository.hpp` + `IHistoryRepository.hpp` + `TemporalFieldTraits.hpp` + `TemporalAt.hpp` + `ActorContext.hpp` | Pure-abstract `Repository` interface set distilled from fewo-webapp's per-entity `*Db` clients. Mixed UUID allocation on `save`, separate `IHistoryRepository` for temporal versions, `TemporalFieldTraits` to map canonical (entity_id, valid_from, valid_until) onto whatever a DTO actually calls them, `ActorContext` placeholder for the scope-guard decorator. | | `repo/TemporalRepository.hpp` | Decorator that wraps any `Repository` and turns it into a temporally-versioned one. **Stable-live + historical-copy semantics (authkit#13):** the live row's `id` PK is preserved across updates; each prior version is captured as a fresh row with a new `id`. `softDelete` closes the live row in place; with `ON UPDATE CASCADE` on consumer-side composite child FKs, child rows follow automatically. `findByEntityIdAt(id, at)` returns the version live at a point in time; implements `IHistoryRepository`. Inner adapter is expected to expose all rows (live + historical) and treat `save` as upsert keyed by **`id`** (per-row PK). DTOs register their four temporal columns via `OATPP_AUTHKIT_REGISTER_TEMPORAL(Dto, id, entity_id, valid_from, valid_until)`. | | `repo/ScopeGuardRepository.hpp` | Generic resource-scope decorator. Takes a `bool(ActorContext, TDto)` predicate at construction; gates every method on it. Throws `ScopeDeniedException` on deny (catchers translate to 403). Knows nothing about consumer-specific concepts like "property" or "tenant" — the predicate decides. | | `repo/IQueryable.hpp` | Optional capability for repos that resolve a typed query AST. `field<&Dto::col>().eq(...)` style DSL composes via `&&` / `||` / `!`; `Query::toSql()` emits parameterised SQL plus a bind bag. Bounded surface — equality, range, IN, LIKE, NULL, ORDER BY, LIMIT/OFFSET. No joins, subqueries, or aggregates. Concrete repos opt in by deriving `IQueryable`. | | `repo/IAuditSink.hpp` + `repo/AuditLogRepository.hpp` | Cross-cutting audit-trail decorator. Emits an `AuditEvent` (actor, entity type/id, op, timestamp) per mutation through a consumer-supplied `IAuditSink`. Ops are `Create` / `Update` / `Delete` / `Read`; pre-write `findByEntityId` lookup distinguishes Create from Update. Configurable enabled-op set (default `{Create,Update,Delete}` — `Read` is opt-in, `list()` never audited). Sink failures are caught and swallowed unless a `bool(const std::exception&)` handler asks to rethrow. Stacks with `TemporalRepository` and `ScopeGuardRepository`. | | `repo/Prereq.hpp` | Per-decorator migration kit. Each decorator that touches schema bundles its prereq SQL alongside its code: `DecoratorPrereq` for additive (`CREATE TABLE IF NOT EXISTS …`) and `ReshapeStep` for non-idempotent reshape with a `detectSql` probe. `applyDecoratorMigrations(table, probe, exec, recorder)` walks the listed decorators at startup, runs every PREREQ, runs every reshape step whose probe returns false, optionally records applied steps via `oatpp_authkit_schema_migrations`. Database-agnostic — consumer wires `probe`/`exec` to whatever DbClient they use. | ## Decorator migrations | Decorator | `PREREQ` | `RESHAPE_STEPS` | |-----------|----------|------------------| | `TemporalRepository` | (none) | `add_valid_from`, `add_valid_until`, `drop_unique_entity_id` (consumer-overridable noop), `composite_unique` — composite `UNIQUE(entity_id, valid_until)`. With stable-live save semantics, no FK deferral is required; consumer-side child FKs use `ON UPDATE CASCADE` to follow `valid_until` flips on delete. | | `AuditLogRepository` | `CREATE TABLE IF NOT EXISTS audit_log (…)` — fixed shape, no `{table}` placeholder. | (none) | | `ScopeGuardRepository` | (none) | (none) | Wiring it up: ```cpp #include "oatpp-authkit/repo/Prereq.hpp" // probe: returns true iff the SQL yields ≥1 row auto probe = [&](const std::string& sql) { /* run SELECT, return bool */ }; auto exec = [&](const std::string& sql) { /* run DDL */ }; oatpp_authkit::repo::applyDecoratorMigrations< oatpp_authkit::repo::TemporalRepository, oatpp_authkit::repo::AuditLogRepository>( "persons", probe, exec); ``` Re-running on every startup is safe by construction. ## Consume via CMake ```cmake # FetchContent (pin to a tag): include(FetchContent) FetchContent_Declare(oatpp-authkit GIT_REPOSITORY https://git.uwe-schuster.info/uwe.admin/oatpp-authkit.git GIT_TAG v0.1.0) FetchContent_MakeAvailable(oatpp-authkit) target_link_libraries(app PRIVATE oatpp::authkit) ``` Or after `cmake --install`: ```cmake find_package(oatpp-authkit 0.1 REQUIRED) target_link_libraries(app PRIVATE oatpp::authkit) ``` ## Browser-friendly 401/403 By default `AuthInterceptor` returns `application/json` for every rejection, which is correct for `/api/*` callers but breaks browser navigation: a user following a stale link or an expired password-reset URL sees a raw `{"status":"Unauthorized"}` instead of a real page. Override `IAuthPolicy::unauthenticatedRedirect(path)` to redirect browser navigations to a login or landing page while keeping JSON responses for `fetch`/`axios` callers (detected via path prefix `/api/`, `X-Requested-With: XMLHttpRequest`, or an `Accept` header that prefers `application/json`): ```cpp class AppAuthPolicy : public oatpp_authkit::IAuthPolicy { public: std::optional unauthenticatedRedirect(const std::string& path) override { return "/?next=" + oatpp_authkit::AuthInterceptor::urlEncode(path); } }; ``` Returning `std::nullopt` (the default) preserves the legacy JSON behaviour for all responses. ## Tests ```bash cmake -B build -DOATPP_AUTHKIT_BUILD_TESTS=ON cmake --build build ctest --test-dir build --output-on-failure ``` Tests are off by default so consumers pulling the library in via `FetchContent` don't pay the cost. ## Roadmap - **v0.2** — `AuthInterceptor` + `requireAdmin` ported onto three seams (`IAuthBackend`, `IAuthPolicy`, `IRuntimeConfig`) so consumers plug in their own user store, public-path list, and admin role set without forking the interceptor. - **Later** — session cookie helpers, API-key rotation, re-encryption migration. See `docs/security-baseline.md` for language-neutral CSP / rate-limit / body-size constants that non-C++ consumers can re-implement directly.