about summary refs log tree commit diff stats
path: root/crates/rocie-server/src/storage/sql/user.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rocie-server/src/storage/sql/user.rs')
-rw-r--r--crates/rocie-server/src/storage/sql/user.rs86
1 files changed, 86 insertions, 0 deletions
diff --git a/crates/rocie-server/src/storage/sql/user.rs b/crates/rocie-server/src/storage/sql/user.rs
new file mode 100644
index 0000000..2bac555
--- /dev/null
+++ b/crates/rocie-server/src/storage/sql/user.rs
@@ -0,0 +1,86 @@
+use std::fmt::Display;
+
+use argon2::{
+    Argon2, PasswordHasher, PasswordVerifier,
+    password_hash::{SaltString, rand_core::OsRng},
+};
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+
+use crate::storage::sql::mk_id;
+
+/// The definition of an rocie user.
+#[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
+pub(crate) struct User {
+    /// The unique ID for this user.
+    pub(crate) id: UserId,
+
+    /// The user-displayed name of this user.
+    pub(crate) name: String,
+
+    /// The hash of the user's password.
+    pub(crate) password_hash: PasswordHash,
+
+    /// An description of this user.
+    #[schema(nullable = false)]
+    pub(crate) description: Option<String>,
+}
+
+/// This is stored as an PHC password string.
+///
+/// This type corresponds to the string representation of a PHC string as
+/// described in the [PHC string format specification][1].
+///
+/// PHC strings have the following format:
+///
+/// ```text
+/// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
+/// ```
+#[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
+pub(crate) struct PasswordHash {
+    value: String,
+}
+impl PasswordHash {
+    pub(crate) fn from_db(password_hash: String) -> PasswordHash {
+        Self {
+            value: password_hash,
+        }
+    }
+
+    pub(crate) fn from_password(password: &str) -> Self {
+        let salt = SaltString::generate(&mut OsRng);
+
+        let argon2 = Argon2::default();
+
+        let password_hash = argon2
+            .hash_password(password.as_bytes(), &salt)
+            .expect("to not fail")
+            .to_string();
+
+        Self {
+            value: password_hash,
+        }
+    }
+
+    /// Check that self, and the other password have the same hash.
+    pub(crate) fn verify(&self, other: &str) -> bool {
+        let argon2 = Argon2::default();
+
+        argon2
+            .verify_password(other.as_bytes(), &self.as_argon_hash())
+            .is_ok()
+    }
+
+    fn as_argon_hash(&self) -> argon2::PasswordHash<'_> {
+        argon2::PasswordHash::new(&self.value)
+            .expect("to be valid, as we are just deserializing a previously serialize value")
+    }
+}
+
+impl Display for PasswordHash {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.value.fmt(f)
+    }
+}
+
+mk_id!(UserId and UserIdStub);