summaryrefslogtreecommitdiffstats
path: root/src/api/users.rs
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-04-13 15:23:05 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-04-13 15:23:05 -0500
commit26fdaefdff192e0a13b976edd46ab75165c0a135 (patch)
tree4be3c64fc8991111e5c34c3f5f3e1a4b89325c58 /src/api/users.rs
parentd96989087a94b7c9a2438b1117a257dac89e9abd (diff)
fix(api): add login route under /api/users
Diffstat (limited to 'src/api/users.rs')
-rw-r--r--src/api/users.rs104
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(())
+ }
}