Skip to main content

pas_external/pas_port/
port.rs

1//! `PasAuthPort` — the network boundary at PAS Authorization Server.
2//!
3//! Production adapter: `AuthClient` (in `crate::oauth`).
4//! Test adapter: `MemoryPasAuth` (in `crate::pas_port::memory`, gated
5//! behind the `test-support` Cargo feature).
6
7use std::future::Future;
8
9use crate::error::Error;
10use crate::oauth::{TokenResponse, UserInfo};
11
12/// Minimum surface the SDK refresh + sv flows need from PAS.
13///
14/// Adapters translate transport-level errors into [`PasFailure`].
15/// Once a `PasFailure` is in hand, all SDK policy code is HTTP-free.
16pub trait PasAuthPort: Send + Sync + 'static {
17    /// `POST /oauth/token` with `grant_type=refresh_token` (RFC 6749 §6).
18    fn refresh(
19        &self,
20        refresh_token: &str,
21    ) -> impl Future<Output = Result<TokenResponse, PasFailure>> + Send;
22
23    /// `GET /oauth/userinfo` with `Authorization: Bearer <access_token>`.
24    fn userinfo(
25        &self,
26        access_token: &str,
27    ) -> impl Future<Output = Result<UserInfo, PasFailure>> + Send;
28}
29
30/// Classified PAS-side failure. Single source of truth for the
31/// HTTP-status → cause mapping; only adapters produce these.
32#[non_exhaustive]
33#[derive(Debug, Clone)]
34pub enum PasFailure {
35    /// PAS returned 4xx — credential is dead. Refresh-token expired,
36    /// revoked, or user logged out elsewhere.
37    Rejected { status: u16, detail: String },
38    /// PAS returned 5xx — service is degraded. Retry; do not invalidate.
39    ServerError { status: u16, detail: String },
40    /// HTTP-level transport issue (timeout, TLS, DNS, connect) OR a
41    /// 2xx whose body failed to parse (CDN/proxy garbage).
42    /// Indistinguishable from a true network blip; fail-open paths
43    /// serve cache, fail-closed paths reject.
44    Transport { detail: String },
45}
46
47impl PasFailure {
48    /// Lossy conversion to the legacy [`Error::OAuth`] shape, used by
49    /// inherent `AuthClient` methods that retain the v4 signature
50    /// (currently just `exchange_code`).
51    #[must_use]
52    pub fn into_legacy_error(self, operation: &'static str) -> Error {
53        match self {
54            Self::Rejected { status, detail } | Self::ServerError { status, detail } => {
55                Error::OAuth { operation, status: Some(status), detail }
56            }
57            Self::Transport { detail } => Error::OAuth { operation, status: None, detail },
58        }
59    }
60}