Skip to main content

babelforce_manager_sdk/resources/
auth.rs

1use crate::error::{map_auth_err, ManagerError};
2use crate::gen::auth::apis::configuration::Configuration;
3use crate::gen::auth::apis::o_auth_api as api;
4use crate::gen::auth::models;
5use crate::retry::{with_retry, RetryPolicy};
6use crate::token::SharedCfg;
7
8/// OAuth 2.0 endpoints — `/oauth/authorize`, `/oauth/token`, `/oauth/revoke`.
9///
10/// These authenticate via client credentials carried in the request itself (per RFC 6749/7009),
11/// independent of the client's configured auth — `token` is how you *obtain* a bearer token.
12pub struct AuthResource {
13    pub(crate) cfg: SharedCfg<Configuration>,
14    pub(crate) retry: RetryPolicy,
15}
16
17/// Optional parameters for the OAuth token endpoint. Set the fields relevant to your grant type
18/// (e.g. `code` + `redirect_uri` + `code_verifier` for `authorization_code`, `refresh_token` for
19/// `refresh_token`, `username` + `password` for `password`, `client_id` + `client_secret` for
20/// `client_credentials`); leave the rest at their default `None`.
21///
22/// The [`Debug`](std::fmt::Debug) impl redacts the secret-bearing fields (`code`, `code_verifier`,
23/// `refresh_token`, `password`, `client_secret`), showing only whether each is set.
24#[derive(Clone, Default)]
25pub struct TokenRequest {
26    /// Authorization code from the redirect (`authorization_code` grant).
27    pub code: Option<String>,
28    /// Redirect URI registered for your application (must match the authorize request).
29    pub redirect_uri: Option<String>,
30    /// PKCE code verifier matching the `code_challenge` sent to `/oauth/authorize`.
31    pub code_verifier: Option<String>,
32    /// Refresh token to exchange (`refresh_token` grant).
33    pub refresh_token: Option<String>,
34    /// Resource-owner username (`password` grant).
35    pub username: Option<String>,
36    /// Resource-owner password (`password` grant).
37    pub password: Option<String>,
38    /// OAuth2 application client id.
39    pub client_id: Option<String>,
40    /// OAuth2 application client secret (`client_credentials` grant).
41    pub client_secret: Option<String>,
42    /// Space-separated scopes to request.
43    pub scope: Option<String>,
44    /// e.g. `"offline"` to request a refresh token alongside the access token.
45    pub access_type: Option<String>,
46}
47
48impl std::fmt::Debug for TokenRequest {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        // Redact secret-bearing fields; show only presence (`Some("***")` / `None`).
51        let redact = |o: &Option<String>| o.as_ref().map(|_| "***");
52        f.debug_struct("TokenRequest")
53            .field("code", &redact(&self.code))
54            .field("redirect_uri", &self.redirect_uri)
55            .field("code_verifier", &redact(&self.code_verifier))
56            .field("refresh_token", &redact(&self.refresh_token))
57            .field("username", &self.username)
58            .field("password", &redact(&self.password))
59            .field("client_id", &self.client_id)
60            .field("client_secret", &redact(&self.client_secret))
61            .field("scope", &self.scope)
62            .field("access_type", &self.access_type)
63            .finish()
64    }
65}
66
67impl AuthResource {
68    /// Exchange a grant for tokens at the OAuth token endpoint. `grant_type` is e.g.
69    /// `authorization_code`, `refresh_token`, `password`, or `client_credentials`.
70    pub async fn token(
71        &self,
72        grant_type: &str,
73        req: TokenRequest,
74    ) -> Result<models::OAuthTokenResponse, ManagerError> {
75        let cfg = self.cfg.get().await?;
76        let cfg = cfg.as_ref();
77        with_retry(&self.retry, false, || {
78            api::token(
79                cfg,
80                grant_type,
81                req.code.as_deref(),
82                req.redirect_uri.as_deref(),
83                req.code_verifier.as_deref(),
84                req.refresh_token.as_deref(),
85                req.username.as_deref(),
86                req.password.as_deref(),
87                req.client_id.as_deref(),
88                req.client_secret.as_deref(),
89                req.scope.as_deref(),
90                req.access_type.as_deref(),
91            )
92        })
93        .await
94        .map_err(map_auth_err)
95    }
96
97    /// Revoke a token (RFC 7009).
98    pub async fn revoke(
99        &self,
100        token: &str,
101        token_type_hint: Option<&str>,
102        client_id: Option<&str>,
103        client_secret: Option<&str>,
104    ) -> Result<(), ManagerError> {
105        let cfg = self.cfg.get().await?;
106        let cfg = cfg.as_ref();
107        with_retry(&self.retry, false, || {
108            api::revoke(cfg, token, token_type_hint, client_id, client_secret)
109        })
110        .await
111        .map_err(map_auth_err)?;
112        Ok(())
113    }
114
115    /// The OAuth authorization endpoint (consent / redirect). Returns the raw response body (the
116    /// authorization redirect / consent page). `state` and the PKCE `code_challenge` are optional.
117    /// A direct pass-through of the standard OAuth authorize query parameters.
118    #[allow(clippy::too_many_arguments)]
119    pub async fn authorize(
120        &self,
121        response_type: &str,
122        client_id: &str,
123        redirect_uri: &str,
124        scope: &str,
125        state: Option<&str>,
126        code_challenge: Option<&str>,
127        code_challenge_method: Option<&str>,
128    ) -> Result<String, ManagerError> {
129        let cfg = self.cfg.get().await?;
130        let cfg = cfg.as_ref();
131        with_retry(&self.retry, true, || {
132            api::authorize(
133                cfg,
134                response_type,
135                client_id,
136                redirect_uri,
137                scope,
138                state,
139                code_challenge,
140                code_challenge_method,
141            )
142        })
143        .await
144        .map_err(map_auth_err)
145    }
146}