You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
4.0 KiB

use std::sync::Arc;
use axum::{
extract::State,
headers::{ContentLength, Cookie as CookieHeader, Header},
http::{HeaderValue, Request, StatusCode},
middleware::Next,
response::{IntoResponse, Redirect, Response},
Extension,
};
use cookie::{Cookie, CookieJar};
use entity::user;
use sea_orm::entity::prelude::*;
use tracing::{debug, error, info, warn};
use crate::{
data::{AppState, CurrentUser, UserSession},
utils,
};
pub async fn try_get_user_session<B>(
State(state): State<Arc<AppState>>,
mut req: Request<B>,
next: Next<B>,
) -> Response {
let cookies = req.headers().get_all(CookieHeader::name());
let mut jar = CookieJar::new();
for cookie in cookies.iter() {
if let Some(cook) = cookie.to_str().map(|s| s.to_string()).ok() {
if let Some(c) = Cookie::parse_encoded(cook).ok() {
jar.add(c);
}
}
}
// invalid session cookie would fail to decrypt and become None
let dec_cookie = jar
.get("_session")
.and_then(|cookie| state.decrypt_cookie(cookie.to_owned()));
let user = dec_cookie
.and_then(|session_cookie| state.sessions.get_mut(session_cookie.value()))
.map(|kv| *kv.value());
debug!(
"user is {}",
match user {
Some(u) => format!("{u}"),
None => "none".to_string(),
}
);
req.extensions_mut().insert(user);
next.run(req).await
}
pub async fn reject_unauthenticated<B>(
Extension(user_session): Extension<Option<UserSession>>,
mut req: Request<B>,
next: Next<B>,
) -> Result<Response, Redirect> {
match user_session.is_some_and(|session| !session.is_expired()) {
true => {
req.extensions_mut()
.insert(user_session.expect("fucking cosmic rays"));
Ok(next.run(req).await)
}
false => {
debug!("unauthenticated request rejected");
Err(Redirect::to("/?redirect_reason=not_logged_in"))
}
}
}
/// get the actual user model. will Probably be fine for performance given that it can only run for logged in requests (thanks, type system <3)
pub async fn fetch_user<B>(
State(state): State<Arc<AppState>>,
Extension(user_session): Extension<UserSession>,
mut req: Request<B>,
next: Next<B>,
) -> Result<impl IntoResponse, StatusCode> {
let u = user::Entity::find()
.filter(user::Column::Id.eq(user_session.user_id))
.one(&state.db)
.await
.map_err(utils::log_into_status_code)?
.ok_or(StatusCode::BAD_REQUEST)?;
debug!("fetched user '{}'", u.name);
req.extensions_mut().insert(u as CurrentUser);
Ok(next.run(req).await)
}
pub async fn admin_only<B>(
Extension(user): Extension<CurrentUser>,
req: Request<B>,
next: Next<B>,
) -> Result<Response, Redirect> {
match user.is_admin {
true => Ok(next.run(req).await),
false => Err(Redirect::to("/?redirect_reason=admin_only")),
}
}
pub async fn errors_for_humans<B>(req: Request<B>, next: Next<B>) -> impl IntoResponse {
let resp = next.run(req).await;
match resp
.headers()
.get(ContentLength::name())
.is_some_and(|len| len == "0")
{
true => {
debug!("filling response body with rendered status code");
// it's empty, so fill it with rendered status code
let status = resp.status();
let body = format!("{}", status);
let content_len = match HeaderValue::try_from(body.len().to_string()) {
Ok(hv) => hv,
Err(e) => {
error!("hrrrng captain, i'm trying to header this value, but the length of my content keeps alerting the guards: {e}");
return resp;
}
};
let (mut parts, _) = resp.into_parts();
parts.headers.remove(ContentLength::name());
parts.headers.insert(ContentLength::name(), content_len);
Response::from_parts(parts, body).into_response()
}
false => resp,
}
}