From 625ea9193202f678ab2799283ddfe2b282ba1e90 Mon Sep 17 00:00:00 2001 From: Jokler Date: Mon, 5 Oct 2026 16:07:20 +0200 Subject: Add user creation --- src/admin.rs | 77 +++++++++++++++++++++++++++++++++++++++++----- src/admin/user_creation.rs | 42 +++++++++++++++++++++---- src/main.rs | 10 ++++-- src/user.rs | 14 ++++----- 4 files changed, 120 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/admin.rs b/src/admin.rs index 2efe02f..912d0a3 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -5,21 +5,32 @@ use actix_http::error::ResponseError; use actix_identity::Identity; use actix_web::{http::header, web, HttpResponse}; use askama::Template; -use serde::Deserialize; +use serde::{Serialize, Deserialize}; use crate::error::ServiceError; use crate::model::{DbExecutor, NewUser}; +use crate::user::LoginData; use crate::util::UserRequest; use crate::Secret; pub mod user_creation; -use user_creation::{PassHashExecutor, PassHashRequest, SaveUserRequest}; +use user_creation::{ChangePasswordRequest, PassHashExecutor, PassHashRequest, SaveUserRequest}; #[derive(Template)] #[template(path = "admin.htm")] -struct AdminTemplate; +struct AdminTemplate { + success: OperationResult, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum OperationResult { + ChangePassword, + CreateUser, + Empty, +} -pub async fn index(ident: Identity, db: web::Data>) -> HttpResponse { +pub async fn handle_index(ident: Identity, db: web::Data>) -> HttpResponse { if let Some(name) = ident.identity() { ident.remember(name.clone()); let user = match db.send(UserRequest(name.clone())).await.unwrap() { @@ -35,7 +46,11 @@ pub async fn index(ident: Identity, db: web::Data>) -> HttpResp return ServiceError::Unauthorized.error_response(); } - let page = AdminTemplate.render().unwrap(); + let page = AdminTemplate { + success: OperationResult::Empty, + } + .render() + .unwrap(); HttpResponse::Ok().content_type("text/html").body(page) } else { @@ -53,7 +68,7 @@ pub struct UserData { pub password: String, } -pub async fn create_user( +pub async fn handle_create_user( ident: Identity, params: web::Form, db: web::Data>, @@ -95,7 +110,7 @@ pub async fn create_user( name: new_login.user, // TODO Let the admin pick power_level: 0, - balance: balance, + balance, }; db.send(SaveUserRequest::new(new_user, hash)) @@ -112,3 +127,51 @@ pub async fn create_user( .finish() } } + +pub async fn handle_change_password( + ident: Identity, + params: web::Form, + db: web::Data>, + hasher: web::Data>, + sec: web::Data, +) -> HttpResponse { + if let Some(name) = ident.identity() { + ident.remember(name.clone()); + let user = db.send(UserRequest(name.clone())).await.unwrap().unwrap(); + if user.power_level != 9001 { + return ServiceError::Unauthorized.error_response(); + } + + change_password(params.into_inner(), &db, &hasher, sec.0.clone()).await; + + let page = AdminTemplate { + success: OperationResult::ChangePassword, + } + .render() + .unwrap(); + + HttpResponse::Ok().content_type("text/html").body(page) + } else { + HttpResponse::Found() + .header(header::LOCATION, "/user/login") + .finish() + } +} + +pub async fn change_password( + login: LoginData, + db: &Addr, + hasher: &Addr, + secret: String, +) { + let hash = hasher + .send(PassHashRequest::new(login.password, secret)) + .await + .unwrap() + .unwrap(); + + db.send(ChangePasswordRequest(login.user, hash)) + .await + .unwrap() + .unwrap(); +} diff --git a/src/admin/user_creation.rs b/src/admin/user_creation.rs index 563b660..d54b063 100644 --- a/src/admin/user_creation.rs +++ b/src/admin/user_creation.rs @@ -4,15 +4,15 @@ use diesel::MysqlConnection; use diesel::{ dsl::sql, types::{Bigint, Unsigned}, - RunQueryDsl, + ExpressionMethods, QueryDsl, RunQueryDsl, }; use crate::error::ServiceError; -use crate::model::{DbExecutor, NewPassword, NewUser}; -use crate::schema::passwords::dsl::passwords; -use crate::schema::users::dsl::users; +use crate::model::{DbExecutor, NewPassword, NewUser, User}; +use crate::schema::passwords::{columns as password_columns, dsl::passwords}; +use crate::schema::users::{columns as user_columns, dsl::users}; -static LAST_ID_SQL: &'static str = "SELECT LAST_INSERT_ID()"; +static LAST_ID_SQL: &str = "SELECT LAST_INSERT_ID()"; pub struct PassHashExecutor(pub Hasher<'static>); @@ -74,7 +74,7 @@ impl Handler for DbExecutor { let id = sql::>(LAST_ID_SQL).get_result(conn)?; let new_pass = NewPassword { - id: id, + id, hash: &msg.pass_hash, }; @@ -85,3 +85,33 @@ impl Handler for DbExecutor { Ok(()) } } + +pub struct ChangePasswordRequest(pub String, pub String); + +impl Message for ChangePasswordRequest { + type Result = Result<(), ServiceError>; +} + +impl Handler for DbExecutor { + type Result = Result<(), ServiceError>; + + fn handle(&mut self, req: ChangePasswordRequest, _: &mut Self::Context) -> Self::Result { + let conn: &MysqlConnection = &self.0.get().unwrap(); + let (name, hash) = (req.0, req.1); + + let user = users + .filter(user_columns::name.eq(name)) + .first::(conn) + .map_err(|_| ServiceError::NotFound)?; + + if user.power_level == 9001 { + return Err(ServiceError::Unauthorized); + } + + diesel::update(passwords.filter(password_columns::id.eq(user.id))) + .set(password_columns::hash.eq(hash)) + .execute(conn)?; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 0f39622..c5de543 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,10 +100,14 @@ async fn main() -> std::io::Result<()> { .route("/settings", web::get().to(user::settings)) .route("/reset-password", web::post().to(user::reset_password)) .route("/logout", web::get().to(user::logout)) + .route("/admin", web::get().to(admin::handle_index)) .service( - web::resource("/admin") - .route(web::get().to(admin::index)) - .route(web::post().to(admin::create_user)), + web::scope("/admin") + .route("/create-user", web::post().to(admin::handle_create_user)) + .route( + "/change-password", + web::post().to(admin::handle_change_password), + ), ) .service(actix_files::Files::new("/static", "static/")) }) diff --git a/src/user.rs b/src/user.rs index 8cb2c5f..6ef4652 100644 --- a/src/user.rs +++ b/src/user.rs @@ -254,7 +254,7 @@ pub async fn confirm_transfer( header::LOCATION, format!( "/transfer?{}", - serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + serde_urlencoded::to_string(query).unwrap_or_else(|_| String::with_capacity(0)) ), ) .finish(); @@ -272,7 +272,7 @@ pub async fn confirm_transfer( header::LOCATION, format!( "/transfer?{}", - serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + serde_urlencoded::to_string(query).unwrap_or_else(|_| String::with_capacity(0)) ), ) .finish(); @@ -297,7 +297,7 @@ pub async fn confirm_transfer( header::LOCATION, format!( "/transfer?{}", - serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + serde_urlencoded::to_string(query).unwrap_or_else(|_| String::with_capacity(0)) ), ) .finish(); @@ -343,7 +343,7 @@ impl Default for PasswordResult { pub async fn settings(ident: Identity, params: web::Query) -> HttpResponse { if let Some(name) = ident.identity() { - ident.remember(name.clone()); + ident.remember(name); let query = params.into_inner(); HttpResponse::Ok().content_type("text/html").body( @@ -398,7 +398,7 @@ pub async fn reset_password( header::LOCATION, format!( "/settings?{}", - serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + serde_urlencoded::to_string(query).unwrap_or_else(|_| String::with_capacity(0)) ), ) .finish(); @@ -422,7 +422,7 @@ pub async fn reset_password( header::LOCATION, format!( "/settings?{}", - serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + serde_urlencoded::to_string(query).unwrap_or_else(|_| String::with_capacity(0)) ), ) .finish(); @@ -448,7 +448,7 @@ pub async fn reset_password( header::LOCATION, format!( "/settings?{}", - serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + serde_urlencoded::to_string(query).unwrap_or_else(|_| String::with_capacity(0)) ), ) .finish() -- cgit v1.3.1