Skip to main content

soma_auth/upstream/
types.rs

1//! Shared types for outbound upstream OAuth.
2
3use serde::Serialize;
4use thiserror::Error;
5
6/// Stable error kinds for upstream OAuth flows.
7///
8/// These must be kept in sync with `docs/dev/ERRORS.md`.
9#[derive(Debug, Error)]
10pub enum OauthError {
11    /// Refresh token was rejected (`invalid_grant`) or decryption failed after key
12    /// rotation.  User must re-initiate the authorization flow.
13    #[error("oauth_needs_reauth: {0}")]
14    NeedsReauth(String),
15
16    /// Callback state is missing, expired, replayed, or bound to a different
17    /// subject / upstream.
18    #[allow(dead_code)]
19    #[error("oauth_state_invalid: {0}")]
20    StateInvalid(String),
21
22    /// Upstream AS refused the `resource` parameter or issued a token with the
23    /// wrong audience (RFC 8707).
24    #[allow(dead_code)]
25    #[error("oauth_resource_mismatch: {0}")]
26    ResourceMismatch(String),
27
28    /// AS metadata `issuer` did not match the discovered AS URL (RFC 8414 ยง3.3).
29    #[error("oauth_issuer_mismatch: {0}")]
30    IssuerMismatch(String),
31
32    /// AS only offered `plain` PKCE or omitted `code_challenge_methods_supported`.
33    #[error("oauth_unsupported_method: {0}")]
34    UnsupportedMethod(String),
35
36    /// Internal / configuration errors that are not caller-recoverable.
37    #[error("internal_error: {0}")]
38    Internal(String),
39}
40
41impl OauthError {
42    /// Stable `kind` string for structured log / envelope fields.
43    pub fn kind(&self) -> &'static str {
44        match self {
45            Self::NeedsReauth(_) => "oauth_needs_reauth",
46            Self::StateInvalid(_) => "oauth_state_invalid",
47            Self::ResourceMismatch(_) => "oauth_resource_mismatch",
48            Self::IssuerMismatch(_) => "oauth_issuer_mismatch",
49            Self::UnsupportedMethod(_) => "oauth_unsupported_method",
50            Self::Internal(_) => "internal_error",
51        }
52    }
53
54    /// Transport-neutral HTTP status code for this error.
55    ///
56    /// Returns a bare `u16` rather than `axum::http::StatusCode` so the upstream
57    /// OAuth runtime carries no transport dependency; the product binary maps it
58    /// onto its own response type at the route boundary.
59    #[allow(dead_code)]
60    #[must_use]
61    pub const fn http_status_code(&self) -> u16 {
62        match self {
63            Self::NeedsReauth(_) => 401,
64            Self::StateInvalid(_) => 400,
65            Self::ResourceMismatch(_) | Self::IssuerMismatch(_) | Self::UnsupportedMethod(_) => 502,
66            Self::Internal(_) => 500,
67        }
68    }
69}
70
71/// Return value of [`crate::upstream::manager::UpstreamOauthManager::begin_authorization`].
72#[derive(Debug, Serialize)]
73pub struct BeginAuthorization {
74    /// URL the operator's browser must navigate to.
75    pub authorization_url: String,
76}