From cbf64a8a5c7d748722369a2ec47c1230650d7b88 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Wed, 29 Jan 2020 20:45:21 -0500 Subject: authentication --- Cargo.lock | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/db.rs | 30 +++++++++++++++---- src/error.rs | 6 ++-- src/lib.rs | 5 +++- src/main.rs | 2 +- src/models.rs | 20 ++++++++----- src/schema.rs | 5 +--- src/server.rs | 71 +++++++++++++++++++++++++++++++++++++++----- templates/login.hbs | 19 ++++++++++++ 10 files changed, 216 insertions(+), 28 deletions(-) create mode 100644 templates/login.hbs diff --git a/Cargo.lock b/Cargo.lock index 6afecd5..2e21f58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,14 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cookie" version = "0.11.2" @@ -240,6 +248,7 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -633,6 +642,14 @@ name = "libc" version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lock_api" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.3.9" @@ -828,6 +845,28 @@ dependencies = [ "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pear" version = "0.1.2" @@ -997,6 +1036,16 @@ dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "r2d2" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scheduled-thread-pool 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.7.3" @@ -1167,15 +1216,29 @@ name = "rocket_contrib" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "diesel 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "handlebars 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "rocket 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rocket_contrib_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rocket_contrib_codegen" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "devise 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rocket_http" version = "0.4.2" @@ -1219,6 +1282,19 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "security-framework" version = "0.3.4" @@ -1781,6 +1857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cookie 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9fac5e7bdefb6160fb181ee0eaa6f96704b625c70e6d61c465cb35750a4ea12" "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" @@ -1834,6 +1911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" @@ -1855,6 +1933,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" +"checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +"checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" "checksum pear 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c26d2b92e47063ffce70d3e3b1bd097af121a9e0db07ca38a6cc1cf0cc85ff25" "checksum pear_codegen 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "336db4a192cc7f54efeb0c4e11a9245394824cc3bcbd37ba3ff51240c35d7a6e" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" @@ -1875,6 +1955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" @@ -1890,11 +1971,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rocket 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "42c1e9deb3ef4fa430d307bfccd4231434b707ca1328fae339c43ad1201cc6f7" "checksum rocket_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "79aa1366f9b2eccddc05971e17c5de7bb75a5431eb12c2b5c66545fd348647f4" "checksum rocket_contrib 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e0fa5c1392135adc0f96a02ba150ac4c765e27c58dbfd32aa40678e948f6e56f" +"checksum rocket_contrib_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "343da6694fc3cfd901242fd6efafc823e7a3a7f4948017a7dda7311775055092" "checksum rocket_http 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b1391457ee4e80b40d4b57fa5765c0f2836b20d73bcbee4e3f35d93cf3b80817" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" "checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" +"checksum scheduled-thread-pool 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5de7bc31f28f8e6c28df5e1bf3d10610f5fdc14cc95f272853512c70a2bd779" +"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" "checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" diff --git a/Cargo.toml b/Cargo.toml index 070fb3e..6e9836a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ serde_json = "1.0" reqwest = { version = "0.10.1", features = ["blocking", "json"] } clap = "2" rocket = "0.4.2" -rocket_contrib = { version = "0.4.2", default-features = false, features = ["handlebars_templates"] } +rocket_contrib = { version = "0.4.2", default-features = false, features = ["handlebars_templates", "diesel_postgres_pool"] } diesel = { version = "1.0.0", features = ["postgres"] } dotenv = "0.9.0" bcrypt = "0.6" diff --git a/src/db.rs b/src/db.rs index 71490d8..6014db9 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,12 +1,13 @@ -use crate::models; use crate::error::Error; +use crate::models; +use base64; use diesel::connection::Connection; use diesel::pg::PgConnection; +use diesel::ExpressionMethods; +use diesel::QueryDsl; use diesel::RunQueryDsl; -use rand::Rng; use rand; -use base64; -use bcrypt; +use rand::Rng; pub const COST: u32 = 12; @@ -28,9 +29,28 @@ pub fn adduser(conn: &PgConnection, username: &str, password: &str) -> Result<() let hashed = bcrypt::hash(password, COST)?; let rows = diesel::insert_into(users::table) - .values(models::User::new(username, &hashed)).execute(conn)?; + .values(models::User::new(username, &hashed)) + .execute(conn)?; if rows != 1 { Err(Error::AlreadyExists)?; } Ok(()) } + +pub fn authenticate( + conn: &PgConnection, + username: &str, + password: &str, +) -> Result { + use crate::schema::users; + + let user: models::User = users::table + .filter(users::username.eq(username)) + .get_result(conn)?; + + if user.verify(password)? { + Ok(user) + } else { + Err(Error::NotFound) + } +} diff --git a/src/error.rs b/src/error.rs index 6ab0741..81647d5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,7 @@ -use std::convert::From; -use std::error::Error as StdError; use bcrypt::BcryptError; use diesel::result::Error as DieselErr; +use std::convert::From; +use std::error::Error as StdError; use std::fmt; #[derive(Debug)] @@ -9,6 +9,7 @@ pub enum Error { DieselError(DieselErr), PasswordError(BcryptError), AlreadyExists, + NotFound, } impl fmt::Display for Error { @@ -17,6 +18,7 @@ impl fmt::Display for Error { Error::DieselError(ref e) => e.fmt(f), Error::PasswordError(ref e) => e.fmt(f), Error::AlreadyExists => f.write_str("AlreadyExists"), + Error::NotFound => f.write_str("NotFound"), } } } diff --git a/src/lib.rs b/src/lib.rs index 5f8118b..60d89cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,9 @@ #[macro_use] extern crate rocket; +#[macro_use] +extern crate rocket_contrib; + #[macro_use] extern crate diesel; @@ -13,8 +16,8 @@ pub struct Config { pub base_url: String, } -pub mod error; pub mod db; +pub mod error; pub mod models; mod schema; pub mod server; diff --git a/src/main.rs b/src/main.rs index f88b7e2..6d8693f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ fn main() { rocket_secret_key: matches.value_of("rocket_secret_key").unwrap(), }; - pjournal::db::create_config(&conn, &config); + pjournal::db::create_config(&conn, &config).unwrap(); } else if let Some(matches) = matches.subcommand_matches("adduser") { let user = matches.value_of("USERNAME").unwrap(); let password = matches.value_of("PASSWORD").unwrap(); diff --git a/src/models.rs b/src/models.rs index 8bee887..28a8b65 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,5 +1,7 @@ +use crate::error::Error; use crate::schema::config; use crate::schema::users; +use bcrypt; #[derive(Insertable, Queryable)] #[table_name = "config"] @@ -11,16 +13,20 @@ pub struct Config<'a> { #[derive(Insertable, Queryable)] #[table_name = "users"] -pub struct User<'a> { - pub username: &'a str, - password: &'a str, +pub struct User { + pub username: String, + password: String, } -impl<'a> User<'a> { - pub fn new(username: &'a str, password: &'a str) -> User<'a> { +impl User { + pub fn new(username: &str, password: &str) -> User { User { - username: username, - password: password, + username: username.to_string(), + password: password.to_string(), } } + + pub fn verify(&self, password: &str) -> Result { + bcrypt::verify(password, &self.password).map_err(|e| From::from(e)) + } } diff --git a/src/schema.rs b/src/schema.rs index 809706c..055d6d0 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -14,7 +14,4 @@ table! { } } -allow_tables_to_appear_in_same_query!( - config, - users, -); +allow_tables_to_appear_in_same_query!(config, users,); diff --git a/src/server.rs b/src/server.rs index 2c7baad..4f98337 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,17 +1,29 @@ -use rocket::config; +use diesel::pg::PgConnection; +use rocket::http::Cookies; +use rocket::http::Cookie; +use rocket::config::Config; use rocket::config::Environment; use rocket::config::Value; +use rocket::http::Status; +use rocket::request::Form; +use rocket::request::FromForm; use rocket::response; +use rocket::response::Redirect; use rocket::State; use rocket_contrib::templates::Template; use std::collections::HashMap; +use crate::db; +use crate::error::Error; use crate::strava; pub struct Params { pub base_url: String, } +#[database("db")] +pub struct Db(diesel::PgConnection); + #[get("/")] fn index() -> Template { let mut context = HashMap::new(); @@ -20,6 +32,36 @@ fn index() -> Template { Template::render("index", context) } +#[get("/login?")] +fn login(failed: bool) -> Template { + let mut context = HashMap::new(); + context.insert("parent", "layout"); + if failed { + context.insert("message", "Incorrect username or password"); + } + Template::render("login", context) +} + +#[derive(FromForm)] +struct LoginData { + username: String, + password: String, +} + +// Request guard for logged in user: https://api.rocket.rs/v0.4/rocket/request/trait.FromRequest.html + +#[post("/login", data = "")] +fn login_submit(conn: Db, data: Form, mut cookies: Cookies) -> Result { + match db::authenticate(&*conn, &data.username, &data.password) { + Ok(user) => { + cookies.add_private(Cookie::new("user", data.username.clone())); + Ok(Redirect::to(uri!(index).to_string())) + }, + Err(Error::NotFound) => Ok(Redirect::to(uri!(login: failed = true).to_string())), + Err(e) => Err(e), + } +} + #[get("/link_strava_callback?")] fn link_strava_callback( config: State, @@ -30,8 +72,8 @@ fn link_strava_callback( } #[get("/link_strava")] -fn link_strava(config: State) -> response::Redirect { - response::Redirect::to(format!( +fn link_strava(config: State) -> Redirect { + Redirect::to(format!( concat!( "https://www.strava.com/oauth/authorize?", "client_id={}&", @@ -45,15 +87,30 @@ fn link_strava(config: State) -> response::Redirect { )) } -pub fn start(db_url: &str, config: Params) { +pub fn start(db_url: &str, params: Params) { let mut database_config = HashMap::new(); let mut databases = HashMap::new(); database_config.insert("url", Value::from(db_url)); databases.insert("db", Value::from(database_config)); - rocket::ignite() - .manage(config) - .mount("/", routes![index, link_strava, link_strava_callback]) + let config = Config::build(Environment::Development) + .extra("databases", databases) + .finalize() + .unwrap(); + + rocket::custom(config) + .manage(params) + .mount( + "/", + routes![ + index, + login, + login_submit, + link_strava, + link_strava_callback + ], + ) .attach(Template::fairing()) + .attach(Db::fairing()) .launch(); } diff --git a/templates/login.hbs b/templates/login.hbs new file mode 100644 index 0000000..074122d --- /dev/null +++ b/templates/login.hbs @@ -0,0 +1,19 @@ +{{#*inline "page"}} +{{#if message}} + {{ message }} +{{/if}} +
+
+ + +
+
+ + +
+
+ +
+
+{{/inline}} +{{~> (parent)~}} -- cgit v1.2.3