Skip to main content

babelforce_manager_sdk/resources/
auth.rs

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