From 1a0ee4af6538b5486d35730d480d00ca4d9edafb Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Sun, 24 Aug 2025 03:32:21 -0400 Subject: Stop returning body data from `POST /api/setup`. This API response was always ad-hoc, and the client doesn't use it. To free up some maneuvering room for server refactorings, stop sending it. We can add a response in the future if there's a need. --- src/setup/app.rs | 11 ++++------- src/setup/handlers/setup/mod.rs | 10 +++++----- src/setup/handlers/setup/test.rs | 12 ++++-------- 3 files changed, 13 insertions(+), 20 deletions(-) (limited to 'src/setup') diff --git a/src/setup/app.rs b/src/setup/app.rs index 1210175..123cff9 100644 --- a/src/setup/app.rs +++ b/src/setup/app.rs @@ -7,10 +7,7 @@ use crate::{ name::Name, password::Password, token::{Secret, repo::Provider as _}, - user::{ - User, - create::{self, Create}, - }, + user::create::{self, Create}, }; pub struct Setup<'a> { @@ -28,7 +25,7 @@ impl<'a> Setup<'a> { name: &Name, password: &Password, created_at: &DateTime, - ) -> Result<(User, Secret), Error> { + ) -> Result { let create = Create::begin(name, password, created_at); let validated = create.validate()?; @@ -42,9 +39,9 @@ impl<'a> Setup<'a> { let secret = tx.tokens().issue(stored.user(), created_at).await?; tx.commit().await?; - let user = stored.publish(self.events); + let _ = stored.publish(self.events); - Ok((user.as_created(), secret)) + Ok(secret) } pub async fn completed(&self) -> Result { diff --git a/src/setup/handlers/setup/mod.rs b/src/setup/handlers/setup/mod.rs index 9e31282..fe24798 100644 --- a/src/setup/handlers/setup/mod.rs +++ b/src/setup/handlers/setup/mod.rs @@ -5,8 +5,8 @@ use axum::{ }; use crate::{ - app::App, clock::RequestedAt, error::Internal, name::Name, password::Password, setup::app, - token::extract::IdentityCookie, user::User, + app::App, clock::RequestedAt, empty::Empty, error::Internal, name::Name, password::Password, + setup::app, token::extract::IdentityCookie, }; #[cfg(test)] @@ -17,14 +17,14 @@ pub async fn handler( RequestedAt(setup_at): RequestedAt, identity: IdentityCookie, Json(request): Json, -) -> Result<(IdentityCookie, Json), Error> { - let (user, secret) = app +) -> Result<(IdentityCookie, Empty), Error> { + let secret = app .setup() .initial(&request.name, &request.password, &setup_at) .await .map_err(Error)?; let identity = identity.set(secret); - Ok((identity, Json(user))) + Ok((identity, Empty)) } #[derive(serde::Deserialize)] diff --git a/src/setup/handlers/setup/test.rs b/src/setup/handlers/setup/test.rs index 8243ac3..69e44c2 100644 --- a/src/setup/handlers/setup/test.rs +++ b/src/setup/handlers/setup/test.rs @@ -1,6 +1,6 @@ use axum::extract::{Json, State}; -use crate::{setup::app, test::fixtures}; +use crate::{empty::Empty, setup::app, test::fixtures}; #[tokio::test] async fn fresh_instance() { @@ -15,15 +15,11 @@ async fn fresh_instance() { name: name.clone(), password: password.clone(), }; - let (identity, Json(response)) = + let (identity, Empty) = super::handler(State(app.clone()), fixtures::now(), identity, Json(request)) .await .expect("setup in a fresh app succeeds"); - // Verify the response - - assert_eq!(name, response.name); - // Verify that the issued token is valid let secret = identity @@ -34,7 +30,7 @@ async fn fresh_instance() { .validate(&secret, &fixtures::now()) .await .expect("newly-issued identity cookie is valid"); - assert_eq!(response, login); + assert_eq!(name, login.name); // Verify that the given credentials can log in @@ -43,7 +39,7 @@ async fn fresh_instance() { .login(&name, &password, &fixtures::now()) .await .expect("credentials given on signup are valid"); - assert_eq!(response, login); + assert_eq!(name, login.name); } #[tokio::test] -- cgit v1.2.3 From 4eb63b8adda4559df3dadcf721e2bb0d1f65a01f Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Sun, 24 Aug 2025 03:48:17 -0400 Subject: Stop returning body data from `POST /api/auth/login`. As with `/api/setup`, the response was an ad-hoc choice, which we are not using and which constrains future development just by existing. --- docs/api/authentication.md | 26 ++++---------------------- src/invite/handlers/accept/test.rs | 7 ++++++- src/setup/handlers/setup/test.rs | 7 ++++++- src/test/fixtures/cookie.rs | 4 ++-- src/token/app.rs | 16 +++++++--------- src/user/handlers/login/mod.rs | 8 ++++---- src/user/handlers/login/test.rs | 7 +++---- src/user/handlers/password/test.rs | 7 ++++++- 8 files changed, 38 insertions(+), 44 deletions(-) (limited to 'src/setup') diff --git a/docs/api/authentication.md b/docs/api/authentication.md index fbd5959..7694609 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -71,32 +71,15 @@ The request must have the following fields: -This endpoint will respond with a status of -`200 Okay` when successful. The body of the response will be a JSON object describing the authenticated user: - -```json -{ - "id": "Uabcd1234", - "name": "Andrea" -} -``` - -The response will include the following fields: - -| Field | Type | Description | -| :----- | :----- | :----------------------------- | -| `id` | string | The authenticated user's ID. | -| `name` | string | The authenticated user's name. | +This endpoint will respond with a status of `204 No Content` when successful. -The response will include a `Set-Cookie` header for the -`identity` cookie, providing the client with a newly-minted identity token associated with the user identified in the request. This token's value must be kept confidential. +The response will include a `Set-Cookie` header for the `identity` cookie, providing the client with a newly-minted identity token associated with the user identified in the request. This token's value must be kept confidential. The cookie will expire if it is not used regularly. ### Authentication failure -This endpoint will respond with a status of -`401 Unauthorized` if the login name and password do not correspond to an existing user. +This endpoint will respond with a status of `401 Unauthorized` if the login name and password do not correspond to an existing user. ## `POST /api/auth/logout` @@ -114,8 +97,7 @@ The request must be an empty JSON object. This endpoint will respond with a status of `204 No Content` when successful. -The response will include a `Set-Cookie` header that clears the -`identity` cookie. Regardless of whether the client clears the cookie, the service also invalidates the token. +The response will include a `Set-Cookie` header that clears the `identity` cookie. Regardless of whether the client clears the cookie, the service also invalidates the token. ## `POST /api/password` diff --git a/src/invite/handlers/accept/test.rs b/src/invite/handlers/accept/test.rs index cb13900..7139985 100644 --- a/src/invite/handlers/accept/test.rs +++ b/src/invite/handlers/accept/test.rs @@ -47,11 +47,16 @@ async fn valid_invite() { // Verify that the given credentials can log in - let (login, _) = app + let secret = app .tokens() .login(&name, &password, &fixtures::now()) .await .expect("credentials given on signup are valid"); + let (_, login) = app + .tokens() + .validate(&secret, &fixtures::now()) + .await + .expect("validating a newly-issued token secret succeeds"); assert_eq!(response, login); } diff --git a/src/setup/handlers/setup/test.rs b/src/setup/handlers/setup/test.rs index 69e44c2..4a37690 100644 --- a/src/setup/handlers/setup/test.rs +++ b/src/setup/handlers/setup/test.rs @@ -34,11 +34,16 @@ async fn fresh_instance() { // Verify that the given credentials can log in - let (login, _) = app + let secret = app .tokens() .login(&name, &password, &fixtures::now()) .await .expect("credentials given on signup are valid"); + let (_, login) = app + .tokens() + .validate(&secret, &fixtures::now()) + .await + .expect("validating a newly-issued token secret succeeds"); assert_eq!(name, login.name); } diff --git a/src/test/fixtures/cookie.rs b/src/test/fixtures/cookie.rs index 41779db..f5a32a6 100644 --- a/src/test/fixtures/cookie.rs +++ b/src/test/fixtures/cookie.rs @@ -18,13 +18,13 @@ pub async fn logged_in( now: &RequestedAt, ) -> IdentityCookie { let (name, password) = credentials; - let (_, token) = app + let secret = app .tokens() .login(name, password, now) .await .expect("should succeed given known-valid credentials"); - IdentityCookie::new().set(token) + IdentityCookie::new().set(secret) } pub fn secret(identity: &IdentityCookie) -> Secret { diff --git a/src/token/app.rs b/src/token/app.rs index 49f9a45..7d70534 100644 --- a/src/token/app.rs +++ b/src/token/app.rs @@ -32,7 +32,7 @@ impl<'a> Tokens<'a> { name: &Name, password: &Password, login_at: &DateTime, - ) -> Result<(User, Secret), LoginError> { + ) -> Result { let mut tx = self.db.begin().await?; let (user, stored_hash) = tx .auth() @@ -47,18 +47,16 @@ impl<'a> Tokens<'a> { // if the account is deleted during that time. tx.commit().await?; - let snapshot = user.as_snapshot().ok_or(LoginError::Rejected)?; + user.as_snapshot().ok_or(LoginError::Rejected)?; - let token = if stored_hash.verify(password)? { + if stored_hash.verify(password)? { let mut tx = self.db.begin().await?; - let token = tx.tokens().issue(&user, login_at).await?; + let secret = tx.tokens().issue(&user, login_at).await?; tx.commit().await?; - token + Ok(secret) } else { - Err(LoginError::Rejected)? - }; - - Ok((snapshot, token)) + Err(LoginError::Rejected) + } } pub async fn change_password( diff --git a/src/user/handlers/login/mod.rs b/src/user/handlers/login/mod.rs index da88885..d3e0e8c 100644 --- a/src/user/handlers/login/mod.rs +++ b/src/user/handlers/login/mod.rs @@ -7,11 +7,11 @@ use axum::{ use crate::{ app::App, clock::RequestedAt, + empty::Empty, error::Internal, name::Name, password::Password, token::{app, extract::IdentityCookie}, - user::User, }; #[cfg(test)] @@ -22,14 +22,14 @@ pub async fn handler( RequestedAt(now): RequestedAt, identity: IdentityCookie, Json(request): Json, -) -> Result<(IdentityCookie, Json), Error> { - let (user, secret) = app +) -> Result<(IdentityCookie, Empty), Error> { + let secret = app .tokens() .login(&request.name, &request.password, &now) .await .map_err(Error)?; let identity = identity.set(secret); - Ok((identity, Json(user))) + Ok((identity, Empty)) } #[derive(serde::Deserialize)] diff --git a/src/user/handlers/login/test.rs b/src/user/handlers/login/test.rs index b8f24f6..bdd1957 100644 --- a/src/user/handlers/login/test.rs +++ b/src/user/handlers/login/test.rs @@ -1,6 +1,6 @@ use axum::extract::{Json, State}; -use crate::{test::fixtures, token::app}; +use crate::{empty::Empty, test::fixtures, token::app}; #[tokio::test] async fn correct_credentials() { @@ -17,14 +17,13 @@ async fn correct_credentials() { name: name.clone(), password, }; - let (identity, Json(response)) = + let (identity, Empty) = super::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!(name, response.name); let secret = identity .secret() .expect("logged in with valid credentials issues an identity cookie"); @@ -38,7 +37,7 @@ async fn correct_credentials() { .await .expect("identity secret is valid"); - assert_eq!(response, validated_login); + assert_eq!(name, validated_login.name); } #[tokio::test] diff --git a/src/user/handlers/password/test.rs b/src/user/handlers/password/test.rs index 42e41d8..278d27b 100644 --- a/src/user/handlers/password/test.rs +++ b/src/user/handlers/password/test.rs @@ -58,10 +58,15 @@ async fn password_change() { assert!(matches!(login_err, LoginError::Rejected)); // Verify that our new password is valid - let (login, _) = app + let secret = app .tokens() .login(&name, &to, &fixtures::now()) .await .expect("logging in with the new password should succeed"); + let (_, login) = app + .tokens() + .validate(&secret, &fixtures::now()) + .await + .expect("validating a newly-issued token secret succeeds"); assert_eq!(identity.user, login); } -- cgit v1.2.3 From ee9812bd35409abe9532b1d508e04c1dae63c941 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Sun, 24 Aug 2025 04:01:13 -0400 Subject: Remove the now-unused return value from the final stage of user creation. --- src/invite/app.rs | 2 +- src/setup/app.rs | 2 +- src/user/app.rs | 3 ++- src/user/create.rs | 5 +---- 4 files changed, 5 insertions(+), 7 deletions(-) (limited to 'src/setup') diff --git a/src/invite/app.rs b/src/invite/app.rs index 14c1440..1c85562 100644 --- a/src/invite/app.rs +++ b/src/invite/app.rs @@ -74,7 +74,7 @@ impl<'a> Invites<'a> { let secret = tx.tokens().issue(stored.user(), accepted_at).await?; tx.commit().await?; - let _ = stored.publish(self.events); + stored.publish(self.events); Ok(secret) } diff --git a/src/setup/app.rs b/src/setup/app.rs index 123cff9..1856519 100644 --- a/src/setup/app.rs +++ b/src/setup/app.rs @@ -39,7 +39,7 @@ impl<'a> Setup<'a> { let secret = tx.tokens().issue(stored.user(), created_at).await?; tx.commit().await?; - let _ = stored.publish(self.events); + stored.publish(self.events); Ok(secret) } diff --git a/src/user/app.rs b/src/user/app.rs index 5f58981..301c39c 100644 --- a/src/user/app.rs +++ b/src/user/app.rs @@ -29,7 +29,8 @@ impl<'a> Users<'a> { let stored = validated.store(&mut tx).await?; tx.commit().await?; - let user = stored.publish(self.events); + let user = stored.user().to_owned(); + stored.publish(self.events); Ok(user.as_created()) } diff --git a/src/user/create.rs b/src/user/create.rs index 0e7a118..5d7bf65 100644 --- a/src/user/create.rs +++ b/src/user/create.rs @@ -73,13 +73,10 @@ pub struct Stored { } impl Stored { - #[must_use = "dropping a user creation attempt is likely a mistake"] - pub fn publish(self, events: &Broadcaster) -> History { + pub fn publish(self, events: &Broadcaster) { let Self { user } = self; events.broadcast(user.events().map(Event::from).collect::>()); - - user } pub fn user(&self) -> &History { -- cgit v1.2.3