From 6a1cb044283b8e953835398bdb5c15a3ebabe1c3 Mon Sep 17 00:00:00 2001 From: Edward Shen Date: Fri, 20 Aug 2021 21:47:14 -0400 Subject: [PATCH] Work --- Cargo.lock | 163 ++++++++++-------- Cargo.toml | 2 +- db_queries/init.sql | 2 +- sqlx-data.json | 20 +-- src/email.rs | 20 +++ src/main.rs | 176 +++++++------------- src/password.rs | 63 +++++++ src/session.rs | 6 +- src/static/css/primary.css | 2 +- src/templates/account.hbs | 10 ++ src/templates/index.hbs | 1 + src/templates/register.hbs | 3 - src/templates/registration_confirmation.hbs | 22 +++ 13 files changed, 282 insertions(+), 208 deletions(-) create mode 100644 src/email.rs create mode 100644 src/password.rs create mode 100644 src/templates/account.hbs create mode 100644 src/templates/registration_confirmation.hbs diff --git a/Cargo.lock b/Cargo.lock index ad69544..84abf79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dddf0af1514897dc80a11a084aa032ec7c1c042af454f502aa81c49af0a25fb" dependencies = [ "actix-web", - "base64 0.13.0", + "base64", "cookie", "rand", "serde", @@ -66,7 +66,7 @@ dependencies = [ "actix-tls", "actix-utils", "ahash", - "base64 0.13.0", + "base64", "bitflags", "brotli2", "bytes", @@ -326,7 +326,7 @@ checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" dependencies = [ "getrandom", "once_cell", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -359,18 +359,23 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "ascii_utils" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" - [[package]] name = "askama_escape" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90c108c1a94380c89d2215d0ac54ce09796823cca0fd91b299cfff3b33e346fb" +[[package]] +name = "async-trait" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atoi" version = "0.4.0" @@ -392,15 +397,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.13.0" @@ -475,12 +471,6 @@ dependencies = [ "libc", ] -[[package]] -name = "bufstream" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" - [[package]] name = "bumpalo" version = "3.7.0" @@ -569,7 +559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" dependencies = [ "aes-gcm", - "base64 0.13.0", + "base64", "hkdf", "hmac", "percent-encoding", @@ -577,7 +567,7 @@ dependencies = [ "sha2", "subtle", "time", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -761,12 +751,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] -name = "fast_chemail" -version = "0.9.6" +name = "fastrand" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" dependencies = [ - "ascii_utils", + "instant", ] [[package]] @@ -939,7 +929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -1061,12 +1051,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.1.5" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", - "winutil", + "match_cfg", + "winapi", ] [[package]] @@ -1092,6 +1083,12 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + [[package]] name = "idna" version = "0.2.3" @@ -1160,20 +1157,31 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lettre" -version = "0.9.6" +version = "0.10.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ed8677138975b573ab4949c35613931a4addeadd0a8a6aa0327e2a979660de" +checksum = "d8697ded52353bdd6fec234b3135972433397e86d0493d9fc38fbf407b7c106a" dependencies = [ - "base64 0.10.1", - "bufstream", - "fast_chemail", + "async-trait", + "base64", + "fastrand", + "futures-io", + "futures-util", "hostname", - "log", + "httpdate", + "idna", + "mime", "native-tls", - "nom 4.2.3", + "nom", + "once_cell", + "quoted_printable", + "r2d2", + "regex", + "rustls", "serde", - "serde_derive", - "serde_json", + "tokio", + "tokio-rustls", + "webpki", + "webpki-roots", ] [[package]] @@ -1260,6 +1268,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.0.1" @@ -1347,16 +1361,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -dependencies = [ - "memchr", - "version_check 0.1.5", -] - [[package]] name = "nom" version = "6.1.2" @@ -1367,7 +1371,7 @@ dependencies = [ "funty", "lexical-core", "memchr", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -1630,6 +1634,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted_printable" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1238256b09923649ec89b08104c4dfe9f6cb2fea734a5db5384e44916d59e9c5" + +[[package]] +name = "r2d2" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "radium" version = "0.5.3" @@ -1759,7 +1780,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.0", + "base64", "log", "ring", "sct", @@ -1791,6 +1812,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7" +dependencies = [ + "parking_lot", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2023,7 +2053,7 @@ checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c" dependencies = [ "lazy_static", "maplit", - "nom 6.1.2", + "nom", "regex", "unicode_categories", ] @@ -2124,7 +2154,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" dependencies = [ - "version_check 0.9.3", + "version_check", ] [[package]] @@ -2269,7 +2299,7 @@ dependencies = [ "standback", "stdweb", "time-macros", - "version_check 0.9.3", + "version_check", "winapi", ] @@ -2480,7 +2510,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.3", + "version_check", ] [[package]] @@ -2550,12 +2580,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.3" @@ -2703,15 +2727,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winutil" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" -dependencies = [ - "winapi", -] - [[package]] name = "wyz" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 3a27350..18d2b06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ actix-session = "0.5.0-beta.2" actix-web = { version = "4.0.0-beta.8", features = [ "rustls" ] } anyhow = "1" handlebars = { version = "4", features = [ "dir_source" ] } -lettre = { version = "0.9", features = [ "serde-impls" ] } +lettre = { version = "0.10.0-rc.3", features = [ "serde", "tokio1-rustls-tls" ] } once_cell = "1" rand = { version = "0.8", features = [ "small_rng" ] } serde = "1" diff --git a/db_queries/init.sql b/db_queries/init.sql index be3a05f..20781bc 100644 --- a/db_queries/init.sql +++ b/db_queries/init.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS users( - email TEXT PRIMARY KEY, + email TEXT NOT NULL UNIQUE PRIMARY KEY, password BLOB NOT NULL ); diff --git a/sqlx-data.json b/sqlx-data.json index 80dfd48..55fa53a 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -1,5 +1,15 @@ { "db": "SQLite", + "15fac42882fc4be06e0f99d3be97fcbf1570c6bd14fcd13cd96ee78892668489": { + "query": "CREATE TABLE IF NOT EXISTS users(\n email TEXT NOT NULL UNIQUE PRIMARY KEY,\n password BLOB NOT NULL\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS ix_email ON users(email);", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + } + }, "57f6668b1fb93316e1beff8c2189a59da3a34995afc962a4704bc7d196a159f3": { "query": "INSERT INTO users (email, password) VALUES (?, ?)", "describe": { @@ -27,15 +37,5 @@ false ] } - }, - "dd98414343cc029a0cc106382532de954050ded749f3a3f7d9dc077ed7515572": { - "query": "CREATE TABLE IF NOT EXISTS users(\n email TEXT PRIMARY KEY,\n password BLOB NOT NULL\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS ix_email ON users(email);", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - } } } \ No newline at end of file diff --git a/src/email.rs b/src/email.rs new file mode 100644 index 0000000..798de95 --- /dev/null +++ b/src/email.rs @@ -0,0 +1,20 @@ +use lettre::error::Error as EmailError; +use lettre::message::Mailbox; +use lettre::transport::stub::StubTransport; +use lettre::{Address, AsyncTransport, Message}; + +pub async fn send_registration_email(address: Address) -> Result<(), EmailError> { + let message = Message::builder() + .from(Mailbox::new( + Some("Some username".to_string()), + "foo@example.com".parse().unwrap(), + )) + .to(Mailbox::new(None, address)) + .subject("Registration for this website") + .body("hell world".to_string()) + .unwrap(); + + StubTransport::new_ok().send(message).await.unwrap(); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 4bd84c3..393429a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,12 @@ +mod email; +mod password; mod session; use std::str::FromStr; -use session::Session; +use crate::email::send_registration_email; +use crate::password::Password; +use crate::session::Session; use actix_csrf::extractor::{CsrfCookie, CsrfToken}; use actix_csrf::Csrf; @@ -10,15 +14,14 @@ use actix_session::CookieSession; use actix_web::cookie::SameSite; use actix_web::error::InternalError; use actix_web::http::header::LOCATION; -use actix_web::http::{Method, StatusCode}; +use actix_web::http::{Method, StatusCode, Uri}; use actix_web::middleware::Logger; use actix_web::web::{Form, Query}; use actix_web::{get, post, App, HttpRequest, HttpResponse, HttpServer, Responder}; use handlebars::Handlebars; -use lettre::EmailAddress; +use lettre::Address as EmailAddress; use once_cell::sync::{Lazy, OnceCell}; use rand::prelude::StdRng; -use serde::de::Visitor; use serde::{Deserialize, Serialize}; use sodiumoxide::crypto::pwhash::argon2id13::{self, HashedPassword, HASHEDPASSWORDBYTES}; use sqlx::sqlite::SqliteConnectOptions; @@ -80,6 +83,7 @@ async fn main() -> std::io::Result<()> { .service(login) .service(register_ui) .service(register) + .service(registration_confirmation) .service(account_ui) .service(logout) .service(actix_files::Files::new("/static", "src/static")) @@ -89,20 +93,25 @@ async fn main() -> std::io::Result<()> { .await } -#[derive(Deserialize, Serialize)] -enum SessionState { - Anonymous, +/// Macro meme to render a template without any context or with a provided one. +macro_rules! render { + ($template_name:literal) => { + render!($template_name, &()) + }; + ($template_name:literal, $template_args:expr $(,)?) => { + match TEMPLATE_ENGINE.render($template_name, $template_args) { + Ok(resp) => Ok(HttpResponse::Ok().body(resp)), + Err(e) => { + error!("{}", e); + Err(InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR)) + } + } + }; } #[get("/")] async fn index() -> impl Responder { - match TEMPLATE_ENGINE.render("index", &()) { - Ok(resp) => Ok(HttpResponse::Ok().body(resp)), - Err(e) => { - error!("{}", e); - Err(InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR)) - } - } + render!("index") } #[derive(Deserialize)] @@ -118,19 +127,13 @@ async fn login_ui(csrf: CsrfToken, mut query: Query) -> impl Respond csrf: CsrfToken, } - match TEMPLATE_ENGINE.render( + render!( "login", &TemplateArgs { error: query.error.take(), csrf, }, - ) { - Ok(resp) => Ok(HttpResponse::Ok().body(resp)), - Err(e) => { - error!("{}", e); - Err(InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR)) - } - } + ) } #[derive(Deserialize)] @@ -140,42 +143,6 @@ struct Login { password: Password, } -#[derive(Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Password(String); - -impl<'de> Deserialize<'de> for Password { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::{Error, Unexpected}; - - struct SecretDeserializer; - - impl<'de> Visitor<'de> for SecretDeserializer { - type Value = Password; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a password between 8 and 64 bytes") - } - - fn visit_str(self, v: &str) -> Result { - if v.len() < 8 || v.len() > 64 { - println!("password failed"); - return Err(Error::invalid_value( - Unexpected::Str("password with invalid size"), - &"a password between 8 and 64 bytes", - )); - } - - Ok(Password(v.to_owned())) - } - } - - deserializer.deserialize_string(SecretDeserializer) - } -} - #[post("/login")] async fn login(csrf_cookie: CsrfCookie, form: Form, session: Session) -> impl Responder { if !csrf_cookie.validate(form.csrf.as_ref()) { @@ -188,23 +155,18 @@ async fn login(csrf_cookie: CsrfCookie, form: Form, session: Session) -> .await; if let Ok(record) = verified { - let verified = argon2id13::pwhash_verify( - &HashedPassword::from_slice(&record.password).unwrap(), - form.password.0.as_bytes(), - ); - if verified { + if form + .password + .verify(&HashedPassword::from_slice(&record.password).unwrap()) + { let redirect_to = session.get_redirect_url(); session.init(&form.email); - - let mut resp = HttpResponse::SeeOther(); - - if let Some(path) = redirect_to { - resp.insert_header((LOCATION, path.to_string())); - } else { - resp.insert_header((LOCATION, "/account")); - } - - return resp.finish(); + return redirect( + &redirect_to + .as_ref() + .map(Uri::to_string) + .unwrap_or("/account".into()), + ); } } else { // To guard against timing attacks, we'll construct a fake password to @@ -220,15 +182,11 @@ async fn login(csrf_cookie: CsrfCookie, form: Form, session: Session) -> // Rust shouldn't optimize this out since it ultimately calls out to // a C function, so it shouldn't find out that the function is pure. - argon2id13::pwhash_verify( - &HashedPassword::from_slice(&data).unwrap(), - form.password.0.as_bytes(), - ); + form.password + .verify(&HashedPassword::from_slice(&data).expect("Should be valid password data")); } - HttpResponse::SeeOther() - .insert_header((LOCATION, "/login?error=true")) - .finish() + redirect("/login?error=true") } #[get("/register")] @@ -238,13 +196,7 @@ async fn register_ui(csrf: CsrfToken) -> impl Responder { csrf: CsrfToken, } - match TEMPLATE_ENGINE.render("register", &TemplateArgs { csrf }) { - Ok(resp) => Ok(HttpResponse::Ok().body(resp)), - Err(e) => { - error!("{}", e); - Err(InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR)) - } - } + render!("register", &TemplateArgs { csrf }) } #[derive(Deserialize)] @@ -255,26 +207,15 @@ struct RegistrationInfo { } #[post("/register")] -async fn register( - csrf_cookie: CsrfCookie, - form: Form, - session: Session, -) -> impl Responder { +async fn register(csrf_cookie: CsrfCookie, form: Form) -> impl Responder { if !csrf_cookie.validate(form.csrf.as_ref()) { return HttpResponse::BadRequest().finish(); } - let hashed = { - let res = argon2id13::pwhash( - form.password.0.as_bytes(), - argon2id13::OPSLIMIT_INTERACTIVE, - argon2id13::MEMLIMIT_INTERACTIVE, - ); - if let Ok(res) = res { - res - } else { - return HttpResponse::InternalServerError().finish(); - } + let hashed = if let Ok(res) = form.password.hashed() { + res + } else { + return HttpResponse::InternalServerError().finish(); }; let hashed = hashed.as_ref(); @@ -288,31 +229,36 @@ async fn register( .await; if insert_res.is_ok() { - session.init(&form.email); - - HttpResponse::SeeOther() - .insert_header((LOCATION, "/account")) - .finish() - } else { - todo!() + send_registration_email(form.email.clone()).await.unwrap(); } + + redirect("/registration_confirmation") +} + +#[get("/registration_confirmation")] +async fn registration_confirmation() -> impl Responder { + render!("registration_confirmation") } #[get("/account")] async fn account_ui(req: HttpRequest, session: Session) -> impl Responder { if let Err(error) = session.validate_or_redirect(req.uri()) { - return error; + return Ok(error); } - HttpResponse::Ok().body(format!("{:?}", session.email())) + render!("account") } #[get("/logout")] async fn logout(session: Session) -> impl Responder { - // It should be ok to logout without a CSRF token; the worst case is that - // the user is logged out, which is fail-safe. + // While this is a state-mutating endpoint, it is fine to not have a CSRF + // token; the worst case is that the user is logged out, which is fail-safe. session.purge(); + redirect("/") +} + +fn redirect(location: &str) -> HttpResponse { HttpResponse::SeeOther() - .append_header((LOCATION, "/")) + .append_header((LOCATION, location)) .finish() } diff --git a/src/password.rs b/src/password.rs new file mode 100644 index 0000000..50c93b0 --- /dev/null +++ b/src/password.rs @@ -0,0 +1,63 @@ +use serde::de::Visitor; +use serde::{Deserialize, Serialize}; +use sodiumoxide::crypto::pwhash::argon2id13::{self, HashedPassword}; +use sodiumoxide::utils::memzero; + +#[derive(Serialize, PartialEq, Eq)] +pub struct Password(String); + +impl Password { + pub fn hashed(&self) -> Result { + argon2id13::pwhash( + self.0.as_bytes(), + argon2id13::OPSLIMIT_INTERACTIVE, + argon2id13::MEMLIMIT_INTERACTIVE, + ) + } + + pub fn verify(&self, password: &HashedPassword) -> bool { + argon2id13::pwhash_verify(password, self.0.as_bytes()) + } +} + +/// A custom drop implementation is necessary for this type as we need to ensure +/// that the password is not stored in memory for an extended period of time. +impl Drop for Password { + fn drop(&mut self) { + // SAFETY: self.0 is never accessed after calling drop. + memzero(unsafe { self.0.as_bytes_mut() }) + } +} + +impl<'de> Deserialize<'de> for Password { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::{Error, Unexpected}; + + struct SecretDeserializer; + + impl<'de> Visitor<'de> for SecretDeserializer { + type Value = Password; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a password between 8 and 64 bytes") + } + + fn visit_str(self, v: &str) -> Result { + if v.len() < 8 || v.len() > 64 { + println!("password failed"); + return Err(Error::invalid_value( + Unexpected::Str("password with invalid size"), + &"a password between 8 and 64 bytes", + )); + } + + Ok(Password(v.to_owned())) + } + } + + deserializer.deserialize_string(SecretDeserializer) + } +} diff --git a/src/session.rs b/src/session.rs index 026d2de..2076df5 100644 --- a/src/session.rs +++ b/src/session.rs @@ -6,7 +6,7 @@ use actix_web::dev::Payload; use actix_web::http::header::LOCATION; use actix_web::http::Uri; use actix_web::{FromRequest, HttpRequest, HttpResponse}; -use lettre::EmailAddress; +use lettre::Address; use rand::{thread_rng, Fill}; pub struct Session(actix_session::Session); @@ -26,7 +26,7 @@ impl FromRequest for Session { } impl Session { - pub fn init(&self, email: &EmailAddress) { + pub fn init(&self, email: &Address) { self.0.clear(); self.0 .insert("email", email) @@ -77,7 +77,7 @@ impl Session { .expect("setting a str to work"); } - pub fn email(&self) -> Option { + pub fn email(&self) -> Option
{ self.0.get("email").ok().flatten() } } diff --git a/src/static/css/primary.css b/src/static/css/primary.css index 89f8a8f..18a1178 100644 --- a/src/static/css/primary.css +++ b/src/static/css/primary.css @@ -3,7 +3,7 @@ h1, h2, h3, h4, h5, h6 { font-family: 'M PLUS 1p', sans-serif; } -p, label, input { +p, label, input, a { font-family: 'Noto Sans', sans-serif; margin-top: 0; } diff --git a/src/templates/account.hbs b/src/templates/account.hbs new file mode 100644 index 0000000..ff0cd61 --- /dev/null +++ b/src/templates/account.hbs @@ -0,0 +1,10 @@ + + +{{> head }} + +

A TLD for everyone

+ +

Logged in!

+ Logout + + \ No newline at end of file diff --git a/src/templates/index.hbs b/src/templates/index.hbs index e952262..604c543 100644 --- a/src/templates/index.hbs +++ b/src/templates/index.hbs @@ -5,5 +5,6 @@

A TLD for everyone

About us

+ Login \ No newline at end of file diff --git a/src/templates/register.hbs b/src/templates/register.hbs index bca4c1f..ef2df7a 100644 --- a/src/templates/register.hbs +++ b/src/templates/register.hbs @@ -6,9 +6,6 @@

A TLD for everyone

Sign up

- {{#if error}} -

The email is already used. Please try again.

- {{/if}}