diff options
Diffstat (limited to 'src/login/repo')
| -rw-r--r-- | src/login/repo/logins.rs | 95 |
1 files changed, 14 insertions, 81 deletions
diff --git a/src/login/repo/logins.rs b/src/login/repo/logins.rs index 5f042fd..26a5b09 100644 --- a/src/login/repo/logins.rs +++ b/src/login/repo/logins.rs @@ -1,10 +1,8 @@ -use argon2::Argon2; -use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; -use rand_core::OsRng; use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; use crate::error::BoxedError; use crate::id::Id as BaseId; +use crate::login::app::StoredHash; pub trait Provider { fn logins(&mut self) -> Logins; @@ -30,77 +28,40 @@ pub struct Login { } impl<'c> Logins<'c> { - /// Create a new login, if the name is not already taken. Returns a [Login] - /// if a new login has actually been created, or `None` if an existing login - /// was found. pub async fn create( &mut self, name: &str, - password: &str, - ) -> Result<Option<Login>, BoxedError> { + password_hash: &StoredHash, + ) -> Result<Login, BoxedError> { let id = Id::generate(); - let password_hash = StoredHash::new(password)?; - let insert_res = sqlx::query_as!( + let login = sqlx::query_as!( Login, r#" insert or fail into login (id, name, password_hash) values ($1, $2, $3) - returning id as "id: Id", name + returning + id as "id: Id", + name "#, id, name, password_hash, ) .fetch_one(&mut *self.0) - .await; + .await?; - let result = match insert_res { - Ok(id) => Ok(Some(id)), - Err(err) => { - if let Some(true) = err - .as_database_error() - .map(|db_err| db_err.is_unique_violation()) - { - // Login with the same username (or, very rarely, same ID) already - // exists. - Ok(None) - } else { - Err(err) - } - } - }?; - Ok(result) + Ok(login) } - /// Authenticates `name` and `password` against an existing [Login]. Returns - /// that [Login] if one was found and the password was correct, or `None` if - /// either condition does not hold. - pub async fn authenticate( + /// Retrieves a login by name, plus its stored password hash for + /// verification. If there's no login with the requested name, this will + /// return [None]. + pub async fn for_login( &mut self, name: &str, - password: &str, - ) -> Result<Option<Login>, BoxedError> { - let found = self.for_name(name).await?; - - let login = if let Some((login, stored_hash)) = found { - if stored_hash.verify(password)? { - // User found and password validation succeeded. - Some(login) - } else { - // Password validation failed. - None - } - } else { - // User not found. - None - }; - - Ok(login) - } - - async fn for_name(&mut self, name: &str) -> Result<Option<(Login, StoredHash)>, BoxedError> { + ) -> Result<Option<(Login, StoredHash)>, BoxedError> { let found = sqlx::query!( r#" select @@ -144,31 +105,3 @@ impl Id { BaseId::generate("L") } } - -#[derive(Debug, sqlx::Type)] -#[sqlx(transparent)] -struct StoredHash(String); - -impl StoredHash { - fn new(password: &str) -> Result<Self, password_hash::Error> { - let salt = SaltString::generate(&mut OsRng); - let argon2 = Argon2::default(); - let hash = argon2 - .hash_password(password.as_bytes(), &salt)? - .to_string(); - Ok(Self(hash)) - } - - fn verify(&self, password: &str) -> Result<bool, password_hash::Error> { - let hash = PasswordHash::new(&self.0)?; - - match Argon2::default().verify_password(password.as_bytes(), &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), - } - } -} |
