Implemented basic auth logic + other stuff
This commit is contained in:
parent
cb26393fcd
commit
260f887d22
45
Cargo.lock
generated
45
Cargo.lock
generated
@ -43,6 +43,7 @@ dependencies = [
|
||||
"actix-identity",
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-threadpool",
|
||||
"actix-web",
|
||||
"actix-web-httpauth",
|
||||
"bcrypt",
|
||||
@ -50,7 +51,9 @@ dependencies = [
|
||||
"chrono",
|
||||
"comp",
|
||||
"custom_error",
|
||||
"derive-new",
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
"dotenv",
|
||||
"env_logger 0.7.1",
|
||||
"futures",
|
||||
@ -832,6 +835,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-new"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.12",
|
||||
"quote 1.0.4",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.9.0"
|
||||
@ -892,6 +906,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_migrations"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c"
|
||||
dependencies = [
|
||||
"migrations_internals",
|
||||
"migrations_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "2.0.2"
|
||||
@ -1610,6 +1634,27 @@ version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
|
||||
[[package]]
|
||||
name = "migrations_internals"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "migrations_macros"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
|
||||
dependencies = [
|
||||
"migrations_internals",
|
||||
"proc-macro2 1.0.12",
|
||||
"quote 1.0.4",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
|
@ -33,6 +33,9 @@ regex = '1.3.7'
|
||||
lazy_static = '1.4.0'
|
||||
lazy-regex = '0.1.2'
|
||||
custom_error = '1.7.1'
|
||||
derive-new = '0.5.8'
|
||||
diesel_migrations = '1.4.0'
|
||||
actix-threadpool = '0.3.1'
|
||||
|
||||
[dependencies.serde]
|
||||
version = '1.0.106'
|
||||
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@ -0,0 +1 @@
|
||||
max_width = 80
|
@ -1,8 +1,9 @@
|
||||
use diesel::prelude::*;
|
||||
use diesel::sqlite::Sqlite;
|
||||
|
||||
use crate::errors;
|
||||
use crate::models;
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn find_user_by_uid(
|
||||
@ -24,11 +25,7 @@ pub fn _find_user_by_name(
|
||||
user_name: String,
|
||||
conn: &SqliteConnection,
|
||||
) -> Result<Option<models::UserDTO>, errors::DomainError> {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
let maybe_user = users
|
||||
.select((name, created_at))
|
||||
.filter(name.eq(user_name))
|
||||
let maybe_user = _get_user_by_name(user_name)
|
||||
.first::<models::UserDTO>(conn)
|
||||
.optional();
|
||||
|
||||
@ -75,9 +72,33 @@ pub fn insert_new_user(
|
||||
diesel::insert_into(users)
|
||||
.values(nu.as_ref())
|
||||
.execute(conn)?;
|
||||
let user = users
|
||||
.select((name, created_at))
|
||||
.filter(name.eq(nu.name.clone()))
|
||||
.first::<models::UserDTO>(conn);
|
||||
Ok(user?)
|
||||
let user =
|
||||
_get_user_by_name(nu.name.clone()).first::<models::UserDTO>(conn)?;
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub fn verify_password(
|
||||
user_name: String,
|
||||
given_password: String,
|
||||
conn: &SqliteConnection,
|
||||
) -> Result<bool, errors::DomainError> {
|
||||
use crate::schema::users::dsl::*;
|
||||
let password_hash = users
|
||||
.select(password)
|
||||
.filter(name.eq(user_name))
|
||||
.first::<String>(conn)?;
|
||||
Ok(verify(given_password.as_str(), password_hash.as_str())?)
|
||||
}
|
||||
fn _get_user_by_name(
|
||||
user_name: String,
|
||||
) -> crate::schema::users::BoxedQuery<
|
||||
'static,
|
||||
Sqlite,
|
||||
(diesel::sql_types::Text, diesel::sql_types::Timestamp),
|
||||
> {
|
||||
use crate::schema::users::dsl::*;
|
||||
users
|
||||
.select((name, created_at))
|
||||
.filter(name.eq(user_name))
|
||||
.into_boxed()
|
||||
}
|
||||
|
@ -6,24 +6,6 @@ use custom_error::custom_error;
|
||||
use crate::models::errors::*;
|
||||
use r2d2;
|
||||
use std::convert::From;
|
||||
// use std::error::Error;
|
||||
|
||||
// pub enum DomainError {
|
||||
// #[display(fmt = "PasswordHashError")]
|
||||
// PwdHashError,
|
||||
// #[display(fmt = "Bad Id")]
|
||||
// IdError,
|
||||
// #[display(fmt = "Generic Error")]
|
||||
// GenericError,
|
||||
// DuplicateValue,
|
||||
// }
|
||||
|
||||
// impl Error for DomainError {
|
||||
// fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
// // Generic error, underlying cause isn't tracked.
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl From<DBError> for DomainError {
|
||||
// fn from(error: DBError) -> DomainError {
|
||||
@ -41,47 +23,43 @@ use std::convert::From;
|
||||
// }
|
||||
// }
|
||||
|
||||
custom_error! { pub DomainError
|
||||
custom_error! { #[derive(new)] pub DomainError
|
||||
PwdHashError {source: BcryptError} = "Failed to has password",
|
||||
DbError {source: diesel::result::Error} = "Database error",
|
||||
DbPoolError {source: r2d2::Error} = "Failed to get connection from pool",
|
||||
PasswordError {cause: String} = "Failed to validate password - {cause}",
|
||||
GenericError {cause: String} = "Generic Error - Reason: {cause}"
|
||||
}
|
||||
|
||||
impl ResponseError for DomainError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let err = self;
|
||||
match self {
|
||||
DomainError::PwdHashError { source } => {
|
||||
HttpResponse::InternalServerError().json(ErrorModel {
|
||||
status_code: 500,
|
||||
reason: format!(
|
||||
"{} {}",
|
||||
"Unexpected Error - Failed to hash password", source
|
||||
),
|
||||
error_code: 500,
|
||||
reason: format!("{} {}", err.to_string(), source).as_str(),
|
||||
})
|
||||
}
|
||||
DomainError::DbError { source } => {
|
||||
HttpResponse::InternalServerError().json(ErrorModel {
|
||||
status_code: 500,
|
||||
reason: format!("{} {}", "Unexpected Database Error", source),
|
||||
error_code: 500,
|
||||
reason: format!("{} {}", err.to_string(), source).as_str(),
|
||||
})
|
||||
}
|
||||
DomainError::DbPoolError { source } => {
|
||||
HttpResponse::InternalServerError().json(ErrorModel {
|
||||
status_code: 500,
|
||||
reason: format!(
|
||||
"{} {}",
|
||||
"Unexpected Error - Failed to get connection from pool", source
|
||||
),
|
||||
error_code: 500,
|
||||
reason: format!("{} {}", err.to_string(), source).as_str(),
|
||||
})
|
||||
}
|
||||
DomainError::PasswordError { cause } => HttpResponse::BadRequest().json(ErrorModel {
|
||||
error_code: 400,
|
||||
reason: format!("{} {}, ", err.to_string(), cause.clone()).as_str(),
|
||||
}),
|
||||
DomainError::GenericError { cause } => HttpResponse::BadRequest().json(ErrorModel {
|
||||
status_code: 400,
|
||||
reason: format!(
|
||||
"{} {}, ",
|
||||
"Unexpected Database Error - ".to_owned(),
|
||||
cause.clone()
|
||||
),
|
||||
error_code: 400,
|
||||
reason: format!("{} {}, ", err.to_string(), cause.clone()).as_str(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
15
src/main.rs
15
src/main.rs
@ -1,17 +1,17 @@
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
#[macro_use]
|
||||
extern crate comp;
|
||||
#[macro_use]
|
||||
extern crate validator_derive;
|
||||
extern crate derive_new;
|
||||
// #[macro_use]
|
||||
// extern crate comp;
|
||||
// #[macro_use]
|
||||
// extern crate validator_derive;
|
||||
extern crate bcrypt;
|
||||
extern crate custom_error;
|
||||
extern crate regex;
|
||||
extern crate validator;
|
||||
|
||||
use actix_web::{
|
||||
error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||
};
|
||||
use actix_web::{error, get, middleware, post, web, App, Error, HttpResponse, HttpServer};
|
||||
|
||||
use yarte::Template;
|
||||
|
||||
@ -94,6 +94,9 @@ async fn main() -> std::io::Result<()> {
|
||||
.build(manager)
|
||||
.expect("Failed to create pool.");
|
||||
|
||||
diesel_migrations::run_pending_migrations(&pool.get().unwrap())
|
||||
.expect("Error running migrations");
|
||||
|
||||
let addr = std::env::var("BIND_ADDRESS").expect("BIND ADDRESS NOT FOUND");
|
||||
info!("Starting server {}", addr);
|
||||
let private_key = rand::thread_rng().gen::<[u8; 32]>();
|
||||
|
@ -1,13 +1,13 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct JsonErrorModel {
|
||||
#[derive(Debug, Clone, Serialize, new)]
|
||||
pub struct JsonErrorModel<'a> {
|
||||
status_code: i16,
|
||||
pub line: String,
|
||||
pub reason: String,
|
||||
pub reason: &'a str,
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ErrorModel {
|
||||
pub status_code: i16,
|
||||
pub reason: String,
|
||||
#[derive(Debug, Clone, Serialize, new)]
|
||||
pub struct ErrorModel<'a> {
|
||||
pub error_code: i16,
|
||||
pub reason: &'a str,
|
||||
}
|
||||
|
@ -2,12 +2,11 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::schema::users;
|
||||
use crate::utils::regexs;
|
||||
use chrono;
|
||||
use validator::Validate;
|
||||
use validator_derive::*;
|
||||
use yarte::Template;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Queryable, Identifiable, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Queryable, Identifiable, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
@ -15,7 +14,7 @@ pub struct User {
|
||||
pub created_at: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Insertable, Deserialize, Validate)]
|
||||
#[derive(Debug, Clone, Insertable, Deserialize, Validate)]
|
||||
#[table_name = "users"]
|
||||
pub struct NewUser {
|
||||
#[validate(regex = "regexs::USERNAME_REG", length(min = 4, max = 10))]
|
||||
|
@ -1,27 +1,53 @@
|
||||
use crate::types::DbPool;
|
||||
use actix_threadpool::BlockingError;
|
||||
use actix_web::{web, ResponseError};
|
||||
use actix_web_httpauth::extractors::basic::BasicAuth;
|
||||
|
||||
use crate::actions::users;
|
||||
use crate::errors;
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{get, post, Error, HttpResponse};
|
||||
use actix_web::{get, Error, HttpResponse};
|
||||
|
||||
#[get("/login")]
|
||||
pub async fn login(id: Identity, credentials: BasicAuth) -> Result<HttpResponse, Error> {
|
||||
pub async fn login(
|
||||
id: Identity,
|
||||
credentials: BasicAuth,
|
||||
pool: web::Data<DbPool>,
|
||||
) -> Result<HttpResponse, impl ResponseError> {
|
||||
let maybe_identity = id.identity();
|
||||
let response = if let Some(identity) = maybe_identity {
|
||||
HttpResponse::Found()
|
||||
Ok(HttpResponse::Found()
|
||||
.header("location", "/")
|
||||
.content_type("text/plain")
|
||||
.json(format!("Already logged in as {}", identity))
|
||||
.json(format!("Already logged in as {}", identity)))
|
||||
} else {
|
||||
id.remember(credentials.user_id().to_string());
|
||||
HttpResponse::Found().header("location", "/").finish()
|
||||
let credentials2 = credentials.clone();
|
||||
web::block(move || validate_basic_auth(credentials2, &pool))
|
||||
.await
|
||||
.and_then(|valid| {
|
||||
if valid {
|
||||
id.remember(credentials.user_id().to_string());
|
||||
Ok(HttpResponse::Found().header("location", "/").finish())
|
||||
} else {
|
||||
Err(BlockingError::Error(
|
||||
errors::DomainError::new_password_error(
|
||||
"Wrong password or account does not exist"
|
||||
.to_string(),
|
||||
),
|
||||
))
|
||||
}
|
||||
})
|
||||
};
|
||||
println!("{}", credentials.user_id());
|
||||
println!("{:?}", credentials.password());
|
||||
Ok(response)
|
||||
// println!("{}", credentials.user_id());
|
||||
// println!("{:?}", credentials.password());
|
||||
response
|
||||
}
|
||||
|
||||
#[get("/logout")]
|
||||
pub async fn logout(id: Identity, _credentials: BasicAuth) -> Result<HttpResponse, Error> {
|
||||
pub async fn logout(
|
||||
id: Identity,
|
||||
_credentials: BasicAuth,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let maybe_identity = id.identity();
|
||||
let response = if let Some(identity) = maybe_identity {
|
||||
info!("Logging out {user}", user = identity);
|
||||
@ -43,3 +69,24 @@ pub async fn index(id: Identity) -> String {
|
||||
id.identity().unwrap_or_else(|| "Anonymous".to_owned())
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_basic_auth(
|
||||
credentials: BasicAuth,
|
||||
pool: &web::Data<DbPool>,
|
||||
) -> Result<bool, errors::DomainError> {
|
||||
let result = if let Some(password_ref) = credentials.password() {
|
||||
let conn = pool.get()?;
|
||||
let password = password_ref.clone().into_owned();
|
||||
let valid = users::verify_password(
|
||||
credentials.user_id().clone().into_owned(),
|
||||
password,
|
||||
&conn,
|
||||
)?;
|
||||
Ok(valid)
|
||||
} else {
|
||||
Err(errors::DomainError::new_password_error(
|
||||
"No password given".to_owned(),
|
||||
))
|
||||
};
|
||||
result
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ pub async fn get_user(
|
||||
if let Some(user) = maybe_user {
|
||||
Ok(HttpResponse::Ok().json(user))
|
||||
} else {
|
||||
let res =
|
||||
HttpResponse::NotFound().body(format!("No user found with uid: {}", user_uid));
|
||||
let res = HttpResponse::NotFound()
|
||||
.body(format!("No user found with uid: {}", user_uid));
|
||||
Ok(res)
|
||||
}
|
||||
});
|
||||
@ -32,7 +32,9 @@ pub async fn get_user(
|
||||
}
|
||||
|
||||
#[get("/api/authzd/users/get")]
|
||||
pub async fn get_all_users(pool: web::Data<DbPool>) -> Result<HttpResponse, impl ResponseError> {
|
||||
pub async fn get_all_users(
|
||||
pool: web::Data<DbPool>,
|
||||
) -> Result<HttpResponse, impl ResponseError> {
|
||||
// use web::block to offload blocking Diesel code without blocking server thread
|
||||
let res = web::block(move || {
|
||||
let conn = pool.get()?;
|
||||
@ -43,19 +45,16 @@ pub async fn get_all_users(pool: web::Data<DbPool>) -> Result<HttpResponse, impl
|
||||
debug!("{:?}", maybe_users);
|
||||
if let Some(users) = maybe_users {
|
||||
if users.is_empty() {
|
||||
let res = HttpResponse::Ok().json(models::ErrorModel {
|
||||
status_code: 200,
|
||||
reason: "No users available".to_string(),
|
||||
});
|
||||
let res = HttpResponse::NotFound()
|
||||
.json(models::ErrorModel::new(40, "No users available"));
|
||||
// let res = crate::errors::DomainError::new_generic_error("".to_owned());
|
||||
Ok(res)
|
||||
} else {
|
||||
Ok(HttpResponse::Ok().json(users))
|
||||
}
|
||||
} else {
|
||||
let res = HttpResponse::Ok().json(models::ErrorModel {
|
||||
status_code: 200,
|
||||
reason: "No users available".to_string(),
|
||||
});
|
||||
let res = HttpResponse::NotFound()
|
||||
.json(models::ErrorModel::new(40, "No users available"));
|
||||
Ok(res)
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user