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