Working user signup, looks ugly.
Still no login or sessions, though.
This commit is contained in:
parent
94f1b35c03
commit
091ddbf48a
8 changed files with 110 additions and 19 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -2447,6 +2447,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -2793,6 +2799,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"urlencoding",
|
||||||
"uuid 1.3.1",
|
"uuid 1.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -23,3 +23,4 @@ justerror = "1.1.0"
|
||||||
password-hash = { version = "0.5.0", features = ["std", "getrandom"] }
|
password-hash = { version = "0.5.0", features = ["std", "getrandom"] }
|
||||||
axum-login = { version = "0.5.0", features = ["sqlite", "sqlx"] }
|
axum-login = { version = "0.5.0", features = ["sqlite", "sqlx"] }
|
||||||
unicode-segmentation = "1.10.1"
|
unicode-segmentation = "1.10.1"
|
||||||
|
urlencoding = "2.1.2"
|
||||||
|
|
|
@ -5,3 +5,5 @@ pub mod db;
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
pub(crate) mod templates;
|
pub(crate) mod templates;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
|
//pub type Db: axum::
|
||||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -2,7 +2,10 @@ use std::net::SocketAddr;
|
||||||
|
|
||||||
use axum::{routing::get, Router};
|
use axum::{routing::get, Router};
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -16,17 +19,9 @@ async fn main() {
|
||||||
|
|
||||||
let pool = db::get_pool().await;
|
let pool = db::get_pool().await;
|
||||||
|
|
||||||
let _ = witch_watch::users::create_user("joe", &None, &None, &[], &pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// build our application with some routes
|
// build our application with some routes
|
||||||
use handlers::*;
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route("/signup", get(get_create_user).post(post_create_user))
|
||||||
"/",
|
|
||||||
get(using_connection_pool_extractor).post(using_connection_extractor),
|
|
||||||
)
|
|
||||||
.with_state(pool);
|
.with_state(pool);
|
||||||
|
|
||||||
tracing::debug!("binding to 0.0.0.0:3000");
|
tracing::debug!("binding to 0.0.0.0:3000");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Debug, Default, Template, Deserialize)]
|
||||||
#[template(path = "signup.html")]
|
#[template(path = "signup.html")]
|
||||||
pub struct CreateUser {
|
pub struct CreateUser {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|
85
src/users.rs
85
src/users.rs
|
@ -2,11 +2,19 @@ use argon2::{
|
||||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||||
Argon2,
|
Argon2,
|
||||||
};
|
};
|
||||||
|
use askama::Template;
|
||||||
|
use axum::{
|
||||||
|
extract::{Form, Path, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
use sqlx::{error::DatabaseError, Sqlite, SqlitePool};
|
use sqlx::{error::DatabaseError, Sqlite, SqlitePool};
|
||||||
use tracing::log::log;
|
use tracing::log::log;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::templates::CreateUser;
|
||||||
|
|
||||||
const CREATE_QUERY: &str =
|
const CREATE_QUERY: &str =
|
||||||
"insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)";
|
"insert into witches (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)";
|
||||||
|
|
||||||
|
@ -38,20 +46,69 @@ impl From<DbUser> for User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_user(
|
pub async fn get_create_user() -> CreateUser {
|
||||||
username: &str,
|
CreateUser::default()
|
||||||
displayname: &Option<String>,
|
}
|
||||||
email: &Option<String>,
|
|
||||||
password: &[u8],
|
#[axum::debug_handler]
|
||||||
pool: &SqlitePool,
|
pub async fn post_create_user(
|
||||||
) -> Result<User, CreateUserError> {
|
State(pool): State<sqlx::Pool<Sqlite>>,
|
||||||
|
Form(signup): Form<CreateUser>,
|
||||||
|
) -> 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 username = username.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 !(1..=20).contains(&name_len) {
|
||||||
return Err(CreateUserErrorKind::BadUsername.into());
|
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<String>,
|
||||||
|
email: &Option<String>,
|
||||||
|
password: &[u8],
|
||||||
|
pool: &SqlitePool,
|
||||||
|
) -> Result<User, CreateUserError> {
|
||||||
// Argon2 with default params (Argon2id v19)
|
// Argon2 with default params (Argon2id v19)
|
||||||
let argon2 = Argon2::default();
|
let argon2 = Argon2::default();
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
@ -103,6 +160,17 @@ pub async fn create_user(
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct CreateUserError(#[from] CreateUserErrorKind);
|
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]
|
#[Error]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CreateUserErrorKind {
|
pub enum CreateUserErrorKind {
|
||||||
|
@ -110,6 +178,9 @@ pub enum CreateUserErrorKind {
|
||||||
#[error(desc = "Usernames must be between 1 and 20 non-whitespace characters long")]
|
#[error(desc = "Usernames must be between 1 and 20 non-whitespace characters long")]
|
||||||
BadUsername,
|
BadUsername,
|
||||||
PasswordMismatch,
|
PasswordMismatch,
|
||||||
|
BadPassword,
|
||||||
|
BadDisplayname,
|
||||||
|
BadEmail,
|
||||||
MissingFields,
|
MissingFields,
|
||||||
UnknownDBError,
|
UnknownDBError,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
{% block footer
|
{% block footer %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,4 +4,18 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<form action="/signup" enctype="application/x-www-form-urlencoded" method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" name="username" id="username" minlength="1" maxlength="20" required>
|
||||||
|
<label for="displayname">Displayname (optional)</label>
|
||||||
|
<input type="text" name="displayname" id="displayname">
|
||||||
|
<label for="email">Email (optional)</label>
|
||||||
|
<input type="text" name="email">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required>
|
||||||
|
<label for="confirm_password">Confirm Password</label>
|
||||||
|
<input type="password" name="pw_verify" id="pw_verify" required>
|
||||||
|
<input type="submit" value="Signup">
|
||||||
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue