use crate::error::AuthError;
use crate::identity::AuthIdentity;
use crate::state::global;
use crate::strategy::{identity_from_parts, require_identity};
use crate::user::AuthUser;
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use http::header;
use std::future::{ready, Future, Ready};
pub struct CurrentUser<U>(pub U);
pub struct MaybeUser<U>(pub Option<U>);
pub struct RequireAuth(pub AuthIdentity);
pub struct AuthToken(pub String);
impl IntoResponse for AuthError {
fn into_response(self) -> Response {
let status = match self {
AuthError::Unauthorized | AuthError::InvalidCredentials | AuthError::InvalidToken => {
StatusCode::UNAUTHORIZED
}
AuthError::EmailTaken => StatusCode::UNPROCESSABLE_ENTITY,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
(status, self.to_string()).into_response()
}
}
impl<S, U> FromRequestParts<S> for CurrentUser<U>
where
S: Send + Sync,
U: AuthUser,
{
type Rejection = AuthError;
fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
let identity_result = require_identity(parts);
let db = global().db.clone();
async move {
let identity = identity_result?;
let id: U::Id = serde_json::from_value(identity.user_id)
.map_err(|e| AuthError::Internal(e.to_string()))?;
U::find_by_id(&db, id)
.await
.map_err(|e| AuthError::Internal(e.to_string()))?
.ok_or(AuthError::Unauthorized)
.map(CurrentUser)
}
}
}
impl<S, U> FromRequestParts<S> for MaybeUser<U>
where
S: Send + Sync,
U: AuthUser,
{
type Rejection = std::convert::Infallible;
fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
let identity = identity_from_parts(parts);
let db = global().db.clone();
async move {
let user = match identity {
Some(identity) => {
let id: U::Id = match serde_json::from_value(identity.user_id) {
Ok(id) => id,
Err(_) => return Ok(MaybeUser(None)),
};
U::find_by_id(&db, id).await.ok().flatten()
}
None => None,
};
Ok(MaybeUser(user))
}
}
}
impl<S> FromRequestParts<S> for RequireAuth
where
S: Send + Sync,
{
type Rejection = AuthError;
fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
ready(require_identity(parts).map(RequireAuth))
}
}
impl<S> FromRequestParts<S> for AuthToken
where
S: Send + Sync,
{
type Rejection = AuthError;
fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
let token = parts
.headers
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.ok_or(AuthError::InvalidToken)
.and_then(|value| {
value
.strip_prefix("Bearer ")
.map(str::trim)
.filter(|t| !t.is_empty())
.map(str::to_string)
.ok_or(AuthError::InvalidToken)
});
ready(token.map(AuthToken))
}
}
pub use axum;
type _ReadyCheck = Ready<Result<(), AuthError>>;