diff --git a/http_client/rhythm/base ping health.yml b/http_client/base ping health.yml similarity index 100% rename from http_client/rhythm/base ping health.yml rename to http_client/base ping health.yml diff --git a/http_client/rhythm/environments/test.yml b/http_client/environments/test.yml similarity index 100% rename from http_client/rhythm/environments/test.yml rename to http_client/environments/test.yml diff --git a/http_client/rhythm/opencollection.yml b/http_client/opencollection.yml similarity index 100% rename from http_client/rhythm/opencollection.yml rename to http_client/opencollection.yml diff --git a/http_client/rhythm/register.yml b/http_client/register.yml similarity index 100% rename from http_client/rhythm/register.yml rename to http_client/register.yml diff --git a/http_client/rhythm/.gitignore b/http_client/rhythm/.gitignore deleted file mode 100644 index e19311f..0000000 --- a/http_client/rhythm/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Secrets -.env* - -# Dependencies -node_modules - -# OS files -.DS_Store -Thumbs.db \ No newline at end of file diff --git a/src/service/auth_service.rs b/src/service/auth_service.rs index b2022a5..b9d46cd 100644 --- a/src/service/auth_service.rs +++ b/src/service/auth_service.rs @@ -14,12 +14,55 @@ use crate::utils::hash; use crate::utils::jwt::generate_access_token; use crate::utils::refresh_token::generate_refresh_token; +const MIN_DELAY_MS: u64 = 150; +const MAX_DELAY_MS: u64 = 300; + +/** +This function role is to parse the email and password from the request and validate the user to give +back in the happy path the access token (jwt) and the refresh token (opaque token). + +An anti enumeration mechanism is in place to have a variable deplay in ms for every case of user authentication (error or happy validation). +TODO: add a bucket strategy for rate limiting for all of this endpoints +*/ pub async fn login( state: &AppState, cookies: Cookies, req: LoginRequest, ) -> Result, AppError> { - todo!() + let start = Instant::now(); + let login_result: Result<(String, String), AppError> = async { + let mut tx = state.db.begin().await?; + + let user = user_repository::get_user_by_email(&mut *tx, &req.email) + .await? + .ok_or(AppError::InvalidCredentials)?; + if !hash::verify(&req.password, &user.password)? { + return Err(AppError::InvalidCredentials); + } + let access_token = generate_access_token(user.id, &state.jwt_secret)?; + let (refresh_plain, refresh_hash) = generate_refresh_token(); + let expires_at = chrono::Utc::now() + Duration::days(7); + + create_refresh_token(&mut *tx, user.id, refresh_hash, expires_at).await?; + tx.commit().await?; + Ok((access_token, refresh_plain)) + } + .await; + + anti_enumeration_delay(start, MIN_DELAY_MS, MAX_DELAY_MS).await; + + return match login_result { + Ok((access_token, refresh_token)) => { + set_refresh_cookie(&cookies, &refresh_token); + Ok(Json(AuthResponse { access_token })) + } + Err(e) => { + if let AppError::InvalidCredentials = e { + tracing::warn!("Invalid login attempt for {}", req.email); + } + Err(e) + } + }; } pub async fn register( state: &AppState,