From 3f222dfd3cf5c9fadb66debeb67afeacb458d8ba Mon Sep 17 00:00:00 2001 From: Jackz Date: Mon, 21 Apr 2025 11:40:06 -0500 Subject: [PATCH] Use new login method for normal login --- src/managers/user.rs | 34 ++++++++++++++++++++++++++++++---- src/models/user.rs | 20 ++++++-------------- src/routes/ui/auth.rs | 2 +- src/routes/ui/auth/login.rs | 22 ++++++++-------------- src/routes/ui/auth/sso.rs | 4 ++-- 5 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/managers/user.rs b/src/managers/user.rs index 37354e2..8ea7d32 100644 --- a/src/managers/user.rs +++ b/src/managers/user.rs @@ -10,9 +10,9 @@ use rocket_session_store::memory::MemoryStore; use sqlx::{query, query_as, Pool, QueryBuilder}; use uuid::Uuid; use crate::config::AppConfig; -use crate::consts::ENCRYPTION_ROUNDS; +use crate::consts::{DISABLE_LOGIN_CHECK, ENCRYPTION_ROUNDS}; use crate::{LoginSessionData, SessionData, DB}; -use crate::models::user::{UserAuthError, UserModel}; +use crate::models::user::{UserAuthError, UserModel, UserModelWithPassword}; pub struct UserManager { pool: DB, @@ -36,7 +36,7 @@ pub enum FindUserOption { #[derive(Hash)] pub struct SSOData { - pub provider_id: String, + pub(crate) provider_id: String, pub(crate) sub: String } @@ -111,7 +111,7 @@ impl UserManager { }) } - pub async fn login_user(&self, user: UserModel, ip_address: IpAddr, sessions: Session<'_, SessionData>) { + pub async fn login_user_session(&self, user: UserModel, ip_address: IpAddr, sessions: &Session<'_, SessionData>) { sessions.set(SessionData { csrf_token: None, login: Some(LoginSessionData { @@ -120,4 +120,30 @@ impl UserManager { }), }).await.unwrap(); } + + pub async fn login_normal_user(&self, email_or_usrname: &str, password: &str, ip: IpAddr, session: &Session<'_, SessionData>) -> Result { + let user = query_as!(UserModelWithPassword, + "select id, username, password, created_at, email, name from storage.users where email = $1 OR username = $1", email_or_usrname + ) + .fetch_optional(&self.pool) + .await + .map_err(|e| UserAuthError::DatabaseError(e))?; + let Some(user) = user else { + return Err(UserAuthError::UserNotFound); + }; + if let Some(db_password) = user.password { + if !*DISABLE_LOGIN_CHECK || bcrypt::verify(password, &db_password).map_err(|e| UserAuthError::EncryptionError(e))? { + let model = UserModel { + id: user.id, + email: user.email, + username: user.username, + created_at: user.created_at, + name: user.name + }; + self.login_user_session(model.clone(), ip, session).await; + return Ok(model) + } + } + Err(UserAuthError::PasswordInvalid) + } } \ No newline at end of file diff --git a/src/models/user.rs b/src/models/user.rs index b2e2e63..6f9c887 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,5 +1,6 @@ use std::error::Error; use std::fmt::{Display, Formatter}; +use std::net::IpAddr; use bcrypt::BcryptError; use chrono::NaiveDateTime; use rocket::form::Context; @@ -9,9 +10,11 @@ use rocket::form::error::Entity; use rocket::response::Responder; use rocket::serde::Serialize; use rocket::serde::uuid::Uuid; +use rocket_session_store::Session; use sqlx::{query_as, FromRow}; use crate::consts::{DISABLE_LOGIN_CHECK, ENCRYPTION_ROUNDS}; use crate::{LoginSessionData, SessionData, DB}; +use crate::managers::user::UsersState; use crate::models::repo::RepoModel; use crate::util::JsonErrorResponse; @@ -95,22 +98,11 @@ pub async fn get_user(pool: &DB, user_id: &str) -> Result, any .fetch_optional(pool) .await.map_err(anyhow::Error::from) } -/// Validates user login form, returning Some on success or None (with ctx containing errors) on failure -pub async fn validate_user_form(ctx: &mut Context<'_>, pool: &DB) -> Option { +/// Validates user login form +pub async fn try_login_user_form(ctx: &mut Context<'_>, users: &UsersState, ip: IpAddr, session: &Session<'_, SessionData>) -> Result { let username = ctx.field_value("username").unwrap(); let password = ctx.field_value("password").unwrap(); // TODO: no unwrap - match validate_user(pool, username, password).await { - Ok(u) => Some(u), - Err(UserAuthError::PasswordInvalid | UserAuthError::UserNotFound) => { - ctx.push_error(form::Error::validation("Username or password is incorrect").with_entity(Entity::Form)); - None - }, - Err(e) => { - ctx.push_error(form::Error::custom(e)); - None - } - } - + users.login_normal_user(username, password, ip, session).await } pub async fn validate_user(pool: &DB, email_or_usrname: &str, password: &str) -> Result { let user = query_as!(UserModelWithPassword, diff --git a/src/routes/ui/auth.rs b/src/routes/ui/auth.rs index 906d922..63be64e 100644 --- a/src/routes/ui/auth.rs +++ b/src/routes/ui/auth.rs @@ -9,7 +9,7 @@ use rocket::http::uri::{Origin, Reference, Uri}; use rocket::response::Redirect; use rocket_dyn_templates::{context, Template}; use rocket_session_store::Session; -use crate::models::user::{validate_user, validate_user_form, UserAuthError, UserModel}; +use crate::models::user::{validate_user, try_login_user_form, UserAuthError, UserModel}; use crate::{GlobalMetadata, LoginSessionData, SessionData, DB}; use crate::guards::AuthUser; use crate::routes::ui; diff --git a/src/routes/ui/auth/login.rs b/src/routes/ui/auth/login.rs index d19c1b2..abecc97 100644 --- a/src/routes/ui/auth/login.rs +++ b/src/routes/ui/auth/login.rs @@ -8,7 +8,8 @@ use rocket_session_store::Session; use crate::{GlobalMetadata, LoginSessionData, SessionData, DB}; use crate::config::AppConfig; use crate::consts::{APP_METADATA, DISABLE_LOGIN_CHECK}; -use crate::models::user::validate_user_form; +use crate::managers::user::UsersState; +use crate::models::user::try_login_user_form; use crate::routes::ui::auth::HackyRedirectBecauseRocketBug; use crate::util::{set_csrf, validate_csrf_form}; @@ -19,6 +20,7 @@ pub async fn page( return_to: Option, logged_out: Option, settings: &State, + ) -> Template { // TODO: redirect if already logged in let csrf_token = set_csrf(&session).await; @@ -48,29 +50,21 @@ struct LoginForm<'r> { #[post("/auth/login?", data = "
")] pub async fn handler( - pool: &State, route: &Route, ip_addr: IpAddr, session: Session<'_, SessionData>, mut form: Form>>, + users: &State, settings: &State, return_to: Option, ) -> Result { trace!("handler"); - if !*DISABLE_LOGIN_CHECK { - validate_csrf_form(&mut form.context, &session).await; - } - let user = validate_user_form(&mut form.context, &pool).await; + validate_csrf_form(&mut form.context, &session).await; + let user = try_login_user_form(&mut form.context, users.inner(), ip_addr, &session).await.ok(); + // TODO: use new users fetch user trace!("check form"); if form.context.status() == Status::Ok { - if let Some(submission) = &form.value { - session.set(SessionData { - csrf_token: None, - login: Some(LoginSessionData { - user: user.expect("failed to acquire user but no errors"), // if validate_user_form returned None, form had errors, this shouldnt run, - ip_address: ip_addr, - }), - }).await.unwrap(); + if let Some(_) = &form.value { let mut return_to_path = return_to.unwrap_or("/".to_string()); if return_to_path == "" { return_to_path.push_str("/"); } debug!("returning user to {:?}", return_to_path); diff --git a/src/routes/ui/auth/sso.rs b/src/routes/ui/auth/sso.rs index b461f27..28e5eaa 100644 --- a/src/routes/ui/auth/sso.rs +++ b/src/routes/ui/auth/sso.rs @@ -101,7 +101,7 @@ async fn callback_handler(sso: &State, ip: IpAddr, code: String, state } #[get("/auth/sso/cb?&")] -pub async fn callback(sessions: Session<'_, SessionData>, config: &State, users: &State, ip: IpAddr, sso: &State, code: String, state: String) -> Result { +pub async fn callback(session: Session<'_, SessionData>, config: &State, users: &State, ip: IpAddr, sso: &State, code: String, state: String) -> Result { let (userinfo, provider_id, return_to) = callback_handler(sso, ip, code, state).await .map_err(|e| (Status::InternalServerError, Template::render("errors/500", context! { error: e.to_string() @@ -141,7 +141,7 @@ pub async fn callback(sessions: Session<'_, SessionData>, config: &State