diff --git a/src/api/v1/middleware.rs b/src/api/v1/middleware.rs index 1cf1393..5ca650a 100644 --- a/src/api/v1/middleware.rs +++ b/src/api/v1/middleware.rs @@ -3,9 +3,10 @@ use std::sync::Arc; use axum::{ body::Body, extract::{Request, State}, - http::header, + http::{header, StatusCode}, middleware::Next, response::IntoResponse, + Json, }; use axum_extra::extract::CookieJar; @@ -14,7 +15,7 @@ use crate::{db::user::User, state::AppState}; use super::errors::AuthError; use super::token::TokenClaims; -pub async fn jwt_auth( +pub async fn jwt( cookie_jar: CookieJar, State(state): State>, mut req: Request, @@ -27,13 +28,8 @@ pub async fn jwt_auth( req.headers() .get(header::AUTHORIZATION) .and_then(|auth_header| auth_header.to_str().ok()) - .and_then(|auth_value| { - if auth_value.starts_with("Bearer ") { - Some(auth_value[7..].to_owned()) - } else { - None - } - }) + .and_then(|auth_value| auth_value.strip_prefix("Bearer ")) + .map(|auth_token| auth_token.to_owned()) }); let token = token.ok_or_else(|| AuthError::MissingToken)?; @@ -51,3 +47,28 @@ pub async fn jwt_auth( req.extensions_mut().insert(user); Ok(next.run(req).await) } + +pub async fn jwt_auth( + cookie_jar: CookieJar, + State(state): State>, + mut req: Request, + next: Next, +) -> Result { + let token = cookie_jar + .get("token") + .map(|cookie| cookie.value().to_string()) + .or_else(|| { + req.headers() + .get(header::AUTHORIZATION) + .and_then(|auth_header| auth_header.to_str().ok()) + .and_then(|auth_value| auth_value.strip_prefix("Bearer ")) + .map(|auth_token| auth_token.to_owned()) + }); + + let user_id = token + .and_then(|token| TokenClaims::validate(token, state.config.jwt.secret.to_owned()).ok()) + .and_then(|claims| uuid::Uuid::parse_str(&claims.sub).ok()); + + req.extensions_mut().insert(user_id); + Ok(next.run(req).await) +} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index 5324f82..3a7fef4 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -34,7 +34,11 @@ pub fn routes(state: Arc) -> Router { .route("/v1/user/remove", post(user::remove)) .route("/v1/user/login", post(user::login)) .route("/v1/user/logout", get(user::logout)) - .route("/v1/user/profile", get(user::profile).route_layer(jwt)) + .route( + "/v1/user/current", + get(user::current).route_layer(jwt.to_owned()), + ) + .route("/v1/user/:login", get(user::profile).route_layer(jwt)) .layer(cors) .fallback(fallback) .with_state(state) diff --git a/src/api/v1/user.rs b/src/api/v1/user.rs index cecf88c..567177d 100644 --- a/src/api/v1/user.rs +++ b/src/api/v1/user.rs @@ -1,5 +1,6 @@ use argon2::Argon2; use argon2::{PasswordHash, PasswordVerifier}; +use axum::extract::Path; use axum::Extension; use axum::{ extract::State, @@ -29,6 +30,7 @@ pub struct RegisterUser { #[derive(serde::Serialize)] pub struct FilteredUser { pub id: String, + pub login: String, pub name: String, pub email: String, pub is_admin: bool, @@ -43,6 +45,7 @@ impl FilteredUser { pub fn from(user: &User) -> Self { FilteredUser { id: user.id.to_string(), + login: user.login.to_string(), name: user.name.to_owned(), email: user.email.to_owned(), is_admin: user.is_admin, @@ -137,7 +140,7 @@ pub async fn login( .http_only(true); let mut response = - Json(json!({"status": StatusCode::OK.to_string(), "token": token})).into_response(); + Json(json!({"status": StatusCode::OK.to_string(), "token": token, "user": json!(FilteredUser::from(&user))})).into_response(); response .headers_mut() .insert(header::SET_COOKIE, cookie.to_string().parse().unwrap()); @@ -162,9 +165,48 @@ pub async fn logout() -> Result, + State(state): State>, + Extension(user_id): Extension>, + Path(login): Path, ) -> Result)> { + let user = User::find(&state.database, User::by_login(login)) + .await + .map_err(|_| ()) + .unwrap(); + + let response = if let Some(user) = user { + json!({"status": StatusCode::OK.to_string(), "user": json!(FilteredUser::from(&user))}) + } else { + json!({"status": StatusCode::NOT_FOUND.to_string()}) + }; + + Ok(Json(response)) +} + +pub async fn current( + State(state): State>, + Extension(user_id): Extension>, +) -> Result> { + let user = get_user(state, user_id).await?; + Ok(Json( - json!({"status":"success","user":json!(FilteredUser::from(&user))}), + json!({"status": StatusCode::OK.to_string(), "user": json!(FilteredUser::from(&user))}), )) } + +async fn get_user( + state: Arc, + user_id: Option, +) -> Result> { + let user = if let Some(user_id) = user_id { + User::find(&state.database, User::by_id(user_id)) + .await + .map_err(AuthError::InternalError) + } else { + Err(AuthError::InvalidCredentials) + }; + + let user = user?.ok_or_else(|| AuthError::MissingUser)?; + + Ok(user) +} diff --git a/src/db/user.rs b/src/db/user.rs index 39ac15f..43cb7d7 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -102,6 +102,13 @@ impl User { .filter(users::id.eq(id)) } + pub fn by_login(login: String) -> BoxedQuery<'static> { + users::table + .into_boxed() + .select(User::as_select()) + .filter(users::login.eq(login)) + } + pub async fn find( pool: &Pool, query: BoxedQuery<'static>,