use std::ops::Deref; use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; use base64::{engine, prelude::BASE64_STANDARD_NO_PAD}; use rocket::{ request::{self, FromRequest}, Request, }; use sqlx::{FromRow, SqlitePool}; #[derive(FromRow, Debug)] pub struct User { id: i64, name: String, pw: String, is_cox: bool, is_admin: bool, is_guest: bool, } pub enum LoginError { SqlxError(sqlx::Error), InvalidAuthenticationCombo, } impl From for LoginError { fn from(sqlx_error: sqlx::Error) -> Self { Self::SqlxError(sqlx_error) } } impl User { pub async fn find_by_name(db: &SqlitePool, name: String) -> Result { let user: User = sqlx::query_as!( User, " SELECT id, name, pw, is_cox, is_admin, is_guest FROM user WHERE name like ? ", name ) .fetch_one(db) .await?; Ok(user) } pub async fn login(db: &SqlitePool, name: String, pw: String) -> Result { let user = User::find_by_name(db, name).await?; let argon2 = Argon2::default(); let salt = SaltString::from_b64("CdR4i0HA9e0CM").unwrap(); //TODO: generate salt for each user let password_hash = argon2 .hash_password(&pw.as_bytes(), &salt) .unwrap() .to_string(); //TODO: fixme let parsed_hash = PasswordHash::new(&password_hash).unwrap(); //TODO: fixme //TODO: If user.pw == "" -> set new pw! match Argon2::default() .verify_password(base64::encode(pw.as_bytes()).as_bytes(), &parsed_hash) { Ok(_) => Ok(user), Err(_) => Err(LoginError::InvalidAuthenticationCombo), } } } pub struct Users { users: Vec, } #[derive(Debug)] pub enum Error {} //#[rocket::async_trait] //impl<'r> FromRequest<'r> for User { // type Error = Error; // // async fn from_request(req: &'r Request<'_>) -> request::Outcome { // //TODO: https://betterprogramming.pub/building-the-rust-web-app-multiple-users-and-authentication-5ca5988ddfe4 // } //} impl Deref for Users { type Target = Vec; fn deref(&self) -> &Self::Target { &self.users } } impl Users { pub async fn new(pool: &SqlitePool) -> Result { let users: Vec = sqlx::query_as!( User, r#" SELECT id, name, pw, is_cox, is_admin, is_guest FROM user "#, ) .fetch_all(pool) .await?; Ok(Self { users }) } } #[cfg(test)] mod test { use super::Users; use sqlx::SqlitePool; #[sqlx::test] fn user_with_test_db() { let pool = SqlitePool::connect(":memory:").await.unwrap(); sqlx::query_file!("./migration.sql") .execute(&pool) .await .unwrap(); sqlx::query_file!("./seeds.sql") .execute(&pool) .await .unwrap(); let users = Users::new(&pool).await.unwrap(); assert_eq!(users.len(), 4); } }