diff options
-rw-r--r-- | .sqlx/query-e3f7e6a9b6f2413adf4467e112b7aa0170d9d3ad0aa78ddc0580e9d0f23c0f7f.json | 22 | ||||
-rw-r--r-- | Cargo.lock | 131 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | TODO.md | 2 | ||||
-rw-r--r-- | src/main.rs | 96 |
5 files changed, 247 insertions, 5 deletions
diff --git a/.sqlx/query-e3f7e6a9b6f2413adf4467e112b7aa0170d9d3ad0aa78ddc0580e9d0f23c0f7f.json b/.sqlx/query-e3f7e6a9b6f2413adf4467e112b7aa0170d9d3ad0aa78ddc0580e9d0f23c0f7f.json new file mode 100644 index 0000000..77c11b6 --- /dev/null +++ b/.sqlx/query-e3f7e6a9b6f2413adf4467e112b7aa0170d9d3ad0aa78ddc0580e9d0f23c0f7f.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "select password from users where username = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "password", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "e3f7e6a9b6f2413adf4467e112b7aa0170d9d3ad0aa78ddc0580e9d0f23c0f7f" +} @@ -120,6 +120,23 @@ dependencies = [ ] [[package]] +name = "actix-session" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6a28f813a6671e1847d005cad0be36ae4d016287690f765c303379837c13d6" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "anyhow", + "async-trait", + "derive_more", + "serde", + "serde_json", + "tracing", +] + +[[package]] name = "actix-utils" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -197,6 +214,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +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.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -239,12 +291,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] name = "arc-swap" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -282,6 +351,12 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + +[[package]] +name = "base64" version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" @@ -437,7 +512,14 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ + "aes-gcm", + "base64 0.20.0", + "hkdf", + "hmac", "percent-encoding", + "rand 0.8.5", + "sha2", + "subtle", "time", "version_check", ] @@ -517,6 +599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -548,6 +631,15 @@ dependencies = [ ] [[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] name = "der" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -859,6 +951,16 @@ dependencies = [ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1432,6 +1534,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] name = "openssl" version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1608,6 +1716,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" name = "pinussy" version = "0.1.0" dependencies = [ + "actix-session", "actix-web", "bcrypt", "mime", @@ -1646,6 +1755,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2679,6 +2800,16 @@ 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 = "url" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10,6 +10,7 @@ build = "src/build.rs" ructe = { version = "0.17.0", features = ["sass", "mime03"] } [dependencies] +actix-session = { version = "0.8.0", features = ["cookie-session"] } actix-web = "4.4.0" bcrypt = "0.15.0" mime = "0.3.17" @@ -3,3 +3,5 @@ - [ ] refactor into different files - [ ] archive pin web sources - [ ] salt hashes +- [ ] proper error handling with custom error type that implements response error, use actix::Result on http handlers +- [ ] css styling diff --git a/src/main.rs b/src/main.rs index c1520ed..2736c5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,16 @@ mod actix_ructe; use std::time::{Duration, SystemTime}; +use actix_session::storage::CookieSessionStore; +use actix_session::{Session, SessionGetError, SessionInsertError, SessionMiddleware}; use actix_web::body::{BoxBody, EitherBody, MessageBody}; +use actix_web::cookie::time::Error; +use actix_web::cookie::Key; use actix_web::dev::ServiceResponse; +use actix_web::http::header::LOCATION; use actix_web::http::{header, StatusCode}; use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; +use actix_web::web::Redirect; use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, ResponseError}; use bcrypt::{hash, verify, DEFAULT_COST}; use serde::Deserialize; @@ -37,6 +43,11 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() + .wrap(SessionMiddleware::new( + // TODO: postgres session store + CookieSessionStore::default(), + Key::generate(), + )) .wrap( ErrorHandlers::new() .handler(StatusCode::NOT_FOUND, render_404) @@ -45,6 +56,7 @@ async fn main() -> std::io::Result<()> { .app_data(web::Data::new(pinussy.clone())) .service(web::resource("/static/{filename}").to(static_file)) .service(home) + // .service(home_auth) .service(get_login) .service(post_login) .service(get_signup) @@ -62,8 +74,12 @@ async fn main() -> std::io::Result<()> { } #[get("/")] -async fn home() -> HttpResponse { - HttpResponse::Ok().body("Hello world!") +async fn home(session: Session) -> Result<HttpResponse> { + if let Some(user_id) = session.get::<i32>("user_id")? { + return Ok(HttpResponse::Ok().body(format!("you are logged in as {}", user_id))); + } else { + return Ok(HttpResponse::Ok().body("Hello world!")); + } } #[get("/signup")] @@ -153,8 +169,63 @@ struct LoginForm { } #[post("/login")] -async fn post_login(form: web::Form<LoginForm>) -> Result<HttpResponse> { - Ok(HttpResponse::Ok().body(render!(templates::login_html, None).unwrap())) +async fn post_login( + state: web::Data<Pinussy>, + session: Session, + form: web::Form<SignupForm>, +) -> Result<HttpResponse> { + match sqlx::query!( + "select id, password from users where username = $1", + &form.username + ) + .fetch_one(&state.db) + .await + { + Ok(user) => { + let password_hash: String = user.password; + if verify(&form.password, &password_hash)? { + session.insert("user_id", user.id)?; + return Ok(HttpResponse::SeeOther() + .insert_header((LOCATION, "/")) + .finish()); + } else { + return Ok(HttpResponse::Unauthorized().body( + render!( + templates::login_html, + Some(Notification { + kind: NotificationKind::Error, + message: "that password is incorrect".to_owned() + }) + ) + .unwrap(), + )); + } + } + Err(sqlx::Error::RowNotFound) => { + return Ok(HttpResponse::NotFound().body( + render!( + templates::login_html, + Some(Notification { + kind: NotificationKind::Error, + message: format!("the user \"{}\" does not exist", &form.username) + }) + ) + .unwrap(), + )); + } + Err(_) => { + return Ok(HttpResponse::InternalServerError().body( + render!( + templates::login_html, + Some(Notification { + kind: NotificationKind::Error, + message: "internal server error. please try again later".to_owned() + }) + ) + .unwrap(), + )); + } + } } #[derive(sqlx::Type)] @@ -203,6 +274,7 @@ async fn get_users(state: web::Data<Pinussy>) -> Result<HttpResponse> { let users: Vec<User> = sqlx::query_as("select * from users") .fetch_all(&state.db) .await + // TODO: no unwrap .unwrap(); println!("lol"); Ok(HttpResponse::Ok().body(render!(templates::users_html, users).unwrap())) @@ -258,9 +330,23 @@ fn error_response( #[derive(Debug)] enum PinussyError { Database(sqlx::Error), + SessionInsertError, + SessionGetError, Bcrypt, } +impl From<SessionInsertError> for PinussyError { + fn from(_: SessionInsertError) -> Self { + Self::SessionInsertError + } +} + +impl From<SessionGetError> for PinussyError { + fn from(_: SessionGetError) -> Self { + Self::SessionGetError + } +} + impl From<sqlx::Error> for PinussyError { fn from(e: sqlx::Error) -> Self { Self::Database(e) @@ -268,7 +354,7 @@ impl From<sqlx::Error> for PinussyError { } impl From<bcrypt::BcryptError> for PinussyError { - fn from(e: bcrypt::BcryptError) -> Self { + fn from(_: bcrypt::BcryptError) -> Self { Self::Bcrypt } } |