use std::sync::Arc;
use crate::{core::user::User, error::AuthError};
#[derive(Debug, Clone)]
pub enum LoginMethod {
Credentials {
identifier: String,
password: String,
},
OAuth2 {
provider: crate::core::oauth::store::OAuth2Provider,
code: String,
state: String,
},
}
#[derive(Debug, Clone)]
pub enum SignupMethod {
Credentials {
identifier: String,
password: String,
},
OAuth2 {
provider: crate::core::oauth::store::OAuth2Provider,
code: String,
state: String,
},
}
pub struct AuthService {
pub vars: Arc<crate::core::vars::AuthServiceVariables>,
pub password_manager: Box<dyn crate::core::password::SecurePasswordManager + Send + Sync>,
pub persistent_users_manager:
Box<dyn crate::core::user::persistence::UserRepository + Send + Sync>,
pub token_manager: Box<dyn crate::core::token::TokenService + Send + Sync>,
pub oauth2_manager: Box<dyn crate::core::oauth::OAuth2Service + Send + Sync>,
}
impl Default for AuthService {
fn default() -> Self {
let vars = Arc::new(crate::core::vars::AuthServiceVariables::default());
Self {
vars: vars.clone(),
password_manager: Box::new(crate::core::password::Argon2PasswordManager::default()),
persistent_users_manager: Box::new(
crate::core::user::persistence::InMemoryUserRepo::new(),
),
token_manager: Box::new(crate::core::token::jwt::JwtTokenService::new(
&vars.secret_key,
vars.token_expiration,
vars.refresh_token_expiration,
)),
oauth2_manager: Box::new(crate::core::oauth::manager::OAuth2Manager::default()),
}
}
}
impl AuthService {
pub fn new(
vars: Arc<crate::core::vars::AuthServiceVariables>,
password_manager: Option<
Box<dyn crate::core::password::SecurePasswordManager + Send + Sync>,
>,
persistent_users_manager: Option<
Box<dyn crate::core::user::persistence::UserRepository + Send + Sync>,
>,
token_manager: Option<Box<dyn crate::core::token::TokenService + Send + Sync>>,
oauth2_manager: Option<Box<dyn crate::core::oauth::OAuth2Service + Send + Sync>>,
) -> Result<Self, AuthError> {
let pwd_manager = match password_manager {
Some(manager) => manager,
None => Box::new(crate::core::password::Argon2PasswordManager::default()),
};
let pum = match persistent_users_manager {
Some(manager) => manager,
None => Box::new(crate::core::user::persistence::InMemoryUserRepo::new()),
};
let tk_manager = match token_manager {
Some(manager) => manager,
None => Box::new(crate::core::token::jwt::JwtTokenService::new(
&vars.secret_key,
vars.token_expiration,
vars.refresh_token_expiration,
)),
};
let oauth_manager = match oauth2_manager {
Some(manager) => manager,
None => Box::new(crate::core::oauth::manager::OAuth2Manager::default()),
};
Ok(AuthService {
vars,
password_manager: pwd_manager,
persistent_users_manager: pum,
token_manager: tk_manager,
oauth2_manager: oauth_manager,
})
}
pub async fn login(
&self,
method: LoginMethod,
) -> Result<(User, crate::core::token::TokenPair), AuthError> {
match method {
LoginMethod::Credentials {
identifier,
password,
} => {
let stored_user = self
.persistent_users_manager
.get_user_by_identifier(&identifier)
.await
.ok_or(AuthError::InvalidCredentials)?;
let credentials = stored_user
.credentials
.as_ref()
.ok_or(AuthError::InvalidCredentials)?;
let is_valid = self
.password_manager
.verify_password(&password, &credentials.password_hash)
.await
.map_err(|e| {
AuthError::PasswordVerificationError(format!(
"Password verification failed: {e}"
))
})?;
if !is_valid {
return Err(AuthError::InvalidCredentials);
}
let tokens = self.get_tokens(stored_user.id.clone()).await?;
Ok((stored_user, tokens))
}
LoginMethod::OAuth2 {
provider,
code,
state,
} => {
let oauth_token = self
.exchange_oauth2_code_for_token(provider, &code, &state)
.await?;
let oauth_user_info = self.fetch_oauth2_user_info(&oauth_token).await?;
let existing_user = self
.persistent_users_manager
.get_user_by_oauth_id(provider, &oauth_user_info.provider_user_id)
.await;
let user = if let Some(mut user) = existing_user {
user.oauth_accounts.insert(provider, oauth_user_info);
user.updated_at = chrono::Utc::now().naive_utc();
self.persistent_users_manager.update_user(&user).await?;
user
} else {
let existing_user_by_email = if let Some(ref email) = oauth_user_info.email {
self.persistent_users_manager
.get_user_by_identifier(email)
.await
} else {
None
};
if let Some(mut user) = existing_user_by_email {
user.oauth_accounts.insert(provider, oauth_user_info);
user.updated_at = chrono::Utc::now().naive_utc();
self.persistent_users_manager.update_user(&user).await?;
user
} else {
let mut new_user = User {
id: uuid::Uuid::new_v4().to_string(),
..User::default()
};
new_user.oauth_accounts.insert(provider, oauth_user_info);
new_user.created_at = chrono::Utc::now().naive_utc();
new_user.updated_at = new_user.created_at;
self.persistent_users_manager
.add_user(new_user.clone())
.await?;
new_user
}
};
let tokens = self.get_tokens(user.id.clone()).await?;
Ok((user, tokens))
}
}
}
pub async fn signup(
&self,
method: SignupMethod,
) -> Result<(User, crate::core::token::TokenPair), AuthError> {
match method {
SignupMethod::Credentials {
identifier,
password,
} => {
let user = User::with_plain_password(
self.password_manager.as_ref(),
uuid::Uuid::new_v4().to_string(),
identifier,
crate::core::credentials::PlainPassword::new(password),
)
.await?;
self.persistent_users_manager
.add_user(user.clone())
.await
.map_err(|e| AuthError::SignupError(format!("signup: {e}")))?;
let tokens = self.get_tokens(user.id.clone()).await?;
Ok((user, tokens))
}
SignupMethod::OAuth2 {
provider,
code,
state,
} => {
let oauth_token = self
.exchange_oauth2_code_for_token(provider, &code, &state)
.await?;
let oauth_user_info = self.fetch_oauth2_user_info(&oauth_token).await?;
let existing_user = self
.persistent_users_manager
.get_user_by_oauth_id(provider, &oauth_user_info.provider_user_id)
.await;
let user = if let Some(mut user) = existing_user {
user.oauth_accounts.insert(provider, oauth_user_info);
user.updated_at = chrono::Utc::now().naive_utc();
self.persistent_users_manager.update_user(&user).await?;
user
} else {
let existing_user_by_email = if let Some(ref email) = oauth_user_info.email {
self.persistent_users_manager
.get_user_by_identifier(email)
.await
} else {
None
};
if let Some(mut user) = existing_user_by_email {
user.oauth_accounts.insert(provider, oauth_user_info);
user.updated_at = chrono::Utc::now().naive_utc();
self.persistent_users_manager.update_user(&user).await?;
user
} else {
let mut new_user = User {
id: uuid::Uuid::new_v4().to_string(),
..User::default()
};
new_user.oauth_accounts.insert(provider, oauth_user_info);
new_user.created_at = chrono::Utc::now().naive_utc();
new_user.updated_at = new_user.created_at;
self.persistent_users_manager
.add_user(new_user.clone())
.await?;
new_user
}
};
let tokens = self.get_tokens(user.id.clone()).await?;
Ok((user, tokens))
}
}
}
pub async fn get_tokens(&self, id: String) -> Result<crate::core::token::TokenPair, AuthError> {
self.token_manager.generate_token_pair(&id).await
}
pub async fn validate_access_token(
&self,
token: &str,
) -> Result<Box<dyn crate::core::token::claims::Claims + Send + Sync>, AuthError> {
self.token_manager.validate_access_token(token).await
}
pub async fn refresh_access_token(
&self,
refresh_token: &str,
) -> Result<crate::core::token::TokenPair, AuthError> {
self.token_manager.refresh_access_token(refresh_token).await
}
pub async fn get_user_id_from_token(&self, token: &str) -> Result<String, AuthError> {
let claims = self.validate_access_token(token).await?;
Ok(claims.get_subject().to_string())
}
pub async fn is_token_expired(&self, token: &str) -> bool {
self.validate_access_token(token).await.is_err()
}
pub async fn get_user_from_token(&self, token: &str) -> Result<User, AuthError> {
let user_id = self.get_user_id_from_token(token).await?;
self.persistent_users_manager
.get_user_by_id(&user_id)
.await
.ok_or(AuthError::UserNotFound)
}
pub async fn generate_oauth2_auth_url(
&self,
provider: crate::core::oauth::store::OAuth2Provider,
state: &str,
scopes: Option<Vec<String>>,
) -> Result<String, AuthError> {
self.oauth2_manager
.generate_auth_url(provider, state, scopes)
.await
}
pub async fn exchange_oauth2_code_for_token(
&self,
provider: crate::core::oauth::store::OAuth2Provider,
code: &str,
state: &str,
) -> Result<crate::core::oauth::store::OAuth2Token, AuthError> {
self.oauth2_manager
.exchange_code_for_token(provider, code, state)
.await
}
pub async fn fetch_oauth2_user_info(
&self,
token: &crate::core::oauth::store::OAuth2Token,
) -> Result<crate::core::oauth::store::OAuth2UserInfo, AuthError> {
self.oauth2_manager.fetch_user_info(token).await
}
pub async fn refresh_oauth2_token(
&self,
token: &crate::core::oauth::store::OAuth2Token,
) -> Result<crate::core::oauth::store::OAuth2Token, AuthError> {
self.oauth2_manager.refresh_token(token).await
}
pub async fn link_oauth_account(
&self,
user_id: &str,
provider: crate::core::oauth::store::OAuth2Provider,
code: &str,
state: &str,
) -> Result<User, AuthError> {
let mut user = self
.persistent_users_manager
.get_user_by_id(user_id)
.await
.ok_or(AuthError::UserNotFound)?;
let oauth_token = self
.exchange_oauth2_code_for_token(provider, code, state)
.await?;
let oauth_user_info = self.fetch_oauth2_user_info(&oauth_token).await?;
user = user.link_oauth_account(oauth_user_info);
self.persistent_users_manager.update_user(&user).await?;
Ok(user)
}
pub async fn unlink_oauth_account(
&self,
user_id: &str,
provider: crate::core::oauth::store::OAuth2Provider,
) -> Result<User, AuthError> {
let mut user = self
.persistent_users_manager
.get_user_by_id(user_id)
.await
.ok_or(AuthError::UserNotFound)?;
user.unlink_oauth_account(provider);
self.persistent_users_manager.update_user(&user).await?;
Ok(user)
}
pub async fn get_linked_oauth_providers(
&self,
user_id: &str,
) -> Result<Vec<crate::core::oauth::store::OAuth2Provider>, AuthError> {
let user = self
.persistent_users_manager
.get_user_by_id(user_id)
.await
.ok_or(AuthError::UserNotFound)?;
Ok(user.oauth_accounts.keys().copied().collect())
}
pub async fn get_oauth2_redirect_frontend_uri(
&self,
provider: crate::core::oauth::store::OAuth2Provider,
) -> Result<String, AuthError> {
self.oauth2_manager
.get_redirect_frontend_uri(provider)
.await
}
}