From cbf64a8a5c7d748722369a2ec47c1230650d7b88 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Wed, 29 Jan 2020 20:45:21 -0500 Subject: authentication --- 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 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 7 files changed, 112 insertions(+), 27 deletions(-) (limited to 'src') 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(); } -- cgit v1.2.3