summaryrefslogtreecommitdiff
path: root/src/login/repo
diff options
context:
space:
mode:
Diffstat (limited to 'src/login/repo')
-rw-r--r--src/login/repo/logins.rs95
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),
- }
- }
-}