Skip to main content

authplane_sdk/
auth.rs

1use reqwest::Client;
2
3use crate::dpop_provider::DpopProvider;
4use crate::metadata::AuthorizationServerMetadata;
5use crate::oauth::{
6    IntrospectionResponse, TokenExchangeOptions, TokenResponse, client_credentials_grant,
7    exchange_token, introspect_token, revoke_token,
8};
9use crate::transport::build_basic_auth_header;
10use crate::{AuthplaneError, FetchSettings};
11
12#[derive(Debug, Clone)]
13pub struct AuthplaneAuth {
14    metadata: AuthorizationServerMetadata,
15    fetch_settings: FetchSettings,
16    http: Client,
17}
18
19impl AuthplaneAuth {
20    pub fn new(
21        metadata: AuthorizationServerMetadata,
22        fetch_settings: FetchSettings,
23        http: Client,
24    ) -> Self {
25        Self {
26            metadata,
27            fetch_settings,
28            http,
29        }
30    }
31
32    pub fn metadata(&self) -> &AuthorizationServerMetadata {
33        &self.metadata
34    }
35
36    /// `client_credentials` grant (RFC 6749 §4.4). Pass `Some(&provider)`
37    /// to attach a DPoP-bound exchange; `None` for the plain bearer path.
38    /// The provider owns the per-origin nonce store, so the RFC 9449 §6.1
39    /// `use_dpop_nonce` retry is transparent across calls.
40    pub async fn client_credentials(
41        &self,
42        client_id: &str,
43        client_secret: &str,
44        scopes: &[String],
45        resources: &[String],
46        dpop: Option<&DpopProvider>,
47    ) -> Result<TokenResponse, AuthplaneError> {
48        let token_endpoint = self.metadata.token_endpoint()?;
49        client_credentials_grant(
50            &self.http,
51            token_endpoint,
52            &build_basic_auth_header(client_id, client_secret),
53            &self.fetch_settings,
54            scopes,
55            resources,
56            dpop,
57        )
58        .await
59    }
60
61    /// `client_credentials` grant using a pre-built `Authorization` header
62    /// value (e.g. from an [`AuthProvider`](crate::auth_provider::AuthProvider)).
63    /// Pass `Some(&provider)` to attach a DPoP-bound exchange; `None` otherwise.
64    pub async fn client_credentials_with_header(
65        &self,
66        auth_header: &str,
67        scopes: &[String],
68        resources: &[String],
69        dpop: Option<&DpopProvider>,
70    ) -> Result<TokenResponse, AuthplaneError> {
71        let token_endpoint = self.metadata.token_endpoint()?;
72        client_credentials_grant(
73            &self.http,
74            token_endpoint,
75            auth_header,
76            &self.fetch_settings,
77            scopes,
78            resources,
79            dpop,
80        )
81        .await
82    }
83
84    /// RFC 8693 token exchange. Pass `Some(&provider)` to attach a DPoP-bound
85    /// exchange at the token endpoint; `None` for the plain bearer path.
86    pub async fn exchange_token(
87        &self,
88        client_id: &str,
89        client_secret: &str,
90        options: &TokenExchangeOptions,
91        dpop: Option<&DpopProvider>,
92    ) -> Result<TokenResponse, AuthplaneError> {
93        let token_endpoint = self.metadata.token_endpoint()?;
94        exchange_token(
95            &self.http,
96            token_endpoint,
97            options,
98            &build_basic_auth_header(client_id, client_secret),
99            &self.fetch_settings,
100            dpop,
101        )
102        .await
103    }
104
105    /// RFC 7662 token introspection. Pass `Some(&provider)` to attach a
106    /// DPoP-bound exchange at the introspection endpoint; `None` otherwise.
107    pub async fn introspect(
108        &self,
109        client_id: &str,
110        client_secret: &str,
111        token: &str,
112        dpop: Option<&DpopProvider>,
113    ) -> Result<IntrospectionResponse, AuthplaneError> {
114        let introspection_endpoint = self.metadata.introspection_endpoint()?;
115        introspect_token(
116            &self.http,
117            introspection_endpoint,
118            token,
119            &build_basic_auth_header(client_id, client_secret),
120            &self.fetch_settings,
121            dpop,
122        )
123        .await
124    }
125
126    /// RFC 7009 token revocation. Pass `Some(&provider)` to attach a DPoP-bound
127    /// exchange at the revocation endpoint; `None` otherwise.
128    pub async fn revoke(
129        &self,
130        client_id: &str,
131        client_secret: &str,
132        token: &str,
133        dpop: Option<&DpopProvider>,
134    ) -> Result<(), AuthplaneError> {
135        let revocation_endpoint = self.metadata.revocation_endpoint()?;
136        revoke_token(
137            &self.http,
138            revocation_endpoint,
139            token,
140            &build_basic_auth_header(client_id, client_secret),
141            &self.fetch_settings,
142            dpop,
143        )
144        .await
145    }
146}