diff --git a/gpodder/src/repository.rs b/gpodder/src/repository.rs index b8a4cd7..dd9d12a 100644 --- a/gpodder/src/repository.rs +++ b/gpodder/src/repository.rs @@ -1,8 +1,8 @@ use std::{collections::HashSet, sync::Arc}; -use argon2::{Argon2, PasswordHash, PasswordVerifier}; +use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; use chrono::{DateTime, TimeDelta, Utc}; -use rand::Rng; +use rand::{rngs::OsRng, Rng}; use crate::{ models, @@ -41,6 +41,17 @@ impl GpodderRepository { self.store.get_user(username)?.ok_or(AuthErr::UnknownUser) } + pub fn create_user(&self, username: &str, password: &str) -> Result { + let salt = SaltString::generate(&mut OsRng); + + let password_hash = Argon2::default() + .hash_password(password.as_bytes(), &salt) + .unwrap() + .to_string(); + + self.store.insert_user(username, &password_hash) + } + pub fn validate_credentials( &self, username: &str, diff --git a/gpodder/src/store.rs b/gpodder/src/store.rs index 4c9ddd7..d4df180 100644 --- a/gpodder/src/store.rs +++ b/gpodder/src/store.rs @@ -41,6 +41,9 @@ pub trait AuthStore { /// Retrieve the user with the given username fn get_user(&self, username: &str) -> Result, AuthErr>; + /// Insert a new user into the data store + fn insert_user(&self, username: &str, password_hash: &str) -> Result; + /// Create a new session for a user with the given session ID fn insert_session(&self, session: &Session) -> Result<(), AuthErr>; diff --git a/gpodder_sqlite/src/models/user.rs b/gpodder_sqlite/src/models/user.rs index b7a15a9..72ea62b 100644 --- a/gpodder_sqlite/src/models/user.rs +++ b/gpodder_sqlite/src/models/user.rs @@ -14,9 +14,9 @@ pub struct User { #[derive(Insertable)] #[diesel(table_name = users)] #[diesel(check_for_backend(diesel::sqlite::Sqlite))] -pub struct NewUser { - pub username: String, - pub password_hash: String, +pub struct NewUser<'a> { + pub username: &'a str, + pub password_hash: &'a str, } // impl NewUser { diff --git a/gpodder_sqlite/src/repository/auth.rs b/gpodder_sqlite/src/repository/auth.rs index e9b867a..1b60028 100644 --- a/gpodder_sqlite/src/repository/auth.rs +++ b/gpodder_sqlite/src/repository/auth.rs @@ -4,7 +4,10 @@ use gpodder::AuthErr; use super::SqliteRepository; use crate::{ - models::{session::Session, user::User}, + models::{ + session::Session, + user::{NewUser, User}, + }, schema::*, DbError, }; @@ -30,18 +33,34 @@ impl gpodder::AuthStore for SqliteRepository { .map(gpodder::User::from)) } + fn insert_user(&self, username: &str, password_hash: &str) -> Result { + let conn = &mut self.pool.get().map_err(DbError::from)?; + + Ok(diesel::insert_into(users::table) + .values(NewUser { + username, + password_hash, + }) + .returning(User::as_returning()) + .get_result(conn) + .map(gpodder::User::from) + .map_err(DbError::from)?) + } + fn get_session(&self, session_id: i64) -> Result, AuthErr> { match sessions::table .inner_join(users::table) .filter(sessions::id.eq(session_id)) .select((Session::as_select(), User::as_select())) .get_result(&mut self.pool.get().map_err(DbError::from)?) + .optional() { - Ok((session, user)) => Ok(Some(gpodder::Session { + Ok(Some((session, user))) => Ok(Some(gpodder::Session { id: session.id, last_seen: DateTime::from_timestamp(session.last_seen, 0).unwrap(), user: user.into(), })), + Ok(None) => Ok(None), Err(err) => Err(DbError::from(err).into()), } }