Skip to content

Commit

Permalink
Super basic login
Browse files Browse the repository at this point in the history
  • Loading branch information
kahnclusions committed Aug 2, 2024
1 parent 929dd6a commit ba52291
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 42 deletions.
85 changes: 85 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
50 changes: 33 additions & 17 deletions app/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<QbtClient, ServerFnError> {
use_context::<QbtClient>()
.ok_or_else(|| ServerFnError::ServerError("Qbt client missing.".into()))
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub fn auth() -> Result<Option<Session>, ServerFnError> {
let session = use_context::<Session>();
Ok(session)
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct AuthSession {
pub session: Option<Session>,
}
impl AuthSession {
pub fn new(session: Option<Session>) -> Self {
Self { session }
}
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Session {
sid: String,
}
Expand All @@ -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::<leptos_axum::ResponseOptions>() {
let encoded: Vec<u8> = bincode::serialize(&session).unwrap();
Expand All @@ -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::<leptos_axum::ResponseOptions>() {
let encoded: Vec<u8> = 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<Session, anyhow::Error> {
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)
}
}

Expand All @@ -70,3 +79,10 @@ pub async fn login(username: String, password: String) -> Result<(), ServerFnErr

Ok(())
}

#[server]
pub async fn has_auth() -> Result<bool, ServerFnError> {
let auth = self::ssr::auth()?;

Ok(auth.is_some())
}
58 changes: 46 additions & 12 deletions app/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -22,7 +23,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
<HydrationScripts options/>
<MetaTags />
</head>
<body class="bg-white text-slate-950 dark:bg-slate-950 dark:text-white">
<body class="bg-background text-foreground ">
<App />
</body>
</html>
Expand All @@ -34,6 +35,10 @@ pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();

let login = ServerAction::<Login>::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! {
<Stylesheet id="leptos" href="/pkg/bittower.css"/>

Expand All @@ -43,16 +48,19 @@ pub fn App() -> impl IntoView {
// content for this welcome page
<Router>
<Navbar>
<NavbarBrand>"bit-tower"</NavbarBrand>
<NavbarBrand class="font-display">"bit-tower"</NavbarBrand>
<ul class="p-2 font-cubic">
<A href="/login">login</A>
</ul>
</Navbar>
<main class="pt-9">
<main class="pt-9 bg-background">
<FlatRoutes fallback=|| {

let mut outside_errors = Errors::default();
outside_errors.insert_with_default_key(AppError::NotFound);
view! { <ErrorTemplate outside_errors/> }.into_view()
}>
<Route path=StaticSegment("") view=HomePage/>
<Route path=StaticSegment("") view=move || view! { <HomePage is_auth=auth action=login /> } />
</FlatRoutes>
</main>
</Router>
Expand All @@ -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<bool>, action: ServerAction<Login>) -> impl IntoView {
let res = move || {
if is_auth() {
Either::Left(view! {
<div>"Hello"</div>
})
} else {
Either::Right(view! {
<ActionForm action=action>
<h1>"Log In"</h1>
<label>
"User ID:"
<input
type="text"
placeholder="User ID"
maxlength="32"
name="username"
class="auth-input"
/>
</label>
<label>
"Password:"
<input type="password" placeholder="Password" name="password" class="auth-input"/>
</label>
<button type="submit" class="button">
"Log In"
</button>
</ActionForm>
})
}
};

view! {
<h1 class="font-bold">"Welcome to Leptos!"</h1>
<button on:click=on_click>"Click Me: " {count}</button>
<div>{res()}</div>
}
}
9 changes: 5 additions & 4 deletions fnord_ui/src/components/navbar.rs
Original file line number Diff line number Diff line change
@@ -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! {
<nav class="fixed top-0 left-0 right-0 h-9 bg-background-highlight">
<nav class=tw_merge!("fixed top-0 left-0 right-0 h-9 bg-background_highlight flex flex-row justify-between items-center", class)>
{children()}
</nav>
}
}

#[component]
pub fn NavbarBrand(children: Children) -> impl IntoView {
pub fn NavbarBrand(#[prop(optional, into)] class: String, children: Children) -> impl IntoView {
view! {
<div class="fixed top-0 left-0 right-0 h-9 bg-background-highlight">
<div class=tw_merge!(" p-2 ", class)>
{children()}
</div>
}
Expand Down
10 changes: 8 additions & 2 deletions qbittorrent_rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,22 @@ impl QbtClient {

#[tracing::instrument]
pub async fn auth_login(&self, username: String, password: String) -> Result<String, QbtError> {
let url = format!("{}{}{}", self.base_url, TORRENTS_API, INFO_API);
tracing::info!("Going to do login");
let url = format!("{}/auth/login", self.base_url);
let client = reqwest::Client::builder().build()?;

let params = [("username", username), ("password", password)];
let response = client.post(url).form(&params).send().await?;
let cookies: Vec<_> = response.cookies().collect();
let status = response.status();

let Some(sid) = response.cookies().find(|c| c.name() == "SID") else {
tracing::info!(cookies = ?cookies, status = ?status);

let Some(sid) = cookies.into_iter().find(|c| c.name() == "SID") else {
return Err(QbtError::Unauthenticated);
};

tracing::info!("Login success");
Ok(sid.value().to_owned())
}

Expand Down
1 change: 1 addition & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ axum.workspace = true
tokio.workspace = true
tower.workspace = true
tower-http.workspace = true
cookie = { version = "0.18.1", features = ["secure"] }
Loading

0 comments on commit ba52291

Please sign in to comment.