diff --git a/Cargo.lock b/Cargo.lock index bb730f2..cbeb319 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,18 @@ version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-trait" version = "0.1.80" @@ -215,6 +227,12 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -227,6 +245,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -985,6 +1012,7 @@ name = "nir" version = "0.1.0" dependencies = [ "anyhow", + "argon2", "axum", "chrono", "derive_more", @@ -1135,6 +1163,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index 88906da..8c56a9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ [dependencies] anyhow = "1.0.82" +argon2 = "0.5.3" axum = { version = "0.7.5", features = ["macros", "ws"] } chrono = { version = "0.4.38", features = ["serde"] } derive_more = "0.99.17" diff --git a/migrations/20240423082838_user_table.sql b/migrations/20240423082838_user_table.sql index 3361772..a390d94 100644 --- a/migrations/20240423082838_user_table.sql +++ b/migrations/20240423082838_user_table.sql @@ -1,8 +1,8 @@ CREATE TABLE IF NOT EXISTS user ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `avatar` VARCHAR, - `username` VARCHAR UNIQUE, - `password` VARCHAR, + `username` VARCHAR UNIQUE NOT NULL, + `password_hash` VARCHAR NOT NULL, `last_seen` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); diff --git a/src/database.rs b/src/database.rs index 1e89e29..66c03d8 100644 --- a/src/database.rs +++ b/src/database.rs @@ -48,18 +48,19 @@ impl Database { Ok(user) } - pub async fn create_user(&self, username: &str, password: &str) -> Result { + pub async fn create_user(&self, username: &str, password_hash: &str) -> Result { let user = self.get_user_by_username(username).await; if user.is_ok() { return Err(Error::UserAlreadyExists); } - let id = - sqlx::query_scalar("INSERT INTO user(username, password) VALUES ($1, $2) RETURNING id") - .bind(username) - .bind(password) - .fetch_one(&self.pool) - .await?; + let id = sqlx::query_scalar( + "INSERT INTO user(username, password_hash) VALUES ($1, $2) RETURNING id", + ) + .bind(username) + .bind(password_hash) + .fetch_one(&self.pool) + .await?; let user = self.get_user_by_id(id).await?; diff --git a/src/entity/user.rs b/src/entity/user.rs index b93dafe..eb3da1a 100644 --- a/src/entity/user.rs +++ b/src/entity/user.rs @@ -8,7 +8,7 @@ pub struct User { pub avatar: Option, pub username: String, #[serde(skip_serializing)] - pub password: String, + pub password_hash: String, pub last_seen: chrono::DateTime, pub created_at: chrono::DateTime, } diff --git a/src/web/error.rs b/src/web/error.rs index 6ab6099..e2a14bb 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -17,6 +17,8 @@ pub enum Error { Database(database::Error), #[from] Jwt(jwt::Error), + #[from] + Hash(argon2::password_hash::Error), #[from] Client(ClientError), diff --git a/src/web/routes/user/login.rs b/src/web/routes/user/login.rs index 3f24049..5e40178 100644 --- a/src/web/routes/user/login.rs +++ b/src/web/routes/user/login.rs @@ -1,3 +1,4 @@ +use argon2::{Argon2, PasswordHash, PasswordVerifier}; use axum::{extract::State, response::IntoResponse, Json}; use chrono::{Duration, Utc}; use serde::Deserialize; @@ -13,7 +14,11 @@ pub async fn login( .get_user_by_username(&payload.username) .await?; - if user.password != payload.password { + let password_hash = PasswordHash::new(&user.password_hash)?; + if Argon2::default() + .verify_password(payload.password.as_bytes(), &password_hash) + .is_err() + { return Err(web::Error::WrongPassword); } diff --git a/src/web/routes/user/register.rs b/src/web/routes/user/register.rs index cdc10c4..b3038c0 100644 --- a/src/web/routes/user/register.rs +++ b/src/web/routes/user/register.rs @@ -1,3 +1,7 @@ +use argon2::{ + password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, + Argon2, +}; use axum::{extract::State, response::IntoResponse, Json}; use serde::Deserialize; @@ -7,9 +11,16 @@ pub async fn register( State(state): State, Json(payload): Json, ) -> web::Result { + let salt = SaltString::generate(&mut OsRng); + let argon = Argon2::default(); + + let password_hash = argon + .hash_password(payload.password.as_bytes(), &salt)? + .to_string(); + let user = state .database - .create_user(&payload.username, &payload.password) + .create_user(&payload.username, &password_hash) .await?; Ok(Json(user))