diff --git a/Cargo.lock b/Cargo.lock index 5055d3c..fbfc938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "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 = "ahash" version = "0.8.11" @@ -351,7 +386,13 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "aes-gcm", + "base64 0.22.1", + "hkdf", "percent-encoding", + "rand", + "sha2", + "subtle", "time", "version_check", ] @@ -436,9 +477,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.20.10" @@ -853,6 +904,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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.31.1" @@ -1648,6 +1709,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "overload" version = "0.1.1" @@ -1863,6 +1930,18 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3174,6 +3253,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 76fccc4..f5904f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" argon2 = "0.5.3" atom_syndication = "0.12.6" chrono = { version = "0.4.34", features = ["serde"] } -rocket = { version = "0.5.1", features = ["json"] } +rocket = { version = "0.5.1", features = ["json", "secrets"] } rocket_db_pools = { version = "0.2.0", features = ["sqlx_sqlite"] } rocket_dyn_templates = { version = "0.2.0", features = ["tera"] } rss = "2.0.11" diff --git a/Rocket.toml b/Rocket.toml index eb2eddd..3231109 100644 --- a/Rocket.toml +++ b/Rocket.toml @@ -1,2 +1,4 @@ [default.databases.rss_data] url = "sqlite:data.sqlite" + +secret_key = "MHSePvm1msyOkYuJ7u+MtyJYCzgdHCS7QNvrk9ts+rI=" diff --git a/src/main.rs b/src/main.rs index 4683229..c49fad9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,8 @@ use rocket::fs::FileServer; use rocket::serde::{json::Json, Serialize}; use rocket_db_pools::{sqlx, Database}; use rocket_dyn_templates::{context, Template}; +use rocket::response::Redirect; +use user::AuthenticatedUser; #[derive(Database)] #[database("rss_data")] @@ -28,10 +30,15 @@ fn message() -> Json { } #[get("/")] -fn index() -> Template { +fn index(_user: AuthenticatedUser) -> Template { Template::render("index", context! {}) } +#[get("/", rank = 2)] +fn index_redirect() -> Redirect { + Redirect::to(uri!(login)) +} + #[get("/login")] fn login() -> Template { Template::render("login", context! {}) @@ -44,11 +51,14 @@ fn rocket() -> _ { "/", routes![ index, + index_redirect, message, login, user::create_user, user::get_users, - user::delete_user + user::delete_user, + user::login, + user::logout ], ) .mount("/static", FileServer::from("static")) diff --git a/src/user.rs b/src/user.rs index ce87fa9..c718c24 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,5 +1,5 @@ use chrono; -use rocket::http::Status; +use rocket::http::{Cookie, CookieJar, Status}; use rocket::serde::{json::Json, Deserialize, Serialize}; use rocket_db_pools::Connection; use uuid::Uuid; @@ -40,6 +40,20 @@ pub struct NewUser { pub display_name: Option, } +#[derive(Debug, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct LoginCredentials { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct LoginResponse { + pub user_id: Uuid, + pub username: String, +} + #[post("/users", data = "")] pub async fn create_user( mut db: Connection, @@ -146,4 +160,82 @@ pub async fn delete_user(mut db: Connection, user_id: &str) -> Status { Status::InternalServerError } } -} \ No newline at end of file +} + +#[post("/login", data = "")] +pub async fn login( + mut db: Connection, + credentials: Json, + cookies: &CookieJar<'_>, +) -> Result, Status> { + let creds = credentials.into_inner(); + + // Find user by username + let user = sqlx::query!( + r#" + SELECT + id as "id: String", + username, + password_hash + FROM users + WHERE username = ? + "#, + creds.username + ) + .fetch_optional(&mut **db) + .await + .map_err(|e| { + eprintln!("Database error: {}", e); + Status::InternalServerError + })?; + + let user = match user { + Some(user) => user, + None => return Err(Status::Unauthorized), + }; + + // Verify password + let valid = bcrypt::verify(creds.password.as_bytes(), &user.password_hash) + .map_err(|_| Status::InternalServerError)?; + + if !valid { + return Err(Status::Unauthorized); + } + + // Set session cookie + let user_id = Uuid::parse_str(&user.id).map_err(|_| Status::InternalServerError)?; + cookies.add_private(Cookie::new("user_id", user_id.to_string())); + + Ok(Json(LoginResponse { + user_id, + username: user.username, + })) +} + +#[post("/logout")] +pub fn logout(cookies: &CookieJar<'_>) -> Status { + cookies.remove_private(Cookie::named("user_id")); + Status::NoContent +} + +// Add auth guard +pub struct AuthenticatedUser { + pub user_id: Uuid, +} + +#[rocket::async_trait] +impl<'r> rocket::request::FromRequest<'r> for AuthenticatedUser { + type Error = (); + + async fn from_request(request: &'r rocket::Request<'_>) -> rocket::request::Outcome { + let cookies = request.cookies(); + + if let Some(user_id_cookie) = cookies.get_private("user_id") { + if let Ok(user_id) = Uuid::parse_str(user_id_cookie.value()) { + return rocket::request::Outcome::Success(AuthenticatedUser { user_id }); + } + } + + rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, ())) + } +} diff --git a/templates/index.html.tera b/templates/index.html.tera index 61afe49..30253cc 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -5,8 +5,49 @@ RSS Reader + +
+ +
+ + diff --git a/templates/login.html.tera b/templates/login.html.tera index 5aba7d5..6304daf 100644 --- a/templates/login.html.tera +++ b/templates/login.html.tera @@ -7,7 +7,7 @@ + +