Use new login method for normal login

This commit is contained in:
Jackzie 2025-04-21 11:40:06 -05:00
parent fe17ca5633
commit 3f222dfd3c
5 changed files with 47 additions and 35 deletions

View file

@ -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<UserModel, UserAuthError> {
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)
}
}

View file

@ -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<Option<UserModel>, 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<UserModel> {
/// Validates user login form
pub async fn try_login_user_form(ctx: &mut Context<'_>, users: &UsersState, ip: IpAddr, session: &Session<'_, SessionData>) -> Result<UserModel, UserAuthError> {
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<UserModel, UserAuthError> {
let user = query_as!(UserModelWithPassword,

View file

@ -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;

View file

@ -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<String>,
logged_out: Option<bool>,
settings: &State<AppConfig>,
) -> Template {
// TODO: redirect if already logged in
let csrf_token = set_csrf(&session).await;
@ -48,29 +50,21 @@ struct LoginForm<'r> {
#[post("/auth/login?<return_to>", data = "<form>")]
pub async fn handler(
pool: &State<DB>,
route: &Route,
ip_addr: IpAddr,
session: Session<'_, SessionData>,
mut form: Form<Contextual<'_, LoginForm<'_>>>,
users: &State<UsersState>,
settings: &State<AppConfig>,
return_to: Option<String>,
) -> Result<HackyRedirectBecauseRocketBug, Template> {
trace!("handler");
if !*DISABLE_LOGIN_CHECK {
validate_csrf_form(&mut form.context, &session).await;
}
let user = validate_user_form(&mut form.context, &pool).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);

View file

@ -101,7 +101,7 @@ async fn callback_handler(sso: &State<SSOState>, ip: IpAddr, code: String, state
}
#[get("/auth/sso/cb?<code>&<state>")]
pub async fn callback(sessions: Session<'_, SessionData>, config: &State<AppConfig>, users: &State<UsersState>, ip: IpAddr, sso: &State<SSOState>, code: String, state: String) -> Result<HackyRedirectBecauseRocketBug, (Status, Template)> {
pub async fn callback(session: Session<'_, SessionData>, config: &State<AppConfig>, users: &State<UsersState>, ip: IpAddr, sso: &State<SSOState>, code: String, state: String) -> Result<HackyRedirectBecauseRocketBug, (Status, Template)> {
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<AppConf
}
}
let user = user.unwrap();
users.login_user(user, ip, sessions).await;
users.login_user_session(user, ip, &session).await;
debug!("user={:?}\nemail={:?}\nname={:?}", userinfo.subject(), userinfo.email(), userinfo.name());
// TODO: login user to session, prob through UserManager/users
let return_to = return_to.unwrap_or("/".to_string());