From e334cea0e2c275202b56cf0f3b2f84ac4e3d20f4 Mon Sep 17 00:00:00 2001 From: Philipp Hofer Date: Sat, 17 May 2025 08:15:59 +0200 Subject: [PATCH 1/3] already in db --- staging-diff.sql | 4 ---- 1 file changed, 4 deletions(-) diff --git a/staging-diff.sql b/staging-diff.sql index da2a13e..6fb21fc 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -3,7 +3,3 @@ INSERT INTO user(name) VALUES('Marie'); INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Marie'),(SELECT id FROM role where name = 'Donau Linz')); INSERT INTO user(name) VALUES('Philipp'); INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz')); - - -insert into role(name, cluster, formatted_name) values('dual_membership', 'financial', 'Doppelmitgliedschaft mit anderem österr. Ruderverein'); -insert into role(name, hide_in_lists) values('participated_schnupperkurs', true); -- 2.47.2 From f7bb394236eb11a2b203c9de8eff88f187b9d737 Mon Sep 17 00:00:00 2001 From: Philipp Hofer Date: Sat, 17 May 2025 09:48:45 +0200 Subject: [PATCH 2/3] remove more logs w/ activities --- src/model/activity.rs | 68 ++++++++++++++++++++++++++--- src/model/user/mod.rs | 67 +++++++++------------------- src/tera/admin/user.rs | 8 +++- src/tera/auth.rs | 4 +- templates/admin/user/view.html.tera | 6 ++- 5 files changed, 94 insertions(+), 59 deletions(-) diff --git a/src/model/activity.rs b/src/model/activity.rs index 5a83180..5586892 100644 --- a/src/model/activity.rs +++ b/src/model/activity.rs @@ -17,10 +17,31 @@ pub struct Activity { pub keep_until: Option, } +#[derive(Serialize, Deserialize, Debug)] +pub struct ActivityWithDetails { + #[serde(flatten)] + pub(crate) activity: Activity, + keep_until_days: Option, +} + +impl From for ActivityWithDetails { + fn from(activity: Activity) -> Self { + let keep_until_days = activity.keep_until.map(|keep_until| { + let now = Utc::now().naive_utc(); + let duration = keep_until.signed_duration_since(now); + duration.num_days() + }); + + Self { + keep_until_days, + activity, + } + } +} + // TODO: add `reason` as additional db field, to be able to query and show this to the users pub enum Reason<'a> { - // `User` tried to login with `String` as UserAgent - SuccLogin(&'a User, String), + Auth(ReasonAuth<'a>), // `User` changed the data of `User`, explanation in `String` UserDataChange(&'a ManageUserUser, &'a User, String), // New Note for User @@ -30,11 +51,7 @@ pub enum Reason<'a> { impl From> for ActivityBuilder { fn from(value: Reason<'_>) -> Self { match value { - Reason::SuccLogin(user, agent) => { - Self::new(&format!("{user} hat sich eingeloggt (User-Agent: {agent})")) - .relevant_for_user(user) - .keep_until_days(7) - } + Reason::Auth(auth) => auth.into(), Reason::UserDataChange(changed_by, changed_user, explanation) => Self::new(&format!( "{changed_by} hat die Daten von {changed_user} aktualisiert: {explanation}" )) @@ -46,6 +63,43 @@ impl From> for ActivityBuilder { } } +pub enum ReasonAuth<'a> { + // `User` tried to login with `String` as UserAgent + SuccLogin(&'a User, String), + // `User` tried to login which was already deleted + DeletedUserLogin(&'a User), + // `User` tried to login, supplied wrong PW + WrongPw(&'a User), +} + +impl<'a> From> for Reason<'a> { + fn from(auth_reason: ReasonAuth<'a>) -> Self { + Reason::Auth(auth_reason) + } +} + +impl From> for ActivityBuilder { + fn from(value: ReasonAuth<'_>) -> Self { + match value { + ReasonAuth::SuccLogin(user, agent) => { + Self::new(&format!("{user} hat sich eingeloggt (User-Agent: {agent})")) + .relevant_for_user(user) + .keep_until_days(7) + } + ReasonAuth::DeletedUserLogin(user) => Self::new(&format!( + "User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde." + )) + .relevant_for_user(user) + .keep_until_days(30), + ReasonAuth::WrongPw(user) => Self::new(&format!( + "User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben." + )) + .relevant_for_user(user) + .keep_until_days(7), + } + } +} + pub struct ActivityBuilder { text: String, relevant_for: String, diff --git a/src/model/user/mod.rs b/src/model/user/mod.rs index 34fed45..a94b996 100644 --- a/src/model/user/mod.rs +++ b/src/model/user/mod.rs @@ -13,7 +13,7 @@ use rocket::{ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; -use super::activity::ActivityBuilder; +use super::activity::{ActivityBuilder, ReasonAuth}; use super::{ log::Log, logbook::Logbook, @@ -465,51 +465,27 @@ ASKÖ Ruderverein Donau Linz", self.name), pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result { let name = name.trim().to_lowercase(); // just to make sure... let Some(user) = User::find_by_name(db, &name).await else { - if ![ - "n-sageder", - "p-hofer", - "marie-birner", - "daniel-kortschak", - "rudernlinz", - "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", - "philipp-hofer", - "d.kortschak", - "[login]", - ] - .contains(&name.as_str()) - { - Log::create(db, format!("Username ({name}) not found (tried to login)")).await; - } + Log::create(db, format!("Username ({name}) not found (tried to login)")).await; return Err(LoginError::InvalidAuthenticationCombo); // Username not found }; if user.deleted { - ActivityBuilder::new(&format!( + if let Some(board) = Role::find_by_name(db, "Vorstand").await { + Notification::create_for_role( + db, + &board, + &format!( "User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde." - )) - .relevant_for_user(&user) - .save(db) - .await; + ), + "Fehlgeschlagener Login", + None, + None, + ) + .await; + } + ActivityBuilder::from(ReasonAuth::DeletedUserLogin(&user)) + .save(db) + .await; return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has //been deleted } @@ -519,12 +495,9 @@ ASKÖ Ruderverein Donau Linz", self.name), if password_hash == user_pw { return Ok(user); } - ActivityBuilder::new(&format!( - "User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben." - )) - .relevant_for_user(&user) - .save(db) - .await; + ActivityBuilder::from(ReasonAuth::WrongPw(&user)) + .save(db) + .await; Err(LoginError::InvalidAuthenticationCombo) } else { info!("User {name} has no PW set"); diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 9e61844..17246a3 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -1,6 +1,6 @@ use crate::{ model::{ - activity::Activity, + activity::{Activity, ActivityWithDetails}, family::Family, log::Log, logbook::Logbook, @@ -141,7 +141,11 @@ async fn view( let member = Member::from(db, user.clone()).await; let fee = user.fee(db).await; - let activities = Activity::for_user(db, &user).await; + let activities: Vec = Activity::for_user(db, &user) + .await + .into_iter() + .map(Into::into) + .collect(); let financial = Role::all_cluster(db, "financial").await; let user_financial = user.financial(db).await; let skill = Role::all_cluster(db, "skill").await; diff --git a/src/tera/auth.rs b/src/tera/auth.rs index 425ce22..8a2340c 100644 --- a/src/tera/auth.rs +++ b/src/tera/auth.rs @@ -14,7 +14,7 @@ use rocket_dyn_templates::{context, tera, Template}; use sqlx::SqlitePool; use crate::model::{ - activity::{self, ActivityBuilder}, + activity::{self, ActivityBuilder, ReasonAuth}, log::Log, user::{LoginError, User}, }; @@ -83,7 +83,7 @@ async fn login( cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id))); - ActivityBuilder::from(activity::Reason::SuccLogin(&user, agent.0)) + ActivityBuilder::from(ReasonAuth::SuccLogin(&user, agent.0)) .save(db) .await; diff --git a/templates/admin/user/view.html.tera b/templates/admin/user/view.html.tera index ce1e54e..52b7014 100644 --- a/templates/admin/user/view.html.tera +++ b/templates/admin/user/view.html.tera @@ -411,7 +411,11 @@
    {% for activity in activities %}
  • - {{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }} + {{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }} + {% if activity.keep_until_days %} + (⏳ {{ activity.keep_until_days }} Tage) + {% endif %} +
  • {% else %}
  • Noch keine Aktivität... Stay tuned 😆
  • -- 2.47.2 From 066f47d99df874aeec3d7b84fbf1f04f44262599 Mon Sep 17 00:00:00 2001 From: Philipp Hofer Date: Sat, 17 May 2025 09:49:11 +0200 Subject: [PATCH 3/3] linting --- src/model/boat.rs | 2 +- src/model/family.rs | 2 +- src/model/mail.rs | 4 +-- src/model/user/fee.rs | 6 ++-- src/model/user/mod.rs | 48 ++++++++++++++++------------- src/model/user/regular.rs | 3 +- src/model/user/scheckbuch.rs | 5 +-- src/model/user/schnupperant.rs | 2 +- src/tera/admin/mod.rs | 4 +-- src/tera/admin/user.rs | 9 +++--- src/tera/auth.rs | 4 +-- templates/admin/user/view.html.tera | 4 +-- 12 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/model/boat.rs b/src/model/boat.rs index 051d645..767107b 100644 --- a/src/model/boat.rs +++ b/src/model/boat.rs @@ -1,8 +1,8 @@ use std::ops::DerefMut; use chrono::NaiveDateTime; -use rocket::serde::{Deserialize, Serialize}; use rocket::FromForm; +use rocket::serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use crate::model::boathouse::Boathouse; diff --git a/src/model/family.rs b/src/model/family.rs index bee9372..3408214 100644 --- a/src/model/family.rs +++ b/src/model/family.rs @@ -1,7 +1,7 @@ use std::ops::DerefMut; use serde::Serialize; -use sqlx::{sqlite::SqliteQueryResult, FromRow, Sqlite, SqlitePool, Transaction}; +use sqlx::{FromRow, Sqlite, SqlitePool, Transaction, sqlite::SqliteQueryResult}; use super::user::User; diff --git a/src/model/mail.rs b/src/model/mail.rs index ecabddd..0e405dd 100644 --- a/src/model/mail.rs +++ b/src/model/mail.rs @@ -1,9 +1,9 @@ use std::{error::Error, fs}; use lettre::{ - message::{header::ContentType, Attachment, MultiPart, SinglePart}, - transport::smtp::authentication::Credentials, Address, Message, SmtpTransport, Transport, + message::{Attachment, MultiPart, SinglePart, header::ContentType}, + transport::smtp::authentication::Credentials, }; use sqlx::{Sqlite, SqlitePool, Transaction}; diff --git a/src/model/user/fee.rs b/src/model/user/fee.rs index 044b750..192cad0 100644 --- a/src/model/user/fee.rs +++ b/src/model/user/fee.rs @@ -1,8 +1,8 @@ use super::User; use crate::{ - model::family::Family, BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, - FAMILY_TWO, FOERDERND, REGULAR, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, TRIAL_ROWING, - TRIAL_ROWING_REDUCED, UNTERSTUETZEND, + BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, + REGULAR, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, TRIAL_ROWING, TRIAL_ROWING_REDUCED, + UNTERSTUETZEND, model::family::Family, }; use chrono::{Datelike, Local, NaiveDate}; use serde::Serialize; diff --git a/src/model/user/mod.rs b/src/model/user/mod.rs index a94b996..57880cb 100644 --- a/src/model/user/mod.rs +++ b/src/model/user/mod.rs @@ -1,20 +1,21 @@ use std::{fmt::Display, ops::DerefMut}; -use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; +use argon2::{Argon2, PasswordHasher, password_hash::SaltString}; use chrono::{Datelike, Local, NaiveDate}; use log::info; use rocket::async_trait; use rocket::{ + Request, http::{Cookie, Status}, request::{FromRequest, Outcome}, time::{Duration, OffsetDateTime}, - Request, }; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use super::activity::{ActivityBuilder, ReasonAuth}; use super::{ + Day, log::Log, logbook::Logbook, mail::Mail, @@ -23,7 +24,6 @@ use super::{ role::Role, stat::Stat, tripdetails::TripDetails, - Day, }; use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD; use scheckbuch::ScheckbuchUser; @@ -487,7 +487,7 @@ ASKÖ Ruderverein Donau Linz", self.name), .save(db) .await; return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has - //been deleted + //been deleted } if let Some(user_pw) = user.pw.as_ref() { @@ -590,9 +590,9 @@ ASKÖ Ruderverein Donau Linz", self.name), pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { if self.allowed_to_steer(db).await { let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, - //december - //has 31 - //days + //december + //has 31 + //days let days_left_in_year = end_of_year .signed_duration_since(Local::now().date_naive()) .num_days() @@ -601,9 +601,9 @@ ASKÖ Ruderverein Donau Linz", self.name), if days_left_in_year <= 31 { let end_of_next_year = NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok, - //december - //has 31 - //days + //december + //has 31 + //days end_of_next_year .signed_duration_since(Local::now().date_naive()) .num_days() @@ -835,8 +835,8 @@ special_user!(SteeringUser, +"cox", +"Bootsführer"); special_user!(AdminUser, +"admin"); special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied"); special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO: - // remove -> - // RegularUser +// remove -> +// RegularUser special_user!(SchnupperBetreuerUser, +"schnupper-betreuer"); special_user!(VorstandUser, +"admin", +"Vorstand"); special_user!(EventUser, +"manage_events"); @@ -950,17 +950,21 @@ mod test { #[sqlx::test] fn wrong_pw() { let pool = testdb!(); - assert!(User::login(&pool, "admin".into(), "admi".into()) - .await - .is_err()); + 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()); + assert!( + User::login(&pool, "admi".into(), "admin".into()) + .await + .is_err() + ); } #[sqlx::test] @@ -980,9 +984,11 @@ mod test { let pool = testdb!(); let user = User::find_by_id(&pool, 1).await.unwrap(); - assert!(User::login(&pool, "admin".into(), "abc".into()) - .await - .is_err()); + assert!( + User::login(&pool, "admin".into(), "abc".into()) + .await + .is_err() + ); user.update_pw(&pool, "abc".into()).await; diff --git a/src/model/user/regular.rs b/src/model/user/regular.rs index f8b5ee4..b90e303 100644 --- a/src/model/user/regular.rs +++ b/src/model/user/regular.rs @@ -1,7 +1,8 @@ use super::{ManageUserUser, User}; use crate::{ + NonEmptyString, model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role}, - special_user, NonEmptyString, + special_user, }; use chrono::NaiveDate; use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt}; diff --git a/src/model/user/scheckbuch.rs b/src/model/user/scheckbuch.rs index bed539f..f9c370e 100644 --- a/src/model/user/scheckbuch.rs +++ b/src/model/user/scheckbuch.rs @@ -2,12 +2,13 @@ use super::foerdernd::FoerderndUser; use super::regular::RegularUser; use super::unterstuetzend::UnterstuetzendUser; use super::{ManageUserUser, User}; +use crate::NonEmptyString; use crate::model::activity::ActivityBuilder; use crate::model::role::Role; -use crate::NonEmptyString; use crate::{ + SCHECKBUCH, model::{mail::Mail, notification::Notification}, - special_user, SCHECKBUCH, + special_user, }; use chrono::NaiveDate; use rocket::async_trait; diff --git a/src/model/user/schnupperant.rs b/src/model/user/schnupperant.rs index 098ece8..183e064 100644 --- a/src/model/user/schnupperant.rs +++ b/src/model/user/schnupperant.rs @@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser; use super::schnupperinterest::SchnupperInterestUser; use super::unterstuetzend::UnterstuetzendUser; use super::{ManageUserUser, User}; +use crate::NonEmptyString; use crate::model::activity::ActivityBuilder; use crate::model::role::Role; -use crate::NonEmptyString; use crate::{ model::{mail::Mail, notification::Notification}, special_user, diff --git a/src/tera/admin/mod.rs b/src/tera/admin/mod.rs index 6d9be71..8e301e6 100644 --- a/src/tera/admin/mod.rs +++ b/src/tera/admin/mod.rs @@ -1,6 +1,6 @@ use csv::ReaderBuilder; -use rocket::{form::Form, get, post, routes, FromForm, Route, State}; -use rocket_dyn_templates::{context, Template}; +use rocket::{FromForm, Route, State, form::Form, get, post, routes}; +use rocket_dyn_templates::{Template, context}; use sqlx::SqlitePool; use crate::model::{activity::Activity, role::Role, user::AdminUser}; diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 17246a3..e0d3ffc 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -7,11 +7,11 @@ use crate::{ mail::valid_mails, role::Role, user::{ + AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails, + UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member, regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser, schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser, - AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails, - UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, }, }, tera::Config, @@ -19,6 +19,7 @@ use crate::{ use chrono::NaiveDate; use futures::future::join_all; use rocket::{ + FromForm, Request, Route, State, form::Form, fs::TempFile, get, @@ -26,9 +27,9 @@ use rocket::{ post, request::{FlashMessage, FromRequest, Outcome}, response::{Flash, Redirect}, - routes, FromForm, Request, Route, State, + routes, }; -use rocket_dyn_templates::{tera::Context, Template}; +use rocket_dyn_templates::{Template, tera::Context}; use sqlx::SqlitePool; // Custom request guard to extract the Referer header diff --git a/src/tera/auth.rs b/src/tera/auth.rs index 8a2340c..4d4cde3 100644 --- a/src/tera/auth.rs +++ b/src/tera/auth.rs @@ -1,4 +1,5 @@ use rocket::{ + FromForm, Request, Route, State, form::Form, get, http::{Cookie, CookieJar}, @@ -8,9 +9,8 @@ use rocket::{ response::{Flash, Redirect}, routes, time::{Duration, OffsetDateTime}, - FromForm, Request, Route, State, }; -use rocket_dyn_templates::{context, tera, Template}; +use rocket_dyn_templates::{Template, context, tera}; use sqlx::SqlitePool; use crate::model::{ diff --git a/templates/admin/user/view.html.tera b/templates/admin/user/view.html.tera index 52b7014..b583a77 100644 --- a/templates/admin/user/view.html.tera +++ b/templates/admin/user/view.html.tera @@ -412,9 +412,7 @@ {% for activity in activities %}
  • {{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }} - {% if activity.keep_until_days %} - (⏳ {{ activity.keep_until_days }} Tage) - {% endif %} + {% if activity.keep_until_days %}(⏳ {{ activity.keep_until_days }} Tage){% endif %}
  • {% else %} -- 2.47.2