From 87d8d8ff0c06f59448a7a874beda6239b2c6ea0e Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 29 Aug 2021 20:30:33 +0200 Subject: [PATCH] Switched to binary-only project --- Cargo.toml | 4 -- src/auth/jwt.rs | 6 +-- src/auth/mod.rs | 91 +++++++++++++++++++++------------------------ src/auth/pass.rs | 61 ++++++++++++++++++++++++++++++ src/db/users.rs | 8 ++-- src/errors.rs | 2 +- src/guards.rs | 11 +++--- src/lib.rs | 17 --------- src/main.rs | 30 ++++++++++++--- src/routes/admin.rs | 19 +++++----- src/routes/auth.rs | 60 ------------------------------ src/routes/mod.rs | 1 - 12 files changed, 152 insertions(+), 158 deletions(-) create mode 100644 src/auth/pass.rs delete mode 100644 src/lib.rs delete mode 100644 src/routes/auth.rs diff --git a/Cargo.toml b/Cargo.toml index ebdf355..ff6ac33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,6 @@ version = "0.1.0" authors = ["Jef Roosens "] edition = "2018" -[lib] -name = "rb" -path = "src/lib.rs" - [[bin]] name = "rbd" path = "src/main.rs" diff --git a/src/auth/jwt.rs b/src/auth/jwt.rs index 8767bb3..4b8923f 100644 --- a/src/auth/jwt.rs +++ b/src/auth/jwt.rs @@ -11,7 +11,7 @@ use crate::{ tokens::{NewRefreshToken, RefreshToken}, users::User, }, - errors::RbError, + errors::{RbError, RbResult}, schema::{refresh_tokens::dsl as refresh_tokens, users::dsl as users}, }; @@ -32,7 +32,7 @@ pub struct Claims pub exp: i64, } -pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result +pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> RbResult { let secret = std::env::var("JWT_KEY").map_err(|_| RbError::Custom("Missing JWT key."))?; let key: Hmac = Hmac::new_from_slice(secret.as_bytes()) @@ -76,7 +76,7 @@ pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result crate::Result +pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> RbResult { let token_bytes = base64::decode(refresh_token).map_err(|_| RbError::AuthInvalidRefreshToken)?; diff --git a/src/auth/mod.rs b/src/auth/mod.rs index b315e82..f239f9f 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -1,63 +1,58 @@ -use argon2::verify_encoded; -use diesel::{insert_into, prelude::*, PgConnection}; -use rand::{thread_rng, Rng}; +use rocket::serde::json::Json; +use serde::Deserialize; -use crate::{ - db::users::{NewUser, User}, - errors::RbError, - schema::users::dsl as users, +use self::{ + jwt::{generate_jwt_token, JWTResponse}, + pass::verify_user, }; +use crate::{guards::User, RbDbConn, RbResult}; pub mod jwt; +pub mod pass; -pub fn verify_user(conn: &PgConnection, username: &str, password: &str) -> crate::Result +#[derive(Deserialize)] +pub struct Credentials { - // TODO handle non-"NotFound" Diesel errors accordingely - let user = users::users - .filter(users::username.eq(username)) - .first::(conn) - .map_err(|_| RbError::AuthUnknownUser)?; - - // Check if a user is blocked - if user.blocked { - return Err(RbError::AuthBlockedUser); - } - - match verify_encoded(user.password.as_str(), password.as_bytes()) { - Ok(true) => Ok(user), - _ => Err(RbError::AuthInvalidPassword), - } + username: String, + password: String, } -pub fn hash_password(password: &str) -> crate::Result +#[post("/login")] +pub async fn already_logged_in(_user: User) -> String { - // Generate a random salt - let mut salt = [0u8; 64]; - thread_rng().fill(&mut salt[..]); - - // Encode the actual password - let config = argon2::Config::default(); - argon2::hash_encoded(password.as_bytes(), &salt, &config) - .map_err(|_| RbError::Custom("Couldn't hash password.")) + String::from("You're already logged in!") } -pub fn create_admin_user(conn: &PgConnection, username: &str, password: &str) - -> crate::Result +#[post("/login", data = "", rank = 2)] +pub async fn login(conn: RbDbConn, credentials: Json) -> RbResult> { - let pass_hashed = hash_password(password)?; - let new_user = NewUser { - username: username.to_string(), - password: pass_hashed, - admin: true, - }; + let credentials = credentials.into_inner(); - insert_into(users::users) - .values(&new_user) - .on_conflict(users::username) - .do_update() - .set(&new_user) - .execute(conn) - .map_err(|_| RbError::Custom("Couldn't create admin."))?; + // Get the user, if credentials are valid + let user = conn + .run(move |c| verify_user(c, &credentials.username, &credentials.password)) + .await?; - Ok(true) + Ok(Json(conn.run(move |c| generate_jwt_token(c, &user)).await?)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RefreshTokenRequest +{ + pub refresh_token: String, +} + +#[post("/refresh", data = "")] +pub async fn refresh_token( + conn: RbDbConn, + refresh_token_request: Json, +) -> RbResult> +{ + let refresh_token = refresh_token_request.into_inner().refresh_token; + + Ok(Json( + conn.run(move |c| crate::auth::jwt::refresh_token(c, &refresh_token)) + .await?, + )) } diff --git a/src/auth/pass.rs b/src/auth/pass.rs new file mode 100644 index 0000000..b67bf11 --- /dev/null +++ b/src/auth/pass.rs @@ -0,0 +1,61 @@ +use argon2::verify_encoded; +use diesel::{insert_into, prelude::*, PgConnection}; +use rand::{thread_rng, Rng}; + +use crate::{ + db::users::{NewUser, User}, + errors::RbError, + schema::users::dsl as users, + RbResult, +}; + +pub fn verify_user(conn: &PgConnection, username: &str, password: &str) -> RbResult +{ + // TODO handle non-"NotFound" Diesel errors accordingely + let user = users::users + .filter(users::username.eq(username)) + .first::(conn) + .map_err(|_| RbError::AuthUnknownUser)?; + + // Check if a user is blocked + if user.blocked { + return Err(RbError::AuthBlockedUser); + } + + match verify_encoded(user.password.as_str(), password.as_bytes()) { + Ok(true) => Ok(user), + _ => Err(RbError::AuthInvalidPassword), + } +} + +pub fn hash_password(password: &str) -> RbResult +{ + // Generate a random salt + let mut salt = [0u8; 64]; + thread_rng().fill(&mut salt[..]); + + // Encode the actual password + let config = argon2::Config::default(); + argon2::hash_encoded(password.as_bytes(), &salt, &config) + .map_err(|_| RbError::Custom("Couldn't hash password.")) +} + +pub fn create_admin_user(conn: &PgConnection, username: &str, password: &str) -> RbResult +{ + let pass_hashed = hash_password(password)?; + let new_user = NewUser { + username: username.to_string(), + password: pass_hashed, + admin: true, + }; + + insert_into(users::users) + .values(&new_user) + .on_conflict(users::username) + .do_update() + .set(&new_user) + .execute(conn) + .map_err(|_| RbError::Custom("Couldn't create admin."))?; + + Ok(true) +} diff --git a/src/db/users.rs b/src/db/users.rs index 79d337a..d6ae131 100644 --- a/src/db/users.rs +++ b/src/db/users.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - errors::RbError, + errors::{RbError, RbResult}, schema::{users, users::dsl::*}, }; @@ -28,7 +28,7 @@ pub struct NewUser pub admin: bool, } -pub fn all(conn: &PgConnection) -> crate::Result> +pub fn all(conn: &PgConnection) -> RbResult> { users .load::(conn) @@ -40,7 +40,7 @@ pub fn find(conn: &PgConnection, user_id: Uuid) -> Option users.find(user_id).first::(conn).ok() } -pub fn create(conn: &PgConnection, new_user: &NewUser) -> crate::Result<()> +pub fn create(conn: &PgConnection, new_user: &NewUser) -> RbResult<()> { let count = diesel::insert_into(users) .values(new_user) @@ -54,7 +54,7 @@ pub fn create(conn: &PgConnection, new_user: &NewUser) -> crate::Result<()> Ok(()) } -pub fn delete(conn: &PgConnection, user_id: Uuid) -> crate::Result<()> +pub fn delete(conn: &PgConnection, user_id: Uuid) -> RbResult<()> { diesel::delete(users.filter(id.eq(user_id))) .execute(conn) diff --git a/src/errors.rs b/src/errors.rs index daca6bb..2257fa5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -84,4 +84,4 @@ impl<'r> Responder<'r, 'static> for RbError } } -pub type Result = std::result::Result; +pub type RbResult = std::result::Result; diff --git a/src/guards.rs b/src/guards.rs index 55df193..532ba97 100644 --- a/src/guards.rs +++ b/src/guards.rs @@ -1,6 +1,5 @@ use hmac::{Hmac, NewMac}; use jwt::VerifyWithKey; -use rb::auth::jwt::Claims; use rocket::{ http::Status, outcome::try_outcome, @@ -8,13 +7,15 @@ use rocket::{ }; use sha2::Sha256; +use crate::auth::jwt::Claims; + /// Extracts a "Authorization: Bearer" string from the headers. pub struct Bearer<'a>(&'a str); #[rocket::async_trait] impl<'r> FromRequest<'r> for Bearer<'r> { - type Error = rb::errors::RbError; + type Error = crate::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { @@ -44,7 +45,7 @@ pub struct Jwt(Claims); #[rocket::async_trait] impl<'r> FromRequest<'r> for Jwt { - type Error = rb::errors::RbError; + type Error = crate::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { @@ -87,7 +88,7 @@ pub struct User(Claims); #[rocket::async_trait] impl<'r> FromRequest<'r> for User { - type Error = rb::errors::RbError; + type Error = crate::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { @@ -108,7 +109,7 @@ pub struct Admin(Claims); #[rocket::async_trait] impl<'r> FromRequest<'r> for Admin { - type Error = rb::errors::RbError; + type Error = crate::errors::RbError; async fn from_request(req: &'r Request<'_>) -> Outcome { diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 2c7d7f1..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[macro_use] -extern crate diesel; - -pub mod auth; -pub mod db; -pub mod errors; -pub(crate) mod schema; - -pub use errors::Result; - -// Any import defaults are defined here -/// Expire time for the JWT tokens in seconds. -const JWT_EXP_SECONDS: i64 = 600; -/// Amount of bytes the refresh tokens should consist of -const REFRESH_TOKEN_N_BYTES: usize = 64; -/// Expire time for refresh tokens; here: one week -const REFRESH_TOKEN_EXP_SECONDS: i64 = 604800; diff --git a/src/main.rs b/src/main.rs index 2ca414d..fc15246 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,38 @@ // This needs to be explicitely included before diesel is imported to make sure // compilation succeeds in the release Docker image. extern crate openssl; - #[macro_use] extern crate rocket; #[macro_use] extern crate diesel_migrations; +#[macro_use] +extern crate diesel; use rocket::{fairing::AdHoc, Build, Rocket}; -use rocket_sync_db_pools::{database, diesel}; +use rocket_sync_db_pools::database; -pub(crate) mod guards; +pub use crate::errors::{RbError, RbResult}; + +pub mod auth; +pub mod db; +pub mod errors; +pub mod guards; mod routes; +pub(crate) mod schema; -embed_migrations!(); +// Any import defaults are defined here +/// Expire time for the JWT tokens in seconds. +const JWT_EXP_SECONDS: i64 = 600; +/// Amount of bytes the refresh tokens should consist of +const REFRESH_TOKEN_N_BYTES: usize = 64; +/// Expire time for refresh tokens; here: one week +const REFRESH_TOKEN_EXP_SECONDS: i64 = 604800; #[database("postgres_rb")] pub struct RbDbConn(diesel::PgConnection); +embed_migrations!(); + async fn run_db_migrations(rocket: Rocket) -> Result, Rocket> { let conn = RbDbConn::get_one(&rocket) @@ -39,7 +54,7 @@ async fn create_admin_user(rocket: Rocket) -> Result, Rocke .await .expect("database connection"); conn.run(move |c| { - rb::auth::create_admin_user(c, &admin_user, &admin_password) + auth::pass::create_admin_user(c, &admin_user, &admin_password) .expect("failed to create admin user") }) .await; @@ -57,6 +72,9 @@ fn rocket() -> _ run_db_migrations, )) .attach(AdHoc::try_on_ignite("Create admin user", create_admin_user)) - .mount("/api/auth", routes::auth::routes()) + .mount( + "/api/auth", + routes![auth::already_logged_in, auth::login, auth::refresh_token,], + ) .mount("/api/admin", routes::admin::routes()) } diff --git a/src/routes/admin.rs b/src/routes/admin.rs index 9da75f5..741f4dd 100644 --- a/src/routes/admin.rs +++ b/src/routes/admin.rs @@ -1,8 +1,12 @@ -use rb::{db, errors::RbError}; use rocket::serde::json::Json; use uuid::Uuid; -use crate::{guards::Admin, RbDbConn}; +use crate::{ + db, + errors::{RbError, RbResult}, + guards::Admin, + RbDbConn, +}; pub fn routes() -> Vec { @@ -10,13 +14,13 @@ pub fn routes() -> Vec } #[get("/users")] -async fn get_users(_admin: Admin, conn: RbDbConn) -> rb::Result>> +async fn get_users(_admin: Admin, conn: RbDbConn) -> RbResult>> { Ok(Json(conn.run(|c| db::users::all(c)).await?)) } #[post("/users", data = "")] -async fn create_user(_admin: Admin, conn: RbDbConn, user: Json) -> rb::Result<()> +async fn create_user(_admin: Admin, conn: RbDbConn, user: Json) -> RbResult<()> { Ok(conn .run(move |c| db::users::create(c, &user.into_inner())) @@ -24,11 +28,8 @@ async fn create_user(_admin: Admin, conn: RbDbConn, user: Json) -> } #[get("/users/")] -async fn get_user_info( - _admin: Admin, - conn: RbDbConn, - user_id_str: &str, -) -> rb::Result> +async fn get_user_info(_admin: Admin, conn: RbDbConn, user_id_str: &str) + -> RbResult> { let user_id = Uuid::parse_str(user_id_str).map_err(|_| RbError::UMUnknownUser)?; diff --git a/src/routes/auth.rs b/src/routes/auth.rs deleted file mode 100644 index 955cfaa..0000000 --- a/src/routes/auth.rs +++ /dev/null @@ -1,60 +0,0 @@ -use rb::auth::{ - jwt::{generate_jwt_token, JWTResponse}, - verify_user, -}; -use rocket::serde::json::Json; -use serde::Deserialize; - -use crate::{guards::User, RbDbConn}; - -pub(crate) fn routes() -> Vec -{ - routes![login, already_logged_in, refresh_token] -} - -#[derive(Deserialize)] -struct Credentials -{ - username: String, - password: String, -} - -#[post("/login")] -async fn already_logged_in(_user: User) -> String -{ - String::from("You're already logged in!") -} - -#[post("/login", data = "", rank = 2)] -async fn login(conn: RbDbConn, credentials: Json) -> rb::Result> -{ - let credentials = credentials.into_inner(); - - // Get the user, if credentials are valid - let user = conn - .run(move |c| verify_user(c, &credentials.username, &credentials.password)) - .await?; - - Ok(Json(conn.run(move |c| generate_jwt_token(c, &user)).await?)) -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct RefreshTokenRequest -{ - pub refresh_token: String, -} - -#[post("/refresh", data = "")] -async fn refresh_token( - conn: RbDbConn, - refresh_token_request: Json, -) -> rb::Result> -{ - let refresh_token = refresh_token_request.into_inner().refresh_token; - - Ok(Json( - conn.run(move |c| rb::auth::jwt::refresh_token(c, &refresh_token)) - .await?, - )) -} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 52815c1..92918b0 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,2 +1 @@ pub mod admin; -pub mod auth;