summaryrefslogtreecommitdiff
path: root/src/invite
diff options
context:
space:
mode:
Diffstat (limited to 'src/invite')
-rw-r--r--src/invite/app.rs33
-rw-r--r--src/invite/routes/invite/post.rs6
-rw-r--r--src/invite/routes/invite/test/post.rs32
3 files changed, 60 insertions, 11 deletions
diff --git a/src/invite/app.rs b/src/invite/app.rs
index 176075f..d4e877a 100644
--- a/src/invite/app.rs
+++ b/src/invite/app.rs
@@ -5,8 +5,11 @@ use super::{repo::Provider as _, Id, Invite, Summary};
use crate::{
clock::DateTime,
db::{Duplicate as _, NotFound as _},
- event::{repo::Provider as _, Broadcaster, Event},
- login::{repo::Provider as _, Login, Password},
+ event::Broadcaster,
+ login::{
+ create::{self, Create},
+ Login, Password,
+ },
name::Name,
token::{repo::Provider as _, Secret},
};
@@ -44,6 +47,8 @@ impl<'a> Invites<'a> {
password: &Password,
accepted_at: &DateTime,
) -> Result<(Login, Secret), AcceptError> {
+ let create = Create::begin(name, password, accepted_at);
+
let mut tx = self.db.begin().await?;
let invite = tx
.invites()
@@ -55,23 +60,20 @@ impl<'a> Invites<'a> {
// the invite. Final validation is in the next tx.
tx.commit().await?;
- let password_hash = password.hash()?;
+ let validated = create.validate()?;
let mut tx = self.db.begin().await?;
// If the invite has been deleted or accepted in the interim, this step will
// catch it.
tx.invites().accept(&invite).await?;
- let created = tx.sequence().next(accepted_at).await?;
- let login = tx
- .logins()
- .create(name, &password_hash, &created)
+ let stored = validated
+ .store(&mut tx)
.await
.duplicate(|| AcceptError::DuplicateLogin(name.clone()))?;
- let secret = tx.tokens().issue(&login, accepted_at).await?;
+ let secret = tx.tokens().issue(stored.login(), accepted_at).await?;
tx.commit().await?;
- self.events
- .broadcast(login.events().map(Event::from).collect::<Vec<_>>());
+ let login = stored.publish(self.events);
Ok((login.as_created(), secret))
}
@@ -92,6 +94,8 @@ impl<'a> Invites<'a> {
pub enum AcceptError {
#[error("invite not found: {0}")]
NotFound(Id),
+ #[error("invalid login name: {0}")]
+ InvalidName(Name),
#[error("name in use: {0}")]
DuplicateLogin(Name),
#[error(transparent)]
@@ -99,3 +103,12 @@ pub enum AcceptError {
#[error(transparent)]
PasswordHash(#[from] password_hash::Error),
}
+
+impl From<create::Error> for AcceptError {
+ fn from(error: create::Error) -> Self {
+ match error {
+ create::Error::InvalidName(name) => Self::InvalidName(name),
+ create::Error::PasswordHash(error) => Self::PasswordHash(error),
+ }
+ }
+}
diff --git a/src/invite/routes/invite/post.rs b/src/invite/routes/invite/post.rs
index 0dd8dba..bb68e07 100644
--- a/src/invite/routes/invite/post.rs
+++ b/src/invite/routes/invite/post.rs
@@ -36,7 +36,8 @@ pub struct Request {
pub password: Password,
}
-#[derive(Debug)]
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
pub struct Error(pub app::AcceptError);
impl IntoResponse for Error {
@@ -44,6 +45,9 @@ impl IntoResponse for Error {
let Self(error) = self;
match error {
app::AcceptError::NotFound(_) => NotFound(error).into_response(),
+ app::AcceptError::InvalidName(_) => {
+ (StatusCode::BAD_REQUEST, error.to_string()).into_response()
+ }
app::AcceptError::DuplicateLogin(_) => {
(StatusCode::CONFLICT, error.to_string()).into_response()
}
diff --git a/src/invite/routes/invite/test/post.rs b/src/invite/routes/invite/test/post.rs
index 65ab61e..40e0580 100644
--- a/src/invite/routes/invite/test/post.rs
+++ b/src/invite/routes/invite/test/post.rs
@@ -206,3 +206,35 @@ async fn conflicting_name() {
matches!(error, AcceptError::DuplicateLogin(error_name) if error_name == conflicting_name)
);
}
+
+#[tokio::test]
+async fn invalid_name() {
+ // Set up the environment
+
+ let app = fixtures::scratch_app().await;
+ let issuer = fixtures::login::create(&app, &fixtures::now()).await;
+ let invite = fixtures::invite::issue(&app, &issuer, &fixtures::now()).await;
+
+ // Call the endpoint
+
+ let name = fixtures::login::propose_invalid_name();
+ let password = fixtures::login::propose_password();
+ let identity = fixtures::cookie::not_logged_in();
+ let request = post::Request {
+ name: name.clone(),
+ password: password.clone(),
+ };
+ let post::Error(error) = post::handler(
+ State(app.clone()),
+ fixtures::now(),
+ identity,
+ Path(invite.id),
+ Json(request),
+ )
+ .await
+ .expect_err("using an invalid name should fail");
+
+ // Verify the response
+
+ assert!(matches!(error, AcceptError::InvalidName(error_name) if name == error_name));
+}