summaryrefslogtreecommitdiff
path: root/src/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/services')
-rw-r--r--src/services/crypto.rs20
-rw-r--r--src/services/db.rs50
-rw-r--r--src/services/id.rs19
-rw-r--r--src/services/mod.rs1
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;