Add docker build and update to actix-web v3

This commit is contained in:
Rohan Sircar 2021-04-21 11:11:48 +05:30
parent 9b7d28c6fe
commit 1d1cff4f74
11 changed files with 1226 additions and 611 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
target
.git
.idea
.vscode
# test.db

1519
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,39 +5,39 @@ authors = ['Rohan Sircar <rohansircar@tuta.io>']
edition = '2018'
[dependencies]
actix-web = "2.0.0"
actix-rt = "1.1.1"
actix-service = "1.0.6"
actix-files = "0.2.2"
bytes = "0.5.6"
futures = "0.3.5"
log = "0.4.11"
env_logger = "0.7.1"
serde_json = "1.0.57"
actix-web = "3.3.2"
# actix-rt = "1.1.1"
actix-service = "2.0.0"
actix-files = "0.5.0"
bytes = "1.0.1"
futures = "0.3.14"
log = "0.4.14"
env_logger = "0.8.3"
serde_json = "1.0.64"
json = "0.12.4"
listenfd = "0.3.3"
dotenv = "0.15.0"
r2d2 = "0.8.9"
validator = "0.10.1"
validator_derive = "0.10.1"
validator = "0.13.0"
validator_derive = "0.13.0"
jsonwebtoken = "7.2.0"
actix-identity = "0.2.1"
actix-web-httpauth = "0.4.2"
rand = "0.7.3"
nanoid = "0.3.0"
bcrypt = "0.8.2"
timeago = "0.2.1"
actix-identity = "0.3.1"
actix-web-httpauth = "0.5.1"
rand = "0.8.3"
nanoid = "0.4.0"
bcrypt = "0.9.0"
timeago = "0.3.0"
comp = "0.2.1"
regex = "1.3.9"
regex = "1.4.5"
lazy_static = "1.4.0"
lazy-regex = "0.1.4"
custom_error = "1.7.1"
derive-new = "0.5.8"
custom_error = "1.9.2"
derive-new = "0.5.9"
diesel_migrations = "1.4.0"
actix-threadpool = "0.3.3"
[dependencies.serde]
version = "1.0.115"
version = "1.0.125"
features = ['derive']
# [dependencies.yarte]
@ -53,7 +53,7 @@ features = [
]
[dependencies.uuid]
version = "0.8.1"
version = "0.8.2"
features = [
'serde',
'v4',
@ -64,7 +64,7 @@ version = "0.23.1"
features = ['bundled']
[dependencies.chrono]
version = "0.4.15"
version = "0.4.19"
features = ['serde']
# [build-dependencies.yarte_helpers]
# version = '0.9.0'

46
Dockerfile Normal file
View File

@ -0,0 +1,46 @@
FROM rust:1.45 as builder
# ENV CARGO_HOME=/actix-demo/.cargo
RUN USER=root cargo new --bin actix-demo
WORKDIR /actix-demo
# COPY ./.cargo ./.cargo
COPY ./Cargo.toml ./Cargo.toml
COPY ./Cargo.lock ./Cargo.lock
RUN cargo build --release
RUN rm src/*.rs
COPY ./src ./src
#RUN rm ./target/release/deps/actix-demo*
RUN cargo build --release
FROM debian:buster-slim
ARG APP=/usr/src/app
RUN apt-get update \
&& apt-get install -y ca-certificates tzdata \
&& rm -rf /var/lib/apt/lists/*
EXPOSE 8000
ENV TZ=Etc/UTC \
APP_USER=appuser
RUN groupadd $APP_USER \
&& useradd -g $APP_USER $APP_USER \
&& mkdir -p ${APP}
COPY ./.env ${APP}/.env
COPY ./migrations ${APP}/migrations
COPY ./static ${APP}/static
COPY ./test.db ${APP}/test.db
COPY --from=builder /actix-demo/target/release/actix-demo ${APP}/actix-demo
RUN chown -R $APP_USER:$APP_USER ${APP}
USER $APP_USER
WORKDIR ${APP}
CMD ["./actix-demo"]

View File

@ -43,19 +43,20 @@ pub fn get_all(
/// Run query using Diesel to insert a new database row and return the result.
pub fn insert_new_user(
mut nu: Rc<models::NewUser>,
nu: models::NewUser,
conn: &SqliteConnection,
) -> Result<models::UserDTO, errors::DomainError> {
// It is common when using Diesel with Actix web to import schema-related
// modules inside a function's scope (rather than the normal module's scope)
// to prevent import collisions and namespace pollution.
use crate::schema::users::dsl::*;
let mut nu2 = Rc::make_mut(&mut nu);
nu2.password = hash(&nu2.password, DEFAULT_COST)?;
let nu = {
let mut nu2 = nu.clone();
nu2.password = hash(&nu2.password, DEFAULT_COST)?;
nu2
};
diesel::insert_into(users)
.values(nu.as_ref())
.execute(conn)?;
diesel::insert_into(users).values(&nu).execute(conn)?;
let user =
query::_get_user_by_name(&nu.name).first::<models::UserDTO>(conn)?;
Ok(user)

27
src/main.rs Normal file → Executable file
View File

@ -7,14 +7,14 @@ extern crate custom_error;
extern crate regex;
extern crate validator;
use actix_web::{middleware, web, App, HttpServer, cookie::SameSite};
use actix_web::{cookie::SameSite, middleware, web, App, HttpServer};
use actix_web_httpauth::middleware::HttpAuthentication;
use actix_files as fs;
use actix_identity::{CookieIdentityPolicy, IdentityService};
use rand::Rng;
use actix_files as fs;
use services::UserServiceImpl;
use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
@ -27,6 +27,7 @@ mod middlewares;
mod models;
mod routes;
mod schema;
mod services;
mod types;
mod utils;
@ -39,14 +40,14 @@ pub struct AppConfig {
pool: DbPool,
}
#[actix_rt::main]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
dotenv::dotenv().ok();
let basic_auth_middleware =
HttpAuthentication::basic(utils::auth::validator);
// let _basic_auth_middleware =
// HttpAuthentication::basic(utils::auth::validator);
// set up database connection pool
let connspec =
@ -67,7 +68,14 @@ async fn main() -> std::io::Result<()> {
8
});
let config: AppConfig = AppConfig { pool, hash_cost };
let config: AppConfig = AppConfig {
pool: pool.clone(),
hash_cost,
};
// let user_controller = UserController {
// user_service: &user_service,
// };
let addr = std::env::var("BIND_ADDRESS").expect("BIND ADDRESS NOT FOUND");
info!("Starting server {}", addr);
@ -79,15 +87,16 @@ async fn main() -> std::io::Result<()> {
CookieIdentityPolicy::new(&private_key)
.name("my-app-auth")
.secure(false)
.same_site(SameSite::Lax)
.same_site(SameSite::Lax),
))
.wrap(middleware::Logger::default())
.service(
web::scope("/api/authzd") // endpoint requiring authentication
.wrap(basic_auth_middleware.clone())
// .wrap(_basic_auth_middleware.clone())
.service(routes::users::get_user)
.service(routes::users::get_all_users),
)
// .route("/api/users/get", web::get().to(user_controller.get_user.into()))
.service(web::scope("/api/public")) // public endpoint - not implemented yet
.service(routes::auth::login)
.service(routes::auth::logout)

View File

@ -68,7 +68,7 @@ pub async fn index(id: Identity) -> String {
/// basic auth middleware function
pub fn validate_basic_auth(
credentials: BasicAuth,
config: &web::Data<AppConfig>,
config: &AppConfig,
) -> Result<bool, errors::DomainError> {
let result = if let Some(password_ref) = credentials.password() {
let pool = &config.pool;

View File

@ -1,10 +1,10 @@
use actix_web::{get, post, web, HttpResponse};
use crate::actions;
use crate::models;
use crate::errors::DomainError;
use crate::services::UserService;
use crate::AppConfig;
use crate::{actions, models};
use actix_web::error::ResponseError;
use std::rc::Rc;
use validator::Validate;
/// Finds user by UID.
@ -12,7 +12,7 @@ use validator::Validate;
pub async fn get_user(
config: web::Data<AppConfig>,
user_id: web::Path<i32>,
) -> Result<HttpResponse, impl ResponseError> {
) -> Result<HttpResponse, DomainError> {
let u_id = user_id.into_inner();
// use web::block to offload blocking Diesel code without blocking server thread
let res = web::block(move || {
@ -21,16 +21,36 @@ pub async fn get_user(
actions::find_user_by_uid(u_id, &conn)
})
.await
.and_then(|maybe_user| {
if let Some(user) = maybe_user {
Ok(HttpResponse::Ok().json(user))
} else {
let res = HttpResponse::NotFound()
.body(format!("No user found with uid: {}", u_id));
Ok(res)
}
});
res
.map_err(|err| {
let res = DomainError::new_generic_error(format!(
"No user found with uid: {}",
u_id
));
res
})?;
if let Some(user) = res {
Ok(HttpResponse::Ok().json(user))
} else {
let res = HttpResponse::NotFound()
.body(format!("No user found with uid: {}", u_id));
Ok(res)
}
}
#[get("/get/users/{user_id}")]
pub async fn get_user2(
user_service: web::Data<dyn UserService>,
user_id: web::Path<i32>,
) -> Result<HttpResponse, DomainError> {
let u_id = user_id.into_inner();
let user = user_service.find_user_by_uid(u_id)?;
if let Some(user) = user {
Ok(HttpResponse::Ok().json(user))
} else {
let res = HttpResponse::NotFound()
.body(format!("No user found with uid: {}", u_id));
Ok(res)
}
}
#[get("/get/users")]
@ -75,7 +95,7 @@ pub async fn add_user(
Ok(_) => web::block(move || {
let pool = &config.pool;
let conn = pool.get()?;
actions::insert_new_user(Rc::new(form.0), &conn)
actions::insert_new_user(form.0, &conn)
})
.await
.and_then(|user| {

2
src/services.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod user_service;
pub use self::user_service::*;

View File

@ -0,0 +1,84 @@
use std::rc::Rc;
use diesel::SqliteConnection;
use crate::{actions, errors, models, types::DbPool};
pub trait UserService {
fn find_user_by_uid(
&self,
uid: i32,
) -> Result<Option<models::UserDTO>, errors::DomainError>;
fn _find_user_by_name(
&self,
user_name: String,
) -> Result<Option<models::UserDTO>, errors::DomainError>;
fn get_all(
&self,
) -> Result<Option<Vec<models::UserDTO>>, errors::DomainError>;
fn insert_new_user(
&self,
nu: models::NewUser,
) -> Result<models::UserDTO, errors::DomainError>;
// fn woot(&self) -> i32;
fn verify_password<'a>(
&self,
user_name: &'a String,
given_password: &'a String,
) -> Result<bool, errors::DomainError>;
}
#[derive(Clone)]
pub struct UserServiceImpl {
pub pool: DbPool,
}
impl UserService for UserServiceImpl {
fn find_user_by_uid(
&self,
uid: i32,
) -> Result<Option<models::UserDTO>, errors::DomainError> {
let conn = self.pool.get()?;
actions::find_user_by_uid(uid, &conn)
}
fn _find_user_by_name(
&self,
user_name: String,
) -> Result<Option<models::UserDTO>, errors::DomainError> {
let conn = self.pool.get()?;
actions::_find_user_by_name(user_name, &conn)
}
fn get_all(
&self,
) -> Result<Option<Vec<models::UserDTO>>, errors::DomainError> {
let conn = self.pool.get()?;
actions::get_all(&conn)
}
fn insert_new_user(
&self,
nu: models::NewUser,
) -> Result<models::UserDTO, errors::DomainError> {
let conn = self.pool.get()?;
actions::insert_new_user(nu, &conn)
}
fn verify_password<'b>(
&self,
user_name: &'b String,
given_password: &'b String,
) -> Result<bool, errors::DomainError> {
let conn = self.pool.get()?;
actions::verify_password(user_name, given_password, &conn)
}
// async fn woot(&self) -> i32 {
// 1
// }
}

43
src/utils/auth.rs Normal file → Executable file
View File

@ -1,28 +1,31 @@
use actix_web_httpauth::extractors::basic::BasicAuth;
use crate::AppConfig;
use crate::routes::validate_basic_auth;
use crate::AppConfig;
use actix_web::{dev::ServiceRequest, web, Error};
// pub async fn validator(
// req: ServiceRequest,
// credentials: BasicAuth,
// ) -> Result<ServiceRequest, Error> {
// println!("{}", credentials.user_id());
// println!("{:?}", credentials.password());
// // verify credentials from db
// let config = req.app_data::<AppConfig>().expect("Error getting config");
pub async fn validator(
req: ServiceRequest,
credentials: BasicAuth,
) -> Result<ServiceRequest, Error> {
println!("{}", credentials.user_id());
println!("{:?}", credentials.password());
// verify credentials from db
let config = req.app_data::<AppConfig>().expect("Error getting config");
// let valid =
// web::block(move || validate_basic_auth(credentials, config)).await?;
// if valid {
// debug!("Success");
// Ok(req)
// } else {
// println!("blah");
// let err: Error = crate::errors::DomainError::new_password_error(
// "Wrong password or account does not exist".to_string(),
// )
// .into();
let valid =
web::block(move || validate_basic_auth(credentials, &config)).await?;
if valid {
debug!("Success");
Ok(req)
} else {
Err(crate::errors::DomainError::new_password_error(
"Wrong password or account does not exist".to_string(),
).into())
}
}
// Err(err)
// }
// }