diff --git a/Cargo.lock b/Cargo.lock index 93ee898..e58f6e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,20 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aes-gcm-siv" version = "0.11.1" @@ -109,11 +123,15 @@ name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +dependencies = [ + "backtrace", +] [[package]] name = "app" version = "0.1.0" dependencies = [ + "anyhow", "base64 0.22.1", "bincode", "cfg-if", @@ -353,6 +371,15 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "brotli" version = "6.0.0" @@ -536,7 +563,14 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "aes-gcm", + "base64 0.22.1", + "hkdf", + "hmac", "percent-encoding", + "rand", + "sha2", + "subtle", "time", "version_check", ] @@ -686,6 +720,17 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "drain_filter_polyfill" version = "0.1.3" @@ -947,6 +992,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.29.0" @@ -1046,6 +1101,24 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -2491,6 +2564,7 @@ version = "0.1.0" dependencies = [ "app", "axum", + "cookie", "leptos", "leptos_axum", "qbittorrent-rs", @@ -2561,6 +2635,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" diff --git a/app/Cargo.toml b/app/Cargo.toml index b79fde6..f3e7e78 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -26,6 +26,7 @@ qbittorrent-rs-proto = { path = "../qbittorrent_rs_proto" } simple_crypt = { version = "0.2.3", optional = true } bincode = { version = "1.3.3", optional = true } base64 = { version = "0.22.1", optional = true } +anyhow.workspace = true [features] default = [] diff --git a/app/src/auth.rs b/app/src/auth.rs index 5652df9..ebdeb61 100644 --- a/app/src/auth.rs +++ b/app/src/auth.rs @@ -8,12 +8,30 @@ pub mod ssr { use qbittorrent_rs::QbtClient; use serde::{Deserialize, Serialize}; + pub static AUTH_COOKIE: &str = "bt-session"; + pub static REMOVE_COOKIE: &str = "bt-session=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"; + pub fn use_qbt() -> Result { use_context::() .ok_or_else(|| ServerFnError::ServerError("Qbt client missing.".into())) } - #[derive(Serialize, Deserialize, PartialEq, Debug)] + pub fn auth() -> Result, ServerFnError> { + let session = use_context::(); + Ok(session) + } + + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + pub struct AuthSession { + pub session: Option, + } + impl AuthSession { + pub fn new(session: Option) -> Self { + Self { session } + } + } + + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] pub struct Session { sid: String, } @@ -23,7 +41,7 @@ pub mod ssr { } } - #[tracing::instrument] + #[tracing::instrument(skip_all)] pub fn set_session(session: Session) -> Result<(), ServerFnError> { if let Some(res) = leptos::context::use_context::() { let encoded: Vec = bincode::serialize(&session).unwrap(); @@ -40,21 +58,12 @@ pub mod ssr { } } - #[tracing::instrument] - pub fn get_session(session: Session) -> Result<(), ServerFnError> { - if let Some(res) = leptos::context::use_context::() { - let encoded: Vec = bincode::serialize(&session).unwrap(); - let value = simple_crypt::encrypt(&encoded, b"test-password-please-ignore").unwrap(); - let value = base64::prelude::BASE64_STANDARD.encode(value); - res.insert_header( - header::SET_COOKIE, - header::HeaderValue::from_str(&format!("bt-session={value}; path=/; HttpOnly")) - .expect("header value couldn't be set"), - ); - Ok(()) - } else { - Err(ServerFnError::ServerError("No ".to_string())) - } + #[tracing::instrument(skip_all)] + pub fn get_session(sealed_token: String) -> Result { + let sealed_bytes = base64::prelude::BASE64_STANDARD.decode(sealed_token)?; + let encoded = simple_crypt::decrypt(&sealed_bytes, b"test-password-please-ignore").unwrap(); + let session: Session = bincode::deserialize(&encoded).unwrap(); + Ok(session) } } @@ -70,3 +79,10 @@ pub async fn login(username: String, password: String) -> Result<(), ServerFnErr Ok(()) } + +#[server] +pub async fn has_auth() -> Result { + let auth = self::ssr::auth()?; + + Ok(auth.is_some()) +} diff --git a/app/src/lib.rs b/app/src/lib.rs index 98391f3..6c27160 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -1,9 +1,10 @@ -mod auth; +pub mod auth; mod routes; use crate::error_template::{AppError, ErrorTemplate}; -use leptos::prelude::*; +use auth::{has_auth, Login}; +use leptos::{either::Either, prelude::*}; use leptos_meta::*; use leptos_router::{components::*, StaticSegment}; @@ -22,7 +23,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView { - + @@ -34,6 +35,10 @@ pub fn App() -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. provide_meta_context(); + let login = ServerAction::::new(); + let is_auth = Resource::new(move || login.version(), move |_| has_auth()); + let auth = Signal::derive(move || is_auth.get().map(|v| v.unwrap_or(false)).unwrap_or(false)); + view! { @@ -43,16 +48,19 @@ pub fn App() -> impl IntoView { // content for this welcome page - "bit-tower" + "bit-tower" + -
+
}.into_view() }> - + } />
@@ -61,13 +69,39 @@ pub fn App() -> impl IntoView { /// Renders the home page of your application. #[component] -fn HomePage() -> impl IntoView { - // Creates a reactive value to update the button - let (count, set_count) = signal(0); - let on_click = move |_| set_count.update(|count| *count += 1); +fn HomePage(is_auth: Signal, action: ServerAction) -> impl IntoView { + let res = move || { + if is_auth() { + Either::Left(view! { +
"Hello"
+ }) + } else { + Either::Right(view! { + +

"Log In"

+ + + +
+ }) + } + }; view! { -

"Welcome to Leptos!"

- +
{res()}
} } diff --git a/fnord_ui/src/components/navbar.rs b/fnord_ui/src/components/navbar.rs index 2cd9add..3ed5fd7 100644 --- a/fnord_ui/src/components/navbar.rs +++ b/fnord_ui/src/components/navbar.rs @@ -1,18 +1,19 @@ use leptos::prelude::*; +use tailwind_fuse::tw_merge; #[component] -pub fn Navbar(children: Children) -> impl IntoView { +pub fn Navbar(#[prop(optional, into)] class: String, children: Children) -> impl IntoView { view! { -