diff options
author | Toby Vincent <tobyv@tobyvin.dev> | 2024-04-13 15:23:05 -0500 |
---|---|---|
committer | Toby Vincent <tobyv@tobyvin.dev> | 2024-04-13 15:23:05 -0500 |
commit | 26fdaefdff192e0a13b976edd46ab75165c0a135 (patch) | |
tree | 4be3c64fc8991111e5c34c3f5f3e1a4b89325c58 /src/api/users.rs | |
parent | d96989087a94b7c9a2438b1117a257dac89e9abd (diff) |
fix(api): add login route under /api/users
Diffstat (limited to 'src/api/users.rs')
-rw-r--r-- | src/api/users.rs | 104 |
1 files changed, 98 insertions, 6 deletions
diff --git a/src/api/users.rs b/src/api/users.rs index 2440e6e..0cac406 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -3,20 +3,30 @@ use std::str::FromStr; use axum::{ extract::{Path, State}, response::IntoResponse, - Json, + routing::get, + Json, Router, +}; +use axum_extra::{ + headers::{authorization::Basic, Authorization}, + routing::Resource, + TypedHeader, }; -use axum_extra::{headers::Authorization, routing::Resource, TypedHeader}; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use time::OffsetDateTime; use uuid::Uuid; -use crate::{auth::AccessClaims, state::AppState}; +use crate::{ + auth::{AccessClaims, RefreshClaims}, + state::AppState, +}; use super::error::Error; -pub fn router() -> Resource<AppState> { - Resource::named("users").create(create).show(show) +pub fn router() -> Router<AppState> { + axum::Router::new() + .route("/users/login", get(login)) + .merge(Resource::named("users").create(create).show(show)) } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)] @@ -36,6 +46,23 @@ pub struct RegisterSchema { pub password: String, } +pub async fn login( + State(state): State<AppState>, + TypedHeader(Authorization(basic)): TypedHeader<Authorization<Basic>>, +) -> Result<(AccessClaims, RefreshClaims), Error> { + let user_id = sqlx::query_scalar!("SELECT id FROM user_ WHERE email = $1", basic.username()) + .fetch_optional(&state.pool) + .await? + .ok_or(Error::UserNotFound)?; + + crate::auth::issue( + State(state.clone()), + TypedHeader(Authorization::basic(&user_id.to_string(), basic.password())), + ) + .await + .map_err(Into::into) +} + pub async fn create( State(state): State<AppState>, Json(RegisterSchema { @@ -100,12 +127,13 @@ mod tests { use axum::{ body::Body, http::{ - header::{CONTENT_TYPE, COOKIE}, + header::{AUTHORIZATION, CONTENT_TYPE, COOKIE}, HeaderValue, Request, StatusCode, }, Router, }; + use axum_extra::headers::authorization::Credentials; use http_body_util::BodyExt; use sqlx::PgPool; use tower::ServiceExt; @@ -276,4 +304,68 @@ mod tests { Ok(()) } + + #[sqlx::test(fixtures(path = "../../fixtures", scripts("users")))] + async fn test_login_ok(pool: PgPool) -> TestResult { + setup_test_env(); + + let router = Router::new().merge(router()).with_state(AppState { pool }); + + let auth = Authorization::basic(USER_EMAIL, USER_PASSWORD); + + let request = Request::builder() + .uri("/users/login") + .method("GET") + .header(AUTHORIZATION, auth.0.encode()) + .body(Body::empty())?; + + let response = router.oneshot(request).await?; + println!("{response:?}"); + + assert_eq!(StatusCode::OK, response.status()); + + Ok(()) + } + + #[sqlx::test(fixtures(path = "../../fixtures", scripts("users")))] + async fn test_issue_unauthorized(pool: PgPool) -> TestResult { + setup_test_env(); + + let router = Router::new().merge(router()).with_state(AppState { pool }); + + let auth = Authorization::basic(USER_EMAIL, "hunter2"); + + let request = Request::builder() + .uri("/users/login") + .method("GET") + .header(AUTHORIZATION, auth.0.encode()) + .body(Body::empty())?; + + let response = router.oneshot(request).await?; + + assert_eq!(StatusCode::UNAUTHORIZED, response.status()); + + Ok(()) + } + + #[sqlx::test] + async fn test_login_not_found(pool: PgPool) -> TestResult { + setup_test_env(); + + let router = Router::new().merge(router()).with_state(AppState { pool }); + + let auth = Authorization::basic(USER_EMAIL, USER_PASSWORD); + + let request = Request::builder() + .uri("/users/login") + .method("GET") + .header(AUTHORIZATION, auth.0.encode()) + .body(Body::empty())?; + + let response = router.oneshot(request).await?; + + assert_eq!(StatusCode::NOT_FOUND, response.status()); + + Ok(()) + } } |