validates form and checks that there's a valid session when you come back

This commit is contained in:
Joe Ardent 2024-02-25 17:24:45 -08:00
parent a31b41cb9e
commit d9d7e7d9c7
2 changed files with 97 additions and 20 deletions

View File

@ -1,18 +1,21 @@
use std::{ use std::{
error::Error,
fmt::{Debug, Display}, fmt::{Debug, Display},
net::SocketAddr, net::SocketAddr,
ops::Range,
}; };
use askama::Template; use askama::Template;
use axum::{ use axum::{
extract::{Form, Path}, extract::{Form, Path},
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, response::{IntoResponse, Redirect, Response},
routing::get, routing::get,
Router, Router,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tower_sessions::{MemoryStore, Session, SessionManagerLayer}; use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
use unicode_segmentation::UnicodeSegmentation;
#[macro_use] #[macro_use]
extern crate justerror; extern crate justerror;
@ -28,13 +31,60 @@ async fn get_signup() -> impl IntoResponse {
} }
/// Receives the form with the user signup fields filled out. /// Receives the form with the user signup fields filled out.
async fn post_signup(session: Session, Form(form): Form<SignupForm>) -> impl IntoResponse { async fn post_signup(
todo!() session: Session,
Form(form): Form<SignupForm>,
) -> Result<impl IntoResponse, CreateUserError> {
let username = form.username.trim();
let password = form.password.trim();
let verify = form.pw_verify.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 pwlen = password.graphemes(true).size_hint().1.unwrap_or(0);
if !(4..=50).contains(&pwlen) {
return Err(CreateUserErrorKind::BadPassword.into());
}
// clean up the optionals
let displayname = validate_optional_length(
&form.displayname,
0..100,
CreateUserErrorKind::BadDisplayname,
)?;
let email = validate_optional_length(&form.email, 5..30, CreateUserErrorKind::BadEmail)?;
let user = User {
username: username.to_string(),
displayname,
email,
password: password.to_string(),
pw_verify: verify.to_string(),
};
session.insert(SIGNUP_KEY, user).await.unwrap();
Ok(Redirect::to(
"https://buy.stripe.com/test_eVa6rrb7ygjNbwk000",
))
} }
/// Called from Stripe with the receipt of payment. /// Called from Stripe with the receipt of payment.
async fn signup_success(session: Session, receipt: Option<Path<String>>) -> impl IntoResponse { async fn signup_success(session: Session, receipt: Option<Path<String>>) -> impl IntoResponse {
todo!() let user: User = session.get(SIGNUP_KEY).await.unwrap().unwrap_or_default();
if user != User::default() {
SignupSuccessPage(user).into_response()
} else {
SignupErrorPage("who you?".to_string()).into_response()
}
} }
#[tokio::main] #[tokio::main]
@ -89,10 +139,27 @@ pub struct SignupForm {
pub email: Option<String>, pub email: Option<String>,
pub password: String, pub password: String,
pub pw_verify: String, pub pw_verify: String,
pub invitation: String,
} }
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)]
#[template(path = "signup.html")]
pub struct SignupPage {
pub username: String,
pub displayname: Option<String>,
pub email: Option<String>,
pub password: String,
pub pw_verify: String,
}
#[derive(Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq)]
#[template(path = "signup_success.html")]
pub struct SignupSuccessPage(pub User);
#[derive(Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq)]
#[template(path = "signup_error.html")]
pub struct SignupErrorPage(pub String);
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct User { pub struct User {
pub username: String, pub username: String,
pub displayname: Option<String>, pub displayname: Option<String>,
@ -145,12 +212,20 @@ where
} }
} }
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] pub(crate) fn validate_optional_length<E: Error>(
#[template(path = "signup.html")] opt: &Option<String>,
pub struct SignupPage { len_range: Range<usize>,
pub username: String, err: E,
pub displayname: Option<String>, ) -> Result<Option<String>, E> {
pub email: Option<String>, if let Some(opt) = opt {
pub password: String, let opt = opt.trim();
pub pw_verify: String, let len = opt.graphemes(true).size_hint().1.unwrap();
if !len_range.contains(&len) {
Err(err)
} else {
Ok(Some(opt.to_string()))
}
} else {
Ok(None)
}
} }

View File

@ -7,11 +7,13 @@
<h1>You did it!</h1> <h1>You did it!</h1>
<div id="signup_success"><p> <div id="signup_success">
{{ self.0 }} <p>
</p> {{ self.0 }}
</p>
</div> </div>
<p>Now, head on over to <a href="/login">the login page</a> and get watchin'!</p> <p>Now, head on over to <a href="/login?redirect_to={{ self.0.username|escape }}">the login page</a> and get watchin'!
</p>
{% endblock %} {% endblock %}