Skip to content

Commit

Permalink
Merge pull request #25 from 8shaws/otp
Browse files Browse the repository at this point in the history
feat: adds fn to generate otp and send html based mail to user
  • Loading branch information
shawakash authored Jul 29, 2024
2 parents 3f1b5a1 + d359e66 commit d39998a
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 20 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ NO_WORKER_THREADS=4
SMTP_USERNAME=
SMTP_PASS=
REPLY_TO_MAIL=
OTP_SECRET=
34 changes: 32 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions crates/api/src/auth/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ use actix_web::{
Error, HttpMessage, HttpResponse,
};
use futures_util::future::{ok, LocalBoxFuture, Ready};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::rc::Rc;
use std::task::{Context, Poll};

// middleware to extract the client ID from the request
pub struct ExtractClientId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct IdKey(pub String);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct JwtKey(pub String);

impl<S, B> Transform<S, ServiceRequest> for ExtractClientId
where
Expand Down Expand Up @@ -90,8 +95,8 @@ where
if !jwt.is_empty() {
match verify_token(&jwt) {
Ok(id) => {
req.extensions_mut().insert(id.clone());
req.extensions_mut().insert(jwt.clone());
req.extensions_mut().insert::<IdKey>(IdKey(id.clone()));
req.extensions_mut().insert::<JwtKey>(JwtKey(jwt.clone()));

let res = srv.call(req).await?;

Expand Down
10 changes: 9 additions & 1 deletion crates/api/src/db/user_db_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::auth;
use crate::models::{self, User};
use diesel::pg::PgConnection;
use diesel::prelude::*;
use uuid;
use uuid::{self, Uuid};

use super::DbError;

Expand Down Expand Up @@ -63,3 +63,11 @@ pub fn get_user_by_contact(

Ok(user_result)
}

pub fn verify_user(con: &mut PgConnection, id: String) -> Result<usize, DbError> {
use crate::schema::users::dsl::*;
diesel::update(users.filter(id.eq(id)))
.set(email_verified.eq(true))
.execute(con)
.map_err(|e| DbError::from(e))
}
1 change: 1 addition & 0 deletions crates/api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod models;
mod redis;
mod route;
mod schema;
mod types;

use crate::route::user::user_config;

Expand Down
82 changes: 79 additions & 3 deletions crates/api/src/route/user.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::auth;
use crate::auth::middleware::ExtractClientId;
use crate::auth::middleware::{ExtractClientId, IdKey, JwtKey};
use crate::db::{self};
use crate::models::*;
use actix_web::{error, post, web, HttpResponse, Responder, Result};
use crate::types::{EmailVerifyData, VerifyEmailBody};
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Responder, Result};
use r2d2_redis::redis;
use serde_json::json;
use std::sync::Arc;

#[post("/login")]
async fn login(state: web::Data<AppState>, form: web::Json<LoginUser>) -> Result<impl Responder> {
Expand Down Expand Up @@ -74,11 +76,17 @@ async fn register(
};

let mail = created_user.email.clone();
let id = created_user.id.clone().to_string();
let result = web::block(move || {
let mut conn = redis_pool.get().map_err(|e| e.to_string())?;

let data = EmailVerifyData { id: id, mail: mail };

let json_data = serde_json::to_string(&data).unwrap();

let _: () = redis::cmd("LPUSH")
.arg("user_email_verify")
.arg(mail)
.arg(json_data)
.query(&mut *conn)
.map_err(|e| e.to_string())?;
Ok::<(), String>(())
Expand Down Expand Up @@ -106,6 +114,69 @@ async fn get_orders() -> impl Responder {
HttpResponse::Ok().body("Orders")
}

async fn verify_email(
state: web::Data<AppState>,
req: HttpRequest,
form: web::Json<VerifyEmailBody>,
) -> Result<impl Responder> {
let id = req.extensions().get::<IdKey>().cloned();

match id {
Some(id) => {
let redis_pool = state.redis_pool.clone();
let id_clone = id.clone();

let otp_result = web::block(move || {
let mut conn = redis_pool.get().map_err(|e| e.to_string())?;

let otp = redis::cmd("GET")
.arg(format!("otp:{}", id.0))
.query::<Option<String>>(&mut *conn)
.map_err(|e| e.to_string());
otp
})
.await
.map_err(|e| actix_web::error::ErrorInternalServerError(e.to_string()))?;

match otp_result {
Ok(Some(otp)) => {
if otp == form.otp {
let id = id_clone.to_owned();
let _ = web::block(move || {
let mut conn = state.db_pool.get()?;
db::user_db_fn::verify_user(&mut conn, id.0)
})
.await?
.map_err(error::ErrorInternalServerError)?;

Ok(HttpResponse::Ok().json(json!({
"status": "ok",
"message": "Email Verified!"
})))
} else {
Ok(HttpResponse::Forbidden().json(json!({
"status": "error",
"message": "Invalid Otp!"
})))
}
}
Ok(None) => Ok(HttpResponse::NotFound().json(json!({
"status": "error",
"message": "Otp Not found, Resend Email?"
}))),
Err(_) => Ok(HttpResponse::Forbidden().json(json!({
"status": "error",
"message": "Invalid Otp!"
}))),
}
}
None => Ok(HttpResponse::BadRequest().json(json!({
"status": "error",
"message": "Invalid token"
}))),
}
}

pub fn user_config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/user")
Expand All @@ -115,6 +186,11 @@ pub fn user_config(cfg: &mut web::ServiceConfig) {
web::resource("/orders")
.wrap(ExtractClientId)
.route(web::get().to(get_orders)),
)
.service(
web::resource("/verify_email")
.wrap(ExtractClientId)
.route(web::post().to(verify_email)),
),
);
}
17 changes: 17 additions & 0 deletions crates/api/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct EmailVerifyData {
pub id: String,
pub mail: String,
}

#[derive(Serialize, Deserialize)]
pub struct VerifyEmailBody {
pub otp: String,
}

#[derive(Serialize, Deserialize)]
pub struct VerifyUser {
pub email_verified: Option<bool>,
}
4 changes: 4 additions & 0 deletions crates/notif_worker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ version = "0.1.0"
edition = "2021"

[dependencies]
base64 = "0.22.1"
chrono = "0.4.38"
dotenvy = "0.15.7"
lettre = "0.11.7"
lettre_email = "0.9.4"
r2d2 = "0.8.10"
r2d2_redis = "0.14.0"
redis = "0.26.0"
serde = "1.0.204"
serde_json = "1.0.121"
tokio = { version = "1.39.2", features = ["full"] }
totp-rs = "5.6.0"
Loading

0 comments on commit d39998a

Please sign in to comment.