From d9c56aaf1ee2305b7e94c2e970bd46e8fce2c1ca Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Tue, 16 May 2023 16:24:24 -0700 Subject: [PATCH 01/10] rename error --- src/users.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/users.rs b/src/users.rs index 47d65a1..a9cf004 100644 --- a/src/users.rs +++ b/src/users.rs @@ -89,13 +89,13 @@ pub async fn create_user( if exit == 2067u32 || exit == 1555 { Err(CreateUserErrorKind::AlreadyExists.into()) } else { - Err(CreateUserErrorKind::Unknown.into()) + Err(CreateUserErrorKind::UnknownDBError.into()) } } else { - Err(CreateUserErrorKind::Unknown.into()) + Err(CreateUserErrorKind::UnknownDBError.into()) } } - _ => Err(CreateUserErrorKind::Unknown.into()), + _ => Err(CreateUserErrorKind::UnknownDBError.into()), } } @@ -111,5 +111,5 @@ pub enum CreateUserErrorKind { BadUsername, PasswordMismatch, MissingFields, - Unknown, + UnknownDBError, } From f12b524457489bd245788bd9de0ff1faef318b61 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Wed, 17 May 2023 10:09:00 -0700 Subject: [PATCH 02/10] filling out templates for account creation --- src/lib.rs | 1 + src/template.rs | 35 ----------------------------------- src/templates.rs | 10 ++++++++++ 3 files changed, 11 insertions(+), 35 deletions(-) delete mode 100644 src/template.rs create mode 100644 src/templates.rs diff --git a/src/lib.rs b/src/lib.rs index e51e517..f97526d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,4 +3,5 @@ extern crate justerror; pub mod db; pub mod handlers; +pub(crate) mod templates; pub mod users; diff --git a/src/template.rs b/src/template.rs deleted file mode 100644 index 2b95388..0000000 --- a/src/template.rs +++ /dev/null @@ -1,35 +0,0 @@ -use askama::Template; -use axum::{ - extract, - http::StatusCode, - response::{Html, IntoResponse, Response}, -}; - -pub(crate) async fn greet(extract::Path(name): extract::Path) -> impl IntoResponse { - let template = HelloTemplate { name }; - HtmlTemplate(template) -} - -#[derive(Template)] -#[template(path = "hello.html")] -struct HelloTemplate { - name: String, -} - -struct HtmlTemplate(T); - -impl IntoResponse for HtmlTemplate -where - T: Template, -{ - fn into_response(self) -> Response { - match self.0.render() { - Ok(html) => Html(html).into_response(), - Err(err) => ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to render template. Error: {}", err), - ) - .into_response(), - } - } -} diff --git a/src/templates.rs b/src/templates.rs new file mode 100644 index 0000000..b76daf5 --- /dev/null +++ b/src/templates.rs @@ -0,0 +1,10 @@ +use askama::Template; + +#[derive(Template)] +pub struct CreateUser { + pub username: String, + pub displayname: Option, + pub email: Option, + pub password: String, + pub pw_verify: String, +} From 94f1b35c0301aa5c55d96bd58e7cc11747280361 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Wed, 17 May 2023 13:40:06 -0700 Subject: [PATCH 03/10] adding html templates --- src/templates.rs | 1 + templates/base.html | 18 ++++++++++++++++++ templates/signup.html | 7 +++++++ 3 files changed, 26 insertions(+) create mode 100644 templates/base.html create mode 100644 templates/signup.html diff --git a/src/templates.rs b/src/templates.rs index b76daf5..9dc5aac 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,6 +1,7 @@ use askama::Template; #[derive(Template)] +#[template(path = "signup.html")] pub struct CreateUser { pub username: String, pub displayname: Option, diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..d3cb10f --- /dev/null +++ b/templates/base.html @@ -0,0 +1,18 @@ + + + + {% block title %}{{ title }} - Witch Watch{% endblock %} + {% block head %}{% endblock %} + + + +
+ {% block content %}{% endblock %} +
+ + + diff --git a/templates/signup.html b/templates/signup.html new file mode 100644 index 0000000..1de3f3c --- /dev/null +++ b/templates/signup.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block title %}Sign Up for Witch Watch, Bish{% endblock %} + +{% block content %} + +{% endblock %} From 091ddbf48a3f1148fb1df0ab6f12aab91127e607 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Thu, 18 May 2023 10:05:29 -0700 Subject: [PATCH 04/10] Working user signup, looks ugly. Still no login or sessions, though. --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/lib.rs | 2 + src/main.rs | 15 +++----- src/templates.rs | 3 +- src/users.rs | 85 +++++++++++++++++++++++++++++++++++++++---- templates/base.html | 2 +- templates/signup.html | 14 +++++++ 8 files changed, 110 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0364a0a..09fecbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2447,6 +2447,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + [[package]] name = "uuid" version = "0.8.2" @@ -2793,6 +2799,7 @@ dependencies = [ "tracing", "tracing-subscriber", "unicode-segmentation", + "urlencoding", "uuid 1.3.1", ] diff --git a/Cargo.toml b/Cargo.toml index cf9377a..49fb315 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ justerror = "1.1.0" password-hash = { version = "0.5.0", features = ["std", "getrandom"] } axum-login = { version = "0.5.0", features = ["sqlite", "sqlx"] } unicode-segmentation = "1.10.1" +urlencoding = "2.1.2" diff --git a/src/lib.rs b/src/lib.rs index f97526d..b8bd189 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,5 @@ pub mod db; pub mod handlers; pub(crate) mod templates; pub mod users; + +//pub type Db: axum:: diff --git a/src/main.rs b/src/main.rs index bb8f66d..47cf3be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,10 @@ use std::net::SocketAddr; use axum::{routing::get, Router}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use witch_watch::{db, handlers}; +use witch_watch::{ + db, + users::{get_create_user, post_create_user}, +}; #[tokio::main] async fn main() { @@ -16,17 +19,9 @@ async fn main() { let pool = db::get_pool().await; - let _ = witch_watch::users::create_user("joe", &None, &None, &[], &pool) - .await - .unwrap(); - // build our application with some routes - use handlers::*; let app = Router::new() - .route( - "/", - get(using_connection_pool_extractor).post(using_connection_extractor), - ) + .route("/signup", get(get_create_user).post(post_create_user)) .with_state(pool); tracing::debug!("binding to 0.0.0.0:3000"); diff --git a/src/templates.rs b/src/templates.rs index 9dc5aac..afbc50d 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,6 +1,7 @@ use askama::Template; +use serde::Deserialize; -#[derive(Template)] +#[derive(Debug, Default, Template, Deserialize)] #[template(path = "signup.html")] pub struct CreateUser { pub username: String, diff --git a/src/users.rs b/src/users.rs index a9cf004..d392081 100644 --- a/src/users.rs +++ b/src/users.rs @@ -2,11 +2,19 @@ use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; +use askama::Template; +use axum::{ + extract::{Form, Path, State}, + http::StatusCode, + response::IntoResponse, +}; use sqlx::{error::DatabaseError, Sqlite, SqlitePool}; use tracing::log::log; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; +use crate::templates::CreateUser; + const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; @@ -38,20 +46,69 @@ impl From for User { } } -pub async fn create_user( - username: &str, - displayname: &Option, - email: &Option, - password: &[u8], - pool: &SqlitePool, -) -> Result { +pub async fn get_create_user() -> CreateUser { + CreateUser::default() +} + +#[axum::debug_handler] +pub async fn post_create_user( + State(pool): State>, + Form(signup): Form, +) -> Result<(), CreateUserError> { + let username = &signup.username; + let displayname = &signup.displayname; + let email = &signup.email; + let password = &signup.password; + let verify = &signup.pw_verify; let username = username.trim(); + let name_len = username.graphemes(true).size_hint().1.unwrap(); // we are not ascii exclusivists around here if !(1..=20).contains(&name_len) { return Err(CreateUserErrorKind::BadUsername.into()); } + if password != verify { + return Err(CreateUserErrorKind::PasswordMismatch.into()); + } + + let password = urlencoding::decode(password) + .map_err(|_| CreateUserErrorKind::BadPassword)? + .to_string(); + let password = password.as_bytes(); + + let displayname = if let Some(dn) = displayname { + let dn = urlencoding::decode(dn) + .map_err(|_| CreateUserErrorKind::BadDisplayname)? + .to_string(); + Some(dn) + } else { + None + }; + let displayname = &displayname; + + // TODO(2023-05-17): validate email + let email = if let Some(email) = email { + let email = urlencoding::decode(email) + .map_err(|_| CreateUserErrorKind::BadEmail)? + .to_string(); + Some(email) + } else { + None + }; + let email = &email; + + let _ = create_user(username, displayname, email, password, &pool).await?; + Ok(()) +} + +async fn create_user( + username: &str, + displayname: &Option, + email: &Option, + password: &[u8], + pool: &SqlitePool, +) -> Result { // Argon2 with default params (Argon2id v19) let argon2 = Argon2::default(); let salt = SaltString::generate(&mut OsRng); @@ -103,6 +160,17 @@ pub async fn create_user( #[non_exhaustive] pub struct CreateUserError(#[from] CreateUserErrorKind); +impl IntoResponse for CreateUserError { + fn into_response(self) -> askama_axum::Response { + match self.0 { + CreateUserErrorKind::UnknownDBError => { + (StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response() + } + _ => (StatusCode::BAD_REQUEST, format!("{self}")).into_response(), + } + } +} + #[Error] #[non_exhaustive] pub enum CreateUserErrorKind { @@ -110,6 +178,9 @@ pub enum CreateUserErrorKind { #[error(desc = "Usernames must be between 1 and 20 non-whitespace characters long")] BadUsername, PasswordMismatch, + BadPassword, + BadDisplayname, + BadEmail, MissingFields, UnknownDBError, } diff --git a/templates/base.html b/templates/base.html index d3cb10f..ba52e06 100644 --- a/templates/base.html +++ b/templates/base.html @@ -12,7 +12,7 @@ {% block content %}{% endblock %} diff --git a/templates/signup.html b/templates/signup.html index 1de3f3c..7eea006 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -4,4 +4,18 @@ {% block content %} +
+ + + + + + + + + + + +
+ {% endblock %} From 46232b42545e751f818d827f00cf68060b2cb94c Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Thu, 18 May 2023 12:15:37 -0700 Subject: [PATCH 05/10] minor cleanup --- src/lib.rs | 2 -- src/users.rs | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b8bd189..f97526d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,5 +5,3 @@ pub mod db; pub mod handlers; pub(crate) mod templates; pub mod users; - -//pub type Db: axum:: diff --git a/src/users.rs b/src/users.rs index d392081..089c615 100644 --- a/src/users.rs +++ b/src/users.rs @@ -2,13 +2,12 @@ use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; -use askama::Template; use axum::{ - extract::{Form, Path, State}, + extract::{Form, State}, http::StatusCode, response::IntoResponse, }; -use sqlx::{error::DatabaseError, Sqlite, SqlitePool}; +use sqlx::SqlitePool; use tracing::log::log; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; @@ -52,7 +51,7 @@ pub async fn get_create_user() -> CreateUser { #[axum::debug_handler] pub async fn post_create_user( - State(pool): State>, + State(pool): State, Form(signup): Form, ) -> Result<(), CreateUserError> { let username = &signup.username; From 79ecd7450061185959702487fe13902f026747c8 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Thu, 18 May 2023 12:24:43 -0700 Subject: [PATCH 06/10] more cleanup --- src/users.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/users.rs b/src/users.rs index 089c615..8ffedf0 100644 --- a/src/users.rs +++ b/src/users.rs @@ -8,7 +8,6 @@ use axum::{ response::IntoResponse, }; use sqlx::SqlitePool; -use tracing::log::log; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; @@ -17,6 +16,7 @@ use crate::templates::CreateUser; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; +#[derive(Debug, Clone, PartialEq, Eq)] pub struct User { id: Uuid, username: String, @@ -45,13 +45,16 @@ impl From for User { } } +/// Get Handler: displays the form to create a user pub async fn get_create_user() -> CreateUser { CreateUser::default() } +/// Post Handler: validates form values and calls the actual, private user +/// creation function #[axum::debug_handler] pub async fn post_create_user( - State(pool): State, + State(pool): State, Form(signup): Form, ) -> Result<(), CreateUserError> { let username = &signup.username; @@ -97,7 +100,8 @@ pub async fn post_create_user( }; let email = &email; - let _ = create_user(username, displayname, email, password, &pool).await?; + let user = create_user(username, displayname, email, password, &pool).await?; + tracing::debug!("created {user:?}"); Ok(()) } From fee7fff3df8348afe14ae9815e54b20a1c722070 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Thu, 18 May 2023 15:49:33 -0700 Subject: [PATCH 07/10] better signup pages --- .env | 1 + src/users.rs | 24 ++++++++++++++++++++++-- templates/signup.html | 12 +++++++----- templates/signup_success.html | 16 ++++++++++++++++ 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 .env create mode 100644 templates/signup_success.html diff --git a/.env b/.env new file mode 100644 index 0000000..04f6365 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=sqlite://${HOME}/.witch-watch.db diff --git a/src/users.rs b/src/users.rs index 8ffedf0..3a1d4b1 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,7 +1,10 @@ +use std::fmt::Display; + use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; +use askama::Template; use axum::{ extract::{Form, State}, http::StatusCode, @@ -24,6 +27,19 @@ pub struct User { email: Option, } +impl Display for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let uname = &self.username; + let dname = if let Some(ref n) = self.displayname { + n + } else { + "" + }; + let email = if let Some(ref e) = self.email { e } else { "" }; + write!(f, "Username: {uname}\nDisplayname: {dname}\nEmail: {email}") + } +} + #[derive(Debug, Clone, sqlx::FromRow, sqlx::Encode)] pub(crate) struct DbUser { id: Uuid, @@ -34,6 +50,10 @@ pub(crate) struct DbUser { pwhash: String, } +#[derive(Debug, Clone, Template)] +#[template(path = "signup_success.html")] +pub struct CreateUserSuccess(User); + impl From for User { fn from(dbu: DbUser) -> Self { User { @@ -56,7 +76,7 @@ pub async fn get_create_user() -> CreateUser { pub async fn post_create_user( State(pool): State, Form(signup): Form, -) -> Result<(), CreateUserError> { +) -> Result { let username = &signup.username; let displayname = &signup.displayname; let email = &signup.email; @@ -102,7 +122,7 @@ pub async fn post_create_user( let user = create_user(username, displayname, email, password, &pool).await?; tracing::debug!("created {user:?}"); - Ok(()) + Ok(CreateUserSuccess(user)) } async fn create_user( diff --git a/templates/signup.html b/templates/signup.html index 7eea006..61b5b44 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -4,18 +4,20 @@ {% block content %} +

- +
- +
- +
- +
- +
+

{% endblock %} diff --git a/templates/signup_success.html b/templates/signup_success.html new file mode 100644 index 0000000..f8e9efc --- /dev/null +++ b/templates/signup_success.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block title %}Thanks for Signing Up for Witch Watch, Bish{% endblock %} + +{% block content %} + +

You did it!

+ +

+{{ self.0 }} +

+
+ +

Now, head on over to the login page and get watchin'!

+ +{% endblock %} From ebfc759fa6c288aa605cb4273c640eb765adb500 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Fri, 19 May 2023 15:13:49 -0700 Subject: [PATCH 08/10] Add redirect response to signup success. --- src/main.rs | 3 ++- src/users.rs | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 47cf3be..07913a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use axum::{routing::get, Router}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use witch_watch::{ db, - users::{get_create_user, post_create_user}, + users::{get_create_user, get_signup_success, post_create_user}, }; #[tokio::main] @@ -22,6 +22,7 @@ async fn main() { // build our application with some routes let app = Router::new() .route("/signup", get(get_create_user).post(post_create_user)) + .route("/signup_success", get(get_signup_success)) .with_state(pool); tracing::debug!("binding to 0.0.0.0:3000"); diff --git a/src/users.rs b/src/users.rs index 3a1d4b1..c5b5c18 100644 --- a/src/users.rs +++ b/src/users.rs @@ -8,7 +8,7 @@ use askama::Template; use axum::{ extract::{Form, State}, http::StatusCode, - response::IntoResponse, + response::{IntoResponse, Response}, }; use sqlx::SqlitePool; use unicode_segmentation::UnicodeSegmentation; @@ -76,7 +76,7 @@ pub async fn get_create_user() -> CreateUser { pub async fn post_create_user( State(pool): State, Form(signup): Form, -) -> Result { +) -> Result { let username = &signup.username; let displayname = &signup.displayname; let email = &signup.email; @@ -122,7 +122,22 @@ pub async fn post_create_user( let user = create_user(username, displayname, email, password, &pool).await?; tracing::debug!("created {user:?}"); - Ok(CreateUserSuccess(user)) + let mut resp = axum::response::Redirect::temporary("/signup_success").into_response(); + resp.headers_mut().append( + "X-Witch-Success", + user.id.simple().to_string().parse().unwrap(), + ); + Ok(resp) +} + +pub async fn get_signup_success( + headers: axum::http::HeaderMap, + State(pool): State, +) -> CreateUserSuccess { + if let Some(id) = headers.get("X-Witch") { + todo!() + } + todo!() } async fn create_user( From b7eb56e0e773a699a6e3264f0b9a12b9ecc5fee2 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Fri, 19 May 2023 17:17:24 -0700 Subject: [PATCH 09/10] redirects weirdly --- src/main.rs | 5 +++- src/users.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 07913a0..ec78e19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,10 @@ async fn main() { // build our application with some routes let app = Router::new() .route("/signup", get(get_create_user).post(post_create_user)) - .route("/signup_success", get(get_signup_success)) + .route( + "/signup_success", + get(get_signup_success).post(get_signup_success), + ) .with_state(pool); tracing::debug!("binding to 0.0.0.0:3000"); diff --git a/src/users.rs b/src/users.rs index c5b5c18..209da60 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{array, fmt::Display, ops::Deref}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, @@ -10,7 +10,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use sqlx::SqlitePool; +use sqlx::{sqlite::SqliteRow, Row, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; @@ -19,12 +19,17 @@ use crate::templates::CreateUser; const CREATE_QUERY: &str = "insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; -#[derive(Debug, Clone, PartialEq, Eq)] +const ID_QUERY: &str = "select * from witches where id = $1"; + +const WITCH_SUCCESS_HEADER: &str = "X-Witch-Success"; + +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct User { id: Uuid, username: String, displayname: Option, email: Option, + last_seen: Option, } impl Display for User { @@ -40,13 +45,13 @@ impl Display for User { } } -#[derive(Debug, Clone, sqlx::FromRow, sqlx::Encode)] +#[derive(Debug, Default, Clone, sqlx::Encode)] pub(crate) struct DbUser { id: Uuid, username: String, displayname: Option, email: Option, - last_seen: Option, + last_seen: Option, pwhash: String, } @@ -54,6 +59,27 @@ pub(crate) struct DbUser { #[template(path = "signup_success.html")] pub struct CreateUserSuccess(User); +impl sqlx::FromRow<'_, SqliteRow> for User { + fn from_row(row: &SqliteRow) -> Result { + let bytes: Vec = row.get("id"); + let bytes = bytes.as_slice(); + let bytes: [u8; 16] = bytes.try_into().unwrap(); + let id = Uuid::from_bytes_le(bytes); + let username: String = row.get("username"); + let displayname: Option = row.get("displayname"); + let last_seen: Option = row.get("last_seen"); + let email: Option = row.get("email"); + + Ok(Self { + id, + username, + displayname, + email, + last_seen, + }) + } +} + impl From for User { fn from(dbu: DbUser) -> Self { User { @@ -61,6 +87,7 @@ impl From for User { username: dbu.username, displayname: dbu.displayname, email: dbu.email, + last_seen: dbu.last_seen, } } } @@ -124,20 +151,39 @@ pub async fn post_create_user( tracing::debug!("created {user:?}"); let mut resp = axum::response::Redirect::temporary("/signup_success").into_response(); resp.headers_mut().append( - "X-Witch-Success", + WITCH_SUCCESS_HEADER, user.id.simple().to_string().parse().unwrap(), ); + Ok(resp) } +/// Get handler for successful signup; only meaningful pub async fn get_signup_success( headers: axum::http::HeaderMap, State(pool): State, -) -> CreateUserSuccess { - if let Some(id) = headers.get("X-Witch") { - todo!() +) -> Response { + let user = if let Some(id) = headers.get(WITCH_SUCCESS_HEADER) { + let id = id.to_str().unwrap(); + let id = Uuid::try_parse(id).unwrap_or_default(); + let id_bytes = id.as_bytes(); + sqlx::query_as(ID_QUERY) + .bind(id_bytes.as_slice()) + .fetch_one(&pool) + .await + .unwrap_or_default() + } else { + User::default() + }; + + let mut resp = CreateUserSuccess(user.clone()).into_response(); + + if user.username.is_empty() { + // redirect to front page if we got here without a valid witch header + *resp.status_mut() = StatusCode::TEMPORARY_REDIRECT; + resp.headers_mut().insert("Location", "/".parse().unwrap()); } - todo!() + resp } async fn create_user( @@ -174,6 +220,7 @@ async fn create_user( username: username.to_string(), displayname: displayname.to_owned(), email: email.to_owned(), + last_seen: None, }; Ok(user) } From 06d7b4a5146934cef0d7c79aea70ef343fdb443f Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Mon, 22 May 2023 15:08:14 -0700 Subject: [PATCH 10/10] Fixed redirect on success, removed DbUser struct. --- src/main.rs | 6 +++--- src/users.rs | 59 +++++++++++++++++----------------------------------- 2 files changed, 22 insertions(+), 43 deletions(-) diff --git a/src/main.rs b/src/main.rs index ec78e19..51e79c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use axum::{routing::get, Router}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use witch_watch::{ db, - users::{get_create_user, get_signup_success, post_create_user}, + users::{get_create_user, handle_signup_success, post_create_user}, }; #[tokio::main] @@ -23,8 +23,8 @@ async fn main() { let app = Router::new() .route("/signup", get(get_create_user).post(post_create_user)) .route( - "/signup_success", - get(get_signup_success).post(get_signup_success), + "/signup_success/:id", + get(handle_signup_success).post(handle_signup_success), ) .with_state(pool); diff --git a/src/users.rs b/src/users.rs index 209da60..a140739 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,4 +1,4 @@ -use std::{array, fmt::Display, ops::Deref}; +use std::fmt::Display; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, @@ -6,7 +6,7 @@ use argon2::{ }; use askama::Template; use axum::{ - extract::{Form, State}, + extract::{Form, Path, State}, http::StatusCode, response::{IntoResponse, Response}, }; @@ -21,8 +21,6 @@ const CREATE_QUERY: &str = const ID_QUERY: &str = "select * from witches where id = $1"; -const WITCH_SUCCESS_HEADER: &str = "X-Witch-Success"; - #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct User { id: Uuid, @@ -45,16 +43,6 @@ impl Display for User { } } -#[derive(Debug, Default, Clone, sqlx::Encode)] -pub(crate) struct DbUser { - id: Uuid, - username: String, - displayname: Option, - email: Option, - last_seen: Option, - pwhash: String, -} - #[derive(Debug, Clone, Template)] #[template(path = "signup_success.html")] pub struct CreateUserSuccess(User); @@ -80,18 +68,6 @@ impl sqlx::FromRow<'_, SqliteRow> for User { } } -impl From for User { - fn from(dbu: DbUser) -> Self { - User { - id: dbu.id, - username: dbu.username, - displayname: dbu.displayname, - email: dbu.email, - last_seen: dbu.last_seen, - } - } -} - /// Get Handler: displays the form to create a user pub async fn get_create_user() -> CreateUser { CreateUser::default() @@ -117,6 +93,12 @@ pub async fn post_create_user( return Err(CreateUserErrorKind::BadUsername.into()); } + if let Some(ref dn) = displayname { + if dn.len() > 50 { + return Err(CreateUserErrorKind::BadDisplayname.into()); + } + } + if password != verify { return Err(CreateUserErrorKind::PasswordMismatch.into()); } @@ -149,31 +131,28 @@ pub async fn post_create_user( let user = create_user(username, displayname, email, password, &pool).await?; tracing::debug!("created {user:?}"); - let mut resp = axum::response::Redirect::temporary("/signup_success").into_response(); - resp.headers_mut().append( - WITCH_SUCCESS_HEADER, - user.id.simple().to_string().parse().unwrap(), - ); + let id = user.id.simple().to_string(); + let location = format!("/signup_success/{id}"); + + let resp = axum::response::Redirect::temporary(&location).into_response(); Ok(resp) } -/// Get handler for successful signup; only meaningful -pub async fn get_signup_success( - headers: axum::http::HeaderMap, +/// Get handler for successful signup +pub async fn handle_signup_success( + Path(id): Path, State(pool): State, ) -> Response { - let user = if let Some(id) = headers.get(WITCH_SUCCESS_HEADER) { - let id = id.to_str().unwrap(); - let id = Uuid::try_parse(id).unwrap_or_default(); - let id_bytes = id.as_bytes(); + let user: User = { + let id = id; + let id = Uuid::try_parse(&id).unwrap_or_default(); + let id_bytes = id.to_bytes_le(); sqlx::query_as(ID_QUERY) .bind(id_bytes.as_slice()) .fetch_one(&pool) .await .unwrap_or_default() - } else { - User::default() }; let mut resp = CreateUserSuccess(user.clone()).into_response();