server: Add a extractor for the authenticated user
This commit is contained in:
parent
f50d7c1076
commit
66249a5e82
3 changed files with 78 additions and 4 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
|
@ -267,6 +267,7 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"headers",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
|
@ -1239,6 +1240,31 @@ dependencies = [
|
||||||
"hashbrown 0.13.2",
|
"hashbrown 0.13.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"headers-core",
|
||||||
|
"http",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"sha1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers-core"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ members = [".", "api", "app", "migration"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
axum = "0.6.18"
|
axum = { version = "0.6.18", features = ["headers"] }
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
config = "0.13.3"
|
config = "0.13.3"
|
||||||
jwt-simple = "0.11.5"
|
jwt-simple = "0.11.5"
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use api::{LoginRequest, LoginResponse};
|
use api::{LoginRequest, LoginResponse};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
async_trait,
|
||||||
http::{header::CONTENT_TYPE, HeaderValue, Method, StatusCode},
|
extract::{FromRef, FromRequestParts, State},
|
||||||
|
headers::{authorization::Bearer, Authorization},
|
||||||
|
http::{header::CONTENT_TYPE, request::Parts, HeaderValue, Method, StatusCode},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::post,
|
routing::post,
|
||||||
Json, Router,
|
Json, Router, TypedHeader,
|
||||||
};
|
};
|
||||||
use jwt_simple::prelude::*;
|
use jwt_simple::prelude::*;
|
||||||
use sea_orm::prelude::*;
|
use sea_orm::prelude::*;
|
||||||
|
|
@ -23,6 +25,10 @@ enum RouteError {
|
||||||
Db(#[from] DbErr),
|
Db(#[from] DbErr),
|
||||||
#[error("JWT error encountered")]
|
#[error("JWT error encountered")]
|
||||||
Jwt(#[from] jwt_simple::Error),
|
Jwt(#[from] jwt_simple::Error),
|
||||||
|
#[error("User provided JWT token is invalid")]
|
||||||
|
UserJwt(jwt_simple::Error),
|
||||||
|
#[error("Request is missing the bearer token")]
|
||||||
|
MissingAuthorization,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for RouteError {
|
impl IntoResponse for RouteError {
|
||||||
|
|
@ -31,6 +37,13 @@ impl IntoResponse for RouteError {
|
||||||
RouteError::UnknownAccount => {
|
RouteError::UnknownAccount => {
|
||||||
(StatusCode::NOT_FOUND, "Account not found").into_response()
|
(StatusCode::NOT_FOUND, "Account not found").into_response()
|
||||||
}
|
}
|
||||||
|
RouteError::MissingAuthorization => {
|
||||||
|
(StatusCode::BAD_REQUEST, "Missing authorization header").into_response()
|
||||||
|
}
|
||||||
|
RouteError::UserJwt(e) => {
|
||||||
|
tracing::debug!("Invalid user JWT: {e:?}");
|
||||||
|
(StatusCode::BAD_REQUEST, "Invalid authorization header").into_response()
|
||||||
|
}
|
||||||
e => {
|
e => {
|
||||||
tracing::error!("Internal error: {e:?}");
|
tracing::error!("Internal error: {e:?}");
|
||||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
|
|
@ -43,6 +56,41 @@ type JsonResult<T, E = RouteError> = Result<Json<T>, E>;
|
||||||
|
|
||||||
type AppState = Arc<crate::AppState>;
|
type AppState = Arc<crate::AppState>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AuthenticatedUser {
|
||||||
|
pub id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<S> FromRequestParts<S> for AuthenticatedUser
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
AppState: FromRef<S>,
|
||||||
|
{
|
||||||
|
type Rejection = RouteError;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
let State(app_state): State<AppState> = State::from_request_parts(parts, state)
|
||||||
|
.await
|
||||||
|
.expect("Could not get state");
|
||||||
|
|
||||||
|
let TypedHeader(Authorization(bearer)) =
|
||||||
|
TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state)
|
||||||
|
.await
|
||||||
|
.map_err(|_| RouteError::MissingAuthorization)?;
|
||||||
|
|
||||||
|
let claims = app_state
|
||||||
|
.jwt_secret
|
||||||
|
.0
|
||||||
|
.verify_token::<NoCustomClaims>(bearer.token(), None)
|
||||||
|
.map_err(RouteError::UserJwt)?;
|
||||||
|
|
||||||
|
Ok(AuthenticatedUser {
|
||||||
|
id: claims.subject.unwrap().parse().unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn login(
|
async fn login(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(req): Json<LoginRequest>,
|
Json(req): Json<LoginRequest>,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue