use std::ops::{Deref, DerefMut}; use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; use chrono::{Datelike, Local, NaiveDate}; use log::info; use rocket::{ async_trait, http::{Cookie, Status}, request::{self, FromRequest, Outcome}, time::{Duration, OffsetDateTime}, Request, }; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use super::{family::Family, log::Log, role::Role, tripdetails::TripDetails, Day}; use crate::tera::admin::user::UserEditForm; const RENNRUDERBEITRAG: i32 = 11000; const BOAT_STORAGE: i32 = 4500; const FAMILY_TWO: i32 = 30000; const FAMILY_THREE_OR_MORE: i32 = 35000; const STUDENT_OR_PUPIL: i32 = 8000; const REGULAR: i32 = 22000; const UNTERSTUETZEND: i32 = 2500; const FOERDERND: i32 = 8500; #[derive(FromRow, Debug, Serialize, Deserialize, Clone)] pub struct User { pub id: i64, pub name: String, pub pw: Option, pub dob: Option, pub weight: Option, pub sex: Option, pub deleted: bool, pub last_access: Option, pub member_since_date: Option, pub birthdate: Option, pub mail: Option, pub nickname: Option, pub notes: Option, pub phone: Option, pub address: Option, pub family_id: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct UserWithRoles { #[serde(flatten)] pub user: User, pub roles: Vec, } impl UserWithRoles { pub async fn from_user(user: User, db: &SqlitePool) -> Self { Self { roles: user.roles(db).await, user, } } } #[derive(Debug, Serialize, Deserialize)] pub struct UserWithWaterStatus { #[serde(flatten)] pub user: User, pub on_water: bool, pub roles: Vec, } impl UserWithWaterStatus { pub async fn from_user(user: User, db: &SqlitePool) -> Self { Self { on_water: user.on_water(db).await, roles: user.roles(db).await, user, } } } impl PartialEq for User { fn eq(&self, other: &Self) -> bool { self.id == other.id } } #[derive(Debug)] pub enum LoginError { InvalidAuthenticationCombo, UserNotFound, UserDeleted, NotLoggedIn, NotAnAdmin, NotACox, NotATech, GuestNotAllowed, NoPasswordSet(User), DeserializationError, } #[derive(Debug, Serialize)] pub struct Fee { pub sum_in_cents: i32, pub parts: Vec<(String, i32)>, pub name: String, pub user_ids: String, pub paid: bool, pub users: Vec, } impl Default for Fee { fn default() -> Self { Self::new() } } impl Fee { pub fn new() -> Self { Self { sum_in_cents: 0, name: "".into(), parts: Vec::new(), user_ids: "".into(), users: Vec::new(), paid: false, } } pub fn add(&mut self, desc: String, price_in_cents: i32) { self.sum_in_cents += price_in_cents; self.parts.push((desc, price_in_cents)); } pub fn add_person(&mut self, user: &User) { if !self.name.is_empty() { self.name.push_str(" + "); self.user_ids.push('&'); } self.name.push_str(&user.name); self.user_ids.push_str(&format!("user_ids[]={}", user.id)); self.users.push(user.clone()); } pub fn paid(&mut self) { self.paid = true; } pub fn merge(&mut self, fee: Fee) { for (desc, price_in_cents) in fee.parts { self.add(desc, price_in_cents); } } } impl User { pub async fn fee(&self, db: &SqlitePool) -> Option { if !self.has_role(db, "Donau Linz").await { return None; } if self.deleted { return None; } let mut fee = Fee::new(); if let Some(family) = Family::find_by_opt_id(db, self.family_id).await { for member in family.members(db).await { fee.add_person(&member); if member.has_role(db, "paid").await { fee.paid(); } fee.merge(member.fee_without_families(db).await); } if family.amount_family_members(db).await > 2 { fee.add("Familie 3+ Personen".into(), FAMILY_THREE_OR_MORE); } else { fee.add("Familie 2 Personen".into(), FAMILY_TWO); } } else { fee.add_person(self); if self.has_role(db, "paid").await { fee.paid(); } fee.merge(self.fee_without_families(db).await); } Some(fee) } async fn fee_without_families(&self, db: &SqlitePool) -> Fee { let mut fee = Fee::new(); if !self.has_role(db, "Donau Linz").await { return fee; } if self.has_role(db, "Rennrudern").await { fee.add("Rennruderbeitrag".into(), RENNRUDERBEITRAG); } let amount_boats = self.amount_boats(db).await; if amount_boats > 0 { fee.add( format!("{}x Bootsplatz", amount_boats), amount_boats * BOAT_STORAGE, ); } if self.has_role(db, "Unterstützend").await { fee.add("Unterstützendes Mitglied".into(), UNTERSTUETZEND); } else if self.has_role(db, "Förderndes Mitglied").await { fee.add("Förderndes Mitglied".into(), FOERDERND); } else if Family::find_by_opt_id(db, self.family_id).await.is_none() { if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await { fee.add("Schüler/Student".into(), STUDENT_OR_PUPIL); } else if self.has_role(db, "Ehrenmitglied").await { fee.add("Ehrenmitglied".into(), 0); } else { fee.add("Mitgliedsbeitrag".into(), REGULAR); } } fee } pub async fn amount_boats(&self, db: &SqlitePool) -> i32 { sqlx::query!( "SELECT COUNT(*) as count FROM boat WHERE owner = ?", self.id ) .fetch_one(db) .await .unwrap() .count } pub async fn has_role(&self, db: &SqlitePool, role: &str) -> bool { if sqlx::query!( "SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)", self.id, role ) .fetch_optional(db) .await .unwrap() .is_some() { return true; } false } pub async fn roles(&self, db: &SqlitePool) -> Vec { sqlx::query!( "SELECT r.name FROM role r JOIN user_role ur ON r.id = ur.role_id JOIN user u ON u.id = ur.user_id WHERE ur.user_id = ? AND u.deleted = 0;", self.id ) .fetch_all(db) .await .unwrap() .into_iter().map(|r| r.name).collect() } pub async fn has_role_tx(&self, db: &mut Transaction<'_, Sqlite>, role: &str) -> bool { if sqlx::query!( "SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)", self.id, role ) .fetch_optional(db.deref_mut()) .await .unwrap() .is_some() { return true; } false } pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE id like ? ", id ) .fetch_one(db) .await .ok() } pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i32) -> Option { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE id like ? ", id ) .fetch_one(db.deref_mut()) .await .ok() } pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE name like ? ", name ) .fetch_one(db) .await .ok() } pub async fn on_water(&self, db: &SqlitePool) -> bool { if sqlx::query!( "SELECT * FROM logbook WHERE shipmaster=? AND arrival is null", self.id ) .fetch_optional(db) .await .unwrap() .is_some() { return true; } if sqlx::query!( "SELECT * FROM logbook JOIN rower ON rower.logbook_id=logbook.id WHERE rower_id=? AND arrival is null", self.id ) .fetch_optional(db) .await .unwrap() .is_some() { return true; } false } pub async fn all(db: &SqlitePool) -> Vec { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE deleted = 0 ORDER BY last_access DESC " ) .fetch_all(db) .await .unwrap() } pub async fn all_with_role(db: &SqlitePool, role: &Role) -> Vec { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user u JOIN user_role ur ON u.id = ur.user_id WHERE ur.role_id = ? AND deleted = 0 ORDER BY name; ", role.id ) .fetch_all(db) .await .unwrap() } pub async fn all_payer_groups(db: &SqlitePool) -> Vec { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE family_id IS NOT NULL GROUP BY family_id UNION -- Select users with a null family_id, without grouping SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE family_id IS NULL; " ) .fetch_all(db) .await .unwrap() } pub async fn ergo(db: &SqlitePool) -> Vec { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE deleted = 0 AND dob != '' and weight != '' and sex != '' ORDER BY name " ) .fetch_all(db) .await .unwrap() } pub async fn cox(db: &SqlitePool) -> Vec { sqlx::query_as!( Self, " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE deleted = 0 AND (SELECT COUNT(*) FROM user_role WHERE user_id=user.id AND role_id = (SELECT id FROM role WHERE name = 'cox')) > 0 ORDER BY last_access DESC " ) .fetch_all(db) .await .unwrap() } pub async fn create(db: &SqlitePool, name: &str) -> bool { sqlx::query!("INSERT INTO USER(name) VALUES (?)", name) .execute(db) .await .is_ok() } pub async fn update(&self, db: &SqlitePool, data: UserEditForm) { let mut family_id = data.family_id; if family_id.is_some_and(|x| x == -1) { family_id = Some(Family::insert(db).await) } sqlx::query!( "UPDATE user SET dob = ?, weight = ?, sex = ?, member_since_date=?, birthdate=?, mail=?, nickname=?, notes=?, phone=?, address=?, family_id = ? where id = ?", data.dob, data.weight, data.sex, data.member_since_date, data.birthdate, data.mail, data.nickname, data.notes, data.phone, data.address, family_id, self.id ) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id // handle roles sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id) .execute(db) .await .unwrap(); for role_id in data.roles.into_keys() { self.add_role( db, &Role::find_by_id(db, role_id.parse::().unwrap()) .await .unwrap(), ) .await; } } pub async fn add_role(&self, db: &SqlitePool, role: &Role) { sqlx::query!( "INSERT INTO user_role(user_id, role_id) VALUES (?, ?)", self.id, role.id ) .execute(db) .await .unwrap(); } pub async fn remove_role(&self, db: &SqlitePool, role: &Role) { sqlx::query!( "DELETE FROM user_role WHERE user_id = ? and role_id = ?", self.id, role.id ) .execute(db) .await .unwrap(); } pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result { let name = name.trim(); // just to make sure... let Some(user) = User::find_by_name(db, name).await else { if ![ "n-sageder", "p-hofer", "m-birner", "s-sollberger", "d-kortschak", "wwwadmin", "wadminw", "admin", "m sageder", "d kortschak", "a almousa", "p hofer", "s sollberger", "n sageder", "wp-system", "s.sollberger", "m.birner", "m-sageder", "a-almousa", "m.sageder", "n.sageder", "a.almousa", "p.hofer", "d.kortschak", "[login]", ] .contains(&name) { Log::create(db, format!("Username ({name}) not found (tried to login)")).await; } return Err(LoginError::InvalidAuthenticationCombo); // Username not found }; if user.deleted { Log::create( db, format!("User ({name}) already deleted (tried to login)."), ) .await; return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has //been deleted } if let Some(user_pw) = user.pw.as_ref() { let password_hash = &Self::get_hashed_pw(pw); if password_hash == user_pw { return Ok(user); } Log::create(db, format!("User {name} supplied the wrong PW")).await; Err(LoginError::InvalidAuthenticationCombo) } else { info!("User {name} has no PW set"); Err(LoginError::NoPasswordSet(user)) } } pub async fn reset_pw(&self, db: &SqlitePool) { sqlx::query!("UPDATE user SET pw = null where id = ?", self.id) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id } pub async fn update_pw(&self, db: &SqlitePool, pw: &str) { let pw = Self::get_hashed_pw(pw); sqlx::query!("UPDATE user SET pw = ? where id = ?", pw, self.id) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id } fn get_hashed_pw(pw: &str) -> String { let salt = SaltString::from_b64("dS/X5/sPEKTj4Rzs/CuvzQ").unwrap(); let argon2 = Argon2::default(); argon2 .hash_password(pw.as_bytes(), &salt) .unwrap() .to_string() } pub async fn logged_in(&self, db: &SqlitePool) { sqlx::query!( "UPDATE user SET last_access = CURRENT_TIMESTAMP where id = ?", self.id ) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id } pub async fn delete(&self, db: &SqlitePool) { sqlx::query!("UPDATE user SET deleted=1 WHERE id=?", self.id) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id } pub async fn get_days(&self, db: &SqlitePool) -> Vec { let mut days = Vec::new(); for i in 0..self.amount_days_to_show(db).await { let date = (Local::now() + chrono::Duration::days(i)).date_naive(); if self.has_role(db, "scheckbuch").await { days.push(Day::new_guest(db, date, false).await); } else { days.push(Day::new(db, date, false).await); } } for date in TripDetails::pinned_days(db, self.amount_days_to_show(db).await - 1).await { if self.has_role(db, "scheckbuch").await { let day = Day::new_guest(db, date, true).await; if !day.planned_events.is_empty() { days.push(day); } } else { days.push(Day::new(db, date, true).await); } } days } async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { if self.has_role(db, "cox").await { let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, //december //has 31 //days end_of_year .signed_duration_since(Local::now().date_naive()) .num_days() + 1 } else { 6 } } } #[async_trait] impl<'r> FromRequest<'r> for User { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { match req.cookies().get_private("loggedin_user") { Some(user_id) => match user_id.value().parse::() { Ok(user_id) => { let db = req.rocket().state::().unwrap(); let Some(user) = User::find_by_id(db, user_id).await else { return Outcome::Error((Status::Forbidden, LoginError::UserNotFound)); }; if user.deleted { return Outcome::Error((Status::Forbidden, LoginError::UserDeleted)); } user.logged_in(db).await; let mut cookie = Cookie::new("loggedin_user", format!("{}", user.id)); cookie.set_expires(OffsetDateTime::now_utc() + Duration::weeks(2)); req.cookies().add_private(cookie); Outcome::Success(user) } Err(_) => Outcome::Error((Status::Unauthorized, LoginError::DeserializationError)), }, None => Outcome::Error((Status::Unauthorized, LoginError::NotLoggedIn)), } } } pub struct TechUser { pub(crate) user: User, } impl Deref for TechUser { type Target = User; fn deref(&self) -> &Self::Target { &self.user } } #[async_trait] impl<'r> FromRequest<'r> for TechUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "tech").await { Outcome::Success(TechUser { user }) } else { Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } pub struct CoxUser { user: User, } impl Deref for CoxUser { type Target = User; fn deref(&self) -> &Self::Target { &self.user } } impl CoxUser { pub async fn new(db: &SqlitePool, user: User) -> Option { if user.has_role(db, "cox").await { Some(CoxUser { user }) } else { None } } } #[async_trait] impl<'r> FromRequest<'r> for CoxUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "cox").await { Outcome::Success(CoxUser { user }) } else { Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } #[derive(Debug, Serialize, Deserialize)] pub struct AdminUser { pub(crate) user: User, } #[async_trait] impl<'r> FromRequest<'r> for AdminUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "admin").await { Outcome::Success(AdminUser { user }) } else { Outcome::Forward(Status::Forbidden) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } #[derive(Debug, Serialize, Deserialize)] pub struct AllowedForPlannedTripsUser(pub(crate) User); #[async_trait] impl<'r> FromRequest<'r> for AllowedForPlannedTripsUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "Donau Linz").await | user.has_role(db, "scheckbuch").await { Outcome::Success(AllowedForPlannedTripsUser(user)) } else { Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } impl From for User { fn from(val: AllowedForPlannedTripsUser) -> Self { val.0 } } #[derive(Debug, Serialize, Deserialize)] pub struct DonauLinzUser(pub(crate) User); impl From for User { fn from(val: DonauLinzUser) -> Self { val.0 } } impl Deref for DonauLinzUser { type Target = User; fn deref(&self) -> &Self::Target { &self.0 } } #[async_trait] impl<'r> FromRequest<'r> for DonauLinzUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "Donau Linz").await && !user.has_role(db, "Unterstützend").await && !user.has_role(db, "Förderndes Mitglied").await { Outcome::Success(DonauLinzUser(user)) } else { Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } #[derive(Debug, Serialize, Deserialize)] pub struct SchnupperBetreuerUser(pub(crate) User); impl From for User { fn from(val: SchnupperBetreuerUser) -> Self { val.0 } } impl Deref for SchnupperBetreuerUser { type Target = User; fn deref(&self) -> &Self::Target { &self.0 } } #[async_trait] impl<'r> FromRequest<'r> for SchnupperBetreuerUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "schnupper-betreuer").await { Outcome::Success(SchnupperBetreuerUser(user)) } else { Outcome::Forward(Status::Forbidden) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } #[derive(Debug, Serialize, Deserialize)] pub struct VorstandUser(pub(crate) User); impl From for User { fn from(val: VorstandUser) -> Self { val.0 } } impl Deref for VorstandUser { type Target = User; fn deref(&self) -> &Self::Target { &self.0 } } #[async_trait] impl<'r> FromRequest<'r> for VorstandUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "Vorstand").await { Outcome::Success(VorstandUser(user)) } else { Outcome::Forward(Status::Forbidden) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } #[derive(Debug, Serialize, Deserialize)] pub struct PlannedEventUser(pub(crate) User); impl From for User { fn from(val: PlannedEventUser) -> Self { val.0 } } impl Deref for PlannedEventUser { type Target = User; fn deref(&self) -> &Self::Target { &self.0 } } #[async_trait] impl<'r> FromRequest<'r> for PlannedEventUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { if user.has_role(db, "planned_event").await { Outcome::Success(PlannedEventUser(user)) } else { Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), Outcome::Forward(f) => Outcome::Forward(f), } } } #[cfg(test)] mod test { use std::collections::HashMap; use crate::{tera::admin::user::UserEditForm, testdb}; use super::User; use sqlx::SqlitePool; #[sqlx::test] fn test_find_correct_id() { let pool = testdb!(); let user = User::find_by_id(&pool, 1).await.unwrap(); assert_eq!(user.id, 1); } #[sqlx::test] fn test_find_wrong_id() { let pool = testdb!(); let user = User::find_by_id(&pool, 1337).await; assert!(user.is_none()); } #[sqlx::test] fn test_find_correct_name() { let pool = testdb!(); let user = User::find_by_name(&pool, "admin".into()).await.unwrap(); assert_eq!(user.id, 1); } #[sqlx::test] fn test_find_wrong_name() { let pool = testdb!(); let user = User::find_by_name(&pool, "name-does-not-exist".into()).await; assert!(user.is_none()); } #[sqlx::test] fn test_all() { let pool = testdb!(); let res = User::all(&pool).await; assert!(res.len() > 3); } #[sqlx::test] fn test_cox() { let pool = testdb!(); let res = User::cox(&pool).await; assert_eq!(res.len(), 3); } #[sqlx::test] fn test_succ_create() { let pool = testdb!(); assert_eq!(User::create(&pool, "new-user-name".into()).await, true); } #[sqlx::test] fn test_duplicate_name_create() { let pool = testdb!(); assert_eq!(User::create(&pool, "admin".into()).await, false); } #[sqlx::test] fn test_update() { let pool = testdb!(); let user = User::find_by_id(&pool, 1).await.unwrap(); user.update( &pool, UserEditForm { id: 1, dob: None, weight: None, sex: Some("m".into()), roles: HashMap::new(), member_since_date: None, birthdate: None, mail: None, nickname: None, notes: None, phone: None, address: None, family_id: None, }, ) .await; let user = User::find_by_id(&pool, 1).await.unwrap(); assert_eq!(user.sex, Some("m".into())); } #[sqlx::test] fn succ_login_with_test_db() { let pool = testdb!(); User::login(&pool, "admin".into(), "admin".into()) .await .unwrap(); } #[sqlx::test] fn wrong_pw() { let pool = testdb!(); assert!(User::login(&pool, "admin".into(), "admi".into()) .await .is_err()); } #[sqlx::test] fn wrong_username() { let pool = testdb!(); assert!(User::login(&pool, "admi".into(), "admin".into()) .await .is_err()); } #[sqlx::test] fn reset() { let pool = testdb!(); let user = User::find_by_id(&pool, 1).await.unwrap(); user.reset_pw(&pool).await; let user = User::find_by_id(&pool, 1).await.unwrap(); assert_eq!(user.pw, None); } #[sqlx::test] fn update_pw() { let pool = testdb!(); let user = User::find_by_id(&pool, 1).await.unwrap(); assert!(User::login(&pool, "admin".into(), "abc".into()) .await .is_err()); user.update_pw(&pool, "abc".into()).await; User::login(&pool, "admin".into(), "abc".into()) .await .unwrap(); } }