diff --git a/src/callers/login.rs b/src/callers/login.rs index 83e6cff..01bb6c4 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -9,6 +9,29 @@ pub mod request { pub username: String, pub password: String, } + + #[derive(Deserialize, utoipa::ToSchema)] + pub struct ServiceUserLoginRequest { + pub username: String, + pub passphrase: String, + } + + impl ServiceUserLoginRequest { + pub fn is_empty(&self) -> (bool, Option) { + if self.username.is_empty() && self.passphrase.is_empty() { + ( + true, + Some(String::from("Username and passphrase are empty")), + ) + } else if self.username.is_empty() { + (true, Some(String::from("Username is empty"))) + } else if self.username.is_empty() { + (true, Some(String::from("Passphrase is empty"))) + } else { + (false, None) + } + } + } } pub mod response { @@ -19,6 +42,12 @@ pub mod response { pub data: Vec, } + #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] + pub struct ServiceUserLoginResponse { + pub message: String, + pub data: Vec, + } + pub async fn extract( response: axum::response::Response, ) -> Result { @@ -160,3 +189,140 @@ pub async fn user_login( } } } + +/// Endpoint for service user login +#[utoipa::path( + post, + path = super::endpoints::LOGIN_SERVICE_USER, + request_body( + content = request::ServiceUserLoginRequest, + description = "Data required for service user to lgoin", + content_type = "application/json" + ), + responses( + (status = 201, description = "Service uuser login successful", body = response::ServiceUserLoginResponse), + (status = 400, description = "Bad data", body = response::ServiceUserLoginResponse), + (status = 500, description = "Something went wrong", body = response::ServiceUserLoginResponse) + ) +)] +pub async fn service_user_login( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, +) -> (axum::http::StatusCode, axum::Json) { + if payload.username.is_empty() || payload.passphrase.is_empty() { + let reason = if payload.username.is_empty() { + String::from("Username not provided") + } else { + String::from("Passphrase not provided") + }; + + ( + axum::http::StatusCode::BAD_REQUEST, + axum::Json(response::LoginResponse { + message: reason, + data: Vec::new(), + }), + ) + } else { + match repo::service::exists(&pool, &payload.username).await { + Ok(exists) => { + if !exists { + println!("User does not exists"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + }), + ) + } else { + let service_user = + match repo::service::get_with_username(&pool, &payload.username).await { + Ok(service_user) => service_user, + Err(_err) => { + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + }), + ); + } + }; + let hashed_password = service_user.passphrase.clone(); + + match hashing::verify_password(&payload.passphrase, hashed_password) { + Ok(matches) => { + if matches { + // Create token + let key = textsender_models::envy::environment::get_secret_key() + .await + .value; + let (token_literal, duration) = + token_stuff::create_token(&key, &service_user.id).unwrap(); + + if token_stuff::verify_token(&key, &token_literal) { + let current_time = time::OffsetDateTime::now_utc(); + let _ = repo::service::update_last_login( + &pool, + &service_user, + ¤t_time, + ) + .await; + + ( + axum::http::StatusCode::OK, + axum::Json(response::LoginResponse { + message: String::from( + super::messages::SUCCESSFUL_MESSAGE, + ), + data: vec![textsender_models::token::LoginResult { + user_id: service_user.id, + access_token: token_literal, + token_type: String::from( + textsender_models::token::TOKEN_TYPE, + ), + issued_at: duration, + ..Default::default() + }], + }), + ) + } else { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + }), + ) + } + } else { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + }), + ) + } + } + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: err.to_string(), + data: Vec::new(), + }), + ), + } + } + } + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: err.to_string(), + data: Vec::new(), + }), + ), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c820ae5..af64ea5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,10 @@ pub mod init { post(callers::register::register_service_user), ) .route(callers::endpoints::LOGIN, post(callers::login::user_login)) + .route( + callers::endpoints::LOGIN_SERVICE_USER, + post(callers::login::service_user_login), + ) .layer(cors::configure_cors().await) } diff --git a/src/repo/service.rs b/src/repo/service.rs index 1f98dcd..e237100 100644 --- a/src/repo/service.rs +++ b/src/repo/service.rs @@ -75,6 +75,39 @@ pub async fn get_with_username( } } +pub async fn update_last_login( + pool: &sqlx::PgPool, + service_user: &textsender_models::user::ServiceUser, + time: &time::OffsetDateTime, +) -> Result { + let result = sqlx::query( + r#" + UPDATE "service_user" SET last_login = $1 WHERE id = $2 RETURNING last_login + "#, + ) + .bind(time) + .bind(service_user.id) + .fetch_optional(pool) + .await + .map_err(|e| { + eprintln!("Error updating time: {e}"); + e + }); + + match result { + Ok(row) => match row { + Some(r) => { + let last_login: time::OffsetDateTime = r + .try_get("last_login") + .map_err(|_e| sqlx::Error::RowNotFound)?; + Ok(last_login) + } + None => Err(sqlx::Error::RowNotFound), + }, + Err(err) => Err(err), + } +} + pub async fn insert( pool: &sqlx::PgPool, service_user: &textsender_models::user::ServiceUser,