backend: relax jwt
This commit is contained in:
parent
1b0e5d53c2
commit
04dcf039c3
@ -3,9 +3,10 @@ use std::sync::Arc;
|
|||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::{Request, State},
|
extract::{Request, State},
|
||||||
http::header,
|
http::{header, StatusCode},
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
|
Json,
|
||||||
};
|
};
|
||||||
use axum_extra::extract::CookieJar;
|
use axum_extra::extract::CookieJar;
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ use crate::{db::user::User, state::AppState};
|
|||||||
use super::errors::AuthError;
|
use super::errors::AuthError;
|
||||||
use super::token::TokenClaims;
|
use super::token::TokenClaims;
|
||||||
|
|
||||||
pub async fn jwt_auth(
|
pub async fn jwt(
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
mut req: Request<Body>,
|
mut req: Request<Body>,
|
||||||
@ -27,13 +28,8 @@ pub async fn jwt_auth(
|
|||||||
req.headers()
|
req.headers()
|
||||||
.get(header::AUTHORIZATION)
|
.get(header::AUTHORIZATION)
|
||||||
.and_then(|auth_header| auth_header.to_str().ok())
|
.and_then(|auth_header| auth_header.to_str().ok())
|
||||||
.and_then(|auth_value| {
|
.and_then(|auth_value| auth_value.strip_prefix("Bearer "))
|
||||||
if auth_value.starts_with("Bearer ") {
|
.map(|auth_token| auth_token.to_owned())
|
||||||
Some(auth_value[7..].to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let token = token.ok_or_else(|| AuthError::MissingToken)?;
|
let token = token.ok_or_else(|| AuthError::MissingToken)?;
|
||||||
@ -51,3 +47,28 @@ pub async fn jwt_auth(
|
|||||||
req.extensions_mut().insert(user);
|
req.extensions_mut().insert(user);
|
||||||
Ok(next.run(req).await)
|
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/remove", post(user::remove))
|
||||||
.route("/v1/user/login", post(user::login))
|
.route("/v1/user/login", post(user::login))
|
||||||
.route("/v1/user/logout", get(user::logout))
|
.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)
|
.layer(cors)
|
||||||
.fallback(fallback)
|
.fallback(fallback)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use argon2::{PasswordHash, PasswordVerifier};
|
use argon2::{PasswordHash, PasswordVerifier};
|
||||||
|
use axum::extract::Path;
|
||||||
use axum::Extension;
|
use axum::Extension;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::State,
|
||||||
@ -29,6 +30,7 @@ pub struct RegisterUser {
|
|||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct FilteredUser {
|
pub struct FilteredUser {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
pub login: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
@ -43,6 +45,7 @@ impl FilteredUser {
|
|||||||
pub fn from(user: &User) -> Self {
|
pub fn from(user: &User) -> Self {
|
||||||
FilteredUser {
|
FilteredUser {
|
||||||
id: user.id.to_string(),
|
id: user.id.to_string(),
|
||||||
|
login: user.login.to_string(),
|
||||||
name: user.name.to_owned(),
|
name: user.name.to_owned(),
|
||||||
email: user.email.to_owned(),
|
email: user.email.to_owned(),
|
||||||
is_admin: user.is_admin,
|
is_admin: user.is_admin,
|
||||||
@ -137,7 +140,7 @@ pub async fn login(
|
|||||||
.http_only(true);
|
.http_only(true);
|
||||||
|
|
||||||
let mut response =
|
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
|
response
|
||||||
.headers_mut()
|
.headers_mut()
|
||||||
.insert(header::SET_COOKIE, cookie.to_string().parse().unwrap());
|
.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(
|
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>)> {
|
) -> 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(
|
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))
|
.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(
|
pub async fn find(
|
||||||
pool: &Pool,
|
pool: &Pool,
|
||||||
query: BoxedQuery<'static>,
|
query: BoxedQuery<'static>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user