1use std::{collections::HashSet, fmt::Debug, time::Duration};
2
3use chrono::{DateTime, Utc};
4use oauth2::{
5 basic::BasicTokenType, AccessToken, CsrfToken, PkceCodeVerifier, RefreshToken, TokenResponse,
6};
7use serde::{Deserialize, Serialize};
8
9pub trait AuthenticationState: private::Sealed {}
11impl AuthenticationState for Token {}
12impl AuthenticationState for Unauthenticated {}
13
14pub trait AuthFlow: private::Sealed + Debug {}
15impl AuthFlow for AuthCodeFlow {}
16impl AuthFlow for AuthCodePkceFlow {}
17impl AuthFlow for ClientCredsFlow {}
18impl AuthFlow for UnknownFlow {}
19
20impl Debug for AuthCodeFlow {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 f.debug_struct("AuthCodeFlow")
23 .field("csrf_token", &"[redacted]")
24 .finish()
25 }
26}
27
28impl Debug for AuthCodePkceFlow {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("AuthCodePkceFlow")
31 .field("csrf_token", &"[redacted]")
32 .field("pkce_verifier", &"[redacted]")
33 .finish()
34 }
35}
36
37pub trait Authorised: private::Sealed {}
38impl Authorised for AuthCodeFlow {}
39impl Authorised for AuthCodePkceFlow {}
40
41impl Authorised for UnknownFlow {}
45
46mod private {
49 pub trait Sealed {}
50
51 impl Sealed for super::Token {}
52 impl Sealed for super::Unauthenticated {}
53 impl Sealed for super::AuthCodeFlow {}
54 impl Sealed for super::AuthCodePkceFlow {}
55 impl Sealed for super::ClientCredsFlow {}
56 impl Sealed for super::UnknownFlow {}
57}
58
59#[derive(Clone, Debug, Default)]
65pub struct Scopes(pub(crate) HashSet<oauth2::Scope>);
66
67impl<I> From<I> for Scopes
68where
69 I: IntoIterator,
70 I::Item: Into<String>,
71{
72 fn from(value: I) -> Self {
73 let scopes = value
74 .into_iter()
75 .map(|i| oauth2::Scope::new(i.into()))
76 .collect();
77 Self(scopes)
78 }
79}
80
81impl Scopes {
82 pub fn new<I>(scopes: I) -> Self
87 where
88 I: IntoIterator,
89 I::Item: Into<String>,
90 {
91 Self::from(scopes)
92 }
93
94 fn inner_vec(self) -> Vec<oauth2::Scope> {
95 self.0.into_iter().collect()
96 }
97}
98
99#[derive(Clone, Debug, Deserialize, Serialize)]
101pub struct Token {
102 pub(crate) access_token: AccessToken,
104 pub(crate) refresh_token: Option<RefreshToken>,
106 pub expires_in: u64,
108
109 #[serde(default = "Utc::now")]
110 pub created_at: DateTime<Utc>,
112
113 #[serde(skip)]
114 pub expires_at: DateTime<Utc>,
116
117 #[serde(deserialize_with = "oauth2::helpers::deserialize_untagged_enum_case_insensitive")]
118 pub(crate) token_type: BasicTokenType,
119 #[serde(rename = "scope")]
120 #[serde(deserialize_with = "oauth2::helpers::deserialize_space_delimited_vec")]
121 #[serde(serialize_with = "oauth2::helpers::serialize_space_delimited_vec")]
122 #[serde(skip_serializing_if = "Option::is_none")]
123 #[serde(default)]
124 pub(crate) scopes: Option<Vec<oauth2::Scope>>,
125}
126
127#[doc = include_str!("docs/internal_implementation_details.md")]
129#[derive(Clone, Copy, Debug)]
130pub struct Unauthenticated;
131
132pub struct AuthCodeFlow {
141 pub(crate) csrf_token: CsrfToken,
142}
143
144pub struct AuthCodePkceFlow {
153 pub(crate) csrf_token: CsrfToken,
154 pub(crate) pkce_verifier: Option<PkceCodeVerifier>,
155}
156
157#[derive(Clone, Copy, Debug)]
166pub struct ClientCredsFlow;
167
168#[derive(Clone, Copy, Debug)]
172pub struct UnknownFlow;
173
174impl Token {
175 pub fn new(
177 access_token: impl Into<String>,
178 refresh_token: Option<&str>,
179 created_at: DateTime<Utc>,
180 expires_in: u64,
181 scopes: Option<Scopes>,
182 ) -> Self {
183 let access_token = AccessToken::new(access_token.into());
184 let refresh_token = refresh_token.map(|t| RefreshToken::new(t.to_owned()));
185 let expires_at =
186 created_at + chrono::Duration::seconds(i64::try_from(expires_in).unwrap_or(i64::MAX));
187
188 let scopes = scopes.map(|s| s.inner_vec());
189
190 Self {
191 access_token,
192 refresh_token,
193 expires_in,
194 created_at,
195 expires_at,
196 token_type: BasicTokenType::Bearer,
197 scopes,
198 }
199 }
200
201 pub fn secret(&self) -> &str {
203 self.access_token.secret()
204 }
205
206 pub fn refresh_secret(&self) -> Option<&str> {
209 self.refresh_token.as_ref().map(|t| t.secret().as_str())
210 }
211
212 pub(crate) fn set_timestamps(self) -> Self {
214 let created_at = Utc::now();
215
216 let expires_at = created_at
219 + chrono::Duration::seconds(i64::try_from(self.expires_in).unwrap_or(i64::MAX));
220
221 Self {
222 created_at,
223 expires_at,
224 ..self
225 }
226 }
227
228 pub fn is_expired(&self) -> bool {
230 Utc::now() >= self.expires_at
231 }
232
233 pub fn is_refreshable(&self) -> bool {
235 self.refresh_token.is_some()
236 }
237}
238
239impl TokenResponse<BasicTokenType> for Token {
240 fn access_token(&self) -> &AccessToken {
241 &self.access_token
242 }
243
244 fn token_type(&self) -> &BasicTokenType {
245 &self.token_type
246 }
247
248 fn expires_in(&self) -> Option<Duration> {
249 Some(Duration::from_secs(self.expires_in))
250 }
251
252 fn refresh_token(&self) -> Option<&RefreshToken> {
253 self.refresh_token.as_ref()
254 }
255
256 fn scopes(&self) -> Option<&Vec<oauth2::Scope>> {
257 self.scopes.as_ref()
258 }
259}