diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2024-10-19 01:51:30 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2024-10-21 00:49:05 -0400 |
| commit | 379e97c2cb145bc3a495aa14746273d83b508214 (patch) | |
| tree | 218bbe2572af9dd4b165ff05495d084dc0bd8905 /src/channel | |
| parent | 98af8ff80da919a1126ba7c6afa65e6654b5ecde (diff) | |
Unicode normalization on input.
This normalizes the following values:
* login names
* passwords
* channel names
* message bodies, because why not
The goal here is to have a canonical representation of these values, so that, for example, the service does not inadvertently host two channels whose names are semantically identical but differ in the specifics of how diacritics are encoded, or two users whose names are identical.
Normalization is done on input from the wire, using Serde hooks, and when reading from the database. The `crate::nfc::String` type implements these normalizations (as well as normalizing whenever converted from a `std::string::String` generally).
This change does not cover:
* Trying to cope with passwords that were created as non-normalized strings, which are now non-verifiable as all the paths to verify passwords normalize the input.
* Trying to ensure that non-normalized data in the database compares reasonably to normalized data. Fortunately, we don't _do_ very many string comparisons (I think only login names), so this isn't a huge deal at this stage. Login names will probably have to Get Fixed later on, when we figure out how to handle case folding for login name verification.
Diffstat (limited to 'src/channel')
| -rw-r--r-- | src/channel/app.rs | 8 | ||||
| -rw-r--r-- | src/channel/mod.rs | 5 | ||||
| -rw-r--r-- | src/channel/name.rs | 30 | ||||
| -rw-r--r-- | src/channel/repo.rs | 14 | ||||
| -rw-r--r-- | src/channel/routes/channel/post.rs | 4 | ||||
| -rw-r--r-- | src/channel/routes/post.rs | 4 | ||||
| -rw-r--r-- | src/channel/snapshot.rs | 4 |
7 files changed, 51 insertions, 18 deletions
diff --git a/src/channel/app.rs b/src/channel/app.rs index 75c662d..ea60943 100644 --- a/src/channel/app.rs +++ b/src/channel/app.rs @@ -2,7 +2,7 @@ use chrono::TimeDelta; use itertools::Itertools; use sqlx::sqlite::SqlitePool; -use super::{repo::Provider as _, Channel, History, Id}; +use super::{repo::Provider as _, Channel, History, Id, Name}; use crate::{ clock::DateTime, db::{Duplicate as _, NotFound as _}, @@ -20,14 +20,14 @@ impl<'a> Channels<'a> { Self { db, events } } - pub async fn create(&self, name: &str, created_at: &DateTime) -> Result<Channel, CreateError> { + pub async fn create(&self, name: &Name, created_at: &DateTime) -> Result<Channel, CreateError> { let mut tx = self.db.begin().await?; let created = tx.sequence().next(created_at).await?; let channel = tx .channels() .create(name, &created) .await - .duplicate(|| CreateError::DuplicateName(name.into()))?; + .duplicate(|| CreateError::DuplicateName(name.clone()))?; tx.commit().await?; self.events @@ -134,7 +134,7 @@ impl<'a> Channels<'a> { #[derive(Debug, thiserror::Error)] pub enum CreateError { #[error("channel named {0} already exists")] - DuplicateName(String), + DuplicateName(Name), #[error(transparent)] Database(#[from] sqlx::Error), } diff --git a/src/channel/mod.rs b/src/channel/mod.rs index eb8200b..fb13e92 100644 --- a/src/channel/mod.rs +++ b/src/channel/mod.rs @@ -2,8 +2,11 @@ pub mod app; pub mod event; mod history; mod id; +mod name; pub mod repo; mod routes; mod snapshot; -pub use self::{event::Event, history::History, id::Id, routes::router, snapshot::Channel}; +pub use self::{ + event::Event, history::History, id::Id, name::Name, routes::router, snapshot::Channel, +}; diff --git a/src/channel/name.rs b/src/channel/name.rs new file mode 100644 index 0000000..fc82dec --- /dev/null +++ b/src/channel/name.rs @@ -0,0 +1,30 @@ +use std::fmt; + +use crate::nfc; + +#[derive( + Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type, +)] +#[serde(transparent)] +#[sqlx(transparent)] +pub struct Name(nfc::String); + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(name) = self; + name.fmt(f) + } +} + +impl From<String> for Name { + fn from(name: String) -> Self { + Self(name.into()) + } +} + +impl From<Name> for String { + fn from(name: Name) -> Self { + let Name(name) = name; + name.into() + } +} diff --git a/src/channel/repo.rs b/src/channel/repo.rs index 27d35f0..3353bfd 100644 --- a/src/channel/repo.rs +++ b/src/channel/repo.rs @@ -1,7 +1,7 @@ use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; use crate::{ - channel::{Channel, History, Id}, + channel::{Channel, History, Id, Name}, clock::DateTime, event::{Instant, ResumePoint, Sequence}, }; @@ -19,7 +19,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Channels<'t>(&'t mut SqliteConnection); impl<'c> Channels<'c> { - pub async fn create(&mut self, name: &str, created: &Instant) -> Result<History, sqlx::Error> { + pub async fn create(&mut self, name: &Name, created: &Instant) -> Result<History, sqlx::Error> { let id = Id::generate(); let channel = sqlx::query!( r#" @@ -28,7 +28,7 @@ impl<'c> Channels<'c> { values ($1, $2, $3, $4) returning id as "id: Id", - name as "name!", -- known non-null as we just set it + name as "name!: Name", -- known non-null as we just set it created_at as "created_at: DateTime", created_sequence as "created_sequence: Sequence" "#, @@ -57,7 +57,7 @@ impl<'c> Channels<'c> { r#" select id as "id: Id", - channel.name, + channel.name as "name: Name", channel.created_at as "created_at: DateTime", channel.created_sequence as "created_sequence: Sequence", deleted.deleted_at as "deleted_at?: DateTime", @@ -89,7 +89,7 @@ impl<'c> Channels<'c> { r#" select id as "id: Id", - channel.name, + channel.name as "name: Name", channel.created_at as "created_at: DateTime", channel.created_sequence as "created_sequence: Sequence", deleted.deleted_at as "deleted_at: DateTime", @@ -125,7 +125,7 @@ impl<'c> Channels<'c> { r#" select id as "id: Id", - channel.name, + channel.name as "name: Name", channel.created_at as "created_at: DateTime", channel.created_sequence as "created_sequence: Sequence", deleted.deleted_at as "deleted_at: DateTime", @@ -235,7 +235,7 @@ impl<'c> Channels<'c> { r#" select channel.id as "id: Id", - channel.name, + channel.name as "name: Name", channel.created_at as "created_at: DateTime", channel.created_sequence as "created_sequence: Sequence", deleted.deleted_at as "deleted_at?: DateTime", diff --git a/src/channel/routes/channel/post.rs b/src/channel/routes/channel/post.rs index b489a77..d0cae05 100644 --- a/src/channel/routes/channel/post.rs +++ b/src/channel/routes/channel/post.rs @@ -9,7 +9,7 @@ use crate::{ clock::RequestedAt, error::{Internal, NotFound}, login::Login, - message::{app::SendError, Message}, + message::{app::SendError, Body, Message}, }; pub async fn handler( @@ -29,7 +29,7 @@ pub async fn handler( #[derive(serde::Deserialize)] pub struct Request { - pub body: String, + pub body: Body, } #[derive(Debug)] diff --git a/src/channel/routes/post.rs b/src/channel/routes/post.rs index a05c312..d354f79 100644 --- a/src/channel/routes/post.rs +++ b/src/channel/routes/post.rs @@ -6,7 +6,7 @@ use axum::{ use crate::{ app::App, - channel::{app, Channel}, + channel::{app, Channel, Name}, clock::RequestedAt, error::Internal, login::Login, @@ -29,7 +29,7 @@ pub async fn handler( #[derive(serde::Deserialize)] pub struct Request { - pub name: String, + pub name: Name, } #[derive(Debug)] diff --git a/src/channel/snapshot.rs b/src/channel/snapshot.rs index 2b7d89a..dc2894d 100644 --- a/src/channel/snapshot.rs +++ b/src/channel/snapshot.rs @@ -1,13 +1,13 @@ use super::{ event::{Created, Event}, - Id, + Id, Name, }; use crate::clock::DateTime; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct Channel { pub id: Id, - pub name: String, + pub name: Name, #[serde(skip_serializing_if = "Option::is_none")] pub deleted_at: Option<DateTime>, } |
