aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui/account/session/guard.rs
blob: 208e45a43bd85fb6ed1f1af492654ebeb9628d68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::{AdminSession, Session};
use crate::{database::Database, routes::ui::error::MyError};
use anyhow::anyhow;
use log::warn;
use rocket::{
    async_trait,
    http::Status,
    outcome::Outcome,
    request::{self, FromRequest},
    Request, State,
};

impl Session {
    pub async fn from_request_ut(req: &Request<'_>) -> Result<Self, MyError> {
        let username;

        #[cfg(not(feature = "bypass-auth"))]
        {
            let token = req
                .query_value("session")
                .map(|e| e.unwrap())
                .or_else(|| req.query_value("api_key").map(|e| e.unwrap()))
                .or_else(|| req.headers().get_one("X-MediaBrowser-Token"))
                .or_else(|| {
                    req.headers()
                        .get_one("Authorization")
                        .and_then(parse_jellyfin_auth)
                }) // for jellyfin compat
                .or(req.cookies().get("session").map(|cookie| cookie.value()))
                .ok_or(anyhow!("not logged in"))?;

            // jellyfin urlescapes the token for *some* requests
            let token = token.replace("%3D", "=");
            username = super::token::validate(&token)?;
        };

        #[cfg(feature = "bypass-auth")]
        {
            username = "admin".to_string();
        }

        let db = req.guard::<&State<Database>>().await.unwrap();

        let user = db.get_user(&username)?.ok_or(anyhow!("user not found"))?;

        Ok(Session { user })
    }
}

fn parse_jellyfin_auth(h: &str) -> Option<&str> {
    for tok in h.split(" ") {
        if let Some(tok) = tok.strip_prefix("Token=\"") {
            if let Some(tok) = tok.strip_suffix("\"") {
                return Some(tok);
            }
        }
    }
    None
}

#[async_trait]
impl<'r> FromRequest<'r> for Session {
    type Error = MyError;
    async fn from_request<'life0>(
        request: &'r Request<'life0>,
    ) -> request::Outcome<Self, Self::Error> {
        match Session::from_request_ut(request).await {
            Ok(x) => Outcome::Success(x),
            Err(e) => {
                warn!("authentificated route rejected: {e:?}");
                Outcome::Forward(Status::Unauthorized)
            }
        }
    }
}

#[async_trait]
impl<'r> FromRequest<'r> for AdminSession {
    type Error = MyError;
    async fn from_request<'life0>(
        request: &'r Request<'life0>,
    ) -> request::Outcome<Self, Self::Error> {
        match Session::from_request_ut(request).await {
            Ok(x) => {
                if x.user.admin {
                    Outcome::Success(AdminSession(x))
                } else {
                    Outcome::Error((
                        Status::Unauthorized,
                        MyError(anyhow!("you are not an admin")),
                    ))
                }
            }
            Err(e) => {
                warn!("authentificated route rejected: {e:?}");
                Outcome::Forward(Status::Unauthorized)
            }
        }
    }
}