1#![warn(missing_docs)]
7
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10
11pub mod pkce;
13
14pub mod strategy;
16
17pub mod error;
19pub use error::AuthError;
20
21pub mod state;
23pub use state::{AuthResult, Identity, OAuth2State, OAuthToken};
24
25pub mod discovery;
27
28pub mod session;
30pub use session::{Session, SessionConfig, SessionStore};
31
32#[cfg(any(feature = "webauthn", feature = "totp"))]
34pub mod store;
35#[cfg(any(feature = "webauthn", feature = "totp"))]
36pub use store::CredentialStore;
37
38#[cfg(feature = "webauthn")]
40pub mod webauthn;
41
42#[cfg(feature = "totp")]
44pub mod totp;
45
46#[cfg(feature = "webauthn")]
48pub trait WebAuthnStarter: Send + Sync {
49 fn start_authentication(
51 &self,
52 passkeys: &[webauthn_rs::prelude::Passkey],
53 ) -> Result<
54 (
55 webauthn_rs::prelude::RequestChallengeResponse,
56 webauthn_rs::prelude::PasskeyAuthentication,
57 ),
58 AuthError,
59 >;
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(tag = "type", content = "data")]
65pub enum AuthInput {
66 Password {
68 identifier: String,
70 password: String,
72 },
73 OAuthCode {
75 code: String,
77 code_verifier: Option<String>,
79 },
80 Token(String),
82 Custom(serde_json::Value),
84 MfaChallenge {
86 mfa_token: String,
88 challenge_input: Box<AuthInput>,
90 },
91 #[cfg(feature = "webauthn")]
93 WebAuthnAuthentication {
94 user_id: String,
96 credential_id: String,
98 client_data_json: String,
100 authenticator_data: String,
102 signature: String,
104 user_handle: Option<String>,
106 #[serde(default)]
108 auth_state_json: Option<String>,
109 },
110 #[cfg(feature = "totp")]
112 Totp {
113 user_id: String,
115 code: String,
117 },
118}
119
120#[async_trait]
122pub trait AuthMethod: Send + Sync {
123 fn name(&self) -> &str;
125
126 #[cfg(feature = "webauthn")]
128 fn as_webauthn_starter(&self) -> Option<&dyn WebAuthnStarter> {
129 None
130 }
131
132 async fn authenticate(&self, input: AuthInput) -> Result<Identity, AuthError>;
134
135 async fn has_enrolled(&self, _user_id: &str) -> Result<bool, AuthError> {
138 Ok(false)
139 }
140
141 fn is_mfa_equivalent(&self) -> bool {
145 false
146 }
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ProviderConfig {
152 pub id: String,
154 pub name: String,
156 pub extra: std::collections::HashMap<String, String>,
158}
159
160#[async_trait]
163pub trait Provider: Send + Sync {
164 async fn config(&self) -> ProviderConfig;
166}
167
168#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
170pub enum SameSite {
171 Lax,
173 Strict,
175 None,
177}
178
179#[async_trait]
181pub trait OAuthProvider: Provider {
182 fn provider_id(&self) -> &str;
184
185 fn get_authorization_url(
187 &self,
188 state: &str,
189 scopes: &[&str],
190 code_challenge: Option<&str>,
191 nonce: Option<&str>,
192 ) -> String;
193
194 async fn exchange_code_for_identity(
196 &self,
197 code: &str,
198 code_verifier: Option<&str>,
199 nonce: Option<&str>,
200 ) -> Result<(Identity, OAuthToken), AuthError>;
201
202 async fn refresh_token(&self, _refresh_token: &str) -> Result<OAuthToken, AuthError> {
204 Err(AuthError::Provider(
205 "Token refresh not supported by this provider".into(),
206 ))
207 }
208
209 async fn revoke_token(&self, _token: &str) -> Result<(), AuthError> {
211 Err(AuthError::Provider(
212 "Token revocation not supported by this provider".into(),
213 ))
214 }
215}
216
217#[async_trait]
219pub trait CredentialsProvider: Send + Sync {
220 type Credentials;
222
223 async fn authenticate(&self, creds: Self::Credentials) -> Result<Identity, AuthError>;
225}
226
227#[async_trait]
229pub trait UserMapper: Send + Sync {
230 type LocalUser: Send + Sync;
232
233 async fn map_user(&self, identity: &Identity) -> Result<Self::LocalUser, AuthError>;
236}
237
238#[async_trait]
240pub trait ErasedOAuthFlow: Send + Sync {
241 fn provider_id(&self) -> String;
243 fn initiate_login(
245 &self,
246 scopes: &[&str],
247 pkce_challenge: Option<&str>,
248 ) -> (String, OAuth2State);
249 async fn finalize_login(
251 &self,
252 code: &str,
253 received_state: &str,
254 expected_state: &OAuth2State,
255 ) -> Result<(Identity, OAuthToken), AuthError>;
256}
257
258#[async_trait]
259impl UserMapper for () {
260 type LocalUser = ();
261 async fn map_user(&self, _identity: &Identity) -> Result<Self::LocalUser, AuthError> {
262 Ok(())
263 }
264}
265
266#[async_trait]
267impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for std::sync::Arc<T> {
268 fn provider_id(&self) -> String {
269 (**self).provider_id()
270 }
271
272 fn initiate_login(
273 &self,
274 scopes: &[&str],
275 pkce_challenge: Option<&str>,
276 ) -> (String, OAuth2State) {
277 (**self).initiate_login(scopes, pkce_challenge)
278 }
279
280 async fn finalize_login(
281 &self,
282 code: &str,
283 received_state: &str,
284 expected_state: &OAuth2State,
285 ) -> Result<(Identity, OAuthToken), AuthError> {
286 (**self)
287 .finalize_login(code, received_state, expected_state)
288 .await
289 }
290}
291
292#[async_trait]
293impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for Box<T> {
294 fn provider_id(&self) -> String {
295 (**self).provider_id()
296 }
297
298 fn initiate_login(
299 &self,
300 scopes: &[&str],
301 pkce_challenge: Option<&str>,
302 ) -> (String, OAuth2State) {
303 (**self).initiate_login(scopes, pkce_challenge)
304 }
305
306 async fn finalize_login(
307 &self,
308 code: &str,
309 received_state: &str,
310 expected_state: &OAuth2State,
311 ) -> Result<(Identity, OAuthToken), AuthError> {
312 (**self)
313 .finalize_login(code, received_state, expected_state)
314 .await
315 }
316}