ppoppo-token 0.2.0

JWT (RFC 9068, EdDSA) issuance + verification engine for the Ppoppo ecosystem. Single deep module with a small interface (issue, verify) hiding RFC 8725 mitigations M01-M45, JWKS handling, and substrate ports (epoch, session, replay).
Documentation
//! OIDC nonce — opaque RP-minted string, gated by M66.
//!
//! OIDC Core 1.0 §3.1.2.1 + §15.5.2: the RP generates an unguessable
//! per-session value, sends it in the Authentication Request, stores a
//! copy bound to the user's browser session, and compares the id_token's
//! `nonce` claim against the stored copy on token receipt. The engine's
//! M66 gate is the *comparison*; generation/storage live RP-side.
//!
//! ── Type discipline ─────────────────────────────────────────────────────
//!
//! `Nonce` is a newtype over `String` with a non-empty invariant — empty
//! nonces would short-circuit the M66 check trivially (any payload
//! missing `nonce` would equal an empty expected value). Higher entropy
//! requirements (length, character set) are RP-side policy and not
//! enforced here: the engine can't tell whether `"abc123"` is a hash of a
//! 256-bit random or a guess; the RP that minted it knows.
//!
//! Comparison uses plain `==` — the nonce is a public correlator, not a
//! cryptographic secret. The RP holds one copy and the wire carries the
//! other; both halves are observable to anyone who can read the auth
//! request and the id_token. Constant-time comparison would be cargo-
//! culted security for a value with no secrecy contract.

use crate::id_token::AuthError;

/// Opaque nonce value. Construction validates non-emptiness; the inner
/// string is private so callers cannot bypass the invariant by minting
/// `Nonce(String::new())` directly.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Nonce(String);

impl Nonce {
    /// Construct from an RP-stored nonce string. Errors on empty input.
    pub fn new(value: impl Into<String>) -> Result<Self, AuthError> {
        let v = value.into();
        if v.is_empty() {
            return Err(AuthError::NonceConfigEmpty);
        }
        Ok(Self(v))
    }

    /// Borrow the inner string for comparison against the token's
    /// `nonce` claim. `pub(crate)` — only the engine's `check_nonce`
    /// submodule consumes the raw bytes.
    pub(crate) fn as_str(&self) -> &str {
        &self.0
    }
}