Skip to content

Commit

Permalink
Add initial wedding implementation thing that is far from complete bu…
Browse files Browse the repository at this point in the history
…t works
  • Loading branch information
itsjunetime committed Jul 18, 2024
1 parent b073f7e commit 096ae82
Show file tree
Hide file tree
Showing 18 changed files with 2,732 additions and 467 deletions.
1,652 changes: 1,554 additions & 98 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
members = ["backend", "frontend", "shared_data"]
resolver = "2"

[workspace.dependencies]
sqlx = { version = "0.7.3", default-features = false, features = ["macros", "postgres", "uuid"] }

[profile.production]
inherits = "release"
opt-level = 2
Expand Down
50 changes: 42 additions & 8 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,61 @@ name = "backend"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
serde = { version = "1.0.197", features = ["serde_derive"] }
tower-sessions = "0.12.0"
build-info = "0.0.37"
leptos = { version = "0.6.12", features = ["hydrate", "nightly", "rustls", "experimental-islands"] }
# leptos = { git = "https://github.com/leptos-rs/leptos.git", rev = "db02d3f581954ed6f21a477aee698440f6285aba", features = ["hydrate", "nightly", "rustls", "experimental-islands"] }
# leptos = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_0.7", features = ["hydrate", "nightly", "rustls", "experimental-islands"] }
leptos_router = { version = "0.6.12", features = ["hydrate"] }
# leptos_router = { git = "https://github.com/leptos-rs/leptos.git", rev = "db02d3f581954ed6f21a477aee698440f6285aba", features = ["nightly"] }
# leptos_router = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_0.7", features = ["nightly"] }
wasm-bindgen = "0.2.92"
http = "1.1.0"
env_logger = "0.11.3"
const_format = "0.2.32"
# i don't actually use this but I need to activate that specific feature to get it to work so like whatever
# getrandom = { version = "0.2.15", features = ["js"] }

[target.'cfg(not(target_family = "wasm"))'.dependencies]
axum = { version = "0.7.4", default-features = false, features = ["multipart", "query", "json", "tokio", "http1", "http2"] }
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] }
dotenv = "0.15.0"
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] }
shared_data = { path = "../shared_data", features = ["sqlx"] }
shared_data = { path = "../shared_data", features = ["sqlx"] }
axum-sqlx-tx = { version = "0.8.0", features = ["postgres", "runtime-tokio-rustls"] }
serde = { version = "1.0.197", features = ["serde_derive"] }
axum-auth = { version = "0.7.0", default-features = false, features = ["auth-basic"] }
argon2 = { version = "0.5.3", features = ["std"] }
tower-sessions = "0.12.0"
sqlx = { workspace = true }
horrorshow = { version = "0.8.4", default-features = false, features = ["std", "ops"] }
once_cell = { version = "1.19.0", default-features = false }
rss = { version = "2.0.7", default-features = false }
sitewriter = { version = "1.0.4", default-features = false }
chrono = { version = "0.4.35", default-features = false }
tower-http = { version = "0.5.2", default-features = false, features = ["fs"] }
oxipng = { version = "9.0.0", default-features = false, features = ["parallel"] }
build-info = "0.0.37"
tower-no-ai = "0.1.1"
uuid = { version = "1.10.0", features = ["serde"] }
leptos_axum = { version = "0.6.12", features = ["experimental-islands"] }
# leptos_axum = { git = "https://github.com/leptos-rs/leptos.git", rev = "db02d3f581954ed6f21a477aee698440f6285aba" }
# leptos_axum = { git = "https://github.com/leptos-rs/leptos.git", branch = "leptos_0.7" }
argon2 = { version = "0.5.3", features = ["std"] }
axum-auth = { version = "0.7.0", default-features = false, features = ["auth-basic"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
http-body-util = "0.1.2"

[target.'cfg(target_family = "wasm")'.dependencies]
console_error_panic_hook = "0.1.7"

[features]
ssr = ["leptos/ssr"]
hydrate = ["leptos/hydrate"]

[build-dependencies]
build-info-build = "0.0.37"

[package.metadata.leptos]
bin-features = ["ssr"]
lib-features = ["hydrate"]
94 changes: 94 additions & 0 deletions backend/src/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use http::StatusCode;
use axum_auth::AuthBasic;
use argon2::{
password_hash::{PasswordHash, PasswordVerifier},
Argon2
};
use sqlx::{Postgres, query, Row};
use axum_sqlx_tx::Tx;
use tower_sessions::Session;

pub const USERNAME_KEY: &str = "authenticated_username";

#[macro_export]
macro_rules! check_auth{
($session:ident) => {
match check_auth!($session, noret) {
Some(user) => user,
None => return (StatusCode::UNAUTHORIZED, "User did not login (/api/login) first".to_string())
}
};
($session:ident, noret) => {
$session.get::<String>($crate::auth::USERNAME_KEY).await.ok().flatten()
}
}

#[cfg_attr(target_family = "wasm", allow(dead_code))]
pub async fn login(
mut tx: Tx<Postgres>,
AuthBasic((username, password)): AuthBasic,
session: Session
) -> Result<(), (StatusCode, &'static str)> {
// Just in case they've already logged in
if check_auth!(session, noret).is_some() {
return Ok(());
};

let session_id = session.id();

// Only get the pass if it's not empty
let Some(pass) = password.and_then(|p| (!p.is_empty()).then_some(p)) else {
eprintln!("Session {session_id:?} sent a login request with an empty password");
return Err((StatusCode::PRECONDITION_FAILED, "Please include a password"));
};

if username.is_empty() {
eprintln!("Session {session_id:?} sent a login request with an empty username");
return Err((StatusCode::PRECONDITION_FAILED, "Please include a username"));
}

println!("User trying to login with session {session_id:?} and username {username}");

let unauth = || (StatusCode::UNAUTHORIZED, "Incorrect username or password");

let hash = query("SELECT hashed_pass FROM users WHERE username = $1")
.bind(&username)
.fetch_one(&mut tx)
.await
.and_then(|row| row.try_get::<String, _>("hashed_pass"))
.map_err(|e| {
eprintln!("Database error when logging in: {e:?}");
// It would make more sense to send an INTERNAL_SERVER_ERROR but that could expose a
// vulnerability if they were able to reliably cause a database error with a certain
// input, so we are just logging the error then giving them a generic response
unauth()
})?;

let hash_struct = PasswordHash::new(&hash)
.map_err(|e| {
eprintln!("Couldn't create password hash from hash in database ({e:?}); has anyone messed with your db?");
unauth()
})?;

match Argon2::default().verify_password(pass.as_bytes(), &hash_struct) {
Ok(()) => {
println!("Trying to log in {username} with session_id {:?}", session.id());

if let Err(err) = session.insert(USERNAME_KEY, username).await {
println!("Could not save session: {err}");
return Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to save session; unable to log you in"));
}

Ok(())
},
Err(e) => {
if e == argon2::password_hash::Error::Password {
println!("Given password '{pass}' is incorrect (ugh)");
} else {
println!("Password verification failed with error {e}");
}

Err(unauth())
}
}
}
Loading

0 comments on commit 096ae82

Please sign in to comment.