Skip to main content

kellnr_auth/
maybe_user.rs

1use axum::RequestPartsExt;
2use axum::extract::FromRequestParts;
3use axum::http::StatusCode;
4use axum::http::request::Parts;
5use axum_extra::extract::PrivateCookieJar;
6use kellnr_appstate::AppStateData;
7use kellnr_settings::constants;
8
9use crate::token;
10
11#[derive(Debug, Clone)]
12pub struct MaybeUser {
13    pub name: String,
14    pub is_admin: bool,
15    pub is_read_only: bool,
16}
17
18impl MaybeUser {
19    pub fn from_token(token: token::Token) -> Self {
20        Self {
21            name: token.user,
22            is_admin: token.is_admin,
23            is_read_only: token.is_read_only,
24        }
25    }
26
27    pub fn from_session(name: String, is_admin: bool) -> Self {
28        Self {
29            name,
30            is_admin,
31            // Session auth does not currently expose read-only state.
32            // Treat as not read-only for API actions.
33            is_read_only: false,
34        }
35    }
36}
37
38impl FromRequestParts<AppStateData> for MaybeUser {
39    type Rejection = StatusCode;
40
41    async fn from_request_parts(
42        parts: &mut Parts,
43        state: &AppStateData,
44    ) -> Result<Self, Self::Rejection> {
45        // 1) Prefer cargo token auth if present
46        if let Ok(t) = token::Token::from_request_parts(parts, state).await {
47            return Ok(Self::from_token(t));
48        }
49
50        // 2) Fallback to session cookie auth
51        let jar: PrivateCookieJar = parts
52            .extract_with_state(state)
53            .await
54            .map_err(|_| StatusCode::UNAUTHORIZED)?;
55
56        let session_cookie = jar
57            .get(constants::COOKIE_SESSION_ID)
58            .ok_or(StatusCode::UNAUTHORIZED)?;
59
60        let (name, is_admin) = state
61            .db
62            .validate_session(session_cookie.value())
63            .await
64            .map_err(|_| StatusCode::UNAUTHORIZED)?;
65
66        Ok(Self::from_session(name, is_admin))
67    }
68}