Skip to content

Commit

Permalink
Merge pull request #40 from traP-jp/feat/#13-put-me-password
Browse files Browse the repository at this point in the history
PUT /users/me/password を実装
  • Loading branch information
kenken714 authored Oct 29, 2024
2 parents e1e6dd9 + 227577d commit a2870a8
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub fn make_router(app_state: Repository) -> Router {
let users_router = Router::new()
.route("/me", get(users::get_me).put(users::put_me))
.route("/me/email", put(users::put_me_email))
.route("/me/password", put(users::put_me_password))
.route("/:userId", get(users::get_user));

Router::new()
Expand Down
2 changes: 1 addition & 1 deletion src/handler/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub async fn sign_up(
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
state
.save_raw_password(id, &body.password)
.save_user_password(id, &body.password)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(StatusCode::CREATED)
Expand Down
60 changes: 55 additions & 5 deletions src/handler/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ use super::Repository;
use crate::repository::users::UpdateUser;
use crate::utils::validator::{RuleType, Validator};

#[derive(Deserialize)]
pub struct EmailUpdate {
email: String,
}

pub async fn get_me(
State(state): State<Repository>,
TypedHeader(cookie): TypedHeader<Cookie>,
Expand All @@ -34,6 +29,11 @@ pub async fn get_me(
Ok(Json(user))
}

#[derive(Deserialize)]
pub struct EmailUpdate {
email: String,
}

pub async fn put_me_email(
State(state): State<Repository>,
TypedHeader(cookie): TypedHeader<Cookie>,
Expand Down Expand Up @@ -73,6 +73,56 @@ https://link/{jwt}"
Ok(StatusCode::NO_CONTENT)
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasswordUpdate {
old_password: String,
new_password: String,
}

impl Validator for PasswordUpdate {
fn validate(&self) -> anyhow::Result<()> {
RuleType::Password.validate(&self.old_password)?;
RuleType::Password.validate(&self.new_password)?;
Ok(())
}
}

pub async fn put_me_password(
State(state): State<Repository>,
TypedHeader(cookie): TypedHeader<Cookie>,
Json(body): Json<PasswordUpdate>,
) -> anyhow::Result<StatusCode, StatusCode> {
body.validate().map_err(|_| StatusCode::BAD_REQUEST)?;
let session_id = cookie.get("session_id").ok_or(StatusCode::UNAUTHORIZED)?;

let display_id = state
.get_display_id_by_session_id(session_id)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::UNAUTHORIZED)?;

let user = state
.get_user_by_display_id(display_id)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

match state
.verify_user_password(user.id, &body.old_password)
.await
{
Ok(true) => {
state
.update_user_password(user.id, &body.new_password)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(StatusCode::NO_CONTENT)
}
_ => Err(StatusCode::UNAUTHORIZED),
}
}

#[derive(serde::Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PutMeRequest {
Expand Down
25 changes: 24 additions & 1 deletion src/repository/user_password.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{users::UserId, Repository};

impl Repository {
pub async fn save_raw_password(&self, id: UserId, password: &str) -> anyhow::Result<()> {
pub async fn save_user_password(&self, id: UserId, password: &str) -> anyhow::Result<()> {
let hash = bcrypt::hash(password, self.bcrypt_cost)?;

sqlx::query("INSERT INTO users_passwords (user_id, password) VALUES (?, ?)")
Expand All @@ -12,4 +12,27 @@ impl Repository {

Ok(())
}

pub async fn update_user_password(&self, id: UserId, password: &str) -> anyhow::Result<()> {
let hash = bcrypt::hash(password, self.bcrypt_cost)?;

sqlx::query("UPDATE users_passwords SET password = ? WHERE user_id = ?")
.bind(&hash)
.bind(id)
.execute(&self.pool)
.await?;

Ok(())
}

pub async fn verify_user_password(&self, id: UserId, password: &str) -> anyhow::Result<bool> {
let hash = sqlx::query_scalar::<_, String>(
"SELECT password FROM users_passwords WHERE user_id = ?",
)
.bind(id)
.fetch_one(&self.pool)
.await?;

Ok(bcrypt::verify(password, &hash)?)
}
}

0 comments on commit a2870a8

Please sign in to comment.