diff --git a/src/callers/login.rs b/src/callers/login.rs index e69de29..7c41ef7 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -0,0 +1,128 @@ +use crate::repo; +use crate::hashing; +use crate::token_stuff; + +pub mod request { + use serde::{Deserialize}; + #[derive(Default, Deserialize, utoipa::ToSchema)] + pub struct LoginRequest { + pub username: String, + pub password: String, + } +} + +pub mod response { + use serde::{Deserialize}; + #[derive(Default, Deserialize, utoipa::ToSchema)] + pub struct LoginResponse { + pub message: String, + pub data: Vec, + } +} + +/// Endpoint for a user login +#[utoipa::path( + post, + path = super::endpoints::LOGIN, + request_body( + content = request::LoginRequest, + description = "Data required for a user to lgoin", + content_type = "application/json" + ), + responses( + (status = 201, description = "User login successful", body = response::LoginResponse), + (status = 400, description = "Bad data", body = response::LoginResponse), + (status = 500, description = "Something went wrong", body = response::LoginResponse) + ) +)] +pub async fn login(axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json) -> (axum::http::StatusCode, axum::Json) { + if payload.username.is_empty() || payload.password.is_empty() { + let reason = if payload.username.is_empty() { + String::from("Username not provided") + } else { + String::from("Password not provided") + }; + + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response::LoginResponse { + message: reason, + data: Vec::new(), + })) + } else { + match repo::user::exists(&pool, &payload.username).await { + Ok(exists) => { + if !exists { + println!("User does not exists"); + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + })); + } else { + let user = match repo::user::get(&pool, &payload.username).await { + Ok(user) => { + 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 = user.password.clone(); + match hashing::verify_password(&payload.password, 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, &user.id).unwrap(); + + if token_stuff::verify_token(&key, &token_literal) { + let current_time = time::OffsetDateTime::now_utc(); + let _ = repo::user::update_last_login(&pool, &user, ¤t_time).await; + + ( + axum::http::StatusCode::OK, + axum::Json(response::LoginResponse { + message: String::from("Successful"), + data: vec![textsender_models::token::LoginResult { + user_id: user.id, + access_token: token_literal, + token_type: String::from(textsender_models::token::TOKEN_TYPE), + expires_in: duration, + ..Default::default() + }], + }), + ) + } else { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + })); + } + } else { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + })); + } + } + Err(err) => { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: err.to_string(), + data: Vec::new(), + })); + } + } + } + } + Err(err) => { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: err.to_string(), + data: Vec::new(), + })); + } + } + } +} diff --git a/src/callers/mod.rs b/src/callers/mod.rs index ab14499..c561eea 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -1,8 +1,11 @@ pub mod common; +pub mod login; pub mod register; pub mod endpoints { pub const ROOT: &str = "/"; pub const REGISTER: &str = "/api/v1/register"; + /// Endpoint for a user to login + pub const LOGIN: &str = "/api/v1/login"; pub const DBTEST: &str = "/api/v1/test/db"; } diff --git a/src/main.rs b/src/main.rs index ca94b2a..ee04c8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,17 +27,19 @@ mod init { use super::callers; use callers::common as common_callers; + use callers::login as login_caller; use callers::register as register_caller; use register_caller::response as register_responses; + use login_caller::response as login_responses; #[derive(utoipa::OpenApi)] #[openapi( paths( common_callers::endpoint::db_ping, common_callers::endpoint::root, - register_caller::register_user, + register_caller::register_user, login_caller::login, ), components(schemas(common_callers::response::TestResult, - register_responses::Response)), + register_responses::Response, login_responses::LoginResponse,)), tags( (name = "TextSender Auth API", description = "Auth API for TextSender API") ) @@ -108,6 +110,10 @@ mod init { callers::endpoints::REGISTER, post(callers::register::register_user), ) + .route( + callers::endpoints::LOGIN, + post(callers::login::login), + ) .layer(cors::configure_cors().await) }