diff options
Diffstat (limited to 'src/services')
| -rw-r--r-- | src/services/crypto.rs | 20 | ||||
| -rw-r--r-- | src/services/db.rs | 50 | ||||
| -rw-r--r-- | src/services/id.rs | 19 | ||||
| -rw-r--r-- | src/services/mod.rs | 1 |
4 files changed, 83 insertions, 7 deletions
diff --git a/src/services/crypto.rs b/src/services/crypto.rs index 11a5149..580e83a 100644 --- a/src/services/crypto.rs +++ b/src/services/crypto.rs @@ -10,7 +10,7 @@ static PEPPER: [u8; 16] = [ /// The configuration used for hashing and verifying passwords static CONFIG: argon2::Config<'_> = argon2::Config { - hash_length: 256, + hash_length: 32, lanes: 4, mem_cost: 5333, time_cost: 4, @@ -27,13 +27,12 @@ static CONFIG: argon2::Config<'_> = argon2::Config { pub struct PasswordHash { hash: Box<[u8]>, salt: Box<[u8]>, + version: u8, } impl Hash for PasswordHash { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - for byte in self.hash.iter() { - state.write_u8(*byte) - } + state.write(&self.hash) } } @@ -47,14 +46,19 @@ impl PasswordHash { let hash = hash_raw(password, &salt, &CONFIG)?.into_boxed_slice(); - Ok(Self { hash, salt }) + Ok(Self { + hash, + salt, + version: 0, + }) } /// Create this structure from a given hash and salt - pub fn from_hash_salt(hash: &[u8], salt: &[u8]) -> Self { + pub fn from_fields(hash: &[u8], salt: &[u8], version: u8) -> Self { Self { hash: Box::from(hash), salt: Box::from(salt), + version, } } @@ -68,6 +72,10 @@ impl PasswordHash { &self.salt } + pub fn version(&self) -> u8 { + self.version + } + /// Check if the given password is the one that was hashed pub fn check_password(&self, password: &str) -> Result<bool, RawUnexpected> { Ok(verify_raw( diff --git a/src/services/db.rs b/src/services/db.rs index a8e4918..efa9584 100644 --- a/src/services/db.rs +++ b/src/services/db.rs @@ -1,8 +1,56 @@ use exun::*; -use sqlx::MySqlPool; +use sqlx::{query, query_scalar, Executor, MySql, MySqlPool}; +use uuid::Uuid; + +use crate::models::User; /// Intialize the connection pool pub async fn initialize(db: &str, user: &str, password: &str) -> Result<MySqlPool, RawUnexpected> { let url = format!("mysql://{user}:{password}@localhost/{db}"); MySqlPool::connect(&url).await.unexpect() } + +pub async fn user_id_exists<'c>( + conn: impl Executor<'c, Database = MySql>, + id: Uuid, +) -> Result<bool, RawUnexpected> { + let exists = query_scalar!( + r#"SELECT EXISTS(SELECT user_id FROM users WHERE user_id = ?) as "e: bool""#, + id + ) + .fetch_one(conn) + .await?; + + Ok(exists) +} + +pub async fn username_is_used<'c>( + conn: impl Executor<'c, Database = MySql>, + username: &str, +) -> Result<bool, RawUnexpected> { + let exists = query_scalar!( + r#"SELECT EXISTS(SELECT user_id FROM users WHERE username = ?) as "e: bool""#, + username + ) + .fetch_one(conn) + .await?; + + Ok(exists) +} + +pub async fn new_user<'c>( + conn: impl Executor<'c, Database = MySql>, + user: User, +) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> { + query!( + r"INSERT INTO users (user_id, username, password_hash, password_salt, password_version) + VALUES (?, ?, ?, ?, ?)", + user.user_id, + user.username(), + user.password_hash(), + user.password_salt(), + user.password_version() + ) + .execute(conn) + .await +} diff --git a/src/services/id.rs b/src/services/id.rs new file mode 100644 index 0000000..7970c60 --- /dev/null +++ b/src/services/id.rs @@ -0,0 +1,19 @@ +use exun::RawUnexpected; +use sqlx::{Executor, MySql}; +use uuid::Uuid; + +use super::db; + +/// Create a unique user id, handling duplicate ID's +pub async fn new_user_id<'c>( + conn: impl Executor<'c, Database = MySql> + Clone, +) -> Result<Uuid, RawUnexpected> { + let uuid = loop { + let uuid = Uuid::new_v4(); + if !db::user_id_exists(conn.clone(), uuid).await? { + break uuid; + } + }; + + Ok(uuid) +} diff --git a/src/services/mod.rs b/src/services/mod.rs index 7163603..57146d8 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,2 +1,3 @@ pub mod crypto; pub mod db; +pub mod id; |
