summaryrefslogtreecommitdiff
path: root/src/password.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/password.rs')
-rw-r--r--src/password.rs67
1 files changed, 67 insertions, 0 deletions
diff --git a/src/password.rs b/src/password.rs
new file mode 100644
index 0000000..6c189fc
--- /dev/null
+++ b/src/password.rs
@@ -0,0 +1,67 @@
+use std::fmt;
+
+use argon2::Argon2;
+use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
+use rand_core::OsRng;
+
+use crate::normalize::nfc;
+
+#[derive(Clone, Eq, PartialEq, sqlx::Type)]
+#[sqlx(transparent)]
+pub struct StoredHash(String);
+
+impl StoredHash {
+ pub fn verify(&self, candidate: &Password) -> Result<bool, password_hash::Error> {
+ let hash = PasswordHash::new(&self.0)?;
+ let candidate = candidate.as_bytes();
+ let algorithm = Argon2::default();
+
+ match algorithm.verify_password(candidate, &hash) {
+ // Successful authentication, not an error
+ Ok(()) => Ok(true),
+ // Unsuccessful authentication, also not an error
+ Err(password_hash::errors::Error::Password) => Ok(false),
+ // Password validation failed for some other reason, treat as an error
+ Err(err) => Err(err),
+ }
+ }
+}
+
+impl fmt::Debug for StoredHash {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("StoredHash").field(&"********").finish()
+ }
+}
+
+#[derive(Clone, serde::Deserialize)]
+#[serde(transparent)]
+pub struct Password(nfc::String);
+
+impl Password {
+ pub fn hash(&self) -> Result<StoredHash, password_hash::Error> {
+ let Self(password) = self;
+ let salt = SaltString::generate(&mut OsRng);
+ let argon2 = Argon2::default();
+ let hash = argon2
+ .hash_password(password.as_bytes(), &salt)?
+ .to_string();
+ Ok(StoredHash(hash))
+ }
+
+ fn as_bytes(&self) -> &[u8] {
+ let Self(value) = self;
+ value.as_bytes()
+ }
+}
+
+impl fmt::Debug for Password {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("Password").field(&"********").finish()
+ }
+}
+
+impl From<String> for Password {
+ fn from(password: String) -> Self {
+ Password(password.into())
+ }
+}