This commit is contained in:
Michael Zhang 2019-02-12 12:05:50 -06:00
parent f2fb3bbc1c
commit fabd64a42e
No known key found for this signature in database
GPG Key ID: 5BAEFE5D04F0CE6C
13 changed files with 254 additions and 75 deletions

32
Cargo.lock generated
View File

@ -47,6 +47,28 @@ name = "autocfg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace-sys"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "base64"
version = "0.9.3"
@ -218,6 +240,7 @@ dependencies = [
name = "core"
version = "0.1.0"
dependencies = [
"backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"bcrypt 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -701,6 +724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "librectf"
version = "0.1.0"
dependencies = [
"backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"core 0.1.0",
"diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1241,6 +1265,11 @@ dependencies = [
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-demangle"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -2033,6 +2062,8 @@ dependencies = [
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
"checksum bcrypt 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a426ab63025c1d21e4e12a218c915fa22097b89ab7ed5765fa803101e004b27"
@ -2163,6 +2194,7 @@ dependencies = [
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
"checksum ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2c4db68a2e35f3497146b7e4563df7d4773a2433230c5e4b448328e31740458a"
"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
"checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9"

View File

@ -11,6 +11,7 @@ members = [
]
[dependencies]
backtrace = "0.3"
diesel = { version = "1.4", optional = true }
env_logger = "0.6"
structopt = "0.2"

View File

@ -2,4 +2,4 @@ doc:
cargo watch -x 'doc --no-deps --document-private-items'
run:
RUST_LOG="frontend=info" cargo watch -x 'run -- run --bind-addr 0.0.0.0:3000 --database-uri=sqlite:///home/michael/Projects/openctf/test.db --secret-key asdfasdfasdfasdfasdfasdfasdfasdf'
RUST_LOG="core=info,warp=info,frontend=info" cargo watch -x 'run -- run --bind-addr 0.0.0.0:3000 --database-uri=sqlite:///home/michael/Projects/openctf/test.db --secret-key asdfasdfasdfasdfasdfasdfasdfasdf'

View File

@ -5,6 +5,7 @@ authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"
[dependencies]
backtrace = "0.3"
bcrypt = "0.3"
chrono = "0.4"
diesel = { version = "1.4", features = ["extras"] }

View File

@ -1,39 +1,66 @@
use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt;
use std::sync::Arc;
use backtrace::Backtrace;
use warp::{Rejection, Reply};
/// ErrorExt trait, based on the avocado lib.
pub trait ErrorExt: StdError {
/// Similar to `std::error::Error::source()`, but with richer type info.
fn reason(&self) -> Option<&(dyn ErrorExt + 'static)> {
None
}
/// Returns the deepest possible backtrace, if any.
fn backtrace(&self) -> Option<&Backtrace> {
None
}
/// Structured error kind.
fn kind(&self) -> ErrorKind;
/// Until subtrait coercions are implemented, this helper method
/// should return the receiver as an `&std::error::Error` trait object.
fn as_std_error(&self) -> &(dyn StdError + 'static);
}
/// An error caused by the user.
#[derive(Clone, Debug)]
pub enum UserError {
#[derive(Copy, Clone, Debug)]
pub enum UserErrorKind {
/// The user supplied bad credentials during login.
BadUsernameOrPassword,
}
/// An error.
#[derive(Clone, Debug)]
pub enum Error {
#[derive(Copy, Clone, Debug)]
pub enum ErrorKind {
#[doc(hidden)]
Bcrypt(Arc<::bcrypt::BcryptError>),
Bcrypt,
#[doc(hidden)]
Diesel(Arc<::diesel::result::Error>),
Diesel,
#[doc(hidden)]
Migrations(Arc<::diesel_migrations::RunMigrationsError>),
Migrations,
#[doc(hidden)]
R2d2(Arc<::r2d2::Error>),
R2d2,
#[doc(hidden)]
Tera(Arc<::tera::ErrorKind>),
Tera,
/// An error caused by the user.
///
/// During rendering, other errors will result in a 500 page, while user
/// errors will be propagated to the generated page and presented to the
/// user.
User(UserError),
// DEBUG
#[doc(hidden)]
Unit,
User(UserErrorKind),
}
/// The main error trait of the crate.
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
message: Cow<'static, str>,
backtrace: Option<Backtrace>,
cause: Option<Box<dyn ErrorExt + Send + Sync + 'static>>,
}
impl Error {
@ -45,6 +72,62 @@ impl Error {
Err(err)
}
}
/// Create an error that was caused by a user
pub fn user<M>(message: M, kind: UserErrorKind) -> Self
where
M: Into<Cow<'static, str>>,
{
Error {
kind: ErrorKind::User(kind),
message: message.into(),
backtrace: None,
cause: None,
}
}
/// Create a message from just a message and a kind
pub fn new<M>(message: M, kind: ErrorKind) -> Self
where
M: Into<Cow<'static, str>>,
{
Error {
message: message.into(),
kind,
backtrace: None,
cause: None,
}
}
/// Chains an error with a cause
pub fn with_cause<M, E>(message: M, cause: E) -> Self
where
M: Into<Cow<'static, str>>,
E: ErrorExt + Send + Sync + 'static,
{
let kind = cause.kind();
let message = message.into();
let backtrace = Some(match cause.backtrace() {
Some(backtrace) => backtrace.clone(),
None => Backtrace::new(),
});
let cause: Option<Box<dyn ErrorExt + Send + Sync + 'static>> = Some(Box::new(cause));
Error {
kind,
message,
backtrace,
cause,
}
}
}
impl ErrorExt for Error {
fn kind(&self) -> ErrorKind {
self.kind
}
fn as_std_error(&self) -> &(dyn StdError + 'static) {
self
}
}
impl fmt::Display for Error {
@ -56,38 +139,42 @@ impl fmt::Display for Error {
impl StdError for Error {}
impl From<::bcrypt::BcryptError> for Error {
fn from(err: ::bcrypt::BcryptError) -> Self {
Error::Bcrypt(Arc::new(err))
}
/// Implementing `ErrorExt` and `From` boilerplate.
macro_rules! impl_error_type {
($ty:path, $kind:ident, $message:expr) => {
impl From<$ty> for Error {
fn from(error: $ty) -> Self {
Self::with_cause($message, error)
}
}
impl ErrorExt for $ty {
fn kind(&self) -> ErrorKind {
ErrorKind::$kind
}
fn as_std_error(&self) -> &(dyn StdError + 'static) {
self
}
}
};
}
impl From<::diesel::result::Error> for Error {
fn from(err: ::diesel::result::Error) -> Self {
Error::Diesel(Arc::new(err))
}
}
impl_error_type!(::bcrypt::BcryptError, Bcrypt, "bcrypt error");
impl_error_type!(::diesel::result::Error, Diesel, "diesel error");
impl_error_type!(
::diesel_migrations::RunMigrationsError,
Migrations,
"migrations error"
);
impl_error_type!(::r2d2::Error, R2d2, "r2d2 error");
impl From<::diesel_migrations::RunMigrationsError> for Error {
fn from(err: ::diesel_migrations::RunMigrationsError) -> Self {
Error::Migrations(Arc::new(err))
impl ErrorExt for ::tera::Error {
fn kind(&self) -> ErrorKind {
ErrorKind::Tera
}
}
impl From<::r2d2::Error> for Error {
fn from(err: ::r2d2::Error) -> Self {
Error::R2d2(Arc::new(err))
}
}
impl From<::tera::Error> for Error {
fn from(err: ::tera::Error) -> Self {
Error::Tera(Arc::new(err.kind))
}
}
impl From<()> for Error {
fn from(_: ()) -> Self {
Error::Unit
fn as_std_error(&self) -> &(dyn StdError + 'static) {
self
}
}

View File

@ -27,5 +27,5 @@ pub mod users;
pub use crate::config::Config;
pub use crate::db::{DatabaseUri, DbConn, DbPool};
pub use crate::errors::{Error, UserError};
pub use crate::errors::{Error, ErrorKind, UserErrorKind};
pub use crate::state::State;

View File

@ -4,7 +4,7 @@ use wtforms::Form;
use crate::db::DbConn;
use crate::models::{NewUser, User};
use crate::{Error, UserError};
use crate::{Error, UserErrorKind};
/// The struct behind the form used in the login page.
#[derive(Form, Serialize, Deserialize)]
@ -16,7 +16,7 @@ pub struct LoginForm {
/// Attempts to log in a user using a LoginForm. Upon success, the user struct
/// associated with that account is returned. If the user supplies bad
/// credentials, a `UserError::BadUsernameOrPassword` will be returned.
/// credentials, a `UserErrorKind::BadUsernameOrPassword` will be returned.
pub fn login_user(db: &DbConn, form: &LoginForm) -> Result<User, Error> {
db.fetch_user(&form.email)
.and_then(|user| {
@ -28,7 +28,10 @@ pub fn login_user(db: &DbConn, form: &LoginForm) -> Result<User, Error> {
if result {
Ok(user)
} else {
Err(Error::User(UserError::BadUsernameOrPassword))
Err(Error::user(
"bad username or password",
UserErrorKind::BadUsernameOrPassword,
))
}
})
}
@ -44,7 +47,7 @@ pub struct RegisterForm {
/// Attempts to create a user using the given information. If the user tries
/// to create an account with an email or username that already exists, then
/// a `UserError` will be generated. (TODO: implement this)
/// a `UserErrorKind` will be generated. (TODO: implement this)
pub fn register_user(db: &DbConn, form: &RegisterForm) -> Result<i32, Error> {
let password = bcrypt::hash(form.password.to_owned(), bcrypt::DEFAULT_COST)?;
let new_user = NewUser {

View File

@ -4,7 +4,7 @@ use tera::Context as TeraContext;
use warp::{Filter, Rejection};
use crate::session::Session;
pub use warp::ext::get as get;
pub use warp::ext::get;
#[derive(Clone, Default)]
pub struct Context(pub TeraContext);
@ -36,7 +36,7 @@ pub fn get_context() -> impl Clone + Filter<Extract = (Context,), Error = Reject
fn render_navbar(session: &Session, ctx: &mut Context) -> Result<(), Error> {
// retrieve the user data
if let Some(user) = &session.user {
if let Some(user) = session.get_user() {
ctx.insert("user", &user);
}

View File

@ -1,4 +1,4 @@
use core::Error;
use core::{Error, ErrorKind};
use tera::Context;
use warp::Rejection;
@ -7,6 +7,6 @@ use crate::Renderer;
pub fn render_template(path: impl AsRef<str>, ctx: Context) -> Result<String, Rejection> {
Renderer
.render(path.as_ref(), ctx)
.map_err(Error::from)
.map_err(|_err| Error::new("render error", ErrorKind::Tera))
.map_err(warp::reject::custom)
}

View File

@ -15,31 +15,65 @@ pub struct SessionRepr {
}
#[derive(Clone, Default)]
pub struct Session {
pub(crate) struct SessionInner {
pub user: Option<User>,
pub team: Option<Team>,
}
#[derive(Clone, Default)]
pub struct Session(pub(crate) Option<SessionInner>);
impl Session {
fn init(&mut self) {
if let None = self.0 {
self.0 = Some(SessionInner::default());
}
}
pub fn set_team(&mut self, team: Team) {
self.init();
if let Some(ref mut inner) = &mut self.0 {
inner.team = Some(team);
}
}
pub fn get_team(&self) -> Option<&Team> {
self.0.as_ref().and_then(|inner| inner.team.as_ref())
}
pub fn set_user(&mut self, user: User) {
self.init();
if let Some(ref mut inner) = &mut self.0 {
inner.user = Some(user);
}
}
pub fn get_user(&self) -> Option<&User> {
self.0.as_ref().and_then(|inner| inner.user.as_ref())
}
pub fn try_from(conn: &DbConn, repr: SessionRepr) -> Result<Session, Error> {
let user = repr.user_id.and_then(|id| conn.fetch_user_id(id).ok());
let team = user
.as_ref()
.and_then(|user| user.team_id)
.and_then(|id| conn.fetch_team_id(id).ok());
Ok(Session { user, team })
}
pub fn repr(self) -> SessionRepr {
SessionRepr {
user_id: self.user.map(|user| user.id),
team_id: self.team.map(|team| team.id),
let mut session = Session::default();
if let Some(user) = user {
session.set_user(user);
}
}
pub fn is_empty(&self) -> bool {
match (&self.user, &self.team) {
(None, None) => true,
_ => false,
if let Some(team) = team {
session.set_team(team);
}
Ok(session)
}
pub fn repr(&self) -> Option<SessionRepr> {
self.0.as_ref().map(|inner| SessionRepr {
user_id: inner.user.as_ref().map(|user| user.id),
team_id: inner.team.as_ref().map(|team| team.id),
})
}
}

View File

@ -2,7 +2,7 @@ use core::models::Team;
use http::uri::Uri;
use warp::Filter;
use crate::extractors::{get_context, get, navbar, Context};
use crate::extractors::{get, get_context, navbar, Context};
use crate::guards::require_login;
use crate::render::render_template;

View File

@ -1,13 +1,13 @@
use core::{
models::User,
users::{LoginForm, RegisterForm},
Error, State,
Error, State, UserErrorKind,
};
use http::uri::Uri;
use warp::Filter;
use wtforms::Form;
use crate::extractors::{get_context, get, navbar, Context};
use crate::extractors::{get, get_context, navbar, Context};
use crate::render::render_template;
use crate::session::Session;
@ -22,7 +22,12 @@ pub fn post_login() -> Resp!() {
warp::body::form()
.and_then(|form: LoginForm| {
form.validate()
.map_err(Error::from)
.map_err(|_| {
Error::user(
"bad username or password",
UserErrorKind::BadUsernameOrPassword,
)
})
.map_err(warp::reject::custom)
})
.and(get::<State>())
@ -34,7 +39,7 @@ pub fn post_login() -> Resp!() {
})
.and(get::<Session>())
.map(|user: User, mut session: Session| {
session.user = Some(user);
session.set_user(user);
warp::ext::set::<Session>(session);
warp::redirect::redirect(Uri::from_static("/users/profile"))
})
@ -65,7 +70,12 @@ pub fn post_register() -> Resp!() {
warp::body::form()
.and_then(|form: RegisterForm| {
form.validate()
.map_err(Error::from)
.map_err(|_| {
Error::user(
"bad registration info",
UserErrorKind::BadUsernameOrPassword,
)
})
.map_err(warp::reject::custom)
})
.and(get::<State>())

View File

@ -2,7 +2,8 @@ use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use core::{DatabaseUri, State};
use backtrace::Backtrace;
use core::{DatabaseUri, Error, State};
use structopt::StructOpt;
#[derive(StructOpt)]
@ -44,10 +45,7 @@ struct RunOpts {
secret_key: String,
}
fn main() {
env_logger::init();
let opt = Opt::from_args();
fn run(opt: Opt) -> Result<(), Error> {
match opt.command {
Commands::Migrate(opts) => {
let db = opts
@ -69,4 +67,17 @@ fn main() {
warp::serve(frontend::routes(state)).run(opts.addr);
}
}
Ok(())
}
fn main() {
env_logger::init();
let backtrace = Backtrace::new();
let opt = Opt::from_args();
match run(opt) {
Ok(_) => (),
Err(err) => {
println!("backtrace: {:?}", backtrace);
}
}
}