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#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(tag = "type", content = "data")]
49pub enum AuthInput {
50 Password {
52 identifier: String,
54 password: String,
56 },
57 OAuthCode {
59 code: String,
61 code_verifier: Option<String>,
63 },
64 Token(String),
66 Custom(serde_json::Value),
68 MfaChallenge {
70 mfa_token: String,
72 challenge_input: Box<AuthInput>,
74 },
75 #[cfg(feature = "webauthn")]
77 WebAuthnAuthentication {
78 user_id: String,
80 credential_id: String,
82 client_data_json: String,
84 authenticator_data: String,
86 signature: String,
88 user_handle: Option<String>,
90 #[serde(default)]
92 auth_state_json: Option<String>,
93 },
94 #[cfg(feature = "totp")]
96 Totp {
97 user_id: String,
99 code: String,
101 },
102}
103
104#[async_trait]
106pub trait AuthMethod: Send + Sync {
107 fn name(&self) -> &str;
109
110 async fn authenticate(&self, input: AuthInput) -> Result<Identity, AuthError>;
112
113 async fn has_enrolled(&self, _user_id: &str) -> Result<bool, AuthError> {
116 Ok(false)
117 }
118
119 fn is_mfa_equivalent(&self) -> bool {
123 false
124 }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ProviderConfig {
130 pub id: String,
132 pub name: String,
134 pub extra: std::collections::HashMap<String, String>,
136}
137
138#[async_trait]
141pub trait Provider: Send + Sync {
142 async fn config(&self) -> ProviderConfig;
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
148pub enum SameSite {
149 Lax,
151 Strict,
153 None,
155}
156
157#[async_trait]
159pub trait OAuthProvider: Provider {
160 fn provider_id(&self) -> &str;
162
163 fn get_authorization_url(
165 &self,
166 state: &str,
167 scopes: &[&str],
168 code_challenge: Option<&str>,
169 nonce: Option<&str>,
170 ) -> String;
171
172 async fn exchange_code_for_identity(
174 &self,
175 code: &str,
176 code_verifier: Option<&str>,
177 nonce: Option<&str>,
178 ) -> Result<(Identity, OAuthToken), AuthError>;
179
180 async fn refresh_token(&self, _refresh_token: &str) -> Result<OAuthToken, AuthError> {
182 Err(AuthError::Provider(
183 "Token refresh not supported by this provider".into(),
184 ))
185 }
186
187 async fn revoke_token(&self, _token: &str) -> Result<(), AuthError> {
189 Err(AuthError::Provider(
190 "Token revocation not supported by this provider".into(),
191 ))
192 }
193}
194
195#[async_trait]
197pub trait CredentialsProvider: Send + Sync {
198 type Credentials;
200
201 async fn authenticate(&self, creds: Self::Credentials) -> Result<Identity, AuthError>;
203}
204
205#[async_trait]
207pub trait UserMapper: Send + Sync {
208 type LocalUser: Send + Sync;
210
211 async fn map_user(&self, identity: &Identity) -> Result<Self::LocalUser, AuthError>;
214}
215
216#[async_trait]
218pub trait ErasedOAuthFlow: Send + Sync {
219 fn provider_id(&self) -> String;
221 fn initiate_login(
223 &self,
224 scopes: &[&str],
225 pkce_challenge: Option<&str>,
226 ) -> (String, OAuth2State);
227 async fn finalize_login(
229 &self,
230 code: &str,
231 received_state: &str,
232 expected_state: &OAuth2State,
233 ) -> Result<(Identity, OAuthToken), AuthError>;
234}
235
236#[async_trait]
237impl UserMapper for () {
238 type LocalUser = ();
239 async fn map_user(&self, _identity: &Identity) -> Result<Self::LocalUser, AuthError> {
240 Ok(())
241 }
242}
243
244#[async_trait]
245impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for std::sync::Arc<T> {
246 fn provider_id(&self) -> String {
247 (**self).provider_id()
248 }
249
250 fn initiate_login(
251 &self,
252 scopes: &[&str],
253 pkce_challenge: Option<&str>,
254 ) -> (String, OAuth2State) {
255 (**self).initiate_login(scopes, pkce_challenge)
256 }
257
258 async fn finalize_login(
259 &self,
260 code: &str,
261 received_state: &str,
262 expected_state: &OAuth2State,
263 ) -> Result<(Identity, OAuthToken), AuthError> {
264 (**self)
265 .finalize_login(code, received_state, expected_state)
266 .await
267 }
268}
269
270#[async_trait]
271impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for Box<T> {
272 fn provider_id(&self) -> String {
273 (**self).provider_id()
274 }
275
276 fn initiate_login(
277 &self,
278 scopes: &[&str],
279 pkce_challenge: Option<&str>,
280 ) -> (String, OAuth2State) {
281 (**self).initiate_login(scopes, pkce_challenge)
282 }
283
284 async fn finalize_login(
285 &self,
286 code: &str,
287 received_state: &str,
288 expected_state: &OAuth2State,
289 ) -> Result<(Identity, OAuthToken), AuthError> {
290 (**self)
291 .finalize_login(code, received_state, expected_state)
292 .await
293 }
294}