use cfg_if::cfg_if; use leptos::*; use validator::Validate; use crate::backend::data::{ApiResponse, PwdChange, User, UserProfile}; use crate::components::data_form::ForValidation; cfg_if! { if #[cfg(feature = "ssr")] { use sqlx::{query_as, Error, PgPool, query}; use actix_session::*; use leptos_actix::{extract, redirect}; use log::{info, warn}; use crate::error::AppError; use crate::backend::get_pool; pub async fn has_admin_user(pool: &PgPool) -> Result { let count: (i64,) = query_as(r#"SELECT COUNT(id) FROM "user" WHERE admin = $1"#) .bind(true) .fetch_one(pool) .await?; Ok(count.0 > 0) } pub async fn create_admin(pool: &PgPool) -> Result<(), AppError> { if !has_admin_user(pool).await? { let pwd = pwhash::bcrypt::hash("admin"); query(r#"INSERT INTO "user"(login, password, full_name, admin) VALUES($1, $2, $3, $4)"#) .bind("admin") .bind(pwd.unwrap()) .bind("Admin User") .bind(true) .execute(pool).await?; } Ok(()) } pub async fn user_from_login(pool: &PgPool, login: &str) -> Result { let usr = query_as::<_, User>(r#"SELECT * FROM "user" WHERE login=$1"#) .bind(login) .fetch_one(pool).await?; Ok(usr) } pub async fn logged_in_user() -> Option { let session = extract::().await; if session.is_err() { return None } session.unwrap().get::("user").unwrap_or(None) /*let mut usr = User::default(); usr.full_name = Some("PokAdm".to_string()); usr.admin = true; Some(usr)*/ } pub async fn is_logged_in() -> bool { logged_in_user().await.is_some() } pub async fn is_admin() -> bool { if let Some(user) = logged_in_user().await { user.admin } else { false } } pub async fn admin_email() -> Option { let pool = get_pool().await.ok()?; let mail: Result<(String,), Error> = query_as(r#"SELECT email FROM "user" WHERE login = 'admin'"#) .fetch_one(&pool) .await; if let Ok(m) = mail { Some(m.0) } else { None } } pub async fn emails_for_notify() -> Result, ServerFnError> { let pool = get_pool().await?; let mails: Result, Error> = query_as(r#"SELECT email FROM "user" WHERE (email IS NOT NULL OR email <> '') AND get_emails = true"#) .fetch_all(&pool) .await; if let Err(e) = mails { if matches!(e, Error::RowNotFound) { Ok(vec![]) } else { Err(e.into()) } } else { Ok(mails.unwrap().into_iter().map(|m| m.0).collect()) } } }} #[server] pub async fn login(username: String, password: String) -> Result, ServerFnError> { use actix_session::*; use leptos_actix::extract; //use actix_web::http::StatusCode; //use leptos_actix::ResponseOptions; use crate::backend::get_pool; use crate::locales::trl; let pool = get_pool().await?; let user = user_from_login(&pool, &username).await.unwrap_or(User::default()); if !user.login.is_empty() && pwhash::bcrypt::verify(password, &user.password) { let session = extract::().await?; let _ = session.insert("user", user); info!("User {} logged in", username); redirect("/admin"); return Ok(ApiResponse::Data(())); } warn!("Login failed for user {}", username); //let response = expect_context::(); //response.set_status(StatusCode::UNAUTHORIZED); Ok(ApiResponse::Error(trl("Bad username or password")())) } #[server] pub async fn logout() -> Result<(), ServerFnError> { let session = extract::().await?; session.clear(); info!("User logged out"); redirect("/login"); Ok(()) } #[server] pub async fn auth_check() -> Result { Ok(is_logged_in().await) } #[server] pub async fn admin_check() -> Result { Ok(is_admin().await) } #[server] pub async fn get_user() -> Result, ServerFnError> { info!("Get user"); Ok(logged_in_user().await) } #[server(GetUsers, "/api", "Url", "get_users")] pub async fn get_users() -> Result>, ServerFnError> { use crate::perm_check; use crate::backend::get_pool; perm_check!(is_admin); let pool = get_pool().await?; let users = sqlx::query_as::<_, User>(r#"SELECT * FROM "user" ORDER BY login"#).fetch_all(&pool).await?; Ok(ApiResponse::Data(users)) } #[server] pub async fn update_profile(user: UserProfile) -> Result, ServerFnError> { use crate::user_check; use crate::backend::get_pool; use crate::locales::trl; user_check!(user.login()); let usr = logged_in_user().await.unwrap_or(User::default()); if !usr.admin && user.admin() { //let response = expect_context::(); //response.set_status(StatusCode::FORBIDDEN); return Ok(ApiResponse::Error(trl("You can't escalate your privileges")())) } let pool = get_pool().await?; sqlx::query(r#"UPDATE "user" SET full_name = $1, email = $2, get_emails = $3, admin = $4 WHERE login = $5"#) .bind(user.full_name()) .bind(user.email()) .bind(user.get_emails()) .bind(user.admin()) .bind(user.login()) .execute(&pool) .await?; if logged_in_user().await.unwrap_or_default().login == user.login() { let usr = user_from_login(&pool, user.login()).await?; let session = extract::().await?; let _ = session.insert("user", usr); } Ok(ApiResponse::Data(())) } impl ForValidation for UpdateProfile { fn entity(&self) -> &dyn Validate { &self.user } } #[server] pub async fn change_pwd(new_pw: PwdChange) -> Result, ServerFnError> { use crate::user_check; use crate::backend::get_pool; use crate::locales::trl; user_check!(new_pw.login()); let pool = get_pool().await?; let usr = user_from_login(&pool, new_pw.login()).await?; let user = logged_in_user().await.unwrap_or(User::default()); if (!user.admin || user.login == new_pw.login()) && !pwhash::bcrypt::verify(new_pw.old_password(), &usr.password) { //let response = expect_context::(); //response.set_status(StatusCode::UNAUTHORIZED); return Ok(ApiResponse::Error(trl("Invalid old password")())) } sqlx::query(r#"UPDATE "user" SET password = $1 WHERE login = $2"#) .bind(pwhash::bcrypt::hash(new_pw.password()).unwrap()) .bind(new_pw.login()) .execute(&pool) .await?; info!("Password changed for user: {}", new_pw.login()); Ok(ApiResponse::Data(())) } impl ForValidation for ChangePwd { fn entity(&self) -> &dyn Validate { &self.new_pw } } #[server] pub async fn create_user(user: UserProfile) -> Result, ServerFnError> { use crate::perm_check; use crate::backend::get_pool; use crate::locales::trl; perm_check!(is_admin); let pool = get_pool().await?; let count: (i64,) = sqlx::query_as(r#"SELECT COUNT(id) FROM "user" WHERE login = $1"#) .bind(user.login()) .fetch_one(&pool) .await?; if count.0 != 0 { //let response = expect_context::(); //response.set_status(StatusCode::CONFLICT); return Ok(ApiResponse::Error(trl("Username already exists")())); } let usr_pw = user.password().clone(); sqlx::query(r#"INSERT INTO "user"(login, password, full_name, email, admin, get_emails) VALUES($1, $2, $3, $4, $5, $6)"#) .bind(user.login()) .bind(pwhash::bcrypt::hash(usr_pw.unwrap_or("".to_string())).unwrap()) .bind(user.full_name()) .bind(user.email()) .bind(user.admin()) .bind(user.get_emails()) .execute(&pool) .await?; info!("Created user {}", user.login()); Ok(ApiResponse::Data(())) } impl ForValidation for CreateUser { fn entity(&self) -> &dyn Validate { &self.user } } #[server] pub async fn delete_user(id: i32) -> Result, ServerFnError> { use crate::perm_check; use crate::backend::get_pool; use crate::locales::trl; perm_check!(is_admin); let user = logged_in_user().await.unwrap_or_default(); if user.id() == id { //let response = expect_context::(); //response.set_status(StatusCode::NOT_ACCEPTABLE); return Ok(ApiResponse::Error(trl("You can't delete yourself")())) } sqlx::query(r#"DELETE FROM "user" WHERE id=$1"#) .bind(id) .execute(&get_pool().await?) .await?; info!("User deleted"); Ok(ApiResponse::Data(())) } #[server] pub async fn get_pow() -> Result { use leptos_captcha::spow::pow::Pow; if !cfg!(debug_assertions) { Ok(Pow::with_difficulty(10, 10)?.to_string()) } else { Ok(Pow::new(10)?.to_string()) } }