summaryrefslogtreecommitdiff
path: root/src/login
diff options
context:
space:
mode:
Diffstat (limited to 'src/login')
-rw-r--r--src/login/app.rs12
-rw-r--r--src/login/mod.rs1
-rw-r--r--src/login/validate.rs23
3 files changed, 34 insertions, 2 deletions
diff --git a/src/login/app.rs b/src/login/app.rs
index 2f5896f..c1bfe6e 100644
--- a/src/login/app.rs
+++ b/src/login/app.rs
@@ -3,7 +3,7 @@ use sqlx::sqlite::SqlitePool;
use super::repo::Provider as _;
#[cfg(test)]
-use super::{Login, Password};
+use super::{validate, Login, Password};
#[cfg(test)]
use crate::{
clock::DateTime,
@@ -35,6 +35,10 @@ impl<'a> Logins<'a> {
password: &Password,
created_at: &DateTime,
) -> Result<Login, CreateError> {
+ if !validate::name(name) {
+ return Err(CreateError::InvalidName(name.clone()));
+ }
+
let password_hash = password.hash()?;
let mut tx = self.db.begin().await?;
@@ -57,9 +61,13 @@ impl<'a> Logins<'a> {
}
}
+#[cfg(test)]
#[derive(Debug, thiserror::Error)]
-#[error(transparent)]
pub enum CreateError {
+ #[error("invalid login name: {0}")]
+ InvalidName(Name),
+ #[error(transparent)]
Database(#[from] sqlx::Error),
+ #[error(transparent)]
PasswordHash(#[from] password_hash::Error),
}
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 279e9a6..6d10e17 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -6,6 +6,7 @@ pub mod password;
pub mod repo;
mod routes;
mod snapshot;
+pub mod validate;
pub use self::{
event::Event, history::History, id::Id, password::Password, routes::router, snapshot::Login,
diff --git a/src/login/validate.rs b/src/login/validate.rs
new file mode 100644
index 0000000..ed3eff8
--- /dev/null
+++ b/src/login/validate.rs
@@ -0,0 +1,23 @@
+use unicode_segmentation::UnicodeSegmentation as _;
+
+use crate::name::Name;
+
+// Picked out of a hat. The power of two is not meaningful.
+const NAME_TOO_LONG: usize = 64;
+
+pub fn name(name: &Name) -> bool {
+ let display = name.display();
+
+ [
+ display.graphemes(true).count() < NAME_TOO_LONG,
+ display.chars().all(|ch| !ch.is_control()),
+ display.chars().next().is_some_and(char::is_alphanumeric),
+ display.chars().last().is_some_and(char::is_alphanumeric),
+ display
+ .chars()
+ .zip(display.chars().skip(1))
+ .all(|(a, b)| !(a.is_whitespace() && b.is_whitespace())),
+ ]
+ .into_iter()
+ .all(|value| value)
+}