Skip to main content

alibi_plugins/oauth_provider/
initial_access_token.rs

1//! Operator authorization for RFC 7591 initial access tokens.
2use alibi_core::endpoint::{EndpointError, is_endpoint_api_error};
3use alibi_core::{AuthError, Headers};
4use async_trait::async_trait;
5use serde_json::{Map, Value};
6
7/// The actual credential, caller headers and parsed registration request, before defaults.
8#[derive(Debug)]
9pub struct OAuthInitialAccessTokenInput<'a> {
10    pub initial_access_token: &'a str,
11    pub headers: &'a Headers,
12    /// Unknown/server-assigned fields are stripped; effective registration defaults are not applied.
13    pub client_metadata: &'a Map<String, Value>,
14}
15
16/// Admission independent of a user session. An optional application reference owns the client.
17#[derive(Debug, Clone, Default)]
18pub struct OAuthInitialAccessTokenAuthorization {
19    pub reference_id: Option<String>,
20}
21
22/// Explicit endpoint failures retain their status, body and headers. Ordinary application
23/// failures are deliberately hidden behind the registration endpoint's OAuth server error.
24#[derive(Debug)]
25pub enum OAuthInitialAccessTokenError {
26    Endpoint(EndpointError),
27    Application(AuthError),
28}
29
30impl From<EndpointError> for OAuthInitialAccessTokenError {
31    fn from(error: EndpointError) -> Self {
32        Self::Endpoint(error)
33    }
34}
35
36impl From<AuthError> for OAuthInitialAccessTokenError {
37    fn from(error: AuthError) -> Self {
38        if is_endpoint_api_error(&error) {
39            Self::Endpoint(error.into())
40        } else {
41            Self::Application(error)
42        }
43    }
44}
45
46impl std::fmt::Display for OAuthInitialAccessTokenError {
47    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            Self::Endpoint(error) => error.fmt(formatter),
50            Self::Application(error) => error.fmt(formatter),
51        }
52    }
53}
54
55impl std::error::Error for OAuthInitialAccessTokenError {
56    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
57        Some(match self {
58            Self::Endpoint(error) => error,
59            Self::Application(error) => error,
60        })
61    }
62}
63
64/// Application-owned async initial-access-token policy. `None` rejects the credential.
65#[async_trait]
66pub trait OAuthInitialAccessTokenValidator: Send + Sync + std::fmt::Debug {
67    async fn validate(
68        &self,
69        input: OAuthInitialAccessTokenInput<'_>,
70    ) -> Result<Option<OAuthInitialAccessTokenAuthorization>, OAuthInitialAccessTokenError>;
71}
72
73impl super::OAuthProviderPlugin {
74    pub(super) async fn authorize_initial_access_token(
75        &self,
76        input: OAuthInitialAccessTokenInput<'_>,
77    ) -> alibi_core::AuthResult<
78        Result<OAuthInitialAccessTokenAuthorization, alibi_core::endpoint::EndpointResponse>,
79    > {
80        use alibi_core::endpoint::EndpointResponse;
81
82        let validation = match &self.config.initial_access_token_validator {
83            Some(validator) => validator.validate(input).await,
84            None => Ok(None),
85        };
86        let response = match validation {
87            Ok(Some(authorization)) => return Ok(Ok(authorization)),
88            Ok(None) => super::clients::status_protocol_error(
89                401,
90                "invalid_token",
91                "Invalid initial access token",
92            )?
93            .with_header("WWW-Authenticate", "Bearer error=\"invalid_token\""),
94            Err(OAuthInitialAccessTokenError::Application(_)) => {
95                super::clients::status_protocol_error(
96                    500,
97                    "server_error",
98                    "Initial access token validation failed",
99                )?
100            }
101            Err(OAuthInitialAccessTokenError::Endpoint(error)) => {
102                let mut response = EndpointResponse::error(error.error);
103                if let Some(body) = error.body {
104                    response = response.with_error_body(*body);
105                }
106                if error.body_absent {
107                    response = response.without_error_body();
108                }
109                if let Some(headers) = error.headers {
110                    response.merge_headers(headers);
111                }
112                response
113            }
114        };
115        Ok(Err(response))
116    }
117}