backend: relax jwt
This commit is contained in:
parent
1b0e5d53c2
commit
04dcf039c3
@ -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<Arc<AppState>>,
|
||||
mut req: Request<Body>,
|
||||
@ -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<Arc<AppState>>,
|
||||
mut req: Request<Body>,
|
||||
next: Next,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
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)
|
||||
}
|
||||
|
@ -34,7 +34,11 @@ pub fn routes(state: Arc<AppState>) -> 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)
|
||||
|
@ -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<impl IntoResponse, (StatusCode, Json<serde_json:
|
||||
}
|
||||
|
||||
pub async fn profile(
|
||||
Extension(user): Extension<User>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Extension(user_id): Extension<Option<uuid::Uuid>>,
|
||||
Path(login): Path<String>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||
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<Arc<AppState>>,
|
||||
Extension(user_id): Extension<Option<uuid::Uuid>>,
|
||||
) -> Result<impl IntoResponse, AuthError<impl std::error::Error>> {
|
||||
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<AppState>,
|
||||
user_id: Option<uuid::Uuid>,
|
||||
) -> Result<User, AuthError<impl std::error::Error>> {
|
||||
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)
|
||||
}
|
||||
|
@ -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>,
|
||||
|
Loading…
Reference in New Issue
Block a user