diff --git a/src/callers/login.rs b/src/callers/login.rs index e39fc3d..c9030cd 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -37,6 +37,24 @@ pub mod request { pub struct RefreshTokenRequest { pub access_token: String, } + + #[derive(Deserialize, utoipa::ToSchema)] + pub struct UpdatePasswordRequest { + pub user_id: uuid::Uuid, + pub current_password: String, + pub updated_password: String, + pub confirmed_password: String, + } + + impl UpdatePasswordRequest { + pub fn is_valid(&self) -> bool { + if self.user_id.is_nil() || !self.current_password.is_empty() || !self.updated_password.is_empty() || !self.confirmed_password.is_empty() { + false + } else { + self.updated_password == self.confirmed_password + } + } + } } pub mod response { @@ -68,6 +86,12 @@ pub mod response { pub message: String, pub data: Vec, } + + #[derive(Deserialize, Serialize, utoipa::ToSchema)] + pub struct UpdatePasswordResponse { + pub message: String, + pub data: Vec + } } /// Endpoint for a user login @@ -509,3 +533,165 @@ pub async fn refresh_token( } } } + + +/// Endpoint for a updating password +#[utoipa::path( + post, + path = super::endpoints::UPDATE_PASSWORD, + request_body( + content = request::UpdatePasswordRequest, + description = "Data required to update password", + content_type = "application/json" + ), + responses( + (status = 200, description = "User login successful", body = response::UpdatePasswordResponse), + (status = 400, description = "Bad data", body = response::UpdatePasswordResponse), + (status = 500, description = "Something went wrong", body = response::UpdatePasswordResponse) + ) +)] +pub async fn update_password( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, +) -> (axum::http::StatusCode, axum::Json) { + if !payload.is_valid() { + ( + axum::http::StatusCode::BAD_REQUEST, + axum::Json(response::UpdatePasswordResponse { + message: String::from("Invalid passwords"), + data: Vec::new(), + }), + ) + } else { + + let verify_password = |current_password, hashed_password| -> Result { + match hashing::verify_password(current_password, hashed_password) { + Ok(matches) => { + Ok(matches) + } + Err(err) => Err(std::io::Error::other(err.to_string())), + } + }; + + match repo::user::get_with_id(&pool, &payload.user_id).await { + Ok(user) => { + let hashed_password = user.password.clone(); + match verify_password(&payload.current_password, hashed_password) { + Ok(matches) => { + if matches { + let (generate_salt, mut salt) = super::register::generate_the_salt(); + salt.id = repo::salt::insert(&pool, &salt).await.unwrap(); + let updated_hashed_password = match hashing::hash_password(&payload.updated_password, &generate_salt) { + Ok(hashed) => { + hashed + } + Err(err) => { + eprintln!("Error: {err:?}"); + String::new() + } + }; + + match repo::user::update_password(&pool, &user, &updated_hashed_password).await { + Ok(()) => { + (axum::http::StatusCode::OK, + axum::Json(response::UpdatePasswordResponse { + message: String::from(super::messages::SUCCESSFUL_MESSAGE), + data: vec![user.id], + })) + } + Err(err) => { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::UpdatePasswordResponse { + message: err.to_string(), + data: Vec::new(), + })) + } + } + } else { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::UpdatePasswordResponse { + message: String::from("Issue updating password"), + data: Vec::new(), + })) + } + } + Err(err) => { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::UpdatePasswordResponse { + message: err.to_string(), + data: Vec::new(), + }), + ) + } + } + } + Err(err) => { + println!("No User found, trying Service User: {err:?}"); + + // Try service user + match repo::service::get(&pool, &payload.user_id).await { + Ok(service_user) => { + let hashed_password = service_user.passphrase.clone(); + match verify_password(&payload.current_password, hashed_password) { + Ok(matches) => { + if matches { + let (generate_salt, mut salt) = super::register::generate_the_salt(); + salt.id = repo::salt::insert(&pool, &salt).await.unwrap(); + let updated_hashed_password = match hashing::hash_password(&payload.updated_password, &generate_salt) { + Ok(hashed) => { + hashed + } + Err(err) => { + eprintln!("Error: {err:?}"); + String::new() + } + }; + + match repo::service::update_passphrase(&pool, &service_user, &updated_hashed_password).await { + Ok(()) => { + (axum::http::StatusCode::OK, + axum::Json(response::UpdatePasswordResponse { + message: String::from(super::messages::SUCCESSFUL_MESSAGE), + data: vec![service_user.id], + })) + } + Err(err) => { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::UpdatePasswordResponse { + message: err.to_string(), + data: Vec::new(), + })) + } + } + } else { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::UpdatePasswordResponse { + message: String::from("Issue updating password"), + data: Vec::new(), + })) + } + } + Err(err) => { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::UpdatePasswordResponse { + message: err.to_string(), + data: Vec::new(), + }), + ) + } + } + } + Err(err) => { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::UpdatePasswordResponse { + message: err.to_string(), + data: Vec::new(), + })) + } + } + } + } + } +} diff --git a/src/callers/register.rs b/src/callers/register.rs index 3474d75..3ac4747 100644 --- a/src/callers/register.rs +++ b/src/callers/register.rs @@ -59,7 +59,7 @@ pub mod response { } } -fn generate_the_salt() -> ( +pub fn generate_the_salt() -> ( argon2::password_hash::SaltString, textsender_models::user::Salt, ) { diff --git a/src/repo/mod.rs b/src/repo/mod.rs index f7aeaad..373375c 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -104,6 +104,31 @@ pub mod user { } } + pub async fn update_password( + pool: &sqlx::PgPool, + user: &textsender_models::user::User, + updated_hashed_password: &String, + ) -> Result<(), sqlx::Error> { + match sqlx::query( + r#" + UPDATE "user" SET password = $1 WHERE id = $2 + "#, + ) + .bind(updated_hashed_password) + .bind(user.id) + .execute(pool) + .await { + Ok(row) => { + if row.rows_affected() > 0 { + Ok(()) + } else { + Err(sqlx::Error::RowNotFound) + } + } + Err(err) => Err(err), + } + } + pub async fn exists(pool: &sqlx::PgPool, username: &String) -> Result { let result = sqlx::query( r#" diff --git a/src/repo/service.rs b/src/repo/service.rs index 078751e..e1966bf 100644 --- a/src/repo/service.rs +++ b/src/repo/service.rs @@ -154,6 +154,31 @@ pub async fn update_last_login( } } +pub async fn update_passphrase( + pool: &sqlx::PgPool, + user: &textsender_models::user::ServiceUser, + updated_hashed_passphrase: &String, +) -> Result<(), sqlx::Error> { + match sqlx::query( + r#" + UPDATE "service_user" SET passphrase = $1 WHERE id = $2 + "#, + ) + .bind(updated_hashed_passphrase) + .bind(user.id) + .execute(pool) + .await { + Ok(row) => { + if row.rows_affected() > 0 { + Ok(()) + } else { + Err(sqlx::Error::RowNotFound) + } + } + Err(err) => Err(err), + } +} + pub async fn insert( pool: &sqlx::PgPool, service_user: &textsender_models::user::ServiceUser,