Compare commits
2 commits
478464ac05
...
1daec80430
Author | SHA1 | Date | |
---|---|---|---|
|
1daec80430 | ||
|
c7bad9350a |
4 changed files with 61 additions and 13 deletions
40
Cargo.lock
generated
40
Cargo.lock
generated
|
@ -163,6 +163,12 @@ version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -379,6 +385,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range-header"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -665,6 +677,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
"tower-sessions",
|
"tower-sessions",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
@ -714,7 +727,7 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -962,6 +975,31 @@ dependencies = [
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"http-range-header",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
|
@ -11,5 +11,6 @@ justerror = { version = "1" }
|
||||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
thiserror = { version = "1" }
|
thiserror = { version = "1" }
|
||||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] }
|
tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] }
|
||||||
|
tower-http = { version = "0.5.2", default-features = false, features = ["fs"] }
|
||||||
tower-sessions = { version = "0.10", default-features = false, features = ["axum-core", "memory-store"] }
|
tower-sessions = { version = "0.10", default-features = false, features = ["axum-core", "memory-store"] }
|
||||||
unicode-segmentation = { version = "1", default-features = false }
|
unicode-segmentation = { version = "1", default-features = false }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{error::Error, fmt::Debug, ops::Range};
|
use std::{error::Error, fmt::Debug, ops::RangeInclusive};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Form, Path},
|
extract::{Form, Path},
|
||||||
|
@ -13,6 +13,11 @@ use crate::{templates::*, User};
|
||||||
|
|
||||||
const SIGNUP_KEY: &str = "meow";
|
const SIGNUP_KEY: &str = "meow";
|
||||||
|
|
||||||
|
const PASSWORD_LEN: RangeInclusive<usize> = 4..=100;
|
||||||
|
const USERNAME_LEN: RangeInclusive<usize> = 1..=50;
|
||||||
|
const DISPLAYNAME_LEN: RangeInclusive<usize> = 0..=100;
|
||||||
|
const EMAIL_LEN: RangeInclusive<usize> = 4..=50;
|
||||||
|
|
||||||
#[Error(desc = "Could not create user.")]
|
#[Error(desc = "Could not create user.")]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct CreateUserError(#[from] CreateUserErrorKind);
|
pub struct CreateUserError(#[from] CreateUserErrorKind);
|
||||||
|
@ -27,10 +32,10 @@ impl IntoResponse for CreateUserError {
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CreateUserErrorKind {
|
pub enum CreateUserErrorKind {
|
||||||
AlreadyExists,
|
AlreadyExists,
|
||||||
#[error(desc = "Usernames must be between 1 and 20 characters long")]
|
#[error(desc = "Usernames must be between 1 and 50 characters long")]
|
||||||
BadUsername,
|
BadUsername,
|
||||||
PasswordMismatch,
|
PasswordMismatch,
|
||||||
#[error(desc = "Password must have at least 4 and at most 50 characters")]
|
#[error(desc = "Password must have at least 4 and at most 100 characters")]
|
||||||
BadPassword,
|
BadPassword,
|
||||||
#[error(desc = "Display name must be less than 100 characters long")]
|
#[error(desc = "Display name must be less than 100 characters long")]
|
||||||
BadDisplayname,
|
BadDisplayname,
|
||||||
|
@ -60,7 +65,7 @@ pub async fn post_signup(
|
||||||
session: Session,
|
session: Session,
|
||||||
Form(form): Form<SignupForm>,
|
Form(form): Form<SignupForm>,
|
||||||
) -> Result<impl IntoResponse, CreateUserError> {
|
) -> Result<impl IntoResponse, CreateUserError> {
|
||||||
let user = verify_user(&form).await?;
|
let user = validate_signup(&form).await?;
|
||||||
session.insert(SIGNUP_KEY, user).await.unwrap();
|
session.insert(SIGNUP_KEY, user).await.unwrap();
|
||||||
|
|
||||||
Ok(Redirect::to(
|
Ok(Redirect::to(
|
||||||
|
@ -96,14 +101,14 @@ pub async fn signup_success(session: Session, receipt: Option<Path<String>>) ->
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
// helpers
|
// helpers
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
async fn verify_user(form: &SignupForm) -> Result<User, CreateUserError> {
|
async fn validate_signup(form: &SignupForm) -> Result<User, CreateUserError> {
|
||||||
let username = form.username.trim();
|
let username = form.username.trim();
|
||||||
let password = form.password.trim();
|
let password = form.password.trim();
|
||||||
let verify = form.pw_verify.trim();
|
let verify = form.pw_verify.trim();
|
||||||
|
|
||||||
let name_len = username.graphemes(true).size_hint().1.unwrap();
|
let name_len = username.graphemes(true).size_hint().1.unwrap();
|
||||||
// we are not ascii exclusivists around here
|
// we are not ascii exclusivists around here
|
||||||
if !(1..=20).contains(&name_len) {
|
if !USERNAME_LEN.contains(&name_len) {
|
||||||
return Err(CreateUserErrorKind::BadUsername.into());
|
return Err(CreateUserErrorKind::BadUsername.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,18 +116,18 @@ async fn verify_user(form: &SignupForm) -> Result<User, CreateUserError> {
|
||||||
return Err(CreateUserErrorKind::PasswordMismatch.into());
|
return Err(CreateUserErrorKind::PasswordMismatch.into());
|
||||||
}
|
}
|
||||||
let pwlen = password.graphemes(true).size_hint().1.unwrap_or(0);
|
let pwlen = password.graphemes(true).size_hint().1.unwrap_or(0);
|
||||||
if !(4..=50).contains(&pwlen) {
|
if !PASSWORD_LEN.contains(&pwlen) {
|
||||||
return Err(CreateUserErrorKind::BadPassword.into());
|
return Err(CreateUserErrorKind::BadPassword.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up the optionals
|
// clean up the optionals
|
||||||
let displayname = validate_optional_length(
|
let displayname = validate_optional_length(
|
||||||
&form.displayname,
|
&form.displayname,
|
||||||
0..100,
|
DISPLAYNAME_LEN,
|
||||||
CreateUserErrorKind::BadDisplayname,
|
CreateUserErrorKind::BadDisplayname,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let email = validate_length(&form.email, 5..30, CreateUserErrorKind::BadEmail)?;
|
let email = validate_length(&form.email, EMAIL_LEN, CreateUserErrorKind::BadEmail)?;
|
||||||
|
|
||||||
let user = User {
|
let user = User {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
|
@ -152,7 +157,7 @@ where
|
||||||
|
|
||||||
pub(crate) fn validate_optional_length<E: Error>(
|
pub(crate) fn validate_optional_length<E: Error>(
|
||||||
opt: &Option<String>,
|
opt: &Option<String>,
|
||||||
len_range: Range<usize>,
|
len_range: RangeInclusive<usize>,
|
||||||
err: E,
|
err: E,
|
||||||
) -> Result<Option<String>, E> {
|
) -> Result<Option<String>, E> {
|
||||||
if let Some(opt) = opt {
|
if let Some(opt) = opt {
|
||||||
|
@ -170,7 +175,7 @@ pub(crate) fn validate_optional_length<E: Error>(
|
||||||
|
|
||||||
pub(crate) fn validate_length<E: Error>(
|
pub(crate) fn validate_length<E: Error>(
|
||||||
thing: &str,
|
thing: &str,
|
||||||
len_range: Range<usize>,
|
len_range: RangeInclusive<usize>,
|
||||||
err: E,
|
err: E,
|
||||||
) -> Result<String, E> {
|
) -> Result<String, E> {
|
||||||
let thing = thing.trim();
|
let thing = thing.trim();
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::{
|
||||||
|
|
||||||
use axum::{routing::get, Router};
|
use axum::{routing::get, Router};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
use tower_sessions::{MemoryStore, SessionManagerLayer};
|
use tower_sessions::{MemoryStore, SessionManagerLayer};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -20,8 +21,11 @@ async fn main() {
|
||||||
let session_store = MemoryStore::default();
|
let session_store = MemoryStore::default();
|
||||||
let session_layer = SessionManagerLayer::new(session_store).with_secure(false);
|
let session_layer = SessionManagerLayer::new(session_store).with_secure(false);
|
||||||
|
|
||||||
|
let assets_dir = std::env::current_dir().unwrap().join("assets");
|
||||||
|
let assets_svc = ServeDir::new(assets_dir.as_path());
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
//.nest_service("/assets", assets_svc)
|
.nest_service("/assets", assets_svc)
|
||||||
.route("/signup", get(get_signup).post(post_signup))
|
.route("/signup", get(get_signup).post(post_signup))
|
||||||
.route("/signup_success/:receipt", get(signup_success))
|
.route("/signup_success/:receipt", get(signup_success))
|
||||||
.layer(session_layer);
|
.layer(session_layer);
|
||||||
|
|
Loading…
Reference in a new issue