master
Edward Shen 2021-08-20 21:47:14 -04:00
parent f08b3e0bcb
commit 6a1cb04428
Signed by: edward
GPG Key ID: 19182661E818369F
13 changed files with 282 additions and 208 deletions

163
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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
);

View File

@ -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": []
}
}
}

20
src/email.rs Normal file
View File

@ -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(())
}

View File

@ -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<LoginQuery>) -> 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<D>(deserializer: D) -> Result<Self, D::Error>
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<E: Error>(self, v: &str) -> Result<Self::Value, E> {
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<Login>, session: Session) -> impl Responder {
if !csrf_cookie.validate(form.csrf.as_ref()) {
@ -188,23 +155,18 @@ async fn login(csrf_cookie: CsrfCookie, form: Form<Login>, 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<Login>, 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<RegistrationInfo>,
session: Session,
) -> impl Responder {
async fn register(csrf_cookie: CsrfCookie, form: Form<RegistrationInfo>) -> 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()
}

63
src/password.rs Normal file
View File

@ -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<HashedPassword, ()> {
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<D>(deserializer: D) -> Result<Self, D::Error>
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<E: Error>(self, v: &str) -> Result<Self::Value, E> {
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)
}
}

View File

@ -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<EmailAddress> {
pub fn email(&self) -> Option<Address> {
self.0.get("email").ok().flatten()
}
}

View File

@ -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;
}

10
src/templates/account.hbs Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
{{> head }}
<body>
<h1>A TLD for everyone</h1>
<h2>Logged in!</h2>
<a href="/logout">Logout</a>
</body>
</html>

View File

@ -5,5 +5,6 @@
<h1>A TLD for everyone</h1>
<h2>About us</h2>
<a href="/login">Login</a>
</body>
</html>

View File

@ -6,9 +6,6 @@
<h1>A TLD for everyone</h1>
<h2>Sign up</h2>
{{#if error}}
<p>The email is already used. Please try again.</p>
{{/if}}
<form action="/register" method="post">
<input name="csrf" type="hidden" value="{{csrf}}">
<label>Email

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
{{> head }}
<body>
<h1>A TLD for everyone</h1>
<h2>Registration Requested</h2>
<p>
If successful, a link to activate your account has been emailed to the address provided.
</p>
<p>
You must register your account before you can log in.
</p>
<h4>I didn't receive an email!</h4>
<p>
An email will not have been sent if you already have an existing account
with us. If this is your first time registering, please also check your spam
folder.
</p>
</body>
</html>