From 5f4cb6c065396f55974538f6502f728f2bc952a2 Mon Sep 17 00:00:00 2001 From: Bat Date: Mon, 23 Apr 2018 10:52:44 +0100 Subject: [PATCH] Authentication --- src/main.rs | 4 +++ src/models/user.rs | 56 ++++++++++++++++++++++++++++++++++-- src/routes/mod.rs | 1 + src/routes/session.rs | 45 +++++++++++++++++++++++++++++ src/routes/user.rs | 11 ++++--- templates/session/login.tera | 19 ++++++++++++ 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 src/routes/session.rs create mode 100644 templates/session/login.tera diff --git a/src/main.rs b/src/main.rs index 0ce5f6e7..02335d7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,9 +51,13 @@ fn main() { routes::instance::configure, routes::instance::post_config, + routes::user::me, routes::user::details, routes::user::new, routes::user::create, + + routes::session::new, + routes::session::create ]) .manage(init_pool()) .attach(Template::fairing()) diff --git a/src/models/user.rs b/src/models/user.rs index 950c8d2f..b4e7fdf0 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,6 +1,13 @@ +use rocket::request; +use rocket::request::{FromRequest, Request}; +use rocket::outcome::IntoOutcome; use diesel; -use diesel::{ QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection }; +use diesel::{QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection}; use schema::users; +use db_conn::DbConn; +use bcrypt; + +pub const AUTH_COOKIE: &'static str = "user_id"; #[derive(Queryable, Identifiable)] pub struct User { @@ -37,7 +44,7 @@ impl User { diesel::insert_into(users::table) .values(new) .get_result(conn) - .expect("Error saving new instance") + .expect("Error saving new user") } pub fn compute_outbox(user: String, hostname: String) -> String { @@ -48,5 +55,48 @@ impl User { format!("https://{}/@/{}/inbox", hostname, user) } - fn get () {} + pub fn get(conn: &PgConnection, id: i32) -> Option { + users::table.filter(users::id.eq(id)) + .limit(1) + .load::(conn) + .expect("Error loading user by id") + .into_iter().nth(0) + } + + pub fn find_by_email(conn: &PgConnection, email: String) -> Option { + users::table.filter(users::email.eq(email)) + .limit(1) + .load::(conn) + .expect("Error loading user by email") + .into_iter().nth(0) + } + + pub fn find_by_name(conn: &PgConnection, username: String) -> Option { + users::table.filter(users::username.eq(username)) + .limit(1) + .load::(conn) + .expect("Error loading user by email") + .into_iter().nth(0) + } + + pub fn hash_pass(pass: String) -> String { + bcrypt::hash(pass.as_str(), bcrypt::DEFAULT_COST).unwrap() + } + + pub fn auth(&self, pass: String) -> bool { + bcrypt::verify(pass.as_str(), self.hashed_password.clone().unwrap().as_str()).is_ok() + } +} + +impl<'a, 'r> FromRequest<'a, 'r> for User { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> request::Outcome { + let conn = request.guard::()?; + request.cookies() + .get_private(AUTH_COOKIE) + .and_then(|cookie| cookie.value().parse().ok()) + .map(|id| User::get(&*conn, id).unwrap()) + .or_forward(()) + } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 5cd0b566..aa195630 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,2 +1,3 @@ pub mod instance; +pub mod session; pub mod user; diff --git a/src/routes/session.rs b/src/routes/session.rs new file mode 100644 index 00000000..b9476982 --- /dev/null +++ b/src/routes/session.rs @@ -0,0 +1,45 @@ +use std::collections::HashMap; +use rocket_contrib::Template; +use rocket::response::Redirect; +use rocket::request::Form; +use models::user::User; +use rocket::response::status::NotFound; +use rocket::http::Cookies; +use db_conn::DbConn; +use rocket::http::Cookie; +use models::user::AUTH_COOKIE; + +#[get("/login")] +fn new() -> Template { + Template::render("session/login", HashMap::::new()) +} + +#[derive(FromForm)] +struct LoginForm { + email_or_name: String, + password: String +} + +#[post("/login", data = "")] +fn create(conn: DbConn, data: Form, mut cookies: Cookies) -> Result> { + let form = data.get(); + let user = match User::find_by_email(&*conn, form.email_or_name.to_string()) { + Some(usr) => Ok(usr), + None => match User::find_by_name(&*conn, form.email_or_name.to_string()) { + Some(usr) => Ok(usr), + None => Err("Invalid username or password") + } + }; + + match user { + Ok(usr) => { + if usr.auth(form.password.to_string()) { + cookies.add_private(Cookie::new(AUTH_COOKIE, usr.id.to_string())); + Ok(Redirect::to("/")) + } else { + Err(NotFound(String::from("Invalid username or password"))) + } + }, + Err(e) => Err(NotFound(String::from(e))) + } +} diff --git a/src/routes/user.rs b/src/routes/user.rs index c8d43fed..a328fdea 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -2,16 +2,19 @@ use rocket::request::Form; use rocket::response::Redirect; use rocket_contrib::Template; use std::collections::HashMap; -use bcrypt::{hash, DEFAULT_COST}; use db_conn::DbConn; use models::user::*; use models::instance::Instance; +#[get("/me")] +fn me(user: User) -> String { + format!("Logged in as {}", user.username.to_string()) +} #[get("/@/")] -fn details(name: String) { - +fn details(name: String) -> String { + format!("Hello, @{}", name) } #[get("/users/new")] @@ -41,7 +44,7 @@ fn create(conn: DbConn, data: Form) -> Redirect { is_admin: !inst.has_admin(&*conn), summary: String::from(""), email: Some(form.email.to_string()), - hashed_password: Some(hash(form.password.as_str(), DEFAULT_COST).unwrap()), + hashed_password: Some(User::hash_pass(form.password.to_string())), instance_id: inst.id }); } diff --git a/templates/session/login.tera b/templates/session/login.tera new file mode 100644 index 00000000..cb1ce97a --- /dev/null +++ b/templates/session/login.tera @@ -0,0 +1,19 @@ + + + + + Login + + +

Login

+
+ + + + + + + +
+ +