diff --git a/Cargo.lock b/Cargo.lock index ba1cbc8..0364a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2792,6 +2792,7 @@ dependencies = [ "tower-http 0.4.0", "tracing", "tracing-subscriber", + "unicode-segmentation", "uuid 1.3.1", ] diff --git a/Cargo.toml b/Cargo.toml index 966dd52..cf9377a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,4 @@ thiserror = "1.0.40" 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" diff --git a/src/users.rs b/src/users.rs index 38cca2e..47d65a1 100644 --- a/src/users.rs +++ b/src/users.rs @@ -4,6 +4,7 @@ use argon2::{ }; use sqlx::{error::DatabaseError, Sqlite, SqlitePool}; use tracing::log::log; +use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; const CREATE_QUERY: &str = @@ -44,9 +45,15 @@ pub async fn create_user( password: &[u8], pool: &SqlitePool, ) -> Result { + 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()); + } + // Argon2 with default params (Argon2id v19) let argon2 = Argon2::default(); - let salt = SaltString::generate(&mut OsRng); let pwhash = argon2 .hash_password(password, &salt) @@ -54,7 +61,8 @@ pub async fn create_user( .to_string(); let id = Uuid::new_v4(); - let id_bytes = id.as_bytes().as_slice(); + let id_bytes = id.to_bytes_le(); + let id_bytes = id_bytes.as_slice(); let res = sqlx::query(CREATE_QUERY) .bind(id_bytes) .bind(username) @@ -99,7 +107,7 @@ pub struct CreateUserError(#[from] CreateUserErrorKind); #[non_exhaustive] pub enum CreateUserErrorKind { AlreadyExists, - #[error(desc = "Usernames must be less than 20 characters long")] + #[error(desc = "Usernames must be between 1 and 20 non-whitespace characters long")] BadUsername, PasswordMismatch, MissingFields,