aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-27 19:25:11 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-27 19:25:11 +0200
commit11a585b3dbe620dcc8772e713b22f1d9ba80d598 (patch)
tree44f8d97137412aefc79a2425a489c34fa3e5f6c5
parentd871aa7c5bba49ff55170b5d2dac9cd440ae7170 (diff)
downloadjellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar
jellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar.bz2
jellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar.zst
move files around
-rw-r--r--server/src/api.rs (renamed from server/src/routes/api.rs)11
-rw-r--r--server/src/compat/jellyfin/mod.rs (renamed from server/src/routes/compat/jellyfin/mod.rs)18
-rw-r--r--server/src/compat/jellyfin/models.rs (renamed from server/src/routes/compat/jellyfin/models.rs)1
-rw-r--r--server/src/compat/mod.rs (renamed from server/src/routes/compat/mod.rs)0
-rw-r--r--server/src/compat/youtube.rs (renamed from server/src/routes/compat/youtube.rs)12
-rw-r--r--server/src/helper/cors.rs20
-rw-r--r--server/src/helper/mod.rs6
-rw-r--r--server/src/locale.rs (renamed from server/src/routes/locale.rs)0
-rw-r--r--server/src/logic/mod.rs9
-rw-r--r--server/src/logic/playersync.rs (renamed from server/src/routes/playersync.rs)3
-rw-r--r--server/src/logic/session.rs208
-rw-r--r--server/src/logic/stream.rs (renamed from server/src/routes/stream.rs)4
-rw-r--r--server/src/logic/userdata.rs (renamed from server/src/routes/userdata.rs)5
-rw-r--r--server/src/main.rs8
-rw-r--r--server/src/routes.rs (renamed from server/src/routes/mod.rs)112
-rw-r--r--server/src/routes/ui/account/session/guard.rs106
-rw-r--r--server/src/routes/ui/account/session/mod.rs24
-rw-r--r--server/src/routes/ui/account/session/token.rs97
-rw-r--r--server/src/ui/account/mod.rs (renamed from server/src/routes/ui/account/mod.rs)13
-rw-r--r--server/src/ui/account/settings.rs (renamed from server/src/routes/ui/account/settings.rs)12
-rw-r--r--server/src/ui/admin/log.rs (renamed from server/src/routes/ui/admin/log.rs)4
-rw-r--r--server/src/ui/admin/mod.rs (renamed from server/src/routes/ui/admin/mod.rs)8
-rw-r--r--server/src/ui/admin/user.rs (renamed from server/src/routes/ui/admin/user.rs)4
-rw-r--r--server/src/ui/assets.rs (renamed from server/src/routes/ui/assets.rs)3
-rw-r--r--server/src/ui/browser.rs (renamed from server/src/routes/ui/browser.rs)5
-rw-r--r--server/src/ui/error.rs (renamed from server/src/routes/ui/error.rs)2
-rw-r--r--server/src/ui/home.rs (renamed from server/src/routes/ui/home.rs)13
-rw-r--r--server/src/ui/layout.rs (renamed from server/src/routes/ui/layout.rs)24
-rw-r--r--server/src/ui/mod.rs (renamed from server/src/routes/ui/mod.rs)7
-rw-r--r--server/src/ui/node.rs (renamed from server/src/routes/ui/node.rs)18
-rw-r--r--server/src/ui/player.rs (renamed from server/src/routes/ui/player.rs)10
-rw-r--r--server/src/ui/search.rs (renamed from server/src/routes/ui/search.rs)3
-rw-r--r--server/src/ui/sort.rs (renamed from server/src/routes/ui/sort.rs)13
-rw-r--r--server/src/ui/stats.rs (renamed from server/src/routes/ui/stats.rs)18
-rw-r--r--server/src/ui/style.rs (renamed from server/src/routes/ui/style.rs)4
35 files changed, 396 insertions, 409 deletions
diff --git a/server/src/routes/api.rs b/server/src/api.rs
index 13708ce..f246eab 100644
--- a/server/src/routes/api.rs
+++ b/server/src/api.rs
@@ -3,14 +3,11 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use super::ui::{
- account::{
- login_logic,
- session::{AdminSession, Session},
- },
- error::MyResult,
+use super::ui::{account::login_logic, error::MyResult};
+use crate::{
+ database::Database,
+ logic::session::{AdminSession, Session},
};
-use crate::database::Database;
use jellybase::assetfed::AssetInner;
use jellycommon::{user::CreateSessionParams, NodeID, Visibility};
use rocket::{
diff --git a/server/src/routes/compat/jellyfin/mod.rs b/server/src/compat/jellyfin/mod.rs
index e37d7d1..9d5c93e 100644
--- a/server/src/routes/compat/jellyfin/mod.rs
+++ b/server/src/compat/jellyfin/mod.rs
@@ -5,14 +5,18 @@
*/
pub mod models;
-use crate::routes::ui::{
- account::{login_logic, session::Session},
- assets::{
- rocket_uri_macro_r_asset, rocket_uri_macro_r_item_backdrop, rocket_uri_macro_r_item_poster,
+use crate::{
+ logic::session::Session,
+ ui::{
+ account::login_logic,
+ assets::{
+ rocket_uri_macro_r_asset, rocket_uri_macro_r_item_backdrop,
+ rocket_uri_macro_r_item_poster,
+ },
+ error::MyResult,
+ node::{aspect_class, DatabaseNodeUserDataExt},
+ sort::{filter_and_sort_nodes, FilterProperty, NodeFilterSort, SortOrder, SortProperty},
},
- error::MyResult,
- node::{aspect_class, DatabaseNodeUserDataExt},
- sort::{filter_and_sort_nodes, FilterProperty, NodeFilterSort, SortOrder, SortProperty},
};
use anyhow::{anyhow, Context};
use jellybase::{database::Database, CONF};
diff --git a/server/src/routes/compat/jellyfin/models.rs b/server/src/compat/jellyfin/models.rs
index be41835..6a68455 100644
--- a/server/src/routes/compat/jellyfin/models.rs
+++ b/server/src/compat/jellyfin/models.rs
@@ -3,7 +3,6 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
diff --git a/server/src/routes/compat/mod.rs b/server/src/compat/mod.rs
index a7b8c0d..a7b8c0d 100644
--- a/server/src/routes/compat/mod.rs
+++ b/server/src/compat/mod.rs
diff --git a/server/src/routes/compat/youtube.rs b/server/src/compat/youtube.rs
index 78eee8a..1df2751 100644
--- a/server/src/routes/compat/youtube.rs
+++ b/server/src/compat/youtube.rs
@@ -3,11 +3,13 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::routes::ui::{
- account::session::Session,
- error::MyResult,
- node::rocket_uri_macro_r_library_node,
- player::{rocket_uri_macro_r_player, PlayerConfig},
+use crate::{
+ logic::session::Session,
+ ui::{
+ error::MyResult,
+ node::rocket_uri_macro_r_library_node,
+ player::{rocket_uri_macro_r_player, PlayerConfig},
+ },
};
use anyhow::anyhow;
use jellybase::database::Database;
diff --git a/server/src/helper/cors.rs b/server/src/helper/cors.rs
new file mode 100644
index 0000000..ca513e3
--- /dev/null
+++ b/server/src/helper/cors.rs
@@ -0,0 +1,20 @@
+/*
+ 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 rocket::{
+ http::Header,
+ response::{self, Responder},
+ Request,
+};
+
+pub struct Cors<T>(pub T);
+impl<'r, T: Responder<'r, 'static>> Responder<'r, 'static> for Cors<T> {
+ fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
+ let mut r = self.0.respond_to(request)?;
+ r.adjoin_header(Header::new("access-controll-allow-origin", "*"));
+ Ok(r)
+ }
+}
diff --git a/server/src/helper/mod.rs b/server/src/helper/mod.rs
new file mode 100644
index 0000000..946e8fa
--- /dev/null
+++ b/server/src/helper/mod.rs
@@ -0,0 +1,6 @@
+/*
+ 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>
+*/
+pub mod cors;
diff --git a/server/src/routes/locale.rs b/server/src/locale.rs
index 6d16c17..6d16c17 100644
--- a/server/src/routes/locale.rs
+++ b/server/src/locale.rs
diff --git a/server/src/logic/mod.rs b/server/src/logic/mod.rs
new file mode 100644
index 0000000..745d11b
--- /dev/null
+++ b/server/src/logic/mod.rs
@@ -0,0 +1,9 @@
+/*
+ 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>
+*/
+pub mod playersync;
+pub mod session;
+pub mod stream;
+pub mod userdata;
diff --git a/server/src/routes/playersync.rs b/server/src/logic/playersync.rs
index 9eb6175..b4cc51b 100644
--- a/server/src/routes/playersync.rs
+++ b/server/src/logic/playersync.rs
@@ -1,4 +1,3 @@
-use super::Cors;
use anyhow::bail;
use chashmap::CHashMap;
use futures::{SinkExt, StreamExt};
@@ -8,6 +7,8 @@ use rocket_ws::{stream::DuplexStream, Channel, Message, WebSocket};
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast::{self, Sender};
+use crate::helper::cors::Cors;
+
#[derive(Default)]
pub struct PlayersyncChannels {
channels: CHashMap<String, broadcast::Sender<Message>>,
diff --git a/server/src/logic/session.rs b/server/src/logic/session.rs
new file mode 100644
index 0000000..790e070
--- /dev/null
+++ b/server/src/logic/session.rs
@@ -0,0 +1,208 @@
+/*
+ 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 aes_gcm_siv::{
+ aead::{generic_array::GenericArray, Aead},
+ KeyInit,
+};
+use anyhow::anyhow;
+use base64::Engine;
+use chrono::{DateTime, Duration, Utc};
+use jellybase::{database::Database, SECRETS};
+use jellycommon::user::{PermissionSet, User};
+use log::warn;
+use rocket::{
+ async_trait,
+ http::Status,
+ outcome::Outcome,
+ request::{self, FromRequest},
+ Request, State,
+};
+use serde::{Deserialize, Serialize};
+use std::sync::LazyLock;
+
+pub struct Session {
+ pub user: User,
+}
+
+pub struct AdminSession(pub Session);
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SessionData {
+ username: String,
+ expire: DateTime<Utc>,
+ permissions: PermissionSet,
+}
+
+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 = 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(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)
+ }
+ }
+ }
+}
+
+static SESSION_KEY: LazyLock<[u8; 32]> = LazyLock::new(|| {
+ if let Some(sk) = &SECRETS.session_key {
+ let r = base64::engine::general_purpose::STANDARD
+ .decode(sk)
+ .expect("key invalid; should be valid base64");
+ r.try_into()
+ .expect("key has the wrong length; should be 32 bytes")
+ } else {
+ warn!("session_key not configured; generating a random one.");
+ [(); 32].map(|_| rand::random())
+ }
+});
+
+pub fn create(username: String, permissions: PermissionSet, expire: Duration) -> String {
+ let session_data = SessionData {
+ expire: Utc::now() + expire,
+ username: username.to_owned(),
+ permissions,
+ };
+ let mut plaintext =
+ bincode::serde::encode_to_vec(&session_data, bincode::config::standard()).unwrap();
+
+ while plaintext.len() % 16 == 0 {
+ plaintext.push(0);
+ }
+
+ let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
+ let nonce = [(); 12].map(|_| rand::random());
+ let mut ciphertext = cipher
+ .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
+ .unwrap();
+ ciphertext.extend(nonce);
+
+ base64::engine::general_purpose::URL_SAFE.encode(&ciphertext)
+}
+
+pub fn validate(token: &str) -> anyhow::Result<String> {
+ let ciphertext = base64::engine::general_purpose::URL_SAFE.decode(token)?;
+ let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
+ let (ciphertext, nonce) = ciphertext.split_at(ciphertext.len() - 12);
+ let plaintext = cipher
+ .decrypt(nonce.into(), ciphertext)
+ .map_err(|e| anyhow!("decryption failed: {e:?}"))?;
+
+ let (session_data, _): (SessionData, _) =
+ bincode::serde::decode_from_slice(&plaintext, bincode::config::standard())?;
+
+ if session_data.expire < Utc::now() {
+ Err(anyhow!("session expired"))?
+ }
+
+ Ok(session_data.username)
+}
+
+#[test]
+fn test() {
+ jellybase::use_test_config();
+ let tok = create(
+ "blub".to_string(),
+ jellycommon::user::PermissionSet::default(),
+ Duration::days(1),
+ );
+ validate(&tok).unwrap();
+}
+
+#[test]
+fn test_crypto() {
+ jellybase::use_test_config();
+ let nonce = [(); 12].map(|_| rand::random());
+ let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
+ let plaintext = b"testing stuff---";
+ let ciphertext = cipher
+ .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
+ .unwrap();
+ let plaintext2 = cipher
+ .decrypt((&nonce).into(), ciphertext.as_slice())
+ .unwrap();
+ assert_eq!(plaintext, plaintext2.as_slice());
+}
diff --git a/server/src/routes/stream.rs b/server/src/logic/stream.rs
index 0fbeb3a..5bba9c2 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/logic/stream.rs
@@ -3,8 +3,8 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use super::ui::{account::session::Session, error::MyError};
-use crate::database::Database;
+use super::session::Session;
+use crate::{database::Database, ui::error::MyError};
use anyhow::{anyhow, Result};
use jellybase::{assetfed::AssetInner, federation::Federation};
use jellycommon::{stream::StreamSpec, TrackSource};
diff --git a/server/src/routes/userdata.rs b/server/src/logic/userdata.rs
index 01776da..64a136f 100644
--- a/server/src/routes/userdata.rs
+++ b/server/src/logic/userdata.rs
@@ -3,8 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use super::ui::{account::session::Session, error::MyResult};
-use crate::routes::ui::node::rocket_uri_macro_r_library_node;
+use crate::{ui::error::MyResult, ui::node::rocket_uri_macro_r_library_node};
use jellybase::database::Database;
use jellycommon::{
user::{NodeUserData, WatchedState},
@@ -15,6 +14,8 @@ use rocket::{
UriDisplayQuery,
};
+use super::session::Session;
+
#[derive(Debug, FromFormField, UriDisplayQuery)]
pub enum UrlWatchedState {
None,
diff --git a/server/src/main.rs b/server/src/main.rs
index ced2f02..b583823 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -7,16 +7,22 @@
#![allow(clippy::needless_borrows_for_generic_args)]
#![recursion_limit = "4096"]
-use crate::routes::ui::{account::hash_password, admin::log::enable_logging};
use anyhow::Context;
use database::Database;
use jellybase::{federation::Federation, CONF, SECRETS};
use log::{error, info, warn};
use routes::build_rocket;
use tokio::fs::create_dir_all;
+use ui::{account::hash_password, admin::log::enable_logging};
pub use jellybase::database;
+pub mod api;
+pub mod compat;
+pub mod helper;
+pub mod locale;
+pub mod logic;
pub mod routes;
+pub mod ui;
#[rocket::main]
async fn main() {
diff --git a/server/src/routes/mod.rs b/server/src/routes.rs
index e7e0a60..4e452c3 100644
--- a/server/src/routes/mod.rs
+++ b/server/src/routes.rs
@@ -3,50 +3,9 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use self::playersync::{r_playersync, PlayersyncChannels};
-use crate::{database::Database, routes::ui::error::MyResult};
-use api::{
- r_api_account_login, r_api_asset_token_raw, r_api_nodes_modified_since, r_api_root,
- r_api_version,
-};
-use base64::Engine;
-use compat::{
- jellyfin::{
- r_jellyfin_artists, r_jellyfin_branding_configuration, r_jellyfin_branding_css,
- r_jellyfin_displaypreferences_usersettings,
- r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_items,
- r_jellyfin_items_image_primary, r_jellyfin_items_images_backdrop, r_jellyfin_items_intros,
- r_jellyfin_items_item, r_jellyfin_items_playbackinfo, r_jellyfin_items_similar,
- r_jellyfin_livetv_programs_recommended, r_jellyfin_persons,
- r_jellyfin_playback_bitratetest, r_jellyfin_quickconnect_enabled,
- r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing,
- r_jellyfin_sessions_playing_progress, r_jellyfin_shows_nextup, r_jellyfin_socket,
- r_jellyfin_system_endpoint, r_jellyfin_system_info, r_jellyfin_system_info_public,
- r_jellyfin_system_info_public_case, r_jellyfin_users_authenticatebyname,
- r_jellyfin_users_authenticatebyname_case, r_jellyfin_users_id, r_jellyfin_users_items,
- r_jellyfin_users_items_item, r_jellyfin_users_public, r_jellyfin_users_views,
- r_jellyfin_video_stream,
- },
- youtube::{r_youtube_channel, r_youtube_embed, r_youtube_watch},
-};
-use jellybase::{federation::Federation, CONF, SECRETS};
-use log::warn;
-use rand::random;
-use rocket::{
- catchers,
- config::SecretKey,
- fairing::AdHoc,
- fs::FileServer,
- get,
- http::Header,
- response::{self, Responder},
- routes,
- shield::Shield,
- Build, Config, Request, Rocket,
-};
-use std::fs::File;
-use stream::r_stream;
-use ui::{
+use crate::database::Database;
+use crate::logic::playersync::{r_playersync, PlayersyncChannels};
+use crate::ui::{
account::{
r_account_login, r_account_login_post, r_account_logout, r_account_logout_post,
r_account_register, r_account_register_post,
@@ -64,22 +23,51 @@ use ui::{
home::r_home,
node::r_library_node_filter,
player::r_player,
- r_index,
+ r_favicon, r_index,
search::r_search,
stats::r_stats,
style::{r_assets_font, r_assets_js, r_assets_js_map, r_assets_style},
};
-use userdata::{
- r_node_userdata, r_node_userdata_progress, r_node_userdata_rating, r_node_userdata_watched,
+use crate::{
+ api::{
+ r_api_account_login, r_api_asset_token_raw, r_api_nodes_modified_since, r_api_root,
+ r_api_version,
+ },
+ compat::{
+ jellyfin::{
+ r_jellyfin_artists, r_jellyfin_branding_configuration, r_jellyfin_branding_css,
+ r_jellyfin_displaypreferences_usersettings,
+ r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_items,
+ r_jellyfin_items_image_primary, r_jellyfin_items_images_backdrop,
+ r_jellyfin_items_intros, r_jellyfin_items_item, r_jellyfin_items_playbackinfo,
+ r_jellyfin_items_similar, r_jellyfin_livetv_programs_recommended, r_jellyfin_persons,
+ r_jellyfin_playback_bitratetest, r_jellyfin_quickconnect_enabled,
+ r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing,
+ r_jellyfin_sessions_playing_progress, r_jellyfin_shows_nextup, r_jellyfin_socket,
+ r_jellyfin_system_endpoint, r_jellyfin_system_info, r_jellyfin_system_info_public,
+ r_jellyfin_system_info_public_case, r_jellyfin_users_authenticatebyname,
+ r_jellyfin_users_authenticatebyname_case, r_jellyfin_users_id, r_jellyfin_users_items,
+ r_jellyfin_users_items_item, r_jellyfin_users_public, r_jellyfin_users_views,
+ r_jellyfin_video_stream,
+ },
+ youtube::{r_youtube_channel, r_youtube_embed, r_youtube_watch},
+ },
+ logic::{
+ stream::r_stream,
+ userdata::{
+ r_node_userdata, r_node_userdata_progress, r_node_userdata_rating,
+ r_node_userdata_watched,
+ },
+ },
+};
+use base64::Engine;
+use jellybase::{federation::Federation, CONF, SECRETS};
+use log::warn;
+use rand::random;
+use rocket::{
+ catchers, config::SecretKey, fairing::AdHoc, fs::FileServer, http::Header, routes,
+ shield::Shield, Build, Config, Rocket,
};
-
-pub mod api;
-pub mod compat;
-pub mod locale;
-pub mod playersync;
-pub mod stream;
-pub mod ui;
-pub mod userdata;
#[macro_export]
macro_rules! uri {
@@ -222,17 +210,3 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build>
],
)
}
-
-#[get("/favicon.ico")]
-fn r_favicon() -> MyResult<File> {
- Ok(File::open(CONF.asset_path.join("favicon.ico"))?)
-}
-
-pub struct Cors<T>(pub T);
-impl<'r, T: Responder<'r, 'static>> Responder<'r, 'static> for Cors<T> {
- fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
- let mut r = self.0.respond_to(request)?;
- r.adjoin_header(Header::new("access-controll-allow-origin", "*"));
- Ok(r)
- }
-}
diff --git a/server/src/routes/ui/account/session/guard.rs b/server/src/routes/ui/account/session/guard.rs
deleted file mode 100644
index 295c2d4..0000000
--- a/server/src/routes/ui/account/session/guard.rs
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- 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")]
- {
- 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(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)
- }
- }
- }
-}
diff --git a/server/src/routes/ui/account/session/mod.rs b/server/src/routes/ui/account/session/mod.rs
deleted file mode 100644
index cb06255..0000000
--- a/server/src/routes/ui/account/session/mod.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- 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 chrono::{DateTime, Utc};
-use jellycommon::user::{PermissionSet, User};
-use serde::{Deserialize, Serialize};
-
-pub mod guard;
-pub mod token;
-
-pub struct Session {
- pub user: User,
-}
-
-pub struct AdminSession(pub Session);
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct SessionData {
- username: String,
- expire: DateTime<Utc>,
- permissions: PermissionSet,
-}
diff --git a/server/src/routes/ui/account/session/token.rs b/server/src/routes/ui/account/session/token.rs
deleted file mode 100644
index 3ada0ec..0000000
--- a/server/src/routes/ui/account/session/token.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- 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::SessionData;
-use aes_gcm_siv::{
- aead::{generic_array::GenericArray, Aead},
- KeyInit,
-};
-use anyhow::anyhow;
-use base64::Engine;
-use chrono::{Duration, Utc};
-use jellybase::SECRETS;
-use jellycommon::user::PermissionSet;
-use log::warn;
-use std::sync::LazyLock;
-
-static SESSION_KEY: LazyLock<[u8; 32]> = LazyLock::new(|| {
- if let Some(sk) = &SECRETS.session_key {
- let r = base64::engine::general_purpose::STANDARD
- .decode(sk)
- .expect("key invalid; should be valid base64");
- r.try_into()
- .expect("key has the wrong length; should be 32 bytes")
- } else {
- warn!("session_key not configured; generating a random one.");
- [(); 32].map(|_| rand::random())
- }
-});
-
-pub fn create(username: String, permissions: PermissionSet, expire: Duration) -> String {
- let session_data = SessionData {
- expire: Utc::now() + expire,
- username: username.to_owned(),
- permissions,
- };
- let mut plaintext =
- bincode::serde::encode_to_vec(&session_data, bincode::config::standard()).unwrap();
-
- while plaintext.len() % 16 == 0 {
- plaintext.push(0);
- }
-
- let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
- let nonce = [(); 12].map(|_| rand::random());
- let mut ciphertext = cipher
- .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
- .unwrap();
- ciphertext.extend(nonce);
-
- base64::engine::general_purpose::URL_SAFE.encode(&ciphertext)
-}
-
-pub fn validate(token: &str) -> anyhow::Result<String> {
- let ciphertext = base64::engine::general_purpose::URL_SAFE.decode(token)?;
- let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
- let (ciphertext, nonce) = ciphertext.split_at(ciphertext.len() - 12);
- let plaintext = cipher
- .decrypt(nonce.into(), ciphertext)
- .map_err(|e| anyhow!("decryption failed: {e:?}"))?;
-
- let (session_data, _): (SessionData, _) =
- bincode::serde::decode_from_slice(&plaintext, bincode::config::standard())?;
-
- if session_data.expire < Utc::now() {
- Err(anyhow!("session expired"))?
- }
-
- Ok(session_data.username)
-}
-
-#[test]
-fn test() {
- jellybase::use_test_config();
- let tok = create(
- "blub".to_string(),
- jellycommon::user::PermissionSet::default(),
- Duration::days(1),
- );
- validate(&tok).unwrap();
-}
-
-#[test]
-fn test_crypto() {
- jellybase::use_test_config();
- let nonce = [(); 12].map(|_| rand::random());
- let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
- let plaintext = b"testing stuff---";
- let ciphertext = cipher
- .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
- .unwrap();
- let plaintext2 = cipher
- .decrypt((&nonce).into(), ciphertext.as_slice())
- .unwrap();
- assert_eq!(plaintext, plaintext2.as_slice());
-}
diff --git a/server/src/routes/ui/account/mod.rs b/server/src/ui/account/mod.rs
index 83a1447..312b40c 100644
--- a/server/src/routes/ui/account/mod.rs
+++ b/server/src/ui/account/mod.rs
@@ -3,7 +3,6 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-pub mod session;
pub mod settings;
use super::{
@@ -12,13 +11,9 @@ use super::{
};
use crate::{
database::Database,
- routes::{
- locale::AcceptLanguage,
- ui::{
- account::session::Session, error::MyResult, home::rocket_uri_macro_r_home,
- layout::DynLayoutPage,
- },
- },
+ locale::AcceptLanguage,
+ logic::session::{self, Session},
+ ui::{error::MyResult, home::rocket_uri_macro_r_home, layout::DynLayoutPage},
uri,
};
use anyhow::anyhow;
@@ -226,7 +221,7 @@ pub fn login_logic(
.retain(|p, val| if *val { !ep.contains(p) } else { true })
}
- Ok(session::token::create(
+ Ok(session::create(
user.name,
user.permissions,
Duration::days(CONF.login_expire.min(expire.unwrap_or(i64::MAX))),
diff --git a/server/src/routes/ui/account/settings.rs b/server/src/ui/account/settings.rs
index 2e170b0..4047e4f 100644
--- a/server/src/routes/ui/account/settings.rs
+++ b/server/src/ui/account/settings.rs
@@ -6,13 +6,11 @@
use super::{format_form_error, hash_password};
use crate::{
database::Database,
- routes::{
- locale::AcceptLanguage,
- ui::{
- account::{rocket_uri_macro_r_account_login, session::Session},
- error::MyResult,
- layout::{trs, DynLayoutPage, LayoutPage},
- },
+ locale::AcceptLanguage,
+ ui::{
+ account::{rocket_uri_macro_r_account_login, session::Session},
+ error::MyResult,
+ layout::{trs, DynLayoutPage, LayoutPage},
},
uri,
};
diff --git a/server/src/routes/ui/admin/log.rs b/server/src/ui/admin/log.rs
index fc85b37..dff6d1b 100644
--- a/server/src/routes/ui/admin/log.rs
+++ b/server/src/ui/admin/log.rs
@@ -4,8 +4,8 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use crate::{
- routes::ui::{
- account::session::AdminSession,
+ logic::session::AdminSession,
+ ui::{
error::MyResult,
layout::{DynLayoutPage, LayoutPage},
},
diff --git a/server/src/routes/ui/admin/mod.rs b/server/src/ui/admin/mod.rs
index f44b36c..de06610 100644
--- a/server/src/routes/ui/admin/mod.rs
+++ b/server/src/ui/admin/mod.rs
@@ -6,13 +6,11 @@
pub mod log;
pub mod user;
-use super::{
- account::session::AdminSession,
- assets::{resolve_asset, AVIF_QUALITY, AVIF_SPEED},
-};
+use super::assets::{resolve_asset, AVIF_QUALITY, AVIF_SPEED};
use crate::{
database::Database,
- routes::ui::{
+ logic::session::AdminSession,
+ ui::{
admin::log::rocket_uri_macro_r_admin_log,
error::MyResult,
layout::{DynLayoutPage, FlashDisplay, LayoutPage},
diff --git a/server/src/routes/ui/admin/user.rs b/server/src/ui/admin/user.rs
index 7ba6d4e..c5239f7 100644
--- a/server/src/routes/ui/admin/user.rs
+++ b/server/src/ui/admin/user.rs
@@ -5,8 +5,8 @@
*/
use crate::{
database::Database,
- routes::ui::{
- account::session::AdminSession,
+ logic::session::AdminSession,
+ ui::{
error::MyResult,
layout::{DynLayoutPage, FlashDisplay, LayoutPage},
},
diff --git a/server/src/routes/ui/assets.rs b/server/src/ui/assets.rs
index c661771..ce2a8e2 100644
--- a/server/src/routes/ui/assets.rs
+++ b/server/src/ui/assets.rs
@@ -3,7 +3,8 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::routes::ui::{account::session::Session, error::MyResult, CacheControlFile};
+use super::{error::MyResult, CacheControlFile};
+use crate::logic::session::Session;
use anyhow::{anyhow, bail, Context};
use base64::Engine;
use jellybase::{
diff --git a/server/src/routes/ui/browser.rs b/server/src/ui/browser.rs
index 96c005d..f7eac93 100644
--- a/server/src/routes/ui/browser.rs
+++ b/server/src/ui/browser.rs
@@ -4,16 +4,13 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::{
- account::session::Session,
error::MyError,
layout::{trs, DynLayoutPage, LayoutPage},
node::NodeCard,
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm, SortOrder, SortProperty},
};
use crate::{
- database::Database,
- routes::{api::AcceptJson, locale::AcceptLanguage},
- uri,
+ api::AcceptJson, database::Database, locale::AcceptLanguage, logic::session::Session, uri,
};
use jellybase::locale::tr;
use jellycommon::{api::ApiItemsResponse, Visibility};
diff --git a/server/src/routes/ui/error.rs b/server/src/ui/error.rs
index ee593a2..c9620bb 100644
--- a/server/src/routes/ui/error.rs
+++ b/server/src/ui/error.rs
@@ -4,7 +4,7 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::layout::{DynLayoutPage, LayoutPage};
-use crate::{routes::ui::account::rocket_uri_macro_r_account_login, uri};
+use crate::{ui::account::rocket_uri_macro_r_account_login, uri};
use jellybase::CONF;
use log::info;
use rocket::{
diff --git a/server/src/routes/ui/home.rs b/server/src/ui/home.rs
index 8f8a876..fbce99b 100644
--- a/server/src/routes/ui/home.rs
+++ b/server/src/ui/home.rs
@@ -4,18 +4,11 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::{
- account::session::Session,
- layout::{trs, LayoutPage},
+ error::MyResult,
+ layout::{trs, DynLayoutPage, LayoutPage},
node::{DatabaseNodeUserDataExt, NodeCard},
};
-use crate::{
- database::Database,
- routes::{
- api::AcceptJson,
- locale::AcceptLanguage,
- ui::{error::MyResult, layout::DynLayoutPage},
- },
-};
+use crate::{api::AcceptJson, database::Database, locale::AcceptLanguage, logic::session::Session};
use anyhow::Context;
use chrono::{Datelike, Utc};
use jellybase::{locale::tr, CONF};
diff --git a/server/src/routes/ui/layout.rs b/server/src/ui/layout.rs
index 0a0d036..0e8d7b9 100644
--- a/server/src/routes/ui/layout.rs
+++ b/server/src/ui/layout.rs
@@ -4,20 +4,18 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use crate::{
- routes::{
- locale::lang_from_request,
- ui::{
- account::{
- rocket_uri_macro_r_account_login, rocket_uri_macro_r_account_logout,
- rocket_uri_macro_r_account_register, session::Session,
- settings::rocket_uri_macro_r_account_settings,
- },
- admin::rocket_uri_macro_r_admin_dashboard,
- browser::rocket_uri_macro_r_all_items,
- node::rocket_uri_macro_r_library_node,
- search::rocket_uri_macro_r_search,
- stats::rocket_uri_macro_r_stats,
+ locale::lang_from_request,
+ logic::session::Session,
+ ui::{
+ account::{
+ rocket_uri_macro_r_account_login, rocket_uri_macro_r_account_logout,
+ rocket_uri_macro_r_account_register, settings::rocket_uri_macro_r_account_settings,
},
+ admin::rocket_uri_macro_r_admin_dashboard,
+ browser::rocket_uri_macro_r_all_items,
+ node::rocket_uri_macro_r_library_node,
+ search::rocket_uri_macro_r_search,
+ stats::rocket_uri_macro_r_stats,
},
uri,
};
diff --git a/server/src/routes/ui/mod.rs b/server/src/ui/mod.rs
index d61ef9e..b98fbec 100644
--- a/server/src/routes/ui/mod.rs
+++ b/server/src/ui/mod.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use account::session::Session;
+use crate::logic::session::Session;
use error::MyResult;
use home::rocket_uri_macro_r_home;
use jellybase::CONF;
@@ -61,6 +61,11 @@ pub async fn r_index(sess: Option<Session>) -> MyResult<Either<Redirect, DynLayo
}
}
+#[get("/favicon.ico")]
+pub async fn r_favicon() -> MyResult<File> {
+ Ok(File::open(CONF.asset_path.join("favicon.ico")).await?)
+}
+
pub struct HtmlTemplate<'a>(pub markup::DynRender<'a>);
impl<'r> Responder<'r, 'static> for HtmlTemplate<'_> {
diff --git a/server/src/routes/ui/node.rs b/server/src/ui/node.rs
index d968f0a..bf65a3e 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/ui/node.rs
@@ -13,21 +13,21 @@ use super::{
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm, SortOrder, SortProperty},
};
use crate::{
+ api::AcceptJson,
database::Database,
- routes::{
- api::AcceptJson,
- locale::AcceptLanguage,
- ui::{
- account::session::Session,
- assets::rocket_uri_macro_r_person_asset,
- layout::{DynLayoutPage, LayoutPage},
- player::{rocket_uri_macro_r_player, PlayerConfig},
- },
+ locale::AcceptLanguage,
+ logic::{
+ session::Session,
userdata::{
rocket_uri_macro_r_node_userdata_rating, rocket_uri_macro_r_node_userdata_watched,
UrlWatchedState,
},
},
+ ui::{
+ assets::rocket_uri_macro_r_person_asset,
+ layout::{DynLayoutPage, LayoutPage},
+ player::{rocket_uri_macro_r_player, PlayerConfig},
+ },
uri,
};
use anyhow::{anyhow, Result};
diff --git a/server/src/routes/ui/player.rs b/server/src/ui/player.rs
index 2bb439b..cd4d03c 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/ui/player.rs
@@ -4,17 +4,15 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::{
- account::session::{token, Session},
layout::LayoutPage,
node::{get_similar_media, DatabaseNodeUserDataExt, NodePage},
sort::NodeFilterSort,
};
use crate::{
database::Database,
- routes::{
- locale::AcceptLanguage,
- ui::{error::MyResult, layout::DynLayoutPage},
- },
+ locale::AcceptLanguage,
+ logic::session::{self, Session},
+ ui::{error::MyResult, layout::DynLayoutPage},
};
use anyhow::anyhow;
use jellybase::CONF;
@@ -87,7 +85,7 @@ pub fn r_player(
conf.t.unwrap_or(0.),
&session.user.native_secret,
&id.to_string(),
- &token::create(
+ &session::create(
session.user.name,
PermissionSet::default(), // TODO
chrono::Duration::hours(24),
diff --git a/server/src/routes/ui/search.rs b/server/src/ui/search.rs
index bc84e57..96be3a6 100644
--- a/server/src/routes/ui/search.rs
+++ b/server/src/ui/search.rs
@@ -4,12 +4,11 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::{
- account::session::Session,
error::MyResult,
layout::{trs, DynLayoutPage, LayoutPage},
node::{DatabaseNodeUserDataExt, NodeCard},
};
-use crate::routes::{api::AcceptJson, locale::AcceptLanguage};
+use crate::{api::AcceptJson, locale::AcceptLanguage, logic::session::Session};
use anyhow::anyhow;
use jellybase::{database::Database, locale::tr};
use jellycommon::{api::ApiSearchResponse, Visibility};
diff --git a/server/src/routes/ui/sort.rs b/server/src/ui/sort.rs
index 6d38e11..a241030 100644
--- a/server/src/routes/ui/sort.rs
+++ b/server/src/ui/sort.rs
@@ -1,3 +1,9 @@
+/*
+ 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::layout::trs;
use jellybase::locale::Language;
use jellycommon::{helpers::SortAnyway, user::NodeUserData, Node, NodeKind, Rating};
use markup::RenderAttributeValue;
@@ -7,8 +13,6 @@ use rocket::{
};
use std::sync::Arc;
-use crate::routes::ui::layout::trs;
-
#[derive(FromForm, UriDisplayQuery, Default, Clone)]
pub struct NodeFilterSort {
pub sort_by: Option<SortProperty>,
@@ -112,7 +116,10 @@ impl FilterProperty {
),
(
"filter_sort.filter.federation",
- &[(FederationLocal, "federation.local"), (FederationRemote, "federation.remote")],
+ &[
+ (FederationLocal, "federation.local"),
+ (FederationRemote, "federation.remote"),
+ ],
),
(
"filter_sort.filter.watched",
diff --git a/server/src/routes/ui/stats.rs b/server/src/ui/stats.rs
index 4e4eef1..4c5bed8 100644
--- a/server/src/routes/ui/stats.rs
+++ b/server/src/ui/stats.rs
@@ -4,21 +4,19 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::{
- account::session::Session,
error::MyError,
layout::{DynLayoutPage, LayoutPage},
};
use crate::{
+ api::AcceptJson,
database::Database,
- routes::{
- api::AcceptJson,
- locale::AcceptLanguage,
- ui::{
- layout::trs,
- node::{
- format_duration, format_duration_long, format_kind, format_size,
- rocket_uri_macro_r_library_node,
- },
+ locale::AcceptLanguage,
+ logic::session::Session,
+ ui::{
+ layout::trs,
+ node::{
+ format_duration, format_duration_long, format_kind, format_size,
+ rocket_uri_macro_r_library_node,
},
},
uri,
diff --git a/server/src/routes/ui/style.rs b/server/src/ui/style.rs
index c935c8a..77f0fe1 100644
--- a/server/src/routes/ui/style.rs
+++ b/server/src/ui/style.rs
@@ -35,7 +35,7 @@ macro_rules! concat_files {
fn css_bundle() -> String {
concat_files!(
- ["../../../../web/style"],
+ ["../../../web/style"],
"layout.css",
"player.css",
"nodepage.css",
@@ -76,7 +76,7 @@ pub fn r_assets_style() -> CachedAsset<(ContentType, String)> {
pub fn r_assets_font() -> CachedAsset<(ContentType, &'static [u8])> {
CachedAsset((
ContentType::WOFF2,
- include_bytes!("../../../../web/cantarell.woff2"),
+ include_bytes!("../../../web/cantarell.woff2"),
))
}