use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; use serde::Serialize; use sqlx::{FromRow, SqlitePool}; #[derive(FromRow, Debug, Serialize)] pub struct User { id: i64, name: String, pw: String, is_cox: bool, is_admin: bool, is_guest: bool, } #[derive(Debug)] pub enum LoginError { SqlxError(sqlx::Error), InvalidAuthenticationCombo, } impl From for LoginError { fn from(sqlx_error: sqlx::Error) -> Self { Self::SqlxError(sqlx_error) } } impl User { 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 salt = SaltString::from_b64("dS/X5/sPEKTj4Rzs/CuvzQ").unwrap(); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(&pw.as_bytes(), &salt) .unwrap() .to_string(); if password_hash == user.pw { return Ok(user); } Err(LoginError::InvalidAuthenticationCombo) } } #[cfg(test)] mod test { use super::User; use sqlx::SqlitePool; async fn setup() -> SqlitePool { 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(); pool } #[sqlx::test] fn succ_login_with_test_db() { let pool = setup().await; User::login(&pool, "admin".into(), "admin".into()) .await .unwrap(); } #[sqlx::test] fn wrong_pw() { let pool = setup().await; assert!(User::login(&pool, "admin".into(), "admi".into()) .await .is_err()); } #[sqlx::test] fn wrong_username() { let pool = setup().await; assert!(User::login(&pool, "admi".into(), "admin".into()) .await .is_err()); } }