diff options
Diffstat (limited to 'src/login/routes')
| -rw-r--r-- | src/login/routes/login/mod.rs | 4 | ||||
| -rw-r--r-- | src/login/routes/login/post.rs | 52 | ||||
| -rw-r--r-- | src/login/routes/login/test.rs | 128 | ||||
| -rw-r--r-- | src/login/routes/logout/mod.rs | 4 | ||||
| -rw-r--r-- | src/login/routes/logout/post.rs | 47 | ||||
| -rw-r--r-- | src/login/routes/logout/test.rs | 79 | ||||
| -rw-r--r-- | src/login/routes/mod.rs | 14 | ||||
| -rw-r--r-- | src/login/routes/password/mod.rs | 4 | ||||
| -rw-r--r-- | src/login/routes/password/post.rs | 54 | ||||
| -rw-r--r-- | src/login/routes/password/test.rs | 68 |
10 files changed, 0 insertions, 454 deletions
diff --git a/src/login/routes/login/mod.rs b/src/login/routes/login/mod.rs deleted file mode 100644 index 36b384e..0000000 --- a/src/login/routes/login/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod post; - -#[cfg(test)] -mod test; diff --git a/src/login/routes/login/post.rs b/src/login/routes/login/post.rs deleted file mode 100644 index 96da5c5..0000000 --- a/src/login/routes/login/post.rs +++ /dev/null @@ -1,52 +0,0 @@ -use axum::{ - extract::{Json, State}, - http::StatusCode, - response::{IntoResponse, Response}, -}; - -use crate::{ - app::App, - clock::RequestedAt, - error::Internal, - login::{Login, Password}, - name::Name, - token::{app, extract::IdentityCookie}, -}; - -pub async fn handler( - State(app): State<App>, - RequestedAt(now): RequestedAt, - identity: IdentityCookie, - Json(request): Json<Request>, -) -> Result<(IdentityCookie, Json<Login>), Error> { - let (login, secret) = app - .tokens() - .login(&request.name, &request.password, &now) - .await - .map_err(Error)?; - let identity = identity.set(secret); - Ok((identity, Json(login))) -} - -#[derive(serde::Deserialize)] -pub struct Request { - pub name: Name, - pub password: Password, -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct Error(#[from] pub app::LoginError); - -impl IntoResponse for Error { - fn into_response(self) -> Response { - let Self(error) = self; - match error { - app::LoginError::Rejected => { - // not error::Unauthorized due to differing messaging - (StatusCode::UNAUTHORIZED, "invalid name or password").into_response() - } - other => Internal::from(other).into_response(), - } - } -} diff --git a/src/login/routes/login/test.rs b/src/login/routes/login/test.rs deleted file mode 100644 index 7399796..0000000 --- a/src/login/routes/login/test.rs +++ /dev/null @@ -1,128 +0,0 @@ -use axum::extract::{Json, State}; - -use super::post; -use crate::{test::fixtures, token::app}; - -#[tokio::test] -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; - - // Call the endpoint - - let identity = fixtures::cookie::not_logged_in(); - let logged_in_at = fixtures::now(); - let request = post::Request { - name: name.clone(), - password, - }; - 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!(name, response.name); - let secret = identity - .secret() - .expect("logged in with valid credentials issues an identity cookie"); - - // Verify the semantics - - let validated_at = fixtures::now(); - let (_, validated_login) = app - .tokens() - .validate(&secret, &validated_at) - .await - .expect("identity secret is valid"); - - assert_eq!(response, validated_login); -} - -#[tokio::test] -async fn invalid_name() { - // Set up the environment - - let app = fixtures::scratch_app().await; - - // Call the endpoint - - let identity = fixtures::cookie::not_logged_in(); - let logged_in_at = fixtures::now(); - let (name, password) = fixtures::login::propose(); - let request = post::Request { - name: name.clone(), - password, - }; - let post::Error(error) = - post::handler(State(app.clone()), logged_in_at, identity, Json(request)) - .await - .expect_err("logged in with an incorrect password fails"); - - // Verify the return value's basic structure - - assert!(matches!(error, app::LoginError::Rejected)); -} - -#[tokio::test] -async fn incorrect_password() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let login = fixtures::login::create(&app, &fixtures::now()).await; - - // Call the endpoint - - let logged_in_at = fixtures::now(); - let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { - name: login.name, - password: fixtures::login::propose_password(), - }; - let post::Error(error) = - post::handler(State(app.clone()), logged_in_at, identity, Json(request)) - .await - .expect_err("logged in with an incorrect password"); - - // Verify the return value's basic structure - - assert!(matches!(error, app::LoginError::Rejected)); -} - -#[tokio::test] -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; - - // Call the endpoint - - let logged_in_at = fixtures::ancient(); - let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { name, password }; - let (identity, _) = post::handler(State(app.clone()), logged_in_at, identity, Json(request)) - .await - .expect("logged in with valid credentials"); - let secret = identity.secret().expect("logged in with valid credentials"); - - // Verify the semantics - - let expired_at = fixtures::now(); - app.tokens() - .expire(&expired_at) - .await - .expect("expiring tokens never fails"); - - let verified_at = fixtures::now(); - let error = app - .tokens() - .validate(&secret, &verified_at) - .await - .expect_err("validating an expired token"); - - assert!(matches!(error, app::ValidateError::InvalidToken)); -} diff --git a/src/login/routes/logout/mod.rs b/src/login/routes/logout/mod.rs deleted file mode 100644 index 36b384e..0000000 --- a/src/login/routes/logout/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod post; - -#[cfg(test)] -mod test; diff --git a/src/login/routes/logout/post.rs b/src/login/routes/logout/post.rs deleted file mode 100644 index bb09b9f..0000000 --- a/src/login/routes/logout/post.rs +++ /dev/null @@ -1,47 +0,0 @@ -use axum::{ - extract::{Json, State}, - http::StatusCode, - response::{IntoResponse, Response}, -}; - -use crate::{ - app::App, - clock::RequestedAt, - error::{Internal, Unauthorized}, - token::{app, extract::IdentityCookie}, -}; - -pub async fn handler( - State(app): State<App>, - RequestedAt(now): RequestedAt, - identity: IdentityCookie, - Json(_): Json<Request>, -) -> Result<(IdentityCookie, StatusCode), Error> { - if let Some(secret) = identity.secret() { - let (token, _) = app.tokens().validate(&secret, &now).await?; - app.tokens().logout(&token).await?; - } - - let identity = identity.clear(); - Ok((identity, StatusCode::NO_CONTENT)) -} - -// This forces the only valid request to be `{}`, and not the infinite -// variation allowed when there's no body extractor. -#[derive(Default, serde::Deserialize)] -pub struct Request {} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct Error(#[from] pub app::ValidateError); - -impl IntoResponse for Error { - fn into_response(self) -> Response { - let Self(error) = self; - #[allow(clippy::match_wildcard_for_single_variants)] - match error { - app::ValidateError::InvalidToken => Unauthorized.into_response(), - other => Internal::from(other).into_response(), - } - } -} diff --git a/src/login/routes/logout/test.rs b/src/login/routes/logout/test.rs deleted file mode 100644 index 775fa9f..0000000 --- a/src/login/routes/logout/test.rs +++ /dev/null @@ -1,79 +0,0 @@ -use axum::{ - extract::{Json, State}, - http::StatusCode, -}; - -use super::post; -use crate::{test::fixtures, token::app}; - -#[tokio::test] -async fn successful() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let now = fixtures::now(); - let creds = fixtures::login::create_with_password(&app, &fixtures::now()).await; - let identity = fixtures::cookie::logged_in(&app, &creds, &now).await; - let secret = fixtures::cookie::secret(&identity); - - // Call the endpoint - - let (response_identity, response_status) = post::handler( - State(app.clone()), - fixtures::now(), - identity.clone(), - Json::default(), - ) - .await - .expect("logged out with a valid token"); - - // Verify the return value's basic structure - - assert!(response_identity.secret().is_none()); - assert_eq!(StatusCode::NO_CONTENT, response_status); - - // Verify the semantics - let error = app - .tokens() - .validate(&secret, &now) - .await - .expect_err("secret is invalid"); - assert!(matches!(error, app::ValidateError::InvalidToken)); -} - -#[tokio::test] -async fn no_identity() { - // Set up the environment - - let app = fixtures::scratch_app().await; - - // Call the endpoint - - let identity = fixtures::cookie::not_logged_in(); - let (identity, status) = post::handler(State(app), fixtures::now(), identity, Json::default()) - .await - .expect("logged out with no token succeeds"); - - // Verify the return value's basic structure - - assert!(identity.secret().is_none()); - assert_eq!(StatusCode::NO_CONTENT, status); -} - -#[tokio::test] -async fn invalid_token() { - // Set up the environment - - let app = fixtures::scratch_app().await; - - // Call the endpoint - - let identity = fixtures::cookie::fictitious(); - let post::Error(error) = post::handler(State(app), fixtures::now(), identity, Json::default()) - .await - .expect_err("logged out with an invalid token fails"); - - // Verify the return value's basic structure - - assert!(matches!(error, app::ValidateError::InvalidToken)); -} diff --git a/src/login/routes/mod.rs b/src/login/routes/mod.rs deleted file mode 100644 index ade96cb..0000000 --- a/src/login/routes/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -use axum::{Router, routing::post}; - -use crate::app::App; - -mod login; -mod logout; -mod password; - -pub fn router() -> Router<App> { - Router::new() - .route("/api/password", post(password::post::handler)) - .route("/api/auth/login", post(login::post::handler)) - .route("/api/auth/logout", post(logout::post::handler)) -} diff --git a/src/login/routes/password/mod.rs b/src/login/routes/password/mod.rs deleted file mode 100644 index 36b384e..0000000 --- a/src/login/routes/password/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod post; - -#[cfg(test)] -mod test; diff --git a/src/login/routes/password/post.rs b/src/login/routes/password/post.rs deleted file mode 100644 index 4723754..0000000 --- a/src/login/routes/password/post.rs +++ /dev/null @@ -1,54 +0,0 @@ -use axum::{ - extract::{Json, State}, - http::StatusCode, - response::{IntoResponse, Response}, -}; - -use crate::{ - app::App, - clock::RequestedAt, - error::Internal, - login::{Login, Password}, - token::{ - app, - extract::{Identity, IdentityCookie}, - }, -}; - -pub async fn handler( - State(app): State<App>, - RequestedAt(now): RequestedAt, - identity: Identity, - cookie: IdentityCookie, - Json(request): Json<Request>, -) -> Result<(IdentityCookie, Json<Login>), Error> { - let (login, secret) = app - .tokens() - .change_password(&identity.login, &request.password, &request.to, &now) - .await - .map_err(Error)?; - let cookie = cookie.set(secret); - Ok((cookie, Json(login))) -} - -#[derive(serde::Deserialize)] -pub struct Request { - pub password: Password, - pub to: Password, -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct Error(#[from] pub app::LoginError); - -impl IntoResponse for Error { - fn into_response(self) -> Response { - let Self(error) = self; - match error { - app::LoginError::Rejected => { - (StatusCode::BAD_REQUEST, "invalid name or password").into_response() - } - other => Internal::from(other).into_response(), - } - } -} diff --git a/src/login/routes/password/test.rs b/src/login/routes/password/test.rs deleted file mode 100644 index c1974bf..0000000 --- a/src/login/routes/password/test.rs +++ /dev/null @@ -1,68 +0,0 @@ -use axum::extract::{Json, State}; - -use super::post; -use crate::{ - test::fixtures, - token::app::{LoginError, ValidateError}, -}; - -#[tokio::test] -async fn password_change() { - // Set up the environment - let app = fixtures::scratch_app().await; - let creds = fixtures::login::create_with_password(&app, &fixtures::now()).await; - let cookie = fixtures::cookie::logged_in(&app, &creds, &fixtures::now()).await; - let identity = fixtures::identity::from_cookie(&app, &cookie, &fixtures::now()).await; - - // Call the endpoint - let (name, password) = creds; - let to = fixtures::login::propose_password(); - let request = post::Request { - password: password.clone(), - to: to.clone(), - }; - let (new_cookie, Json(response)) = post::handler( - State(app.clone()), - fixtures::now(), - identity.clone(), - cookie.clone(), - Json(request), - ) - .await - .expect("changing passwords succeeds"); - - // Verify that we have a new session - assert_ne!(cookie.secret(), new_cookie.secret()); - - // Verify that we're still ourselves - assert_eq!(identity.login, response); - - // Verify that our original token is no longer valid - let validate_err = app - .tokens() - .validate( - &cookie - .secret() - .expect("original identity cookie has a secret"), - &fixtures::now(), - ) - .await - .expect_err("validating the original identity secret should fail"); - assert!(matches!(validate_err, ValidateError::InvalidToken)); - - // Verify that our original password is no longer valid - let login_err = app - .tokens() - .login(&name, &password, &fixtures::now()) - .await - .expect_err("logging in with the original password should fail"); - assert!(matches!(login_err, LoginError::Rejected)); - - // Verify that our new password is valid - let (login, _) = app - .tokens() - .login(&name, &to, &fixtures::now()) - .await - .expect("logging in with the new password should succeed"); - assert_eq!(identity.login, login); -} |
