diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2024-10-22 19:12:34 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2024-10-22 19:12:34 -0400 |
| commit | 6430854352745f45281021c305b4e350bc92d535 (patch) | |
| tree | c6901c22a45e36415f63efe988d4d4f2a309df81 /src/login | |
| parent | 98af8ff80da919a1126ba7c6afa65e6654b5ecde (diff) | |
| parent | db940bacd096a33a65f29759e70ea1acf6186a67 (diff) | |
Merge branch 'unicode-normalization'
Diffstat (limited to 'src/login')
| -rw-r--r-- | src/login/app.rs | 25 | ||||
| -rw-r--r-- | src/login/mod.rs | 1 | ||||
| -rw-r--r-- | src/login/password.rs | 7 | ||||
| -rw-r--r-- | src/login/repo.rs | 129 | ||||
| -rw-r--r-- | src/login/routes/login/post.rs | 3 | ||||
| -rw-r--r-- | src/login/routes/logout/test.rs | 1 | ||||
| -rw-r--r-- | src/login/snapshot.rs | 3 |
7 files changed, 118 insertions, 51 deletions
diff --git a/src/login/app.rs b/src/login/app.rs index b6f7e1c..2f5896f 100644 --- a/src/login/app.rs +++ b/src/login/app.rs @@ -1,24 +1,37 @@ use sqlx::sqlite::SqlitePool; -use super::{repo::Provider as _, Login, Password}; +use super::repo::Provider as _; + +#[cfg(test)] +use super::{Login, Password}; +#[cfg(test)] use crate::{ clock::DateTime, event::{repo::Provider as _, Broadcaster, Event}, + name::Name, }; pub struct Logins<'a> { db: &'a SqlitePool, + #[cfg(test)] events: &'a Broadcaster, } impl<'a> Logins<'a> { + #[cfg(not(test))] + pub const fn new(db: &'a SqlitePool) -> Self { + Self { db } + } + + #[cfg(test)] pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self { Self { db, events } } + #[cfg(test)] pub async fn create( &self, - name: &str, + name: &Name, password: &Password, created_at: &DateTime, ) -> Result<Login, CreateError> { @@ -34,6 +47,14 @@ impl<'a> Logins<'a> { Ok(login.as_created()) } + + pub async fn recanonicalize(&self) -> Result<(), sqlx::Error> { + let mut tx = self.db.begin().await?; + tx.logins().recanonicalize().await?; + tx.commit().await?; + + Ok(()) + } } #[derive(Debug, thiserror::Error)] diff --git a/src/login/mod.rs b/src/login/mod.rs index 98cc3d7..64a3698 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -1,4 +1,3 @@ -#[cfg(test)] pub mod app; pub mod event; pub mod extract; diff --git a/src/login/password.rs b/src/login/password.rs index 14fd981..c27c950 100644 --- a/src/login/password.rs +++ b/src/login/password.rs @@ -4,6 +4,8 @@ use argon2::Argon2; use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; use rand_core::OsRng; +use crate::normalize::nfc; + #[derive(sqlx::Type)] #[sqlx(transparent)] pub struct StoredHash(String); @@ -31,7 +33,7 @@ impl fmt::Debug for StoredHash { #[derive(serde::Deserialize)] #[serde(transparent)] -pub struct Password(String); +pub struct Password(nfc::String); impl Password { pub fn hash(&self) -> Result<StoredHash, password_hash::Error> { @@ -56,9 +58,8 @@ impl fmt::Debug for Password { } } -#[cfg(test)] impl From<String> for Password { fn from(password: String) -> Self { - Self(password) + Password(password.into()) } } diff --git a/src/login/repo.rs b/src/login/repo.rs index 7d0fcb1..c6bc734 100644 --- a/src/login/repo.rs +++ b/src/login/repo.rs @@ -1,9 +1,11 @@ +use futures::stream::{StreamExt as _, TryStreamExt as _}; use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; use crate::{ clock::DateTime, event::{Instant, ResumePoint, Sequence}, login::{password::StoredHash, History, Id, Login}, + name::{self, Name}, }; pub trait Provider { @@ -21,48 +23,48 @@ pub struct Logins<'t>(&'t mut SqliteConnection); impl<'c> Logins<'c> { pub async fn create( &mut self, - name: &str, + name: &Name, password_hash: &StoredHash, created: &Instant, ) -> Result<History, sqlx::Error> { let id = Id::generate(); + let display_name = name.display(); + let canonical_name = name.canonical(); - let login = sqlx::query!( + sqlx::query!( r#" insert - into login (id, name, password_hash, created_sequence, created_at) - values ($1, $2, $3, $4, $5) - returning - id as "id: Id", - name, - created_sequence as "created_sequence: Sequence", - created_at as "created_at: DateTime" + into login (id, display_name, canonical_name, password_hash, created_sequence, created_at) + values ($1, $2, $3, $4, $5, $6) "#, id, - name, + display_name, + canonical_name, password_hash, created.sequence, created.at, ) - .map(|row| History { + .execute(&mut *self.0) + .await?; + + let login = History { + created: *created, login: Login { - id: row.id, - name: row.name, + id, + name: name.clone(), }, - created: Instant::new(row.created_at, row.created_sequence), - }) - .fetch_one(&mut *self.0) - .await?; + }; Ok(login) } - pub async fn all(&mut self, resume_at: ResumePoint) -> Result<Vec<History>, sqlx::Error> { - let channels = sqlx::query!( + pub async fn all(&mut self, resume_at: ResumePoint) -> Result<Vec<History>, LoadError> { + let logins = sqlx::query!( r#" select id as "id: Id", - name, + display_name as "display_name: String", + canonical_name as "canonical_name: String", created_sequence as "created_sequence: Sequence", created_at as "created_at: DateTime" from login @@ -71,24 +73,30 @@ impl<'c> Logins<'c> { "#, resume_at, ) - .map(|row| History { - login: Login { - id: row.id, - name: row.name, - }, - created: Instant::new(row.created_at, row.created_sequence), + .map(|row| { + Ok::<_, LoadError>(History { + login: Login { + id: row.id, + name: Name::new(row.display_name, row.canonical_name)?, + }, + created: Instant::new(row.created_at, row.created_sequence), + }) }) - .fetch_all(&mut *self.0) + .fetch(&mut *self.0) + .map(|res| res?) + .try_collect() .await?; - Ok(channels) + Ok(logins) } - pub async fn replay(&mut self, resume_at: ResumePoint) -> Result<Vec<History>, sqlx::Error> { - let messages = sqlx::query!( + + pub async fn replay(&mut self, resume_at: ResumePoint) -> Result<Vec<History>, LoadError> { + let logins = sqlx::query!( r#" select id as "id: Id", - name, + display_name as "display_name: String", + canonical_name as "canonical_name: String", created_sequence as "created_sequence: Sequence", created_at as "created_at: DateTime" from login @@ -96,22 +104,59 @@ impl<'c> Logins<'c> { "#, resume_at, ) - .map(|row| History { - login: Login { - id: row.id, - name: row.name, - }, - created: Instant::new(row.created_at, row.created_sequence), + .map(|row| { + Ok::<_, name::Error>(History { + login: Login { + id: row.id, + name: Name::new(row.display_name, row.canonical_name)?, + }, + created: Instant::new(row.created_at, row.created_sequence), + }) }) + .fetch(&mut *self.0) + .map(|res| Ok::<_, LoadError>(res??)) + .try_collect() + .await?; + + Ok(logins) + } + + pub async fn recanonicalize(&mut self) -> Result<(), sqlx::Error> { + let logins = sqlx::query!( + r#" + select + id as "id: Id", + display_name as "display_name: String" + from login + "#, + ) .fetch_all(&mut *self.0) .await?; - Ok(messages) + for login in logins { + let name = Name::from(login.display_name); + let canonical_name = name.canonical(); + + sqlx::query!( + r#" + update login + set canonical_name = $1 + where id = $2 + "#, + canonical_name, + login.id, + ) + .execute(&mut *self.0) + .await?; + } + + Ok(()) } } -impl<'t> From<&'t mut SqliteConnection> for Logins<'t> { - fn from(tx: &'t mut SqliteConnection) -> Self { - Self(tx) - } +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub enum LoadError { + Database(#[from] sqlx::Error), + Name(#[from] name::Error), } diff --git a/src/login/routes/login/post.rs b/src/login/routes/login/post.rs index 67eaa6d..20430db 100644 --- a/src/login/routes/login/post.rs +++ b/src/login/routes/login/post.rs @@ -9,6 +9,7 @@ use crate::{ clock::RequestedAt, error::Internal, login::{Login, Password}, + name::Name, token::{app, extract::IdentityToken}, }; @@ -29,7 +30,7 @@ pub async fn handler( #[derive(serde::Deserialize)] pub struct Request { - pub name: String, + pub name: Name, pub password: Password, } diff --git a/src/login/routes/logout/test.rs b/src/login/routes/logout/test.rs index 0e70e4c..91837fe 100644 --- a/src/login/routes/logout/test.rs +++ b/src/login/routes/logout/test.rs @@ -33,7 +33,6 @@ async fn successful() { assert_eq!(StatusCode::NO_CONTENT, response_status); // Verify the semantics - let error = app .tokens() .validate(&secret, &now) diff --git a/src/login/snapshot.rs b/src/login/snapshot.rs index 1a92f5c..e1eb96c 100644 --- a/src/login/snapshot.rs +++ b/src/login/snapshot.rs @@ -2,6 +2,7 @@ use super::{ event::{Created, Event}, Id, }; +use crate::name::Name; // This also implements FromRequestParts (see `./extract.rs`). As a result, it // can be used as an extractor for endpoints that want to require login, or for @@ -10,7 +11,7 @@ use super::{ #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct Login { pub id: Id, - pub name: String, + pub name: Name, // The omission of the hashed password is deliberate, to minimize the // chance that it ends up tangled up in debug output or in some other chunk // of logic elsewhere. |
