Skip to content

Commit

Permalink
Add AuthMiddleware and change password
Browse files Browse the repository at this point in the history
  • Loading branch information
Daelon022 committed May 27, 2024
1 parent 842f83d commit d64176a
Show file tree
Hide file tree
Showing 20 changed files with 373 additions and 178 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ openssl = "0.10.64"
alcoholic_jwt = "4091.0.0"
utoipa = { version = "4.2.3", features = ["actix_extras", "chrono", "yaml", "uuid"] }
utoipa-swagger-ui = { version = "7.0.1", features = ["actix-web"] }
futures-util = "0.3.30"
1 change: 0 additions & 1 deletion migrations/2024-03-09-212028_create_user_table/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ CREATE TABLE users (
id UUID PRIMARY KEY,
username VARCHAR(255),
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
is_email_activate BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
Expand Down
24 changes: 1 addition & 23 deletions src/actors/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::actors::messages::{
CheckIfRegisteredUser, CheckUser, CreateUser, DeleteUser, UpdateActivateEmail, UpdateEmail,
UpdatePassword, UpdateUsername,
UpdateUsername,
};
use crate::db::postgres_db::DbService;
use crate::db::tables::Users;
Expand All @@ -19,7 +19,6 @@ impl Handler<CreateUser> for DbService {
id: msg.id,
username: msg.username,
email: msg.email,
password: msg.password,
is_email_activate: false,
created_at: chrono::Utc::now(),
updated_at: None,
Expand Down Expand Up @@ -83,27 +82,6 @@ impl Handler<DeleteUser> for DbService {
}
}

impl Handler<UpdatePassword> for DbService {
type Result = AtomicResponse<Self, crate::errors::Result<()>>;

fn handle(&mut self, msg: UpdatePassword, _: &mut Self::Context) -> Self::Result {
let db = self.clone();
let conn = async move { db.pool.get().await };
let query = async move {
let _ = diesel::update(crate::db::schema::users::table)
.filter(crate::db::schema::users::id.eq(msg.user_id))
.set(crate::db::schema::users::password.eq(msg.password))
.execute(&mut conn.await?)
.await?;
Ok(())
};
log::info!("Updating user password {}", msg.user_id);

let db = self.clone();
AtomicResponse::new(Box::pin(query.into_actor(&db)))
}
}

impl Handler<UpdateEmail> for DbService {
type Result = AtomicResponse<Self, crate::errors::Result<()>>;

Expand Down
7 changes: 0 additions & 7 deletions src/actors/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ pub(crate) struct DeleteUser {
pub user_id: Uuid,
}

#[derive(Message)]
#[rtype(result = "crate::errors::Result<()>")]
pub(crate) struct UpdatePassword {
pub user_id: Uuid,
pub password: String,
}

#[derive(Message)]
#[rtype(result = "crate::errors::Result<()>")]
pub(crate) struct UpdateEmail {
Expand Down
1 change: 0 additions & 1 deletion src/db/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ diesel::table!(users {
id -> Uuid,
username -> Varchar,
email -> Varchar,
password -> Varchar,
is_email_activate -> Bool,
created_at -> Timestamptz,
updated_at -> Nullable<Timestamptz>
Expand Down
1 change: 0 additions & 1 deletion src/db/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ pub struct Users {
pub id: Uuid,
pub username: String,
pub email: String,
pub password: String,
pub is_email_activate: bool,
pub created_at: DateTime<Utc>,
pub updated_at: Option<DateTime<Utc>>,
Expand Down
42 changes: 24 additions & 18 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,31 @@ pub mod actors;
pub mod consts;
pub mod db;
pub mod errors;
pub mod middleware;
pub mod models;
pub mod services;
pub mod user_flow;
pub mod utils;

use crate::db::postgres_db::DbService;
use crate::errors::Result;
use crate::user_flow::requests::{check_token, login, register};
use crate::utils::init_logging;
use crate::utils::{configure_data, configure_routes, init_logging};
use actix::Actor;
use actix_web::web::Data;
use utoipa_swagger_ui::SwaggerUi;

use crate::services::auth0::Auth0Service;
use utoipa::OpenApi;

#[derive(OpenApi)]
#[openapi(
paths(
crate::user_flow::requests::login,
crate::user_flow::requests::register
crate::user_flow::requests::register,
crate::user_flow::requests::change_password
),
components(
schemas(crate::models::RegisteredUserData),
schemas(crate::models::UserData)
schemas(crate::models::UserData),
schemas(crate::models::UpdatePasswordData),
)
)]
struct ApiDoc;
Expand All @@ -38,19 +40,23 @@ async fn main() -> Result<()> {
let pool = db::utils::create_connection_pool(database_url).await?;
let db = DbService::new(pool);
let db = db.start();
let openapi = ApiDoc::openapi();
actix_web::HttpServer::new(move || {

let client_id = dotenv::var("CLIENT_ID").unwrap_or_else(|_| "admin".to_string());
let client = dotenv::var("CLIENT").unwrap_or_else(|_| "localhost:8080".to_string());
let client_secret = dotenv::var("CLIENT_SECRET").unwrap_or_else(|_| "admin".to_string());
let connection = dotenv::var("CONNECTION")
.unwrap_or_else(|_| "Username-Password-Authentication".to_string());
let bind = dotenv::var("BIND").unwrap_or_else(|_| "localhost:8080".to_string());

let auth0_service = Auth0Service::new(client_id, client_secret, connection, client);

let server = actix_web::HttpServer::new(move || {
actix_web::App::new()
.route("/login", actix_web::web::post().to(login))
.route("/register", actix_web::web::post().to(register))
.route("/check_token", actix_web::web::get().to(check_token))
.service(
SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-docs/openapi.json", openapi.clone()),
)
.app_data(Data::new(db.clone()))
.configure(configure_routes)
.configure(configure_data(db.clone(), auth0_service.clone()))
})
.bind("localhost:8080")?
.run()
.await?;
.bind(bind)?;

server.run().await?;
Ok(())
}
107 changes: 107 additions & 0 deletions src/middleware/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use actix_web::body::BoxBody;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::{Error, HttpResponse};
use futures_util::future::{ok, LocalBoxFuture, Ready};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::Deserialize;
use std::fs::File;
use std::io::Read;
use std::rc::Rc;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::RwLock;

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Claims {
sub: String,
exp: usize,
}

pub struct AuthMiddleware;

pub struct CheckAuthMiddleware<S> {
service: Rc<S>,
decoding_key: Arc<RwLock<DecodingKey>>,
}

impl<S> Transform<S, ServiceRequest> for AuthMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> + 'static,
S::Future: 'static,
{
type Response = ServiceResponse<BoxBody>;
type Error = Error;
type Transform = CheckAuthMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;

fn new_transform(&self, service: S) -> Self::Future {
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "secret".to_string());
let decoding_key = AuthMiddleware::new_from_file(&jwt_secret).expect("Failed to load key");
ok(CheckAuthMiddleware {
service: Rc::new(service),
decoding_key: Arc::new(RwLock::new(decoding_key)),
})
}
}

impl<S> Service<ServiceRequest> for CheckAuthMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> + 'static,
S::Future: 'static,
{
type Response = ServiceResponse<BoxBody>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&self, req: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service);
let decoding_key = self.decoding_key.clone();

Box::pin(async move {
let auth_header = req.headers().get("Authorization");

if req.path() == "/login" || req.path() == "/register" || req.path() == "/" {
return Ok(req.into_response(HttpResponse::Ok().finish()));
}

if let Some(auth_header) = auth_header {
if let Ok(auth_token) = auth_header.to_str() {
if let Some(auth_str) = auth_token.strip_prefix("Bearer ") {
let token = &auth_str[7..];

let decoding_key = decoding_key.read().await;

return match decode::<Claims>(
token,
&decoding_key,
&Validation::new(Algorithm::HS256),
) {
Ok(_) => service.call(req).await,
Err(_) => Ok(req.into_response(HttpResponse::Unauthorized().finish())),
};
}
}
}

Ok(req.into_response(HttpResponse::Unauthorized().finish()))
})
}
}

impl AuthMiddleware {
pub fn new_from_file(path: &str) -> Result<DecodingKey, std::io::Error> {
let mut file = File::open(path)?;
let mut key_data = Vec::new();
file.read_to_end(&mut key_data)?;

let decoding_key = DecodingKey::from_secret(&key_data);

Ok(decoding_key)
}
}
1 change: 1 addition & 0 deletions src/middleware/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod auth;
25 changes: 3 additions & 22 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,8 @@ pub struct RegisteredUserData {
pub password: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectToAuth0<T: Serialize> {
pub client_id: String,
pub client_secret: String,
pub audience: String,
pub grant_type: String,
pub user_id: String,
pub connection: String,
#[serde(flatten)]
pub extra: T,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistrationFlow {
pub username: String,
pub password: String,
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UpdatePasswordData {
pub user_id: Uuid,
pub email: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginFlow {
pub username: String,
pub password: String,
}
83 changes: 83 additions & 0 deletions src/services/auth0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::consts::{AUDIENCE, GRANT_TYPE_PASS};
use crate::user_flow::auth0_models::{ChangePassFlow, ConnectToAuth0, LoginFlow, RegistrationFlow};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Auth0Service {
pub client_id: String,
pub client_secret: String,
pub connection: String,
pub client_url: String,
}

impl Auth0Service {
pub fn new(
client_id: String,
client_secret: String,
connection: String,
client_url: String,
) -> Self {
Auth0Service {
client_id,
client_secret,
connection,
client_url,
}
}

pub fn generate_body_for_registration(
&self,
user_id: Uuid,
username: String,
password: String,
email: String,
) -> ConnectToAuth0<RegistrationFlow> {
ConnectToAuth0 {
client_id: self.client_id.clone(),
client_secret: self.client_secret.clone(),
audience: AUDIENCE.to_string(),
grant_type: GRANT_TYPE_PASS.to_string(),
user_id: user_id.to_string(),
connection: self.connection.clone(),
extra: RegistrationFlow {
username,
password,
email,
},
}
}

pub async fn generate_body_for_login(
&self,
user_id: Uuid,
username: String,
password: String,
) -> ConnectToAuth0<LoginFlow> {
ConnectToAuth0 {
client_id: self.client_id.clone(),
client_secret: self.client_secret.clone(),
audience: AUDIENCE.to_string(),
grant_type: GRANT_TYPE_PASS.to_string(),
user_id: user_id.to_string(),
connection: self.connection.clone(),
extra: LoginFlow { username, password },
}
}

pub async fn generate_body_for_change_password(
&self,
user_id: Uuid,
email: String,
) -> ConnectToAuth0<ChangePassFlow> {
ConnectToAuth0 {
client_id: self.client_id.clone(),
client_secret: self.client_secret.clone(),
audience: AUDIENCE.to_string(),
grant_type: GRANT_TYPE_PASS.to_string(),
user_id: user_id.to_string(),
connection: self.connection.clone(),
extra: ChangePassFlow { email },
}
}
}
1 change: 1 addition & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod auth0;
Loading

0 comments on commit d64176a

Please sign in to comment.