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; 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 { extract(|session: Session| async move { session.get::("user").unwrap_or(None) }).await.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 } } }} #[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; 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) { extract(|session: Session| async move { let _ = session.insert("user", user); }) .await?; 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); return Ok(ApiResponse::Error("Bad username or password".to_string())) } #[server] pub async fn logout() -> Result<(), ServerFnError> { extract(|session: Session| async move { session.clear(); }).await?; 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> { 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; 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("You can't escalate your privileges".to_string())) } 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?; extract(|session: Session| async move { let _ = session.insert("user", usr); }).await?; } 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; 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("Invalid old password".to_string())) } 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?; 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; 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("Username already exists".to_string())); } 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; 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("You can't delete yourself".to_string())) } sqlx::query(r#"DELETE FROM "user" WHERE id=$1"#) .bind(id) .execute(&get_pool().await?) .await?; info!("User deleted"); Ok(ApiResponse::Data(())) }