Configured Rustfmt
parent
b13b760e2f
commit
16ddc9aecd
|
@ -0,0 +1,69 @@
|
|||
binop_separator = "Front"
|
||||
blank_lines_lower_bound = 0
|
||||
blank_lines_upper_bound = 1
|
||||
# Trying something new
|
||||
brace_style = "AlwaysNextLine"
|
||||
color = "Auto"
|
||||
combine_control_expr = false
|
||||
comment_width = 80
|
||||
condense_wildcard_suffixes = false
|
||||
control_brace_style = "AlwaysSameLine"
|
||||
disable_all_formatting = false
|
||||
edition = "2018"
|
||||
emit_mode = "Files"
|
||||
empty_item_single_line = true
|
||||
enum_discrim_align_threshold = 0
|
||||
error_on_line_overflow = false
|
||||
error_on_unformatted = false
|
||||
fn_args_layout = "Tall"
|
||||
fn_single_line = false
|
||||
force_explicit_abi = true
|
||||
force_multiline_blocks = false
|
||||
format_code_in_doc_comments = false
|
||||
format_macro_bodies = true
|
||||
format_macro_matchers = false
|
||||
format_strings = false
|
||||
group_imports = "StdExternalCrate"
|
||||
hard_tabs = false
|
||||
hide_parse_errors = false
|
||||
ignore = []
|
||||
imports_granularity = "Crate"
|
||||
imports_indent = "Block"
|
||||
imports_layout = "Mixed"
|
||||
indent_style = "Block"
|
||||
inline_attribute_width = 0
|
||||
license_template_path = ""
|
||||
make_backup = false
|
||||
match_arm_blocks = true
|
||||
match_arm_leading_pipes = "Never"
|
||||
match_block_trailing_comma = false
|
||||
max_width = 100
|
||||
merge_derives = true
|
||||
newline_style = "Auto"
|
||||
normalize_comments = false
|
||||
normalize_doc_attributes = false
|
||||
overflow_delimited_expr = false
|
||||
remove_nested_parens = true
|
||||
reorder_impl_items = false
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
report_fixme = "Always"
|
||||
report_todo = "Always"
|
||||
required_version = "1.4.36"
|
||||
skip_children = false
|
||||
space_after_colon = true
|
||||
space_before_colon = false
|
||||
spaces_around_ranges = false
|
||||
struct_field_align_threshold = 0
|
||||
struct_lit_single_line = true
|
||||
tab_spaces = 4
|
||||
trailing_comma = "Vertical"
|
||||
trailing_semicolon = true
|
||||
type_punctuation_density = "Wide"
|
||||
unstable_features = false
|
||||
use_field_init_shorthand = false
|
||||
use_small_heuristics = "Default"
|
||||
use_try_shorthand = false
|
||||
version = "One"
|
||||
where_single_line = false
|
||||
wrap_comments = false
|
45
src/auth.rs
45
src/auth.rs
|
@ -1,21 +1,23 @@
|
|||
use crate::errors::RBError;
|
||||
use crate::db::{
|
||||
users::{User, NewUser},
|
||||
tokens::{RefreshToken, NewRefreshToken}
|
||||
};
|
||||
use crate::schema::refresh_tokens::dsl as refresh_tokens;
|
||||
use crate::schema::users::dsl as users;
|
||||
use argon2::verify_encoded;
|
||||
use chrono::Utc;
|
||||
use diesel::prelude::*;
|
||||
use diesel::{insert_into, PgConnection};
|
||||
use diesel::{insert_into, prelude::*, PgConnection};
|
||||
use hmac::{Hmac, NewMac};
|
||||
use jwt::SignWithKey;
|
||||
use rand::{thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
|
||||
pub fn verify_user(conn: &PgConnection, username: &str, password: &str) -> crate::Result<User> {
|
||||
use crate::{
|
||||
db::{
|
||||
tokens::{NewRefreshToken, RefreshToken},
|
||||
users::{NewUser, User},
|
||||
},
|
||||
errors::RBError,
|
||||
schema::{refresh_tokens::dsl as refresh_tokens, users::dsl as users},
|
||||
};
|
||||
|
||||
pub fn verify_user(conn: &PgConnection, username: &str, password: &str) -> crate::Result<User>
|
||||
{
|
||||
// TODO handle non-"NotFound" Diesel errors accordingely
|
||||
let user = users::users
|
||||
.filter(users::username.eq(username))
|
||||
|
@ -35,20 +37,23 @@ pub fn verify_user(conn: &PgConnection, username: &str, password: &str) -> crate
|
|||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JWTResponse {
|
||||
pub struct JWTResponse
|
||||
{
|
||||
token: String,
|
||||
refresh_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub struct Claims
|
||||
{
|
||||
pub id: uuid::Uuid,
|
||||
pub username: String,
|
||||
pub admin: bool,
|
||||
pub exp: i64,
|
||||
}
|
||||
|
||||
pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result<JWTResponse> {
|
||||
pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result<JWTResponse>
|
||||
{
|
||||
let secret = std::env::var("JWT_KEY").map_err(|_| RBError::MissingJWTKey)?;
|
||||
let key: Hmac<Sha256> =
|
||||
Hmac::new_from_slice(secret.as_bytes()).map_err(|_| RBError::JWTCreationError)?;
|
||||
|
@ -92,7 +97,8 @@ pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> crate::Result<JWT
|
|||
})
|
||||
}
|
||||
|
||||
pub fn hash_password(password: &str) -> crate::Result<String> {
|
||||
pub fn hash_password(password: &str) -> crate::Result<String>
|
||||
{
|
||||
// Generate a random salt
|
||||
let mut salt = [0u8; 64];
|
||||
thread_rng().fill(&mut salt[..]);
|
||||
|
@ -102,11 +108,9 @@ pub fn hash_password(password: &str) -> crate::Result<String> {
|
|||
argon2::hash_encoded(password.as_bytes(), &salt, &config).map_err(|_| RBError::PWSaltError)
|
||||
}
|
||||
|
||||
pub fn create_admin_user(
|
||||
conn: &PgConnection,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> crate::Result<bool> {
|
||||
pub fn create_admin_user(conn: &PgConnection, username: &str, password: &str)
|
||||
-> crate::Result<bool>
|
||||
{
|
||||
let pass_hashed = hash_password(password)?;
|
||||
let new_user = NewUser {
|
||||
username: username.to_string(),
|
||||
|
@ -125,7 +129,8 @@ pub fn create_admin_user(
|
|||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> crate::Result<JWTResponse> {
|
||||
pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> crate::Result<JWTResponse>
|
||||
{
|
||||
let token_bytes = base64::decode(refresh_token).map_err(|_| RBError::InvalidRefreshToken)?;
|
||||
|
||||
// First, we request the token from the database to see if it's really a valid token
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pub mod users;
|
||||
pub mod tokens;
|
||||
pub mod users;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use diesel::{Insertable, Queryable};
|
||||
use uuid::Uuid;
|
||||
use diesel::{Queryable, Insertable};
|
||||
|
||||
use crate::schema::refresh_tokens;
|
||||
|
||||
|
||||
#[derive(Queryable)]
|
||||
pub struct RefreshToken {
|
||||
pub struct RefreshToken
|
||||
{
|
||||
pub token: Vec<u8>,
|
||||
pub user_id: Uuid,
|
||||
pub expires_at: chrono::NaiveDateTime,
|
||||
|
@ -13,7 +14,8 @@ pub struct RefreshToken {
|
|||
|
||||
#[derive(Insertable)]
|
||||
#[table_name = "refresh_tokens"]
|
||||
pub struct NewRefreshToken {
|
||||
pub struct NewRefreshToken
|
||||
{
|
||||
pub token: Vec<u8>,
|
||||
pub user_id: Uuid,
|
||||
pub expires_at: chrono::NaiveDateTime,
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use crate::schema::users;
|
||||
use diesel::{AsChangeset, Insertable, Queryable, prelude::*};
|
||||
use diesel::{prelude::*, AsChangeset, Insertable, Queryable};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
use crate::schema::users::dsl::*;
|
||||
use crate::errors::RBError;
|
||||
|
||||
use crate::{
|
||||
errors::RBError,
|
||||
schema::{users, users::dsl::*},
|
||||
};
|
||||
|
||||
#[derive(Queryable, Serialize)]
|
||||
pub struct User {
|
||||
pub struct User
|
||||
{
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
#[serde(skip_serializing)]
|
||||
|
@ -18,12 +21,14 @@ pub struct User {
|
|||
|
||||
#[derive(Insertable, AsChangeset)]
|
||||
#[table_name = "users"]
|
||||
pub struct NewUser {
|
||||
pub struct NewUser
|
||||
{
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub admin: bool,
|
||||
}
|
||||
|
||||
pub fn all(conn: &PgConnection) -> crate::Result<Vec<User>> {
|
||||
pub fn all(conn: &PgConnection) -> crate::Result<Vec<User>>
|
||||
{
|
||||
users.load::<User>(conn).map_err(|_| RBError::DBError)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
use rocket::http::Status;
|
||||
use rocket::request::Request;
|
||||
use rocket::response::{self, Responder, Response};
|
||||
use std::io;
|
||||
|
||||
use rocket::{
|
||||
http::Status,
|
||||
request::Request,
|
||||
response::{self, Responder, Response},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RBError {
|
||||
pub enum RBError
|
||||
{
|
||||
/// When the login requests an unknown user
|
||||
UnknownUser,
|
||||
BlockedUser,
|
||||
|
@ -26,8 +30,10 @@ pub enum RBError {
|
|||
DBError,
|
||||
}
|
||||
|
||||
impl<'r> Responder<'r, 'static> for RBError {
|
||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
||||
impl<'r> Responder<'r, 'static> for RBError
|
||||
{
|
||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static>
|
||||
{
|
||||
let (status, message): (Status, &str) = match self {
|
||||
RBError::UnknownUser => (Status::NotFound, "Unknown user"),
|
||||
RBError::BlockedUser => (Status::Unauthorized, "This user is blocked"),
|
||||
|
|
|
@ -12,10 +12,12 @@ use sha2::Sha256;
|
|||
pub struct Bearer(String);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for Bearer {
|
||||
impl<'r> FromRequest<'r> for Bearer
|
||||
{
|
||||
type Error = rb::errors::RBError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error>
|
||||
{
|
||||
// If the header isn't present, just forward to the next route
|
||||
let header = match req.headers().get_one("Authorization") {
|
||||
None => return Outcome::Forward(()),
|
||||
|
@ -40,10 +42,12 @@ impl<'r> FromRequest<'r> for Bearer {
|
|||
pub struct JWT(Claims);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for JWT {
|
||||
impl<'r> FromRequest<'r> for JWT
|
||||
{
|
||||
type Error = rb::errors::RBError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error>
|
||||
{
|
||||
let bearer = try_outcome!(req.guard::<Bearer>().await).0;
|
||||
|
||||
// Get secret & key
|
||||
|
@ -73,10 +77,12 @@ impl<'r> FromRequest<'r> for JWT {
|
|||
pub struct User(Claims);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for User {
|
||||
impl<'r> FromRequest<'r> for User
|
||||
{
|
||||
type Error = rb::errors::RBError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error>
|
||||
{
|
||||
let claims = try_outcome!(req.guard::<JWT>().await).0;
|
||||
|
||||
// Verify key hasn't yet expired
|
||||
|
@ -92,10 +98,12 @@ impl<'r> FromRequest<'r> for User {
|
|||
pub struct Admin(Claims);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for Admin {
|
||||
impl<'r> FromRequest<'r> for Admin
|
||||
{
|
||||
type Error = rb::errors::RBError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error>
|
||||
{
|
||||
let user = try_outcome!(req.guard::<User>().await);
|
||||
if user.0.admin {
|
||||
Outcome::Success(Self(user.0))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#[macro_use]
|
||||
extern crate diesel;
|
||||
|
||||
pub mod db;
|
||||
pub mod auth;
|
||||
pub mod db;
|
||||
pub mod errors;
|
||||
pub(crate) mod schema;
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@ embed_migrations!();
|
|||
#[database("postgres_rb")]
|
||||
pub struct RbDbConn(diesel::PgConnection);
|
||||
|
||||
async fn run_db_migrations(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>> {
|
||||
async fn run_db_migrations(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>>
|
||||
{
|
||||
let conn = RbDbConn::get_one(&rocket)
|
||||
.await
|
||||
.expect("database connection");
|
||||
|
@ -29,7 +30,8 @@ async fn run_db_migrations(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocke
|
|||
.await
|
||||
}
|
||||
|
||||
async fn create_admin_user(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>> {
|
||||
async fn create_admin_user(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>>
|
||||
{
|
||||
let admin_user = std::env::var("ADMIN_USER").unwrap_or(String::from("admin"));
|
||||
let admin_password = std::env::var("ADMIN_PASSWORD").unwrap_or(String::from("password"));
|
||||
|
||||
|
@ -46,7 +48,8 @@ async fn create_admin_user(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocke
|
|||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
fn rocket() -> _
|
||||
{
|
||||
rocket::build()
|
||||
.attach(RbDbConn::fairing())
|
||||
.attach(AdHoc::try_on_ignite(
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use crate::guards::Admin;
|
||||
use crate::RbDbConn;
|
||||
use rb::db::users::User;
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
pub fn routes() -> Vec<rocket::Route> {
|
||||
use crate::{guards::Admin, RbDbConn};
|
||||
|
||||
pub fn routes() -> Vec<rocket::Route>
|
||||
{
|
||||
routes![get_users]
|
||||
}
|
||||
|
||||
#[get("/users")]
|
||||
async fn get_users(admin: Admin, conn: RbDbConn) -> rb::Result<Json<Vec<User>>> {
|
||||
async fn get_users(admin: Admin, conn: RbDbConn) -> rb::Result<Json<Vec<User>>>
|
||||
{
|
||||
Ok(Json(conn.run(|c| rb::db::users::all(c)).await?))
|
||||
}
|
||||
|
|
|
@ -1,26 +1,30 @@
|
|||
use crate::guards::User;
|
||||
use crate::RbDbConn;
|
||||
use rb::auth::{generate_jwt_token, verify_user, JWTResponse};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||
use crate::{guards::User, RbDbConn};
|
||||
|
||||
pub(crate) fn routes() -> Vec<rocket::Route>
|
||||
{
|
||||
routes![login, already_logged_in, refresh_token]
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Credentials {
|
||||
struct Credentials
|
||||
{
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
async fn already_logged_in(_user: User) -> String {
|
||||
async fn already_logged_in(_user: User) -> String
|
||||
{
|
||||
String::from("You're already logged in!")
|
||||
}
|
||||
|
||||
#[post("/login", data = "<credentials>", rank = 2)]
|
||||
async fn login(conn: RbDbConn, credentials: Json<Credentials>) -> rb::Result<Json<JWTResponse>> {
|
||||
async fn login(conn: RbDbConn, credentials: Json<Credentials>) -> rb::Result<Json<JWTResponse>>
|
||||
{
|
||||
let credentials = credentials.into_inner();
|
||||
|
||||
// Get the user, if credentials are valid
|
||||
|
@ -33,7 +37,8 @@ async fn login(conn: RbDbConn, credentials: Json<Credentials>) -> rb::Result<Jso
|
|||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct RefreshTokenRequest {
|
||||
struct RefreshTokenRequest
|
||||
{
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
||||
|
@ -41,7 +46,8 @@ struct RefreshTokenRequest {
|
|||
async fn refresh_token(
|
||||
conn: RbDbConn,
|
||||
refresh_token_request: Json<RefreshTokenRequest>,
|
||||
) -> rb::Result<Json<JWTResponse>> {
|
||||
) -> rb::Result<Json<JWTResponse>>
|
||||
{
|
||||
let refresh_token = refresh_token_request.into_inner().refresh_token;
|
||||
|
||||
Ok(Json(
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pub mod auth;
|
||||
pub mod admin;
|
||||
pub mod auth;
|
||||
|
|
Reference in New Issue