use std::sync::Arc; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use uuid::Uuid; use crate::{ config::Config, domain::user::User, error::AppError, ports::UserRepository, }; /// Claims do JWT de acesso. #[derive(Debug, Serialize, Deserialize)] pub struct AccessClaims { pub sub: Uuid, pub email: String, pub exp: i64, pub iat: i64, } /// Claims do JWT de refresh. #[derive(Debug, Serialize, Deserialize)] pub struct RefreshClaims { pub sub: Uuid, pub exp: i64, pub iat: i64, } /// Par de tokens emitido após login/registro bem-sucedido. #[derive(Debug)] pub struct TokenPair { pub access_token: String, pub refresh_token: String, } /// Caso de uso de autenticação: registro, login, OAuth, refresh e logout. pub struct AuthService { user_repo: Arc, config: Config, } impl AuthService { pub fn new(user_repo: Arc, config: Config) -> Self { Self { user_repo, config } } /// Expõe o repositório de usuários para uso em outros handlers (ex: user_handler). pub fn user_repo_ref(&self) -> &dyn UserRepository { self.user_repo.as_ref() } // ------------------------------------------------------------------------- // Registro com email/senha // ------------------------------------------------------------------------- pub async fn register(&self, email: String, password: String) -> Result { if self.user_repo.find_by_email(&email).await?.is_some() { return Err(AppError::Conflict("email already registered".into())); } let password_hash = hash_password(&password)?; let new_user = User::new_with_password(email, password_hash); Ok(self.user_repo.create(&new_user).await?) } // ------------------------------------------------------------------------- // Login com email/senha // ------------------------------------------------------------------------- pub async fn login(&self, email: String, password: String) -> Result { let user = self .user_repo .find_by_email(&email) .await? .ok_or(AppError::Unauthorized)?; let hash = user.password_hash.as_deref().ok_or(AppError::Unauthorized)?; verify_password(&password, hash)?; if !user.email_verified { return Err(AppError::EmailNotVerified); } self.issue_token_pair(&user) } // ------------------------------------------------------------------------- // OAuth Google // ------------------------------------------------------------------------- pub async fn google_oauth(&self, google_id: String, email: String) -> Result { let user = match self.user_repo.find_by_google_id(&google_id).await? { Some(u) => u, None => { // Verificar se o email já existe (conta pode ter sido criada com email/senha) match self.user_repo.find_by_email(&email).await? { Some(mut u) => { u.google_id = Some(google_id); u.email_verified = true; self.user_repo.update(&u).await? } None => { let new_user = User::new_with_google(email, google_id); self.user_repo.create(&new_user).await? } } } }; self.issue_token_pair(&user) } // ------------------------------------------------------------------------- // Refresh de token // ------------------------------------------------------------------------- pub async fn refresh_token(&self, refresh_token: &str) -> Result { let claims = self.decode_refresh_token(refresh_token)?; let user = self .user_repo .find_by_id(claims.sub) .await? .ok_or(AppError::Unauthorized)?; self.issue_token_pair(&user) } // ------------------------------------------------------------------------- // Validação de access token (usado pelo middleware) // ------------------------------------------------------------------------- pub fn validate_access_token(&self, token: &str) -> Result { let validation = Validation::new(Algorithm::HS256); decode::( token, &DecodingKey::from_secret(self.config.jwt_secret.as_bytes()), &validation, ) .map(|data| data.claims) .map_err(|_| AppError::Unauthorized) } // ------------------------------------------------------------------------- // Helpers privados // ------------------------------------------------------------------------- fn issue_token_pair(&self, user: &User) -> Result { let now = OffsetDateTime::now_utc().unix_timestamp(); let access_exp = now + self.config.jwt_expiry_secs as i64; let refresh_exp = now + self.config.jwt_refresh_expiry_secs as i64; let access_claims = AccessClaims { sub: user.id, email: user.email.clone(), exp: access_exp, iat: now, }; let refresh_claims = RefreshClaims { sub: user.id, exp: refresh_exp, iat: now, }; let key = EncodingKey::from_secret(self.config.jwt_secret.as_bytes()); let access_token = encode(&Header::default(), &access_claims, &key) .map_err(|e| AppError::Internal(e.into()))?; let refresh_token = encode(&Header::default(), &refresh_claims, &key) .map_err(|e| AppError::Internal(e.into()))?; Ok(TokenPair { access_token, refresh_token }) } fn decode_refresh_token(&self, token: &str) -> Result { let validation = Validation::new(Algorithm::HS256); decode::( token, &DecodingKey::from_secret(self.config.jwt_secret.as_bytes()), &validation, ) .map(|data| data.claims) .map_err(|_| AppError::Unauthorized) } } // ------------------------------------------------------------------------- // Funções utilitárias de hash (fora do impl para facilitar testes) // ------------------------------------------------------------------------- pub fn hash_password(password: &str) -> Result { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); argon2 .hash_password(password.as_bytes(), &salt) .map(|h| h.to_string()) .map_err(|e| AppError::Internal(anyhow::anyhow!("Password hashing failed: {e}"))) } pub fn verify_password(password: &str, hash: &str) -> Result<(), AppError> { let parsed_hash = PasswordHash::new(hash) .map_err(|e| AppError::Internal(anyhow::anyhow!("Invalid password hash: {e}")))?; Argon2::default() .verify_password(password.as_bytes(), &parsed_hash) .map_err(|_| AppError::Unauthorized) }