Expand description
Concrete session-token codecs — origin-side.
Three impls of the cheers_core capability traits:
PasetoV4Codec— PASETO v4.local (XChaCha20-Poly1305 + BLAKE2b). Encrypted and authenticated claims, opaque to whoever holds the cookie. Symmetric, so it impls bothTokenMinterandTokenVerifier— origin-only verification (decrypting needs the secret key).HmacBlobCodec— JSON payload + HMAC-SHA256 tag, base64url-encoded. Cleartext-readable; authenticated, not encrypted. Symmetric (both traits).PasetoV4SecretMinter— PASETO v4.public (Ed25519) mint only. The matching verify-onlyPasetoV4PublicVerifierlives incheers-verify; the origin signs, the edge verifies.
The symmetric codecs impl both traits on one type, so they MUST live here in
cheers-server, never in cheers-verify — putting them there would re-grant
mint to the edge through the back door.
use cheers_core::{Claims, DeviceBinding, DeviceId, TokenMinter, TokenVerifier, UserId};
use cheers_server::HmacBlobCodec;
let codec = HmacBlobCodec::new([0u8; 32]);
let claims = Claims::new(
UserId::new("u1"),
DeviceId::new("d1"),
DeviceBinding::Passkey,
1_700_000_000,
i64::MAX, // far-future expiry for the doctest
);
let token = codec.mint(&claims).unwrap();
let back = codec.verify(&token).unwrap();
assert_eq!(back, claims);@yah:ticket(R020-T15, “McpClaims mint/verify helpers on PasetoV4 codecs (cheers-server)”) @yah:assignee(agent:claude) @yah:at(2026-06-04T18:25:48Z) @yah:status(review) @yah:phase(P2) @yah:parent(R020) @yah:verify(“cargo test -p cheers-server (round-trip: mint_mcp → verify_mcp_at returns the same McpClaims; expired token → CodecError::Expired; wrong-key sig → SignatureMismatch).”) @yah:verify(“cargo test -p cheers-verify (no regression: existing Claims-typed verify path still passes).”) @yah:gotcha(“TokenMinter::mint / TokenVerifier::verify_at are hard-coded to cheers_core::Claims (session contract). McpClaims is a peer shape (R020-F3) — do NOT change the existing trait signatures; add sibling inherent methods instead. Generalising the traits is a deeper refactor that should wait for a second MCP-style claim shape to land.”) @arch:see(.yah/docs/working/mcp-auth-and-ownership.md) @yah:next(“R020-F6/F7 mint paths now have: (a) expand_scopes (F5) → (b) validate_grant per expanded Scope (defense in depth) → (c) McpClaims::new + with_* builders (F3) → (d) PasetoV4SecretMinter::mint_mcp (this ticket). All ingredients present — F6 (user-passkey) and F7 (camp-bootstrap) compose them differently around the principal lookup + bundle resolution.”) @yah:next(“When R020-T16 (Bearer/McpClaims middleware in cheers-axum) lands, the edge will call verify_mcp_at and surface Malformed for cross-shape tokens — wire it as a 401, not 500.”) @yah:handoff(“Landed: PasetoV4SecretMinter::mint_mcp(&McpClaims) -> Result<String, CodecError> in cheers-server/src/codec.rs as an inherent sibling to TokenMinter::mint — same v4.public sign path. PasetoV4PublicVerifier::verify_mcp_at(token, now) -> Result<McpClaims, CodecError> in cheers-verify/src/public_verifier.rs.”) @yah:handoff(“Key separation is structural: MCP_CLAIM_KEY = "mcp" (exported from cheers-verify so the server-side minter uses the same identifier) vs the session-claim "cheers". Cross-shape mix-ups surface as CodecError::Malformed at the get_claim() step — verified by mcp_token_not_verifiable_under_session_verify_at and session_token_not_verifiable_under_mcp_verify_at.”) @yah:handoff(“Kept TokenMinter/TokenVerifier trait signatures untouched (still Claims-typed) — the generalisation refactor waits for a 2nd MCP-style claim shape, per the in-source gotcha.”) @yah:handoff(“Caller owns iat/exp in McpClaims (parity with the Claims path); TTL policy belongs upstream (R020-F6/F7’s mint paths will set per-call lifetime per the doc §TTLs).”) @yah:handoff(“Owns is #[non_exhaustive] in cheers-core so the test fixture uses Owns::default() + field assignment (struct literal blocked outside the defining crate).”) @yah:handoff(“Verified GREEN: cargo test -p cheers-core (no change), cheers-server 49 → 55 unit tests incl. 6 new mcp-codec tests, cheers-verify unchanged. Bundle work from R020-F5 still green.”)
Structs§
- Hmac
Blob Codec - HMAC-SHA256 blob codec. Cleartext payload, authenticated only.
- Paseto
V4Codec - PASETO v4.local codec. Encrypts and authenticates with a 32-byte symmetric key.
- Paseto
V4Secret Minter - PASETO v4.public minter — signs claims with an Ed25519 secret key.