aboutsummaryrefslogtreecommitdiff
path: root/server/src/helper/session.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/helper/session.rs')
-rw-r--r--server/src/helper/session.rs109
1 files changed, 109 insertions, 0 deletions
diff --git a/server/src/helper/session.rs b/server/src/helper/session.rs
new file mode 100644
index 0000000..b77f9fa
--- /dev/null
+++ b/server/src/helper/session.rs
@@ -0,0 +1,109 @@
+/*
+ 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 crate::ui::error::MyError;
+use anyhow::anyhow;
+use jellybase::database::Database;
+use jellylogic::session::{validate, AdminSession, Session};
+use log::warn;
+use rocket::{
+ async_trait,
+ http::Status,
+ outcome::Outcome,
+ request::{self, FromRequest},
+ Request, State,
+};
+
+use super::A;
+
+impl A<Session> {
+ 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 = validate(&token)?;
+ };
+
+ #[cfg(feature = "bypass-auth")]
+ {
+ parse_jellyfin_auth("a"); // unused warning is annoying
+ 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(A(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 A::<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 A<AdminSession> {
+ type Error = MyError;
+ async fn from_request<'life0>(
+ request: &'r Request<'life0>,
+ ) -> request::Outcome<Self, Self::Error> {
+ match A::<Session>::from_request_ut(request).await {
+ Ok(x) => {
+ if x.0.user.admin {
+ Outcome::Success(A(AdminSession(x.0)))
+ } else {
+ Outcome::Error((
+ Status::Unauthorized,
+ MyError(anyhow!("you are not an admin")),
+ ))
+ }
+ }
+ Err(e) => {
+ warn!("authentificated route rejected: {e:?}");
+ Outcome::Forward(Status::Unauthorized)
+ }
+ }
+ }
+}