use actix::{Actor, Handler, Message, SyncContext}; use argonautica::{input::SecretKey, Hasher}; use diesel::MysqlConnection; use diesel::{ dsl::sql, types::{Bigint, Unsigned}, ExpressionMethods, QueryDsl, RunQueryDsl, }; use crate::error::ServiceError; 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: &str = "SELECT LAST_INSERT_ID()"; pub struct PassHashExecutor(pub Hasher<'static>); impl Actor for PassHashExecutor { type Context = SyncContext; } pub struct PassHashRequest { password: String, secret: String, } impl PassHashRequest { pub fn new(password: String, secret: String) -> Self { Self { password, secret } } } impl Message for PassHashRequest { type Result = Result; } impl Handler for PassHashExecutor { type Result = Result; fn handle(&mut self, req: PassHashRequest, _: &mut Self::Context) -> Self::Result { let secret = SecretKey::from_base64_encoded(req.secret)?; Ok(self .0 .with_password(req.password) .with_secret_key(secret) .hash()?) } } pub struct SaveUserRequest { user: NewUser, pass_hash: String, } impl SaveUserRequest { pub fn new(user: NewUser, pass_hash: String) -> Self { Self { user, pass_hash } } } impl Message for SaveUserRequest { type Result = Result<(), ServiceError>; } impl Handler for DbExecutor { type Result = Result<(), ServiceError>; fn handle(&mut self, msg: SaveUserRequest, _: &mut Self::Context) -> Self::Result { let conn: &MysqlConnection = &self.0.get().unwrap(); diesel::insert_into(users).values(&msg.user).execute(conn)?; let id = sql::>(LAST_ID_SQL).get_result(conn)?; let new_pass = NewPassword { id, hash: &msg.pass_hash, }; diesel::insert_into(passwords) .values(&new_pass) .execute(conn)?; 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(()) } }