User accounts, DAO trait, registration.
Not very thorough, but it does work.
This commit is contained in:
parent
881518cc8f
commit
0530011138
|
@ -79,6 +79,18 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -126,12 +138,29 @@ version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2b_simd"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"constant_time_eq",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
@ -208,6 +237,12 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
@ -224,6 +259,17 @@ dependencies = [
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-mac"
|
name = "crypto-mac"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -1086,6 +1132,18 @@ dependencies = [
|
||||||
"unicode-xid 0.1.0",
|
"unicode-xid 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-argon2"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.0",
|
||||||
|
"blake2b_simd",
|
||||||
|
"constant_time_eq",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
|
@ -1246,8 +1304,11 @@ name = "tenebrous-sheets"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"diesel",
|
"diesel",
|
||||||
|
"log 0.4.11",
|
||||||
|
"rand",
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_contrib",
|
"rocket_contrib",
|
||||||
|
"rust-argon2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -12,6 +12,9 @@ serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
diesel = "1.4"
|
diesel = "1.4"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
rust-argon2 = "0.8"
|
||||||
|
log = "0.4"
|
||||||
|
rand = "0.7"
|
||||||
rocket = { version= "0.4.6", features = ["private-cookies"] }
|
rocket = { version= "0.4.6", features = ["private-cookies"] }
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE users;
|
|
@ -0,0 +1,5 @@
|
||||||
|
CREATE TABLE users (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
95
src/db.rs
95
src/db.rs
|
@ -1,39 +1,74 @@
|
||||||
use crate::models::characters::{CharacterEntry, NewCharacter};
|
use crate::models::characters::{CharacterEntry, NewCharacter};
|
||||||
|
use crate::models::users::{NewUser, User};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use diesel::SqliteConnection;
|
||||||
|
|
||||||
#[database("tenebrous_db")]
|
#[database("tenebrous_db")]
|
||||||
pub(crate) struct TenebrousDbConn(diesel::SqliteConnection);
|
pub(crate) struct TenebrousDbConn(SqliteConnection);
|
||||||
|
|
||||||
pub(crate) fn load_character_list(
|
pub(crate) trait Dao {
|
||||||
conn: TenebrousDbConn,
|
fn load_user_by_id(&self, id: i32) -> QueryResult<Option<User>>;
|
||||||
for_user_id: i32,
|
fn load_user(&self, for_username: &str) -> QueryResult<Option<User>>;
|
||||||
) -> QueryResult<Vec<CharacterEntry>> {
|
|
||||||
use crate::schema::characters::dsl::*;
|
fn insert_user(&self, new_user: &NewUser) -> QueryResult<User>;
|
||||||
characters.filter(user_id.eq(for_user_id)).load(&*conn)
|
|
||||||
|
fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<CharacterEntry>>;
|
||||||
|
|
||||||
|
fn load_character(&self, character_id: i32) -> QueryResult<Option<CharacterEntry>>;
|
||||||
|
|
||||||
|
fn insert_character(&self, new_character: &NewCharacter) -> QueryResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_character(
|
impl Dao for TenebrousDbConn {
|
||||||
conn: TenebrousDbConn,
|
fn load_user_by_id(&self, user_id: i32) -> QueryResult<Option<User>> {
|
||||||
character_id: i32,
|
use crate::schema::users::dsl::*;
|
||||||
) -> QueryResult<Option<CharacterEntry>> {
|
|
||||||
use crate::schema::characters::dsl::*;
|
|
||||||
|
|
||||||
characters
|
users.filter(id.eq(user_id)).first(&self.0).optional()
|
||||||
.filter(id.eq(character_id))
|
}
|
||||||
.limit(1)
|
|
||||||
.first(&*conn)
|
fn load_user(&self, for_username: &str) -> QueryResult<Option<User>> {
|
||||||
.optional()
|
use crate::schema::users::dsl::*;
|
||||||
}
|
|
||||||
|
users
|
||||||
pub(crate) fn insert_character(
|
.filter(username.eq(for_username))
|
||||||
conn: TenebrousDbConn,
|
.first(&self.0)
|
||||||
new_character: &NewCharacter,
|
.optional()
|
||||||
) -> QueryResult<()> {
|
}
|
||||||
use crate::schema::characters;
|
|
||||||
|
fn insert_user(&self, new_user: &NewUser) -> QueryResult<User> {
|
||||||
diesel::insert_into(characters::table)
|
use crate::schema::users;
|
||||||
.values(new_character)
|
|
||||||
.execute(&*conn)?;
|
diesel::insert_into(users::table)
|
||||||
|
.values(new_user)
|
||||||
Ok(())
|
.execute(&self.0)?;
|
||||||
|
|
||||||
|
use crate::schema::users::dsl::*;
|
||||||
|
Ok(users
|
||||||
|
.filter(username.eq(new_user.username))
|
||||||
|
.first(&self.0)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<CharacterEntry>> {
|
||||||
|
use crate::schema::characters::dsl::*;
|
||||||
|
characters.filter(user_id.eq(for_user_id)).load(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_character(&self, character_id: i32) -> QueryResult<Option<CharacterEntry>> {
|
||||||
|
use crate::schema::characters::dsl::*;
|
||||||
|
|
||||||
|
characters
|
||||||
|
.filter(id.eq(character_id))
|
||||||
|
.first(&self.0)
|
||||||
|
.optional()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_character(&self, new_character: &NewCharacter) -> QueryResult<()> {
|
||||||
|
use crate::schema::characters;
|
||||||
|
|
||||||
|
diesel::insert_into(characters::table)
|
||||||
|
.values(new_character)
|
||||||
|
.execute(&self.0)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,10 @@ pub enum Error {
|
||||||
QueryError(#[from] diesel::result::Error),
|
QueryError(#[from] diesel::result::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("internal eror")]
|
||||||
|
pub struct SensitiveError(Error);
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
fn is_sensitive(&self) -> bool {
|
fn is_sensitive(&self) -> bool {
|
||||||
use Error::*;
|
use Error::*;
|
||||||
|
@ -33,6 +37,7 @@ impl Error {
|
||||||
|
|
||||||
impl<'r> Responder<'r> for Error {
|
impl<'r> Responder<'r> for Error {
|
||||||
fn respond_to(self, req: &Request) -> response::Result<'r> {
|
fn respond_to(self, req: &Request) -> response::Result<'r> {
|
||||||
|
log::error!("error: {0}", self.to_string());
|
||||||
//Hide sensitive error information
|
//Hide sensitive error information
|
||||||
let message: String = if self.is_sensitive() {
|
let message: String = if self.is_sensitive() {
|
||||||
"internal error".into()
|
"internal error".into()
|
||||||
|
@ -53,3 +58,22 @@ impl<'r> Responder<'r> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'r> Responder<'r> for SensitiveError {
|
||||||
|
fn respond_to(self, req: &Request) -> response::Result<'r> {
|
||||||
|
log::error!("sensitive error: {0}", self.0.to_string());
|
||||||
|
let message: String = self.to_string();
|
||||||
|
|
||||||
|
let mut context = HashMap::new();
|
||||||
|
context.insert("message", message);
|
||||||
|
let resp = Template::render("error", context).respond_to(req)?;
|
||||||
|
|
||||||
|
use Error::*;
|
||||||
|
match self.0 {
|
||||||
|
NotFound => status::NotFound(resp).respond_to(req),
|
||||||
|
NotLoggedIn => status::Forbidden(Some(resp)).respond_to(req),
|
||||||
|
NoPermission => status::Forbidden(Some(resp)).respond_to(req),
|
||||||
|
_ => status::Custom(Status::InternalServerError, resp).respond_to(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,44 +1,2 @@
|
||||||
use rocket::http::RawStr;
|
|
||||||
use rocket::outcome::IntoOutcome;
|
|
||||||
use rocket::request::{self, FromParam, FromRequest, Request};
|
|
||||||
use serde_derive::Serialize;
|
|
||||||
|
|
||||||
pub mod characters;
|
pub mod characters;
|
||||||
|
pub mod users;
|
||||||
#[derive(Eq, PartialEq, Serialize, Debug)]
|
|
||||||
pub struct User {
|
|
||||||
pub id: i32,
|
|
||||||
pub username: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
|
||||||
type Error = !;
|
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<User, !> {
|
|
||||||
request
|
|
||||||
.cookies()
|
|
||||||
.get_private("user_id")
|
|
||||||
.and_then(|cookie| cookie.value().parse().ok())
|
|
||||||
.map(|id|
|
|
||||||
//TODO load from db
|
|
||||||
User {
|
|
||||||
id: id,
|
|
||||||
username: "somebody".to_string(),
|
|
||||||
})
|
|
||||||
.or_forward(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> FromParam<'r> for User {
|
|
||||||
type Error = &'r str;
|
|
||||||
|
|
||||||
fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
|
|
||||||
let username: String = param.url_decode().or(Err("Invalid character ID"))?;
|
|
||||||
|
|
||||||
//TODO load from DB
|
|
||||||
Ok(User {
|
|
||||||
id: 1,
|
|
||||||
username: username,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::db::{Dao, TenebrousDbConn};
|
||||||
|
use crate::schema::users;
|
||||||
|
use argon2::{self, Config, Error as ArgonError};
|
||||||
|
use rand::Rng;
|
||||||
|
use rocket::outcome::IntoOutcome;
|
||||||
|
use rocket::request::{self, FromRequest, Request};
|
||||||
|
use serde_derive::Serialize;
|
||||||
|
|
||||||
|
pub(crate) fn hash_password(raw_password: &str) -> Result<String, ArgonError> {
|
||||||
|
let salt = rand::thread_rng().gen::<[u8; 16]>();
|
||||||
|
let config = Config::default();
|
||||||
|
argon2::hash_encoded(raw_password.as_bytes(), &salt, &config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Serialize, Debug, Queryable)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i32,
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub fn verify_password(&self, raw_password: &str) -> bool {
|
||||||
|
argon2::verify_encoded(&self.password, raw_password.as_bytes()).unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for &'a User {
|
||||||
|
type Error = !;
|
||||||
|
|
||||||
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, !> {
|
||||||
|
let user: &Option<User> = request.local_cache(|| {
|
||||||
|
let attempt_load_user = |id| -> Option<User> {
|
||||||
|
TenebrousDbConn::from_request(request)
|
||||||
|
.map(|conn| conn.load_user_by_id(id).ok().flatten())
|
||||||
|
.succeeded()
|
||||||
|
.flatten()
|
||||||
|
};
|
||||||
|
|
||||||
|
request
|
||||||
|
.cookies()
|
||||||
|
.get_private("user_id")
|
||||||
|
.and_then(|cookie| cookie.value().parse().ok())
|
||||||
|
.and_then(attempt_load_user)
|
||||||
|
});
|
||||||
|
|
||||||
|
user.as_ref().or_forward(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[table_name = "users"]
|
||||||
|
pub struct NewUser<'a> {
|
||||||
|
pub username: &'a str,
|
||||||
|
pub password: &'a str,
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::models::User;
|
use crate::db::{Dao, TenebrousDbConn};
|
||||||
|
use crate::models::users::{self, NewUser, User};
|
||||||
|
use log::error;
|
||||||
use rocket::http::{Cookie, Cookies};
|
use rocket::http::{Cookie, Cookies};
|
||||||
use rocket::request::{FlashMessage, Form};
|
use rocket::request::{FlashMessage, Form};
|
||||||
use rocket::response::{Flash, Redirect};
|
use rocket::response::{Flash, Redirect};
|
||||||
|
@ -6,7 +8,14 @@ use rocket_contrib::templates::Template;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub(crate) fn routes() -> Vec<rocket::Route> {
|
pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||||
routes![login, logout, logged_in_user, login_page]
|
routes![
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
logged_in_user,
|
||||||
|
login_page,
|
||||||
|
register_page,
|
||||||
|
register
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
|
@ -15,27 +24,58 @@ struct Login {
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Registration {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_login_cookie(cookies: &mut Cookies, user: &User) {
|
||||||
|
cookies.add_private(Cookie::new("user_id", user.id.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_login_cookie(cookies: &mut Cookies) {
|
||||||
|
cookies.remove_private(Cookie::named("user_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn login_error_redirect<S: AsRef<str>>(message: S) -> Flash<Redirect> {
|
||||||
|
Flash::error(Redirect::to(uri!(login_page)), message.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn registration_error_redirect<S: AsRef<str>>(message: S) -> Flash<Redirect> {
|
||||||
|
Flash::error(Redirect::to(uri!(register_page)), message.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/login", data = "<login>")]
|
#[post("/login", data = "<login>")]
|
||||||
fn login(mut cookies: Cookies, login: Form<Login>) -> Result<Redirect, Flash<Redirect>> {
|
fn login(
|
||||||
if login.username == "test" && login.password == "test" {
|
mut cookies: Cookies,
|
||||||
cookies.add_private(Cookie::new("user_id", 1.to_string()));
|
login: Form<Login>,
|
||||||
|
conn: TenebrousDbConn,
|
||||||
|
) -> Result<Redirect, Flash<Redirect>> {
|
||||||
|
let user = conn
|
||||||
|
.load_user(&login.username)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("login - error loading user user: {}", e);
|
||||||
|
login_error_redirect("Internal error.")
|
||||||
|
})?
|
||||||
|
.ok_or_else(|| login_error_redirect("Invalid username or password."))?;
|
||||||
|
|
||||||
|
if user.verify_password(&login.password) {
|
||||||
|
add_login_cookie(&mut cookies, &user);
|
||||||
Ok(Redirect::to(uri!(super::root::index)))
|
Ok(Redirect::to(uri!(super::root::index)))
|
||||||
} else {
|
} else {
|
||||||
Err(Flash::error(
|
Err(login_error_redirect("Invalid username or password."))
|
||||||
Redirect::to(uri!(login_page)),
|
|
||||||
"Invalid username orpassword.",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/logout")]
|
#[post("/logout")]
|
||||||
fn logout(mut cookies: Cookies) -> Flash<Redirect> {
|
fn logout(mut cookies: Cookies) -> Flash<Redirect> {
|
||||||
cookies.remove_private(Cookie::named("user_id"));
|
remove_login_cookie(&mut cookies);
|
||||||
Flash::success(Redirect::to(uri!(login_page)), "Successfully logged out.")
|
Flash::success(Redirect::to(uri!(login_page)), "Successfully logged out.")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/login")]
|
#[get("/login")]
|
||||||
fn logged_in_user(_user: User) -> Redirect {
|
fn logged_in_user(_user: &User) -> Redirect {
|
||||||
Redirect::to(uri!(super::root::index))
|
Redirect::to(uri!(super::root::index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,3 +88,50 @@ fn login_page(flash: Option<FlashMessage>) -> Template {
|
||||||
|
|
||||||
Template::render("login", &context)
|
Template::render("login", &context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/register")]
|
||||||
|
fn register_page(flash: Option<FlashMessage>) -> Template {
|
||||||
|
let mut context = HashMap::new();
|
||||||
|
if let Some(ref msg) = flash {
|
||||||
|
context.insert("flash", msg.msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
Template::render("registration", &context)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/register", data = "<registration>")]
|
||||||
|
fn register(
|
||||||
|
mut cookies: Cookies,
|
||||||
|
registration: Form<Registration>,
|
||||||
|
conn: TenebrousDbConn,
|
||||||
|
) -> Result<Redirect, Flash<Redirect>> {
|
||||||
|
let existing_user = conn.load_user(®istration.username).map_err(|e| {
|
||||||
|
error!("registration - error loading existing user: {}", e);
|
||||||
|
registration_error_redirect("There was an error attempting to register.")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if existing_user.is_some() {
|
||||||
|
return Err(registration_error_redirect(format!(
|
||||||
|
"The username {} is already taken.",
|
||||||
|
registration.username
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashed_pw = users::hash_password(®istration.password).map_err(|e| {
|
||||||
|
error!("registration - password hashing error: {}", e);
|
||||||
|
registration_error_redirect("There was an error attempting to register.")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let user = NewUser {
|
||||||
|
username: ®istration.username,
|
||||||
|
password: &hashed_pw,
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = conn.insert_user(&user).map_err(|e| {
|
||||||
|
error!("registration - could not insert user: {}", e);
|
||||||
|
registration_error_redirect("There was an error completing registration.")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
add_login_cookie(&mut cookies, &user);
|
||||||
|
Ok(Redirect::to(uri!(super::root::index)))
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::db::{self, TenebrousDbConn};
|
use crate::db::{Dao, TenebrousDbConn};
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
characters::{CharacterEntry, NewCharacter},
|
characters::{CharacterEntry, NewCharacter},
|
||||||
User,
|
users::User,
|
||||||
};
|
};
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
|
@ -21,20 +21,20 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||||
//TODO make private -- currently is referenced in homepage route.
|
//TODO make private -- currently is referenced in homepage route.
|
||||||
//or move to common place.
|
//or move to common place.
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct TemplateContext {
|
pub struct TemplateContext<'a> {
|
||||||
pub characters: Vec<CharacterEntry>,
|
pub characters: Vec<CharacterEntry>,
|
||||||
pub user: User,
|
pub user: &'a User,
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO should return result based on whether or not character is publicly viewable.
|
//TODO should return result based on whether or not character is publicly viewable.
|
||||||
#[get("/<user>/<character_id>")]
|
#[get("/<username>/<character_id>")]
|
||||||
fn view_character(
|
fn view_character(
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
user: Option<User>,
|
username: String,
|
||||||
conn: TenebrousDbConn,
|
conn: TenebrousDbConn,
|
||||||
) -> Result<Template, Error> {
|
) -> Result<Template, Error> {
|
||||||
let user = user.ok_or(Error::NotFound)?;
|
let user = conn.load_user(&username)?.ok_or(Error::NotFound)?;
|
||||||
let character = db::load_character(conn, character_id)?.ok_or(Error::NotFound)?;
|
let character = conn.load_character(character_id)?.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
let mut context = HashMap::new();
|
let mut context = HashMap::new();
|
||||||
context.insert("name", character.name);
|
context.insert("name", character.name);
|
||||||
|
@ -43,7 +43,7 @@ fn view_character(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/new")]
|
#[get("/new")]
|
||||||
fn new_character(logged_in_user: User, conn: TenebrousDbConn) -> Result<Template, Error> {
|
fn new_character(logged_in_user: &User, conn: TenebrousDbConn) -> Result<Template, Error> {
|
||||||
let context = HashMap::<String, String>::new();
|
let context = HashMap::<String, String>::new();
|
||||||
Ok(Template::render("new_character", context))
|
Ok(Template::render("new_character", context))
|
||||||
}
|
}
|
||||||
|
@ -56,15 +56,15 @@ fn new_character_not_logged_in() -> Redirect {
|
||||||
#[get("/<owner>/<character_id>/edit")]
|
#[get("/<owner>/<character_id>/edit")]
|
||||||
fn edit_character(
|
fn edit_character(
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
owner: Option<User>,
|
owner: String,
|
||||||
logged_in_user: Option<User>,
|
logged_in_user: Option<&User>,
|
||||||
conn: TenebrousDbConn,
|
conn: TenebrousDbConn,
|
||||||
) -> Result<Template, Error> {
|
) -> Result<Template, Error> {
|
||||||
let owner = owner.ok_or(Error::NotFound)?;
|
let owner = conn.load_user(&owner)?.ok_or(Error::NotFound)?;
|
||||||
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
||||||
let character = db::load_character(conn, character_id)?.ok_or(Error::NotFound)?;
|
let character = conn.load_character(character_id)?.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
if logged_in_user != owner {
|
if logged_in_user != &owner {
|
||||||
return Err(Error::NoPermission);
|
return Err(Error::NoPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::db;
|
use crate::db::{Dao, TenebrousDbConn};
|
||||||
use crate::db::TenebrousDbConn;
|
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::models::{characters::CharacterEntry, User};
|
use crate::models::{characters::CharacterEntry, users::User};
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
|
|
||||||
|
@ -10,9 +9,9 @@ pub fn routes() -> Vec<rocket::Route> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn user_index(user: User, conn: TenebrousDbConn) -> Result<Template, Error> {
|
fn user_index(user: &User, conn: TenebrousDbConn) -> Result<Template, Error> {
|
||||||
use crate::routes::characters::TemplateContext;
|
use crate::routes::characters::TemplateContext;
|
||||||
let characters = db::load_character_list(conn, user.id)?;
|
let characters = conn.load_character_list(user.id)?;
|
||||||
|
|
||||||
let context = TemplateContext {
|
let context = TemplateContext {
|
||||||
characters: characters,
|
characters: characters,
|
||||||
|
|
|
@ -7,3 +7,16 @@ table! {
|
||||||
character_data -> Nullable<Binary>,
|
character_data -> Nullable<Binary>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
users (id) {
|
||||||
|
id -> Integer,
|
||||||
|
username -> Text,
|
||||||
|
password -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!(
|
||||||
|
characters,
|
||||||
|
users,
|
||||||
|
);
|
||||||
|
|
|
@ -13,5 +13,11 @@
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<form action="/logout" method="post">
|
||||||
|
<input type="submit" value="Logout" />
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<input type="text" name="username" id="username" value="" />
|
<input type="text" name="username" id="username" value="" />
|
||||||
<label for="password">password</label>
|
<label for="password">password</label>
|
||||||
<input type="password" name="password" id="password" value="" />
|
<input type="password" name="password" id="password" value="" />
|
||||||
<p><input type="submit" value="login"></p>
|
<p><input type="submit" value="Login"></p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "base" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
<h1>Registration</h1>
|
||||||
|
|
||||||
|
{% if flash %}
|
||||||
|
<p>Error: {{ flash }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p>Please register with a username and password.</p>
|
||||||
|
<form action="/register" method="post" accept-charset="utf-8">
|
||||||
|
<div>
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input id="username" name="username" type="text" value="" />
|
||||||
|
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input id="password" name="password" type="password" value="" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="submit" value="Register" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
Loading…
Reference in New Issue