Authentication

This commit is contained in:
Bat 2018-04-23 10:52:44 +01:00
parent f8372f6383
commit 5f4cb6c065
6 changed files with 129 additions and 7 deletions

View File

@ -51,9 +51,13 @@ fn main() {
routes::instance::configure, routes::instance::configure,
routes::instance::post_config, routes::instance::post_config,
routes::user::me,
routes::user::details, routes::user::details,
routes::user::new, routes::user::new,
routes::user::create, routes::user::create,
routes::session::new,
routes::session::create
]) ])
.manage(init_pool()) .manage(init_pool())
.attach(Template::fairing()) .attach(Template::fairing())

View File

@ -1,6 +1,13 @@
use rocket::request;
use rocket::request::{FromRequest, Request};
use rocket::outcome::IntoOutcome;
use diesel; use diesel;
use diesel::{QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection}; use diesel::{QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection};
use schema::users; use schema::users;
use db_conn::DbConn;
use bcrypt;
pub const AUTH_COOKIE: &'static str = "user_id";
#[derive(Queryable, Identifiable)] #[derive(Queryable, Identifiable)]
pub struct User { pub struct User {
@ -37,7 +44,7 @@ impl User {
diesel::insert_into(users::table) diesel::insert_into(users::table)
.values(new) .values(new)
.get_result(conn) .get_result(conn)
.expect("Error saving new instance") .expect("Error saving new user")
} }
pub fn compute_outbox(user: String, hostname: String) -> String { pub fn compute_outbox(user: String, hostname: String) -> String {
@ -48,5 +55,48 @@ impl User {
format!("https://{}/@/{}/inbox", hostname, user) format!("https://{}/@/{}/inbox", hostname, user)
} }
fn get () {} pub fn get(conn: &PgConnection, id: i32) -> Option<User> {
users::table.filter(users::id.eq(id))
.limit(1)
.load::<User>(conn)
.expect("Error loading user by id")
.into_iter().nth(0)
}
pub fn find_by_email(conn: &PgConnection, email: String) -> Option<User> {
users::table.filter(users::email.eq(email))
.limit(1)
.load::<User>(conn)
.expect("Error loading user by email")
.into_iter().nth(0)
}
pub fn find_by_name(conn: &PgConnection, username: String) -> Option<User> {
users::table.filter(users::username.eq(username))
.limit(1)
.load::<User>(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<User, ()> {
let conn = request.guard::<DbConn>()?;
request.cookies()
.get_private(AUTH_COOKIE)
.and_then(|cookie| cookie.value().parse().ok())
.map(|id| User::get(&*conn, id).unwrap())
.or_forward(())
}
} }

View File

@ -1,2 +1,3 @@
pub mod instance; pub mod instance;
pub mod session;
pub mod user; pub mod user;

45
src/routes/session.rs Normal file
View File

@ -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::<String, String>::new())
}
#[derive(FromForm)]
struct LoginForm {
email_or_name: String,
password: String
}
#[post("/login", data = "<data>")]
fn create(conn: DbConn, data: Form<LoginForm>, mut cookies: Cookies) -> Result<Redirect, NotFound<String>> {
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)))
}
}

View File

@ -2,16 +2,19 @@ use rocket::request::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;
use std::collections::HashMap; use std::collections::HashMap;
use bcrypt::{hash, DEFAULT_COST};
use db_conn::DbConn; use db_conn::DbConn;
use models::user::*; use models::user::*;
use models::instance::Instance; use models::instance::Instance;
#[get("/me")]
fn me(user: User) -> String {
format!("Logged in as {}", user.username.to_string())
}
#[get("/@/<name>")] #[get("/@/<name>")]
fn details(name: String) { fn details(name: String) -> String {
format!("Hello, @{}", name)
} }
#[get("/users/new")] #[get("/users/new")]
@ -41,7 +44,7 @@ fn create(conn: DbConn, data: Form<NewUserForm>) -> Redirect {
is_admin: !inst.has_admin(&*conn), is_admin: !inst.has_admin(&*conn),
summary: String::from(""), summary: String::from(""),
email: Some(form.email.to_string()), 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 instance_id: inst.id
}); });
} }

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form method="post">
<label for="email_or_name">Username or email</label>
<input name="email_or_name">
<label for="password">Password</label>
<input type="password" name="password">
<input type="submit" value="Login"/>
</form>
</body>
</html>