summaryrefslogtreecommitdiffstats
path: root/src/user.rs
diff options
context:
space:
mode:
authorJokler <jokler@protonmail.com>2020-06-21 06:37:46 +0200
committerJokler <jokler@protonmail.com>2020-06-21 06:37:46 +0200
commite6468b012d5b33dd16992652da57f11dd5a6e82f (patch)
treee89add440df79d4036b9b44d8c77ee6d69e67201 /src/user.rs
downloadjoklerpoints-e6468b012d5b33dd16992652da57f11dd5a6e82f.tar.gz
joklerpoints-e6468b012d5b33dd16992652da57f11dd5a6e82f.zip
Initial commitHEADmaster
Diffstat (limited to 'src/user.rs')
-rw-r--r--src/user.rs467
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()
+}