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
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, |
|
} |
|
}
|
|
|