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
|
/*
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::A;
use crate::ui::error::MyError;
use anyhow::anyhow;
use jellylogic::{
session::{validate, AdminSession, Session},
Database,
};
use log::warn;
use rocket::{
async_trait,
http::Status,
outcome::Outcome,
request::{self, FromRequest},
Request, State,
};
pub(super) async fn session_from_request(req: &Request<'_>) -> Result<Session, MyError> {
let username;
if cfg!(feature = "bypass-auth") {
username = "admin".to_string();
} else {
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 = validate(&token)?;
};
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 A<Session> {
type Error = MyError;
async fn from_request<'life0>(
request: &'r Request<'life0>,
) -> request::Outcome<Self, Self::Error> {
match session_from_request(request).await {
Ok(x) => Outcome::Success(A(x)),
Err(e) => {
warn!("authentificated route rejected: {e:?}");
Outcome::Forward(Status::Unauthorized)
}
}
}
}
#[async_trait]
impl<'r> FromRequest<'r> for A<AdminSession> {
type Error = MyError;
async fn from_request<'life0>(
request: &'r Request<'life0>,
) -> request::Outcome<Self, Self::Error> {
match session_from_request(request).await {
Ok(x) => {
if x.user.admin {
Outcome::Success(A(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)
}
}
}
}
|