1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//! M35 — jti replay defense port (RFC_2026-05-04_jwt-full-adoption Phase 5).
//!
//! `ReplayDefense` is an *engine-facing* port (deep-module discipline):
//! the engine sees one method (`check_and_record`); concrete adapters
//! own the substrate-specific composition (KVRocks `SET NX EX`, Postgres
//! unique-constraint + retention sweeper, etc).
//!
//! ── Atomicity contract (TOCTOU defense) ─────────────────────────────────
//!
//! `check_and_record` is intentionally a *single* atomic primitive rather
//! than the split pair `check + record`. A split shape leaves a window
//! where two simultaneous verifies of the same jti both observe "not
//! seen" before either records. The atomic primitive maps directly to
//! the substrate's compare-and-set / set-if-absent operation, eliminating
//! the race.
//!
//! ── Failure-mode contract (fail-closed) ────────────────────────────────
//!
//! Adapters MUST NOT translate substrate transient failures into "admit"
//! — a KVRocks outage during a replay attack would otherwise let every
//! replay through. The error variants below distinguish the two
//! conditions so the engine maps each to its own `AuthError` and audit
//! signal.
//!
//! ── Phase 10 split (RFC §6.11) ──────────────────────────────────────────
//!
//! This trait moves to `access_token::ReplayDefense` in Phase 10 D1.
//! id_token does not import — id tokens are not bearers (see
//! STANDARDS_JWT_DETAILS_MITIGATION_PPOPPO line 225 "M35-M38 inheritance"
//! exclusion).
use Duration;
/// Atomic check-and-record over a per-token uniqueness key.
///
/// `jti_hash` is the SHA-256 prefix of the raw token (per
/// STANDARDS_JWT_DETAILS_MITIGATION_PPOPPO §E M35), already hashed by
/// the engine before this call so adapters never see jti-equivalent
/// secret material in their substrate logs.
///
/// `ttl` is `claims.exp - now` at verify time — the replay window is
/// bounded by the token's own admissibility window, not a fixed cache
/// TTL. Adapters set the substrate key's expiry to this value so a key
/// outliving its token is impossible.
/// Failure modes from a `ReplayDefense` substrate call.
///
/// Two variants — adapters MUST NOT collapse them. `Replayed` is an
/// *attack signal* (someone presented the same jti twice within TTL);
/// `Transient` is an *infrastructure signal* (substrate unreachable).
/// Audit log routing differs (security incident vs ops alert), and
/// the engine maps each to its own `AuthError` variant.