use crate::bearer_auth::parse_bearer_header;
use crate::login_required::current_session_user_id;
use crate::token::AuthToken;
use crate::{AuthUser, auth_user};
use axum_core::extract::FromRequestParts;
use axum_core::response::{IntoResponse, Response};
use http::request::Parts;
use http::{HeaderMap, StatusCode};
use umbral::auth::Identity;
pub struct OptionalIdentity(pub Option<Identity>);
impl<S> FromRequestParts<S> for OptionalIdentity
where
S: Send + Sync,
{
type Rejection = std::convert::Infallible;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
Ok(Self(resolve_identity(&parts.headers).await))
}
}
pub struct CurrentIdentity(pub Identity);
impl<S> FromRequestParts<S> for CurrentIdentity
where
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
match resolve_identity(&parts.headers).await {
Some(id) => Ok(Self(id)),
None => Err((
StatusCode::UNAUTHORIZED,
axum_core::body::Body::from(
r#"{"error":"authentication required","code":"unauthenticated"}"#,
),
)
.into_response()),
}
}
}
pub async fn resolve_identity(headers: &HeaderMap) -> Option<Identity> {
if let Some(id) = identity_from_session(headers).await {
return Some(id);
}
identity_from_bearer(headers).await
}
async fn identity_from_session(headers: &HeaderMap) -> Option<Identity> {
let user_id = current_session_user_id(headers).await?;
let user: AuthUser = AuthUser::objects()
.filter(auth_user::ID.eq(user_id) & auth_user::IS_ACTIVE.eq(true))
.first()
.await
.ok()
.flatten()?;
Some(
Identity::user(crate::UserModel::id_string(&user))
.with_staff(user.is_staff)
.with_superuser(user.is_superuser)
.with_extra("auth", serde_json::json!("session")),
)
}
async fn identity_from_bearer(headers: &HeaderMap) -> Option<Identity> {
let plaintext = parse_bearer_header(headers)?;
let token = AuthToken::lookup(plaintext).await.ok().flatten()?;
let user: AuthUser = AuthUser::objects()
.filter(auth_user::ID.eq(token.user_id.id()) & auth_user::IS_ACTIVE.eq(true))
.first()
.await
.ok()
.flatten()?;
token.touch_last_used().await;
Some(
Identity::user(crate::UserModel::id_string(&user))
.with_staff(user.is_staff)
.with_superuser(user.is_superuser)
.with_extra("auth", serde_json::json!("bearer")),
)
}