| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | | use super::{csrf_token, mac_equal, token_hash, SESSION_COOKIE}; |
| 5 | | use crate::db::{self, accounts::Account}; |
| 6 | | use crate::Shared; |
| 7 | | use axum::extract::FromRequestParts; |
| 8 | | use axum::http::request::Parts; |
| 9 | | use axum::http::{header, HeaderMap}; |
| 10 | | use std::convert::Infallible; |
| 11 | |
|
| 12 | | #[derive(Clone, Debug)] |
| 13 | | pub struct ViewerAccount { |
| 14 | | pub account: Account, |
| 15 | | |
| 16 | | pub session: String, |
| 17 | | |
| 18 | | pub session_id: i64, |
| 19 | | pub csrf: String, |
| 20 | | |
| 21 | | pub unread: i64, |
| 22 | | } |
| 23 | |
|
| 24 | |
|
| 25 | | #[derive(Clone, Debug, Default)] |
| 26 | | pub struct Viewer(pub Option<ViewerAccount>); |
| 27 | |
|
| 28 | | impl Viewer { |
| 29 | 6924× | pub fn account(&self) -> Option<&Account> { |
| 30 | 6924× | self.0.as_ref().map(|v| &v.account) |
| 31 | 6924× | } |
| 32 | |
|
| 33 | 133× | pub fn signed_in(&self) -> bool { |
| 34 | 133× | self.0.is_some() |
| 35 | 133× | } |
| 36 | |
|
| 37 | 1585× | pub fn is_admin(&self) -> bool { |
| 38 | 1585× | self.account().is_some_and(|a| a.is_admin) |
| 39 | 1585× | } |
| 40 | |
|
| 41 | 1568× | pub fn csrf(&self) -> &str { |
| 42 | 1568× | self.0.as_ref().map(|v| v.csrf.as_str()).unwrap_or("") |
| 43 | 1568× | } |
| 44 | |
|
| 45 | | |
| 46 | 455× | pub fn csrf_ok(&self, token: &str) -> bool { |
| 47 | 455× | self.0.as_ref().is_some_and(|v| mac_equal(&v.csrf, token)) |
| 48 | 455× | } |
| 49 | | } |
| 50 | |
|
| 51 | |
|
| 52 | 1615× | pub fn cookie(headers: &HeaderMap, name: &str) -> Option<String> { |
| 53 | 1615× | headers.get_all(header::COOKIE).iter().filter_map(|v| v.to_str().ok()).flat_map(|line| line.split(';')).find_map(|kv| { |
| 54 | 1161× | let (k, v) = kv.trim().split_once('=')?; |
| 55 | 1152× | (k == name).then(|| v.trim().to_string()) |
| 56 | 1161× | }) |
| 57 | 1615× | } |
| 58 | |
|
| 59 | |
|
| 60 | |
|
| 61 | 1064× | pub async fn resolve(app: &Shared, value: &str) -> Option<(ViewerAccount, db::sessions::Session)> { |
| 62 | 1064× | if value.is_empty() || value.len() > 128 { |
| 63 | 0× | return None; |
| 64 | 1064× | } |
| 65 | 1064× | let session = db::sessions::by_hash(&app.db, &token_hash(value)).await.ok()??; |
| 66 | 1060× | let account = db::accounts::account_by_id(&app.db, session.account_id).await.ok()??; |
| 67 | 1060× | if account.blocked() || !session.live(&account.session_salt, db::now_ms()) { |
| 68 | 4× | return None; |
| 69 | 1056× | } |
| 70 | 1056× | let csrf = csrf_token(&app.cookie_key, value); |
| 71 | 1056× | let unread = db::notify::unread(&app.db, account.id).await.unwrap_or(0); |
| 72 | 1056× | let viewer = ViewerAccount { account, session: value.to_string(), session_id: session.id, csrf, unread }; |
| 73 | 1056× | Some((viewer, session)) |
| 74 | 1064× | } |
| 75 | |
|
| 76 | | impl FromRequestParts<Shared> for Viewer { |
| 77 | | type Rejection = Infallible; |
| 78 | |
|
| 79 | 1565× | async fn from_request_parts(parts: &mut Parts, app: &Shared) -> Result<Viewer, Infallible> { |
| 80 | 1565× | let Some(value) = cookie(&parts.headers, SESSION_COOKIE) else { return Ok(Viewer(None)) }; |
| 81 | 1064× | let Some((viewer, session)) = resolve(app, &value).await else { return Ok(Viewer(None)) }; |
| 82 | 1056× | let address = crate::api::client_addr(app, &parts.headers, &parts.extensions); |
| 83 | 1056× | let agent: String = parts |
| 84 | 1056× | .headers |
| 85 | 1056× | .get(header::USER_AGENT) |
| 86 | 1056× | .and_then(|v| v.to_str().ok()) |
| 87 | 1056× | .unwrap_or("") |
| 88 | 1056× | .chars() |
| 89 | 1056× | .filter(|c| !c.is_control()) |
| 90 | 1056× | .take(300) |
| 91 | 1056× | .collect(); |
| 92 | 1056× | let pool = app.db.clone(); |
| 93 | 1056× | let id = viewer.account.id; |
| 94 | 1056× | tokio::spawn(async move { |
| 95 | 1053× | let _ = db::accounts::touch_seen(&pool, id).await; |
| 96 | 1053× | let _ = db::sessions::touch(&pool, &session, &address, &agent, db::now_ms()).await; |
| 97 | 1053× | }); |
| 98 | 1056× | Ok(Viewer(Some(viewer))) |
| 99 | 1565× | } |
| 100 | | } |