chore: record pre-existing uncommitted repository state
Snapshot of the in-flight state that already existed, identically, in both this worktree and the main checkout before this session began: the initial HTTP Client platform implementation (previously untracked), the redis-lab removal, and the JPA / object-storage / notification integration work. Kept separate from this session's HTTP Client review response, which lands in the following commit, so the two bodies of work stay reviewable apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1a3b560678
commit
5f10b791d3
@@ -0,0 +1,79 @@
|
||||
# Redis Session HTTP Boundary Design
|
||||
|
||||
**Date:** 2026-08-02
|
||||
**Status:** approved by the user's instruction to apply the reviewed P1/P2 work sequentially
|
||||
**Scope:** composition of inbound browser-session security with the outbound versioned Redis session repository
|
||||
|
||||
## Context
|
||||
|
||||
Inbound-web unit contracts prove CSRF, fixation, hardened cookie settings, and primitive security
|
||||
snapshot behavior with `MockHttpSession`/in-memory repositories. Cache-redis contracts prove the
|
||||
versioned session repository and Lua semantics against Redis. No test currently crosses the actual
|
||||
Spring Session filter, production SecurityFilterChain, real Redis, and a second application context.
|
||||
|
||||
Putting this test in inbound-web would require a forbidden dependency on the outbound Redis leaf.
|
||||
The composition root already depends on both leaves and owns the `redisCompositionTest` source set,
|
||||
so app-bootstrap is the correct boundary owner.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a tagged `redis-session-http` integration contract under app-bootstrap's existing
|
||||
`redisCompositionTest` source set. Ordinary `redisCompositionTest` excludes the tag. A new explicit
|
||||
`redisSessionHttpIntegrationTest` task includes only that tag, fails on no discovery or any skip,
|
||||
always reruns, pins UTC, and passes the checked-in Redis image registry path.
|
||||
|
||||
The task is deliberately not attached to ordinary local `check`, because it requires Docker. It is
|
||||
added to the existing release-blocking `redis-standalone` CI job, which is the Docker-capable Redis
|
||||
lane. Docker availability and container startup are attempted directly; no condition, assumption,
|
||||
or environment flag may convert absence into a skip.
|
||||
|
||||
The test loads `redis.approved.image` from `src/gradle/redis-test-images.properties` and rejects an
|
||||
unpinned reference. It creates an ephemeral CA/server certificate and a named, least-privilege ACL
|
||||
user, then connects with TLS, full hostname verification, and explicit CA trust. A
|
||||
runtime-generated Redis password and 32-byte HMAC are supplied through caller-owned versioned
|
||||
material; no secret value is checked in, passed on the Redis command line, or logged. Missing
|
||||
Docker or OpenSSL is a hard failure, not a skip.
|
||||
|
||||
The custom source set needs the Spring Session API at compile time. App-bootstrap therefore adds
|
||||
`spring-session-core` only to `redisCompositionTestImplementation`; the existing version is reused
|
||||
and the lockfile records the new custom compile configuration without changing a dependency
|
||||
version.
|
||||
|
||||
## HTTP/Session Contract
|
||||
|
||||
1. A state-changing request without CSRF is 403.
|
||||
2. Accessing the CSRF endpoint emits the configured Secure, non-HttpOnly CSRF cookie.
|
||||
3. Login with matching cookie/header creates only the bounded primitive authentication snapshot.
|
||||
4. The session cookie is host-only, Secure, HttpOnly, SameSite=Lax, path `/`, and session-scoped.
|
||||
5. After the first web context closes, a second independent context restores `/whoami` from the
|
||||
same cookie through real Redis.
|
||||
6. Logout force-revokes/tombstones the session; the old cookie is unauthenticated and a previously
|
||||
loaded stale session object cannot save over the tombstone.
|
||||
7. If Redis becomes unavailable during session lookup, the request fails closed before the
|
||||
protected controller and the surfaced exception graph contains only the repository's fixed
|
||||
availability message, not endpoint/password/session material.
|
||||
|
||||
The RED run exposed two production composition gaps which are part of this boundary:
|
||||
|
||||
- the primitive security-context repository must wrap the response and persist before response
|
||||
commit, otherwise a successful response can commit before the first session is created;
|
||||
- the API security chain disables Spring Security's request cache, otherwise an unauthenticated
|
||||
request stores a `DefaultSavedRequest` framework graph that the primitive session codec correctly
|
||||
rejects.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Inbound-web remains provider-neutral and has no outbound dependency.
|
||||
- Cache-redis keeps Redis keys, Lua, codec, HMAC, and tombstone policy private.
|
||||
- App-bootstrap assembles both adapters only for a cross-module composition contract.
|
||||
- No production dependency edge or dependency version changes; only a custom-test compile
|
||||
configuration is added to the existing lock entry.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Redis Sentinel/Cluster sessions (production activation explicitly rejects them today);
|
||||
- browser-engine proof of SameSite behavior;
|
||||
- credential/certificate rotation qualification (the fixture still uses mandatory TLS, full
|
||||
hostname verification, explicit trust, and a named ACL user);
|
||||
- attaching Docker work to ordinary `check`;
|
||||
- staging, commit, amend, or push by an agent.
|
||||
Reference in New Issue
Block a user