summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/channel/routes/channel/post.rs23
-rw-r--r--src/channel/routes/channel/test.rs2
-rw-r--r--src/channel/routes/post.rs14
-rw-r--r--src/channel/routes/test.rs16
-rw-r--r--src/invite/app.rs4
-rw-r--r--src/invite/routes/invite/post.rs8
-rw-r--r--src/login/history.rs6
-rw-r--r--src/login/routes/login/post.rs10
-rw-r--r--src/login/routes/login/test.rs22
-rw-r--r--src/setup/app.rs6
-rw-r--r--src/setup/routes/post.rs12
-rw-r--r--src/test/fixtures/identity.rs12
-rw-r--r--src/test/fixtures/login.rs7
-rw-r--r--src/token/app.rs6
14 files changed, 91 insertions, 57 deletions
diff --git a/src/channel/routes/channel/post.rs b/src/channel/routes/channel/post.rs
index a71a3a0..b489a77 100644
--- a/src/channel/routes/channel/post.rs
+++ b/src/channel/routes/channel/post.rs
@@ -1,7 +1,7 @@
use axum::{
extract::{Json, Path, State},
http::StatusCode,
- response::{IntoResponse, Response},
+ response::{self, IntoResponse},
};
use crate::{
@@ -9,7 +9,7 @@ use crate::{
clock::RequestedAt,
error::{Internal, NotFound},
login::Login,
- message::app::SendError,
+ message::{app::SendError, Message},
};
pub async fn handler(
@@ -18,12 +18,13 @@ pub async fn handler(
RequestedAt(sent_at): RequestedAt,
login: Login,
Json(request): Json<Request>,
-) -> Result<StatusCode, Error> {
- app.messages()
+) -> Result<Response, Error> {
+ let message = app
+ .messages()
.send(&channel, &login, &sent_at, &request.body)
.await?;
- Ok(StatusCode::ACCEPTED)
+ Ok(Response(message))
}
#[derive(serde::Deserialize)]
@@ -31,12 +32,22 @@ pub struct Request {
pub body: String,
}
+#[derive(Debug)]
+pub struct Response(pub Message);
+
+impl IntoResponse for Response {
+ fn into_response(self) -> response::Response {
+ let Self(message) = self;
+ (StatusCode::ACCEPTED, Json(message)).into_response()
+ }
+}
+
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct Error(#[from] pub SendError);
impl IntoResponse for Error {
- fn into_response(self) -> Response {
+ fn into_response(self) -> response::Response {
let Self(error) = self;
#[allow(clippy::match_wildcard_for_single_variants)]
match error {
diff --git a/src/channel/routes/channel/test.rs b/src/channel/routes/channel/test.rs
index c6541cd..b895b69 100644
--- a/src/channel/routes/channel/test.rs
+++ b/src/channel/routes/channel/test.rs
@@ -27,7 +27,7 @@ async fn messages_in_order() {
for (sent_at, body) in &requests {
let request = post::Request { body: body.clone() };
- post::handler(
+ let _ = post::handler(
State(app.clone()),
Path(channel.id.clone()),
sent_at.clone(),
diff --git a/src/channel/routes/post.rs b/src/channel/routes/post.rs
index d694f8b..a05c312 100644
--- a/src/channel/routes/post.rs
+++ b/src/channel/routes/post.rs
@@ -17,14 +17,14 @@ pub async fn handler(
_: Login, // requires auth, but doesn't actually care who you are
RequestedAt(created_at): RequestedAt,
Json(request): Json<Request>,
-) -> Result<Json<Channel>, Error> {
+) -> Result<Response, Error> {
let channel = app
.channels()
.create(&request.name, &created_at)
.await
.map_err(Error)?;
- Ok(Json(channel))
+ Ok(Response(channel))
}
#[derive(serde::Deserialize)]
@@ -33,6 +33,16 @@ pub struct Request {
}
#[derive(Debug)]
+pub struct Response(pub Channel);
+
+impl IntoResponse for Response {
+ fn into_response(self) -> response::Response {
+ let Self(channel) = self;
+ (StatusCode::ACCEPTED, Json(channel)).into_response()
+ }
+}
+
+#[derive(Debug)]
pub struct Error(pub app::CreateError);
impl IntoResponse for Error {
diff --git a/src/channel/routes/test.rs b/src/channel/routes/test.rs
index ffd8484..7879ba0 100644
--- a/src/channel/routes/test.rs
+++ b/src/channel/routes/test.rs
@@ -20,9 +20,10 @@ async fn new_channel() {
let name = fixtures::channel::propose();
let request = post::Request { name: name.clone() };
- let Json(response) = post::handler(State(app.clone()), creator, fixtures::now(), Json(request))
- .await
- .expect("creating a channel in an empty app succeeds");
+ let post::Response(response) =
+ post::handler(State(app.clone()), creator, fixtures::now(), Json(request))
+ .await
+ .expect("creating a channel in an empty app succeeds");
// Verify the structure of the response
@@ -96,7 +97,7 @@ async fn name_reusable_after_delete() {
// Call the endpoint (first time)
let request = post::Request { name: name.clone() };
- let Json(response) = post::handler(
+ let post::Response(response) = post::handler(
State(app.clone()),
creator.clone(),
fixtures::now(),
@@ -115,9 +116,10 @@ async fn name_reusable_after_delete() {
// Call the endpoint (second time)
let request = post::Request { name: name.clone() };
- let Json(response) = post::handler(State(app.clone()), creator, fixtures::now(), Json(request))
- .await
- .expect("new channel in an empty app");
+ let post::Response(response) =
+ post::handler(State(app.clone()), creator, fixtures::now(), Json(request))
+ .await
+ .expect("new channel in an empty app");
// Verify the structure of the response
diff --git a/src/invite/app.rs b/src/invite/app.rs
index 4162470..ee7f74f 100644
--- a/src/invite/app.rs
+++ b/src/invite/app.rs
@@ -45,7 +45,7 @@ impl<'a> Invites<'a> {
name: &str,
password: &Password,
accepted_at: &DateTime,
- ) -> Result<Secret, AcceptError> {
+ ) -> Result<(Login, Secret), AcceptError> {
let mut tx = self.db.begin().await?;
let invite = tx
.invites()
@@ -72,7 +72,7 @@ impl<'a> Invites<'a> {
let secret = tx.tokens().issue(&login, accepted_at).await?;
tx.commit().await?;
- Ok(secret)
+ Ok((login.as_created(), secret))
}
pub async fn expire(&self, relative_to: &DateTime) -> Result<(), sqlx::Error> {
diff --git a/src/invite/routes/invite/post.rs b/src/invite/routes/invite/post.rs
index 12c2e21..c072929 100644
--- a/src/invite/routes/invite/post.rs
+++ b/src/invite/routes/invite/post.rs
@@ -9,7 +9,7 @@ use crate::{
clock::RequestedAt,
error::{Internal, NotFound},
invite::app,
- login::Password,
+ login::{Login, Password},
token::extract::IdentityToken,
};
@@ -19,14 +19,14 @@ pub async fn handler(
identity: IdentityToken,
Path(invite): Path<super::PathInfo>,
Json(request): Json<Request>,
-) -> Result<(IdentityToken, StatusCode), Error> {
- let secret = app
+) -> Result<(IdentityToken, Json<Login>), Error> {
+ let (login, secret) = app
.invites()
.accept(&invite, &request.name, &request.password, &accepted_at)
.await
.map_err(Error)?;
let identity = identity.set(secret);
- Ok((identity, StatusCode::NO_CONTENT))
+ Ok((identity, Json(login)))
}
#[derive(serde::Deserialize)]
diff --git a/src/login/history.rs b/src/login/history.rs
index f8d81bb..daad579 100644
--- a/src/login/history.rs
+++ b/src/login/history.rs
@@ -20,7 +20,6 @@ impl History {
// if this returns a redacted or modified version of the login. If we implement
// renames by redacting the original name, then this should return the edited
// login, not the original, even if that's not how it was "as created.")
- #[cfg(test)]
pub fn as_created(&self) -> Login {
self.login.clone()
}
@@ -30,6 +29,11 @@ impl History {
.filter(Sequence::up_to(resume_point.into()))
.collect()
}
+
+ // Snapshot of this login, as of all events recorded in this history.
+ pub fn as_snapshot(&self) -> Option<Login> {
+ self.events().collect()
+ }
}
// Events interface
diff --git a/src/login/routes/login/post.rs b/src/login/routes/login/post.rs
index e33acad..67eaa6d 100644
--- a/src/login/routes/login/post.rs
+++ b/src/login/routes/login/post.rs
@@ -8,7 +8,7 @@ use crate::{
app::App,
clock::RequestedAt,
error::Internal,
- login::Password,
+ login::{Login, Password},
token::{app, extract::IdentityToken},
};
@@ -17,14 +17,14 @@ pub async fn handler(
RequestedAt(now): RequestedAt,
identity: IdentityToken,
Json(request): Json<Request>,
-) -> Result<(IdentityToken, StatusCode), Error> {
- let token = app
+) -> Result<(IdentityToken, Json<Login>), Error> {
+ let (login, secret) = app
.tokens()
.login(&request.name, &request.password, &now)
.await
.map_err(Error)?;
- let identity = identity.set(token);
- Ok((identity, StatusCode::NO_CONTENT))
+ let identity = identity.set(secret);
+ Ok((identity, Json(login)))
}
#[derive(serde::Deserialize)]
diff --git a/src/login/routes/login/test.rs b/src/login/routes/login/test.rs
index d431612..c94f14c 100644
--- a/src/login/routes/login/test.rs
+++ b/src/login/routes/login/test.rs
@@ -1,7 +1,4 @@
-use axum::{
- extract::{Json, State},
- http::StatusCode,
-};
+use axum::extract::{Json, State};
use super::post;
use crate::{test::fixtures, token::app};
@@ -11,24 +8,24 @@ async fn correct_credentials() {
// Set up the environment
let app = fixtures::scratch_app().await;
- let (name, password) = fixtures::login::create_with_password(&app, &fixtures::now()).await;
+ let (login, password) = fixtures::login::create_with_password(&app, &fixtures::now()).await;
// Call the endpoint
let identity = fixtures::identity::not_logged_in();
let logged_in_at = fixtures::now();
let request = post::Request {
- name: name.clone(),
+ name: login.name.clone(),
password,
};
- let (identity, status) =
+ let (identity, Json(response)) =
post::handler(State(app.clone()), logged_in_at, identity, Json(request))
.await
.expect("logged in with valid credentials");
// Verify the return value's basic structure
- assert_eq!(StatusCode::NO_CONTENT, status);
+ assert_eq!(login, response);
let secret = identity.secret().expect("logged in with valid credentials");
// Verify the semantics
@@ -40,7 +37,7 @@ async fn correct_credentials() {
.await
.expect("identity secret is valid");
- assert_eq!(name, validated_login.name);
+ assert_eq!(login, validated_login);
}
#[tokio::test]
@@ -98,13 +95,16 @@ async fn token_expires() {
// Set up the environment
let app = fixtures::scratch_app().await;
- let (name, password) = fixtures::login::create_with_password(&app, &fixtures::now()).await;
+ let (login, password) = fixtures::login::create_with_password(&app, &fixtures::now()).await;
// Call the endpoint
let logged_in_at = fixtures::ancient();
let identity = fixtures::identity::not_logged_in();
- let request = post::Request { name, password };
+ let request = post::Request {
+ name: login.name.clone(),
+ password,
+ };
let (identity, _) = post::handler(State(app.clone()), logged_in_at, identity, Json(request))
.await
.expect("logged in with valid credentials");
diff --git a/src/setup/app.rs b/src/setup/app.rs
index 24e0010..d015813 100644
--- a/src/setup/app.rs
+++ b/src/setup/app.rs
@@ -4,7 +4,7 @@ use super::repo::Provider as _;
use crate::{
clock::DateTime,
event::{repo::Provider as _, Broadcaster, Event},
- login::{repo::Provider as _, Password},
+ login::{repo::Provider as _, Login, Password},
token::{repo::Provider as _, Secret},
};
@@ -23,7 +23,7 @@ impl<'a> Setup<'a> {
name: &str,
password: &Password,
created_at: &DateTime,
- ) -> Result<Secret, Error> {
+ ) -> Result<(Login, Secret), Error> {
let password_hash = password.hash()?;
let mut tx = self.db.begin().await?;
@@ -39,7 +39,7 @@ impl<'a> Setup<'a> {
self.events
.broadcast(login.events().map(Event::from).collect::<Vec<_>>());
- Ok(secret)
+ Ok((login.as_created(), secret))
}
pub async fn completed(&self) -> Result<bool, sqlx::Error> {
diff --git a/src/setup/routes/post.rs b/src/setup/routes/post.rs
index 9e6776f..34f4ed2 100644
--- a/src/setup/routes/post.rs
+++ b/src/setup/routes/post.rs
@@ -5,7 +5,11 @@ use axum::{
};
use crate::{
- app::App, clock::RequestedAt, error::Internal, login::Password, setup::app,
+ app::App,
+ clock::RequestedAt,
+ error::Internal,
+ login::{Login, Password},
+ setup::app,
token::extract::IdentityToken,
};
@@ -14,14 +18,14 @@ pub async fn handler(
RequestedAt(setup_at): RequestedAt,
identity: IdentityToken,
Json(request): Json<Request>,
-) -> Result<(IdentityToken, StatusCode), Error> {
- let secret = app
+) -> Result<(IdentityToken, Json<Login>), Error> {
+ let (login, secret) = app
.setup()
.initial(&request.name, &request.password, &setup_at)
.await
.map_err(Error)?;
let identity = identity.set(secret);
- Ok((identity, StatusCode::NO_CONTENT))
+ Ok((identity, Json(login)))
}
#[derive(serde::Deserialize)]
diff --git a/src/test/fixtures/identity.rs b/src/test/fixtures/identity.rs
index 56b4ffa..c434473 100644
--- a/src/test/fixtures/identity.rs
+++ b/src/test/fixtures/identity.rs
@@ -3,7 +3,7 @@ use uuid::Uuid;
use crate::{
app::App,
clock::RequestedAt,
- login::Password,
+ login::{Login, Password},
token::{
extract::{Identity, IdentityToken},
Secret,
@@ -14,11 +14,11 @@ pub fn not_logged_in() -> IdentityToken {
IdentityToken::new()
}
-pub async fn logged_in(app: &App, login: &(String, Password), now: &RequestedAt) -> IdentityToken {
- let (name, password) = login;
- let token = app
+pub async fn logged_in(app: &App, login: &(Login, Password), now: &RequestedAt) -> IdentityToken {
+ let (login, password) = login;
+ let (_, token) = app
.tokens()
- .login(name, password, now)
+ .login(&login.name, password, now)
.await
.expect("should succeed given known-valid credentials");
@@ -36,7 +36,7 @@ pub async fn from_token(app: &App, token: &IdentityToken, issued_at: &RequestedA
Identity { token, login }
}
-pub async fn identity(app: &App, login: &(String, Password), issued_at: &RequestedAt) -> Identity {
+pub async fn identity(app: &App, login: &(Login, Password), issued_at: &RequestedAt) -> Identity {
let secret = logged_in(app, login, issued_at).await;
from_token(app, &secret, issued_at).await
}
diff --git a/src/test/fixtures/login.rs b/src/test/fixtures/login.rs
index e5ac716..b6766fe 100644
--- a/src/test/fixtures/login.rs
+++ b/src/test/fixtures/login.rs
@@ -7,14 +7,15 @@ use crate::{
login::{self, Login, Password},
};
-pub async fn create_with_password(app: &App, created_at: &RequestedAt) -> (String, Password) {
+pub async fn create_with_password(app: &App, created_at: &RequestedAt) -> (Login, Password) {
let (name, password) = propose();
- app.logins()
+ let login = app
+ .logins()
.create(&name, &password, created_at)
.await
.expect("should always succeed if the login is actually new");
- (name, password)
+ (login, password)
}
pub async fn create(app: &App, created_at: &RequestedAt) -> Login {
diff --git a/src/token/app.rs b/src/token/app.rs
index cb5d75f..0dc1a46 100644
--- a/src/token/app.rs
+++ b/src/token/app.rs
@@ -30,7 +30,7 @@ impl<'a> Tokens<'a> {
name: &str,
password: &Password,
login_at: &DateTime,
- ) -> Result<Secret, LoginError> {
+ ) -> Result<(Login, Secret), LoginError> {
let mut tx = self.db.begin().await?;
let (login, stored_hash) = tx
.auth()
@@ -45,6 +45,8 @@ impl<'a> Tokens<'a> {
// if the account is deleted during that time.
tx.commit().await?;
+ let snapshot = login.as_snapshot().ok_or(LoginError::Rejected)?;
+
let token = if stored_hash.verify(password)? {
let mut tx = self.db.begin().await?;
let token = tx.tokens().issue(&login, login_at).await?;
@@ -54,7 +56,7 @@ impl<'a> Tokens<'a> {
Err(LoginError::Rejected)?
};
- Ok(token)
+ Ok((snapshot, token))
}
pub async fn validate(