#![warn(missing_docs)]
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
pub mod pkce;
pub mod strategy;
pub mod error;
pub use error::AuthError;
pub mod state;
pub use state::{AuthResult, Identity, OAuth2State, OAuthToken};
pub mod discovery;
pub mod session;
pub use session::{Session, SessionConfig, SessionStore};
#[cfg(any(feature = "webauthn", feature = "totp"))]
pub mod store;
#[cfg(any(feature = "webauthn", feature = "totp"))]
pub use store::CredentialStore;
#[cfg(feature = "webauthn")]
pub mod webauthn;
#[cfg(feature = "totp")]
pub mod totp;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum AuthInput {
Password {
identifier: String,
password: String,
},
OAuthCode {
code: String,
code_verifier: Option<String>,
},
Token(String),
Custom(serde_json::Value),
MfaChallenge {
mfa_token: String,
challenge_input: Box<AuthInput>,
},
#[cfg(feature = "webauthn")]
WebAuthnAuthentication {
user_id: String,
credential_id: String,
client_data_json: String,
authenticator_data: String,
signature: String,
user_handle: Option<String>,
#[serde(default)]
auth_state_json: Option<String>,
},
#[cfg(feature = "totp")]
Totp {
user_id: String,
code: String,
},
}
#[async_trait]
pub trait AuthMethod: Send + Sync {
fn name(&self) -> &str;
async fn authenticate(&self, input: AuthInput) -> Result<Identity, AuthError>;
async fn has_enrolled(&self, _user_id: &str) -> Result<bool, AuthError> {
Ok(false)
}
fn is_mfa_equivalent(&self) -> bool {
false
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderConfig {
pub id: String,
pub name: String,
pub extra: std::collections::HashMap<String, String>,
}
#[async_trait]
pub trait Provider: Send + Sync {
async fn config(&self) -> ProviderConfig;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SameSite {
Lax,
Strict,
None,
}
#[async_trait]
pub trait OAuthProvider: Provider {
fn provider_id(&self) -> &str;
fn get_authorization_url(
&self,
state: &str,
scopes: &[&str],
code_challenge: Option<&str>,
nonce: Option<&str>,
) -> String;
async fn exchange_code_for_identity(
&self,
code: &str,
code_verifier: Option<&str>,
nonce: Option<&str>,
) -> Result<(Identity, OAuthToken), AuthError>;
async fn refresh_token(&self, _refresh_token: &str) -> Result<OAuthToken, AuthError> {
Err(AuthError::Provider(
"Token refresh not supported by this provider".into(),
))
}
async fn revoke_token(&self, _token: &str) -> Result<(), AuthError> {
Err(AuthError::Provider(
"Token revocation not supported by this provider".into(),
))
}
}
#[async_trait]
pub trait CredentialsProvider: Send + Sync {
type Credentials;
async fn authenticate(&self, creds: Self::Credentials) -> Result<Identity, AuthError>;
}
#[async_trait]
pub trait UserMapper: Send + Sync {
type LocalUser: Send + Sync;
async fn map_user(&self, identity: &Identity) -> Result<Self::LocalUser, AuthError>;
}
#[async_trait]
pub trait ErasedOAuthFlow: Send + Sync {
fn provider_id(&self) -> String;
fn initiate_login(
&self,
scopes: &[&str],
pkce_challenge: Option<&str>,
) -> (String, OAuth2State);
async fn finalize_login(
&self,
code: &str,
received_state: &str,
expected_state: &OAuth2State,
) -> Result<(Identity, OAuthToken), AuthError>;
}
#[async_trait]
impl UserMapper for () {
type LocalUser = ();
async fn map_user(&self, _identity: &Identity) -> Result<Self::LocalUser, AuthError> {
Ok(())
}
}
#[async_trait]
impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for std::sync::Arc<T> {
fn provider_id(&self) -> String {
(**self).provider_id()
}
fn initiate_login(
&self,
scopes: &[&str],
pkce_challenge: Option<&str>,
) -> (String, OAuth2State) {
(**self).initiate_login(scopes, pkce_challenge)
}
async fn finalize_login(
&self,
code: &str,
received_state: &str,
expected_state: &OAuth2State,
) -> Result<(Identity, OAuthToken), AuthError> {
(**self)
.finalize_login(code, received_state, expected_state)
.await
}
}
#[async_trait]
impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for Box<T> {
fn provider_id(&self) -> String {
(**self).provider_id()
}
fn initiate_login(
&self,
scopes: &[&str],
pkce_challenge: Option<&str>,
) -> (String, OAuth2State) {
(**self).initiate_login(scopes, pkce_challenge)
}
async fn finalize_login(
&self,
code: &str,
received_state: &str,
expected_state: &OAuth2State,
) -> Result<(Identity, OAuthToken), AuthError> {
(**self)
.finalize_login(code, received_state, expected_state)
.await
}
}