pas-external 0.12.0

Ppoppo Accounts System (PAS) external SDK — OAuth2 PKCE, JWT verification port, Axum middleware, session liveness
Documentation
//! `RefreshOutcome` — typed boundary return for
//! [`super::RelyingParty::refresh`].
//!
//! Symmetric with [`super::Completion`] (the deep return of
//! [`super::RelyingParty::complete`]): both wrap the OAuth wire DTO at
//! the SDK boundary so consumers never touch
//! [`crate::oauth::TokenResponse`] directly.
//!
//! Phase 11.Y added this type. The 0.7.x shape exposed
//! `oauth::TokenResponse` (an OAuth-wire-shaped DTO with `expires_in:
//! Option<u64>`) at the refresh boundary; the typed boundary is
//! deeper — `expires_in: Option<Duration>` lets the consumer drop a
//! manual `Duration::from_secs` mapping at every call site.

use std::time::Duration;

use crate::oauth::TokenResponse;

/// Outcome of a successful PAS refresh-token round-trip.
///
/// Returned by [`super::RelyingParty::refresh`] on the success path.
/// Failures (4xx / 5xx / transport) surface as
/// [`super::RefreshError`] variants.
///
/// All fields except `access_token` are `Option` because:
///
/// - **`refresh_token`** — PAS may or may not rotate the refresh
///   credential per RFC 6749 §6 (rotation is implementation-defined).
///   When `None`, the consumer reuses the existing refresh_token.
/// - **`id_token`** — refresh-grant id_token return is OIDC Core §12
///   "MAY", not "MUST". When present, the consumer can rebuild the
///   `IdAssertion<S>` for sv-axis comparison; when absent, the prior
///   id_token's claims persist with the access_token rotation.
/// - **`expires_in`** — RFC 6749 §5.1 makes this OPTIONAL; the consumer
///   falls back to a sensible default (typically 1h) when PAS omits it.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct RefreshOutcome {
    /// Fresh access_token. Always present on the success path.
    pub access_token: String,
    /// Fresh refresh_token, when PAS rotated the credential.
    pub refresh_token: Option<String>,
    /// Fresh id_token, when PAS included one in the refresh response.
    pub id_token: Option<String>,
    /// Access-token TTL hint, when PAS included `expires_in`.
    pub expires_in: Option<Duration>,
}

impl From<TokenResponse> for RefreshOutcome {
    fn from(t: TokenResponse) -> Self {
        Self {
            access_token: t.access_token,
            refresh_token: t.refresh_token,
            id_token: t.id_token,
            expires_in: t.expires_in.map(Duration::from_secs),
        }
    }
}