diff options
| author | Jokler <jokler@protonmail.com> | 2020-06-21 06:37:46 +0200 |
|---|---|---|
| committer | Jokler <jokler@protonmail.com> | 2020-06-21 06:37:46 +0200 |
| commit | e6468b012d5b33dd16992652da57f11dd5a6e82f (patch) | |
| tree | e89add440df79d4036b9b44d8c77ee6d69e67201 /src/user.rs | |
| download | joklerpoints-master.tar.gz joklerpoints-master.zip | |
Diffstat (limited to 'src/user.rs')
| -rw-r--r-- | src/user.rs | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..8cb2c5f --- /dev/null +++ b/src/user.rs @@ -0,0 +1,467 @@ +use serde::{Deserialize, Serialize}; + +use actix::Addr; +use actix_identity::Identity; +use actix_web::{http::header, web, HttpResponse, Responder}; +use askama::Template; + +use log::error; + +use crate::model::DbExecutor; + +use crate::util::UserRequest; +use crate::Secret; + +pub mod login; +use login::{HashVerifyExecutor, HashVerifyRequest, LoginRequest}; + +mod settings; +use settings::UpdatePasswordRequest; + +mod transactions; +use transactions::{Transaction, TransactionsRequest, TransferRequest}; + +use crate::util::filters; + +use crate::admin::user_creation::{PassHashExecutor, PassHashRequest}; + +pub async fn index(ident: Identity) -> impl Responder { + if ident.identity().is_some() { + HttpResponse::Found() + .header(header::LOCATION, "/transactions") + .finish() + } else { + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() + } +} + +#[derive(Template)] +#[template(path = "login.htm")] +struct LoginTemplate { + failed: bool, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct LoginError { + #[serde(default)] + pub error: bool, +} + +pub async fn login(ident: Identity, params: web::Query<LoginError>) -> impl Responder { + if ident.identity().is_some() { + HttpResponse::Found() + .header(header::LOCATION, "/transactions") + .finish() + } else { + let failed = params.into_inner().error; + HttpResponse::Ok() + .content_type("text/html") + .body(LoginTemplate { failed }.render().unwrap()) + } +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct LoginData { + pub user: String, + pub password: String, +} + +pub async fn confirm_login( + ident: Identity, + params: web::Form<LoginData>, + db: web::Data<Addr<DbExecutor>>, + verifier: web::Data<Addr<HashVerifyExecutor>>, + sec: web::Data<Secret>, +) -> impl Responder { + let login = params.into_inner(); + + let (user, hash) = match db.send(LoginRequest(login.user)).await.unwrap() { + Ok(v) => v, + Err(_) => { + return HttpResponse::Found() + .header(header::LOCATION, "/login?error=true") + .finish() + } + }; + + if !verifier + .send(HashVerifyRequest::new(hash, login.password, sec.0.clone())) + .await + .unwrap() + .unwrap_or(false) + { + return HttpResponse::Found() + .header(header::LOCATION, "/login?error=true") + .finish(); + } + + ident.remember(user.name); + HttpResponse::Found() + .header(header::LOCATION, "/transactions") + .finish() +} + +#[derive(Template)] +#[template(path = "transactions.htm")] +struct TransactionsTemplate<'a> { + name: &'a str, + amount: u64, + transactions: &'a [Transaction], +} + +pub async fn transactions(ident: Identity, db: web::Data<Addr<DbExecutor>>) -> HttpResponse { + if let Some(name) = ident.identity() { + ident.remember(name.clone()); + let user = match db.send(UserRequest(name.clone())).await.unwrap() { + Ok(v) => v, + Err(_) => { + ident.forget(); + return HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish(); + } + }; + + let transactions = match db.send(TransactionsRequest::new(user.id)).await.unwrap() { + Ok(transactions) => transactions, + Err(e) => { + error!("Transactions error: {}", e); + Vec::with_capacity(0) + } + }; + + let page = TransactionsTemplate { + name: &name, + amount: user.balance, + transactions: &transactions, + } + .render() + .unwrap(); + + HttpResponse::Ok().content_type("text/html").body(page) + } else { + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() + } +} + +#[derive(Template)] +#[template(path = "transfer.htm")] +struct TransferTemplate { + balance: u64, + recipient: String, + amount: u64, + purpose: String, + error: TransferResult, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TransferQuery { + #[serde(default)] + pub result: TransferResult, + #[serde(default)] + pub recipient: String, + #[serde(default)] + pub amount: u64, + #[serde(default)] + pub purpose: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub enum TransferResult { + ZeroPoints, + SelfSend, + TransferError, + Empty, +} + +impl Default for TransferResult { + fn default() -> Self { + TransferResult::Empty + } +} + +pub async fn transfer( + ident: Identity, + db: web::Data<Addr<DbExecutor>>, + params: web::Query<TransferQuery>, +) -> HttpResponse { + if let Some(name) = ident.identity() { + ident.remember(name.clone()); + let user = match db.send(UserRequest(name.clone())).await.unwrap() { + Ok(v) => v, + Err(_) => { + ident.forget(); + return HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish(); + } + }; + + let query = params.into_inner(); + let page = TransferTemplate { + balance: user.balance, + recipient: query.recipient, + amount: query.amount, + purpose: query.purpose, + error: query.result, + } + .render() + .unwrap(); + + HttpResponse::Ok().content_type("text/html").body(page) + } else { + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() + } +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TransferData { + pub recipient: String, + pub amount: u64, + pub purpose: String, +} + +pub async fn confirm_transfer( + ident: Identity, + params: web::Form<TransferData>, + db: web::Data<Addr<DbExecutor>>, +) -> HttpResponse { + if let Some(name) = ident.identity() { + ident.remember(name.clone()); + + let form = params.into_inner(); + + if form.amount == 0 { + let query = TransferQuery { + result: TransferResult::ZeroPoints, + recipient: form.recipient, + amount: form.amount, + purpose: form.purpose, + }; + return HttpResponse::Found() + .header( + header::LOCATION, + format!( + "/transfer?{}", + serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + ), + ) + .finish(); + } + + if name.to_lowercase() == form.recipient.to_lowercase() { + let query = TransferQuery { + result: TransferResult::SelfSend, + recipient: form.recipient, + amount: form.amount, + purpose: form.purpose, + }; + return HttpResponse::Found() + .header( + header::LOCATION, + format!( + "/transfer?{}", + serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + ), + ) + .finish(); + } + let request = TransferRequest { + from: name, + to: form.recipient.clone(), + amount: form.amount, + purpose: form.purpose.clone(), + }; + if let Err(e) = db.send(request).await.unwrap() { + error!("Failed to transfer points: {}", e); + let query = TransferQuery { + result: TransferResult::TransferError, + recipient: form.recipient, + amount: form.amount, + purpose: form.purpose, + }; + + return HttpResponse::Found() + .header( + header::LOCATION, + format!( + "/transfer?{}", + serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + ), + ) + .finish(); + } + + HttpResponse::Found() + .header(header::LOCATION, "/transactions") + .finish() + } else { + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() + } +} + +#[derive(Template)] +#[template(path = "settings.htm")] +struct SettingsTemplate { + result: PasswordResult, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct PasswordQuery { + #[serde(default)] + pub result: PasswordResult, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub enum PasswordResult { + Success, + InvalidOld, + Missmatch, + Empty, +} + +impl Default for PasswordResult { + fn default() -> Self { + PasswordResult::Empty + } +} + +pub async fn settings(ident: Identity, params: web::Query<PasswordQuery>) -> HttpResponse { + if let Some(name) = ident.identity() { + ident.remember(name.clone()); + + let query = params.into_inner(); + HttpResponse::Ok().content_type("text/html").body( + SettingsTemplate { + result: query.result, + } + .render() + .unwrap(), + ) + } else { + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() + } +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct PasswordReset { + pub old_password: String, + pub new_password: String, + pub confirm_password: String, +} + +pub async fn reset_password( + ident: Identity, + params: web::Form<PasswordReset>, + db: web::Data<Addr<DbExecutor>>, + verifier: web::Data<Addr<HashVerifyExecutor>>, + hasher: web::Data<Addr<PassHashExecutor>>, + sec: web::Data<Secret>, +) -> HttpResponse { + if let Some(name) = ident.identity() { + ident.remember(name.clone()); + let (user, hash) = match db.send(LoginRequest(name)).await.unwrap() { + Ok(v) => v, + Err(_) => { + ident.forget(); + return HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish(); + } + }; + + let reset = params.into_inner(); + if reset.new_password != reset.confirm_password { + let query = PasswordQuery { + result: PasswordResult::Missmatch, + }; + return HttpResponse::Found() + .header( + header::LOCATION, + format!( + "/settings?{}", + serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + ), + ) + .finish(); + } + + if !verifier + .send(HashVerifyRequest::new( + hash, + reset.old_password, + sec.0.clone(), + )) + .await + .unwrap() + .unwrap_or(false) + { + let query = PasswordQuery { + result: PasswordResult::InvalidOld, + }; + return HttpResponse::Found() + .header( + header::LOCATION, + format!( + "/settings?{}", + serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + ), + ) + .finish(); + } + + let hash = hasher + .send(PassHashRequest::new(reset.new_password, sec.0.clone())) + .await + .unwrap() + .unwrap(); + + db.send(UpdatePasswordRequest::new(user.id, hash)) + .await + .unwrap() + .unwrap(); + + let query = PasswordQuery { + result: PasswordResult::Success, + }; + + HttpResponse::Found() + .header( + header::LOCATION, + format!( + "/settings?{}", + serde_urlencoded::to_string(query).unwrap_or(String::with_capacity(0)) + ), + ) + .finish() + } else { + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() + } +} + +pub fn logout(ident: Identity) -> HttpResponse { + ident.forget(); + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() +} |
