diff --git a/imphnen-backend/src/bin/seed_events.rs b/imphnen-backend/src/bin/seed_events.rs index fec6e68..d61721c 100644 --- a/imphnen-backend/src/bin/seed_events.rs +++ b/imphnen-backend/src/bin/seed_events.rs @@ -6,7 +6,7 @@ use surrealdb::{opt::auth::Root, sql::Thing, Uuid}; // Added Uuid #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { username: &env.surrealdb_username, diff --git a/imphnen-backend/src/bin/seed_gacha_rolls.rs b/imphnen-backend/src/bin/seed_gacha_rolls.rs index ef78b53..d285bd8 100644 --- a/imphnen-backend/src/bin/seed_gacha_rolls.rs +++ b/imphnen-backend/src/bin/seed_gacha_rolls.rs @@ -5,7 +5,7 @@ use surrealdb::sql::Thing; #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; use surrealdb::engine::any; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { diff --git a/imphnen-backend/src/bin/seed_mentor_user.rs b/imphnen-backend/src/bin/seed_mentor_user.rs index c5940a8..1909b33 100644 --- a/imphnen-backend/src/bin/seed_mentor_user.rs +++ b/imphnen-backend/src/bin/seed_mentor_user.rs @@ -5,7 +5,7 @@ use surrealdb::opt::auth::Root; #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; use surrealdb::engine::any; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { diff --git a/imphnen-backend/src/bin/seed_permissions.rs b/imphnen-backend/src/bin/seed_permissions.rs index 14efe25..78ef536 100644 --- a/imphnen-backend/src/bin/seed_permissions.rs +++ b/imphnen-backend/src/bin/seed_permissions.rs @@ -7,7 +7,7 @@ use surrealdb::opt::auth::Root; #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { username: &env.surrealdb_username, diff --git a/imphnen-backend/src/bin/seed_roles.rs b/imphnen-backend/src/bin/seed_roles.rs index 7764895..d605cef 100644 --- a/imphnen-backend/src/bin/seed_roles.rs +++ b/imphnen-backend/src/bin/seed_roles.rs @@ -5,7 +5,7 @@ use surrealdb::engine::any; use surrealdb::opt::auth::Root; #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { username: &env.surrealdb_username, diff --git a/imphnen-backend/src/bin/seed_roles_permissions.rs b/imphnen-backend/src/bin/seed_roles_permissions.rs index 039c7de..2e6597f 100644 --- a/imphnen-backend/src/bin/seed_roles_permissions.rs +++ b/imphnen-backend/src/bin/seed_roles_permissions.rs @@ -5,7 +5,7 @@ use surrealdb::opt::auth::Root; #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { username: &env.surrealdb_username, diff --git a/imphnen-backend/src/bin/seed_teams.rs b/imphnen-backend/src/bin/seed_teams.rs index f12cf2c..f9418a6 100644 --- a/imphnen-backend/src/bin/seed_teams.rs +++ b/imphnen-backend/src/bin/seed_teams.rs @@ -5,7 +5,7 @@ use surrealdb::{opt::auth::Root, sql::Thing}; #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; use surrealdb::engine::any; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { diff --git a/imphnen-backend/src/bin/seed_users.rs b/imphnen-backend/src/bin/seed_users.rs index 50db3f4..10f0287 100644 --- a/imphnen-backend/src/bin/seed_users.rs +++ b/imphnen-backend/src/bin/seed_users.rs @@ -5,7 +5,7 @@ use std::error::Error; use surrealdb::{opt::auth::Root, sql::Thing}; #[tokio::main] async fn main() -> Result<(), Box> { - let env = &imphnen_libs::enviroment::ENV; + let env = &imphnen_libs::environment::ENV; use surrealdb::engine::any; let db = any::connect(&env.surrealdb_url).await?; db.signin(Root { diff --git a/imphnen-dimentorin/src/v1/mentors/mentors_service.rs b/imphnen-dimentorin/src/v1/mentors/mentors_service.rs index c553275..0a4156e 100644 --- a/imphnen-dimentorin/src/v1/mentors/mentors_service.rs +++ b/imphnen-dimentorin/src/v1/mentors/mentors_service.rs @@ -187,11 +187,11 @@ impl MentorsService { let otp = imphnen_utils::generate_otp::OtpManager::generate_otp(); match auth_repo - .query_store_otp(final_user_email.clone(), otp) + .query_store_otp(final_user_email.clone(), otp.clone()) .await { Ok(_) => { - let message = format!("your otp code is {otp}"); + let message = format!("your otp code is {}", otp.code); if let Err(_err) = imphnen_utils::send_email(&final_user_email, "OTP Verification", &message) { diff --git a/imphnen-gacha/src/lib.rs b/imphnen-gacha/src/lib.rs index b7bd2cf..79b791f 100644 --- a/imphnen-gacha/src/lib.rs +++ b/imphnen-gacha/src/lib.rs @@ -32,7 +32,6 @@ pub use imphnen_utils::{ get_id, logger, make_thing, - mock_test, query_builder, query_list, response_format, diff --git a/imphnen-iam/src/v1/auth/auth_repository.rs b/imphnen-iam/src/v1/auth/auth_repository.rs index 492550a..9104bdf 100644 --- a/imphnen-iam/src/v1/auth/auth_repository.rs +++ b/imphnen-iam/src/v1/auth/auth_repository.rs @@ -3,13 +3,14 @@ use super::UserCacheSchema; use imphnen_entities::{PermissionsQueryDto, RolesDetailQueryDto, UsersDetailQueryDto}; use crate::ResourceEnum; use anyhow::{Result, anyhow, bail}; -use chrono::{Duration, Utc}; +use chrono::Utc; use surrealdb::sql::Thing; use tracing::instrument; use tracing::info; use async_trait::async_trait; use imphnen_libs::AuthRepositoryTrait; use imphnen_libs::SurrealMemClient; +use imphnen_utils::generate_otp::OtpData; pub struct AuthRepository { @@ -162,15 +163,13 @@ impl AuthRepository { } } - #[instrument(skip(self, email, otp), err)] - pub async fn query_store_otp(&self, email: String, otp: u32) -> Result { - let expires_at = Utc::now() + Duration::seconds(300); + pub async fn query_store_otp(&self, email: String, otp: OtpData) -> Result { let table: String = ResourceEnum::OtpCache.to_string(); info!(query = %format!("CREATE {}:{}", table, email), "Executing SurrealDB query"); let record: Option = self .db .create((table.as_str(), email.as_str())) - .content(AuthOtpSchema { otp, expires_at }) + .content(AuthOtpSchema { otp: otp.code, hash: otp.hash, expires_at: otp.expires_at }) .await?; match record { Some(_) => Ok("Success store otp".to_string()), diff --git a/imphnen-iam/src/v1/auth/auth_schema.rs b/imphnen-iam/src/v1/auth/auth_schema.rs index cf715ae..3e7e94c 100644 --- a/imphnen-iam/src/v1/auth/auth_schema.rs +++ b/imphnen-iam/src/v1/auth/auth_schema.rs @@ -4,5 +4,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AuthOtpSchema { pub otp: u32, + pub hash: String, pub expires_at: DateTime, } diff --git a/imphnen-iam/src/v1/auth/auth_service.rs b/imphnen-iam/src/v1/auth/auth_service.rs index 19a1d60..c85b294 100644 --- a/imphnen-iam/src/v1/auth/auth_service.rs +++ b/imphnen-iam/src/v1/auth/auth_service.rs @@ -1,7 +1,7 @@ use std::pin::Pin; use std::future::Future; use imphnen_utils as generate_otp; -use imphnen_libs::enviroment; +use imphnen_libs::environment; use super::{ AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, AuthRegisterRequestDto, AuthRepository, @@ -310,9 +310,9 @@ impl AuthServiceTrait for AuthService { phone_number: payload.phone_number, }; let otp = generate_otp::OtpManager::generate_otp(); - match auth_repo.query_store_otp(new_user.email.clone(), otp).await { + match auth_repo.query_store_otp(new_user.email.clone(), otp.clone()).await { Ok(_) => { - let message = format!("your otp code is {otp}"); + let message = format!("your otp code is {}", otp.code); if let Err(err_send) = send_email(&new_user.email, "OTP Verification", &message) { @@ -383,7 +383,7 @@ impl AuthServiceTrait for AuthService { let auth_repo = AuthRepository::new(state.surrealdb_mem.clone()); let _ = auth_repo.query_get_stored_otp(payload.email.clone()).await; let otp = generate_otp::OtpManager::generate_otp(); - let message = format!("Your OTP code is {otp}"); + let message = format!("Your OTP code is {}", otp.code); match auth_repo.query_store_otp(payload.email.clone(), otp).await { Ok(_) => match send_email(&payload.email, "OTP Verification", &message) { Ok(_) => common_response(StatusCode::OK, "OTP resent successfully"), @@ -477,7 +477,7 @@ impl AuthServiceTrait for AuthService { } }; - let env = &enviroment::ENV; + let env = &environment::ENV; let fe_url = env.fe_url.clone(); let message = format!( "You have requested a password reset. Please click the link below to continue: {fe_url}/auth/reset-password?token={token}" diff --git a/imphnen-iam/src/v1/auth/google/google_oauth_controller.rs b/imphnen-iam/src/v1/auth/google/google_oauth_controller.rs index af0522a..1357a8e 100644 --- a/imphnen-iam/src/v1/auth/google/google_oauth_controller.rs +++ b/imphnen-iam/src/v1/auth/google/google_oauth_controller.rs @@ -7,7 +7,7 @@ use axum::{ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use std::sync::Arc; -use imphnen_libs::enviroment::ENV; // Import ENV +use imphnen_libs::environment::ENV; // Import ENV use crate::v1::auth::google::google_oauth_service::{AuthRequest, GoogleOauthService, GoogleOauthServiceImpl}; use imphnen_entities::error_dto::error::Error; diff --git a/imphnen-iam/src/v1/auth/google/google_oauth_service.rs b/imphnen-iam/src/v1/auth/google/google_oauth_service.rs index 93cbfc5..19fb895 100644 --- a/imphnen-iam/src/v1/auth/google/google_oauth_service.rs +++ b/imphnen-iam/src/v1/auth/google/google_oauth_service.rs @@ -12,7 +12,7 @@ use oauth2::TokenResponse; use tracing::{info, error}; use imphnen_entities::error_dto::error::Error; -use imphnen_libs::{jsonwebtoken::{encode_access_token, encode_refresh_token}, enviroment::Env, AppState}; +use imphnen_libs::{jsonwebtoken::{encode_access_token, encode_refresh_token}, environment::Env, AppState}; use imphnen_utils::{generate_oauth_csrf_token, validate_oauth_csrf_token, validate_csrf_token}; use crate::v1::auth::TokenDto; use crate::v1::auth::auth_service::AuthServiceTrait; diff --git a/imphnen-iam/src/v1/teams/teams_service.rs b/imphnen-iam/src/v1/teams/teams_service.rs index 5b5046f..c117a1c 100644 --- a/imphnen-iam/src/v1/teams/teams_service.rs +++ b/imphnen-iam/src/v1/teams/teams_service.rs @@ -79,7 +79,7 @@ impl TeamsService { } async fn generate_invitation_token() -> String { - format!("team_{}_{}", Uuid::new_v4(), OtpManager::generate_otp()) + format!("team_{}_{}", Uuid::new_v4(), OtpManager::generate_otp().code) } async fn get_user_info_with_privacy( diff --git a/imphnen-libs/src/axum/mod.rs b/imphnen-libs/src/axum/mod.rs index 88db171..0b26768 100644 --- a/imphnen-libs/src/axum/mod.rs +++ b/imphnen-libs/src/axum/mod.rs @@ -7,7 +7,7 @@ use crate::{surrealdb_init_mem, surrealdb_init_ws, SurrealMemClient, SurrealWsCl use axum::{Router, serve}; use std::{future::Future, net::SocketAddr}; use tokio::net::TcpListener; -use crate::enviroment::ENV; +use crate::environment::ENV; /// Initialize and start the Axum server with SurrealDB connections. /// diff --git a/imphnen-libs/src/enviroment/mod.rs b/imphnen-libs/src/environment/mod.rs similarity index 100% rename from imphnen-libs/src/enviroment/mod.rs rename to imphnen-libs/src/environment/mod.rs diff --git a/imphnen-libs/src/jsonwebtoken/mod.rs b/imphnen-libs/src/jsonwebtoken/mod.rs index d4e7f3e..4276df5 100644 --- a/imphnen-libs/src/jsonwebtoken/mod.rs +++ b/imphnen-libs/src/jsonwebtoken/mod.rs @@ -4,7 +4,7 @@ //! for authentication purposes, including access tokens, refresh tokens, //! and password reset tokens. -use crate::enviroment::ENV; +use crate::environment::ENV; use axum::http::StatusCode; use chrono::{Duration, TimeDelta, Utc}; use jsonwebtoken::{ diff --git a/imphnen-libs/src/lettre/mod.rs b/imphnen-libs/src/lettre/mod.rs index 5d0bbfc..e83425b 100644 --- a/imphnen-libs/src/lettre/mod.rs +++ b/imphnen-libs/src/lettre/mod.rs @@ -3,7 +3,7 @@ //! This module provides functionality for sending emails through SMTP //! with proper error handling and logging. -use crate::enviroment::ENV; +use crate::environment::ENV; use lettre::message::Mailbox; use lettre::transport::smtp::authentication::Credentials; use lettre::{Message, SmtpTransport, Transport}; @@ -88,7 +88,7 @@ fn build_email_message( to: &str, subject: &str, body: &str, - env: &crate::enviroment::Env, + env: &crate::environment::Env, ) -> Result> { let sender_name = env.smtp_name.replace("-", " "); // Normalize sender name @@ -107,7 +107,7 @@ fn build_email_message( /// /// # Returns /// Configured SMTP transport or error -fn create_smtp_transport(env: &crate::enviroment::Env) -> Result> { +fn create_smtp_transport(env: &crate::environment::Env) -> Result> { let credentials = Credentials::new( env.smtp_email.clone(), env.smtp_password.replace("-", " "), // Normalize password diff --git a/imphnen-libs/src/lib.rs b/imphnen-libs/src/lib.rs index e456041..2fcc726 100644 --- a/imphnen-libs/src/lib.rs +++ b/imphnen-libs/src/lib.rs @@ -1,8 +1,25 @@ +/*! +# imphnen-libs + +A collection of utility libraries and services for the imphnen project, providing integrations +with various external services and common functionality. + +This crate includes modules for: +- Password hashing with Argon2 (`argon`) +- Axum web framework utilities (`axum`) +- Environment configuration (`environment`) +- JWT token handling (`jsonwebtoken`) +- Email sending with Lettre (`lettre`) +- MinIO object storage client (`minio`) +- Service abstractions (`services`) +- SurrealDB database client (`surrealdb`) +*/ + use std::sync::Arc; pub mod argon; pub mod axum; -pub mod enviroment; +pub mod environment; pub mod jsonwebtoken; pub mod lettre; pub mod minio; @@ -11,7 +28,7 @@ pub mod surrealdb; pub use argon::{hash_password, verify_password}; pub use axum::axum_init; -pub use enviroment::{ENV, Env}; +pub use environment::{ENV, Env}; pub use imphnen_entities::{ MessageResponseDto, MetaRequestDto, diff --git a/imphnen-libs/src/minio.rs b/imphnen-libs/src/minio.rs index f997d0f..cd4e7bc 100644 --- a/imphnen-libs/src/minio.rs +++ b/imphnen-libs/src/minio.rs @@ -4,7 +4,7 @@ use chrono::Utc; use hmac::{Hmac, Mac}; use sha2::{Digest, Sha256}; use uuid::Uuid; -use crate::enviroment::ENV; +use crate::environment::ENV; @@ -108,12 +108,6 @@ impl MinioService { let url = format!("https://{}/{}/{}", host, self.bucket_name, object_name); - // Debug logging - log::debug!("MinIO Endpoint config: {}", self.endpoint); - log::debug!("MinIO Region config: {}", self.region); - log::debug!("Upload URL: {}", url); - log::debug!("Object name: {}", object_name); - log::debug!("File hash: {}", short_hash); let now = Utc::now(); let amz_date = now.format("%Y%m%dT%H%M%SZ").to_string(); @@ -135,7 +129,6 @@ impl MinioService { canonical_uri, canonical_headers, signed_headers, payload_hash ); - log::debug!("Canonical request:\n{}", canonical_request); let scope = format!("{}/{}/s3/aws4_request", date_stamp, self.region); let string_to_sign = format!( @@ -153,7 +146,6 @@ impl MinioService { mac.update(string_to_sign.as_bytes()); let signature = hex::encode(mac.finalize().into_bytes()); - log::debug!("Generated signature: {}", signature); let auth_header = format!( "AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}", @@ -211,13 +203,6 @@ impl MinioService { let url = format!("https://{}/{}/{}", host, self.bucket_name, object_name); - // Debug logging - log::debug!("MinIO Endpoint config: {}", self.endpoint); - log::debug!("MinIO Region config: {}", self.region); - log::debug!("MinIO Access Key: {}", self.access_key); - log::debug!("MinIO Bucket: {}", self.bucket_name); - log::debug!("Extracted host: {}", host); - log::debug!("Final URL: {}", url); let now = Utc::now(); let amz_date = now.format("%Y%m%dT%H%M%SZ").to_string(); @@ -242,14 +227,6 @@ impl MinioService { canonical_uri, canonical_headers, signed_headers, payload_hash ); - // Debug logging - log::debug!("URL: {}", url); - log::debug!("Host: {}", host); - log::debug!("Bucket: {}", self.bucket_name); - log::debug!("Object: {}", object_name); - log::debug!("Canonical URI: {}", canonical_uri); - log::debug!("Payload hash: {}", payload_hash); - log::debug!("Canonical Request:\n{}", canonical_request); let scope = format!("{}/{}/s3/aws4_request", date_stamp, self.region); let string_to_sign = format!( @@ -259,15 +236,12 @@ impl MinioService { hex::encode(Sha256::digest(canonical_request.as_bytes())) ); - log::debug!("Scope: {}", scope); - log::debug!("String to sign:\n{}", string_to_sign); let signing_key = self.get_signature_key(&date_stamp)?; let mut mac = Hmac::::new_from_slice(&signing_key)?; mac.update(string_to_sign.as_bytes()); let signature = hex::encode(mac.finalize().into_bytes()); - log::debug!("Generated signature: {}", signature); let auth_header = format!( "AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}", @@ -475,15 +449,12 @@ impl MinioService { // Debug logging for signature calculation log::debug!("Region: {}", self.region); - log::debug!("Scope: {}", scope); - log::debug!("String to sign:\n{}", string_to_sign); let signing_key = self.get_signature_key(&date_stamp)?; let mut mac = Hmac::::new_from_slice(&signing_key)?; mac.update(string_to_sign.as_bytes()); let signature = hex::encode(mac.finalize().into_bytes()); - log::debug!("Final signature: {}", signature); let auth_header = format!( "AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}", diff --git a/imphnen-libs/src/surrealdb/mod.rs b/imphnen-libs/src/surrealdb/mod.rs index 0681b5c..44af974 100644 --- a/imphnen-libs/src/surrealdb/mod.rs +++ b/imphnen-libs/src/surrealdb/mod.rs @@ -3,7 +3,7 @@ //! This module provides utilities for initializing SurrealDB connections //! for both WebSocket and in-memory databases, along with resource definitions. -use crate::enviroment::ENV; +use crate::environment::ENV; use surrealdb::engine::any; use surrealdb::engine::local::{Db, Mem}; use surrealdb::opt::auth::Root; diff --git a/imphnen-middleware/src/cors_middleware/mod.rs b/imphnen-middleware/src/cors_middleware/mod.rs index 22c46d6..f748039 100644 --- a/imphnen-middleware/src/cors_middleware/mod.rs +++ b/imphnen-middleware/src/cors_middleware/mod.rs @@ -1,5 +1,5 @@ use axum::http::{HeaderValue, Method, header}; -use imphnen_libs::enviroment::ENV; +use imphnen_libs::environment::ENV; use tower_http::cors::CorsLayer; pub fn cors_middleware() -> CorsLayer { diff --git a/imphnen-utils/src/csrf_token.rs b/imphnen-utils/src/csrf_token.rs index dabb871..b49476d 100644 --- a/imphnen-utils/src/csrf_token.rs +++ b/imphnen-utils/src/csrf_token.rs @@ -1,9 +1,14 @@ +//! CSRF token generation and validation utilities. +//! +//! This module provides stateless CSRF token management using signed tokens +//! with timestamp validation to prevent cross-site request forgery attacks. + use std::time::{SystemTime, UNIX_EPOCH}; use serde::{Deserialize, Serialize}; use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; use sha2::{Sha256, Digest}; use imphnen_entities::error_dto::error::Error; -use tracing::{info, error}; // Added this line +use tracing::error; #[derive(Debug, Serialize, Deserialize)] struct CsrfPayload { @@ -24,33 +29,28 @@ pub fn generate_csrf_token(secret: &str) -> Result { .duration_since(UNIX_EPOCH) .map_err(|_| Error::Auth("Failed to get timestamp".to_string()))? .as_secs(); - info!("CSRF Token Generation: Timestamp = {}", timestamp); // Log after definition - + let random = uuid::Uuid::new_v4().to_string(); - info!("CSRF Token Generation: Random string generated."); // Log after definition - + let payload = CsrfPayload { timestamp, random, }; - + let payload_json = serde_json::to_string(&payload) - .map_err(|e| { // Changed to capture error + .map_err(|e| { error!("CSRF Token Generation: Failed to serialize CSRF payload: {:?}", e); Error::Auth("Failed to serialize CSRF payload".to_string()) })?; - info!("CSRF Token Generation: Payload JSON = {}", payload_json); // Log after definition - + let payload_b64 = URL_SAFE_NO_PAD.encode(payload_json.as_bytes()); - info!("CSRF Token Generation: Payload Base64 = {}", payload_b64); // Log after definition - + // Create signature let mut hasher = Sha256::new(); hasher.update(payload_b64.as_bytes()); hasher.update(secret.as_bytes()); let signature = URL_SAFE_NO_PAD.encode(hasher.finalize()); - info!("CSRF Token Generation: Signature = {}", signature); // Log after definition - + Ok(format!("{}.{}", payload_b64, signature)) } @@ -60,35 +60,29 @@ pub fn generate_oauth_csrf_token(secret: &str, pkce_verifier: &str) -> Result Option { - info!(?headers, "extract_email called with headers"); let auth_header = match headers.get(AUTHORIZATION) { Some(h) => h, None => { @@ -27,16 +32,13 @@ pub fn extract_email(headers: &HeaderMap) -> Option { return None; } }; - info!(token, "Extracted bearer token in extract_email"); - + // First try to decode as our internal JWT token match decode_access_token(token) { Ok(data) => { - info!(email = %data.claims.sub, "Successfully decoded internal access token in extract_email"); Some(data.claims.sub) } Err(_) => { - info!("Failed to decode as internal JWT, checking if it's a Google token"); // If it fails, it might be a Google access token // For Google tokens, we need async validation, so we'll return None here // and handle Google tokens separately in the calling code @@ -48,7 +50,6 @@ pub fn extract_email(headers: &HeaderMap) -> Option { /// Async version that can handle Google access tokens pub async fn extract_email_async(headers: &HeaderMap) -> Option { - info!(?headers, "extract_email_async called with headers"); let auth_header = match headers.get(AUTHORIZATION) { Some(h) => h, None => { @@ -70,16 +71,13 @@ pub async fn extract_email_async(headers: &HeaderMap) -> Option { return None; } }; - info!(token, "Extracted bearer token in extract_email_async"); - + // First try to decode as our internal JWT token match decode_access_token(token) { Ok(data) => { - info!(email = %data.claims.sub, "Successfully decoded internal access token in extract_email_async"); Some(data.claims.sub) } Err(_) => { - info!("Failed to decode as internal JWT, trying Google token validation"); // If it fails, try to validate as Google access token extract_email_from_google_token(token).await } @@ -126,14 +124,11 @@ async fn extract_email_from_google_token(token: &str) -> Option { /// Extracts the email from a JWT token string. /// Supports both our internal JWT tokens and Google access tokens. pub fn extract_email_token(token: String) -> Option { - info!(token = %token, "extract_email_token called with token"); match decode_access_token(&token) { Ok(data) => { - info!(email = %data.claims.sub, "Successfully decoded token in extract_email_token"); Some(data.claims.sub) } Err(_) => { - info!("Failed to decode as internal JWT in extract_email_token, checking if it's a Google token"); // If it fails, it might be a Google access token // For Google tokens, we need async validation, so we'll return None here // and handle Google tokens separately in the calling code @@ -151,20 +146,16 @@ fn is_jwt(token: &str) -> bool { /// Async version of extract_email_token that can handle Google access tokens pub async fn extract_email_token_async(token: String) -> Option { - info!(token = %token, "extract_email_token_async called with token"); - if is_jwt(&token) { match decode_access_token(&token) { Ok(data) => { - info!(email = %data.claims.sub, "Successfully decoded internal token in extract_email_token_async"); return Some(data.claims.sub); } Err(_) => { - info!("Failed to decode as internal JWT in extract_email_token_async, trying Google token validation"); } } } - + // If it's not a valid internal JWT, try to validate as Google access token extract_email_from_google_token(&token).await } \ No newline at end of file diff --git a/imphnen-utils/src/generate_otp.rs b/imphnen-utils/src/generate_otp.rs index ee98cb1..587355f 100644 --- a/imphnen-utils/src/generate_otp.rs +++ b/imphnen-utils/src/generate_otp.rs @@ -1,13 +1,45 @@ +//! OTP generation utilities with time-based expiration and secure hashing. +//! +//! This module provides functionality to generate one-time passwords (OTPs) with +//! a 5-minute expiration time and SHA256 hashing for secure storage and validation, +//! preventing replay attacks. + use rand::{Rng, rng}; +use sha2::{Sha256, Digest}; +use chrono::{DateTime, Utc, Duration}; + +/// Represents an OTP with its code, hashed value and expiration time +#[derive(Debug, Clone)] +pub struct OtpData { + pub code: u32, + pub hash: String, + pub expires_at: DateTime, +} pub struct OtpManager; impl OtpManager { - pub fn generate_otp() -> u32 { - rng().random_range(100_000..1_000_000) - } + /// Generates a new OTP with a 5-minute expiration and SHA256 hash for secure storage + pub fn generate_otp() -> OtpData { + let code = rng().random_range(100_000..1_000_000); + let otp_str = code.to_string(); + let mut hasher = Sha256::new(); + hasher.update(otp_str.as_bytes()); + let hash = format!("{:x}", hasher.finalize()); + let expires_at = Utc::now() + Duration::minutes(5); + OtpData { code, hash, expires_at } + } - pub fn validate_otp(stored_otp: u32, user_otp: u32) -> bool { - stored_otp == user_otp - } + /// Validates the user-provided OTP against the stored OTP data + /// Checks both hash match and expiration + pub fn validate_otp(stored: &OtpData, user_otp: u32) -> bool { + if Utc::now() > stored.expires_at { + return false; + } + let user_otp_str = user_otp.to_string(); + let mut hasher = Sha256::new(); + hasher.update(user_otp_str.as_bytes()); + let user_hash = format!("{:x}", hasher.finalize()); + user_hash == stored.hash + } } diff --git a/imphnen-utils/src/lib.rs b/imphnen-utils/src/lib.rs index 7d24d17..a32db0b 100644 --- a/imphnen-utils/src/lib.rs +++ b/imphnen-utils/src/lib.rs @@ -1,3 +1,11 @@ +//! # imphnen-utils +//! +//! A collection of utility functions and types for the imphnen project. +//! +//! This crate provides various utilities including OTP generation with expiration and hashing, +//! CSRF token management, email extraction from tokens, query building for SurrealDB, +//! and standardized response formatting. + pub mod bind_filter; pub mod csrf_token; pub mod extract_email; @@ -6,7 +14,6 @@ pub mod generate_otp; pub mod get_id; pub mod logger; pub mod make_thing; -pub mod mock_test; pub mod query_builder; pub mod query_list; pub mod response_format; diff --git a/imphnen-utils/src/mock_test.rs b/imphnen-utils/src/mock_test.rs deleted file mode 100644 index 8b13789..0000000 --- a/imphnen-utils/src/mock_test.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/imphnen-utils/src/query_builder.rs b/imphnen-utils/src/query_builder.rs index ca07026..22d26d4 100644 --- a/imphnen-utils/src/query_builder.rs +++ b/imphnen-utils/src/query_builder.rs @@ -1,3 +1,9 @@ +//! Query builder utilities for SurrealDB. +//! +//! This module provides builders for constructing SurrealDB queries with +//! support for pagination, filtering, sorting, and binding parameters. +//! Includes both list queries and detail queries with unique binding keys. + use anyhow::Result; use imphnen_libs::MetaRequestDto; use serde_json::{Map, Value}; @@ -131,11 +137,11 @@ impl ListQueryBuilder { format!( r#" - SELECT {} FROM {} - {} - {} - LIMIT {} START {} - {} + SELECT {} FROM {} + {} + {} + LIMIT {} START {} + {} "#, select_clause, self.resource, @@ -166,6 +172,7 @@ pub struct DetailQueryBuilder { fetch_fields: Vec, conditions: Vec, bindings: Map, + binding_counter: usize, } impl DetailQueryBuilder { @@ -178,6 +185,7 @@ impl DetailQueryBuilder { fetch_fields: vec![], conditions: vec![], bindings: Map::new(), + binding_counter: 0, } } @@ -202,7 +210,6 @@ impl DetailQueryBuilder { self } - // Modified with_where method pub fn with_where( mut self, field: impl Into, @@ -213,11 +220,11 @@ impl DetailQueryBuilder { } let field_str = field.into(); if let Some(val) = value { - // Using a distinct binding key to avoid conflicts - self.conditions.push(format!("{field_str} = $value_where")); - self - .bindings - .insert("value_where".to_string(), Value::String(val.into())); + // Using a unique binding key to avoid conflicts + let key = format!("value_where_{}", self.binding_counter); + self.binding_counter += 1; + self.conditions.push(format!("{field_str} = ${key}")); + self.bindings.insert(key, Value::String(val.into())); } else { // If no value, assume it's a direct condition string (e.g., "is_active = true") self.conditions.push(field_str); @@ -231,15 +238,15 @@ impl DetailQueryBuilder { } pub fn with_thing_equals(mut self, field: &str, thing: &Thing) -> Self { - let condition = build_thing_condition(field, thing); - self.conditions.push(condition); - self + let condition = build_thing_condition(field, thing); + self.conditions.push(condition); + self } pub fn with_things_equals(mut self, conditions: &[(&str, &Thing)]) -> Self { - let condition = build_multi_thing_condition(conditions); - self.conditions.push(condition); - self + let condition = build_multi_thing_condition(conditions); + self.conditions.push(condition); + self } pub fn with_select_fields(mut self, fields: Vec<&str>) -> Self { diff --git a/imphnen-utils/src/response_format.rs b/imphnen-utils/src/response_format.rs index bcba3b8..4a0067a 100644 --- a/imphnen-utils/src/response_format.rs +++ b/imphnen-utils/src/response_format.rs @@ -1,7 +1,13 @@ +//! Standardized response formatting utilities. +//! +//! This module provides consistent response formatting for API endpoints, +//! including success responses, error responses, and list responses with +//! configurable versioning from Cargo.toml. + use axum::{ - Json, - http::StatusCode, - response::{IntoResponse, Response}, + Json, + http::StatusCode, + response::{IntoResponse, Response}, }; use serde::Serialize; use serde_json::json; @@ -13,7 +19,7 @@ pub fn success_response(params: ResponseSuccessDto) -> Response StatusCode::OK, Json(json!({ "data": params.data, - "version": "0.1.0", + "version": env!("CARGO_PKG_VERSION"), })), ) .into_response() @@ -27,7 +33,7 @@ pub fn success_list_response( Json(json!({ "data": params.data, "meta": params.meta, - "version": "0.1.0", + "version": env!("CARGO_PKG_VERSION"), })), ) .into_response() @@ -38,7 +44,7 @@ pub fn common_response(status: StatusCode, message: &str) -> Response { status, Json(json!({ "message": message, - "version": "0.1.0", + "version": env!("CARGO_PKG_VERSION"), })), ) .into_response() @@ -49,7 +55,7 @@ pub fn success_created_response(params: ResponseSuccessDto) -> StatusCode::CREATED, Json(json!({ "data": params.data, - "version": "0.1.0", + "version": env!("CARGO_PKG_VERSION"), })), ) .into_response() diff --git a/tests/src/iam/auth/google/google_oauth_flow_test.rs b/tests/src/iam/auth/google/google_oauth_flow_test.rs index 550f74a..83b980c 100644 --- a/tests/src/iam/auth/google/google_oauth_flow_test.rs +++ b/tests/src/iam/auth/google/google_oauth_flow_test.rs @@ -17,7 +17,7 @@ mod tests { use imphnen_iam::v1::users::users_dto::{UsersDetailItemDto, UsersCreateRequestDto}; // Corrected: removed UserDto alias, used UsersCreateRequestDto use imphnen_entities::error_dto::ErrorResponse; use imphnen_libs::jsonwebtoken::generate_jwt; - use imphnen_libs::enviroment::{ENV, Env}; // Import ENV and Env + use imphnen_libs::environment::{ENV, Env}; // Import ENV and Env mock! { pub GoogleOauthServiceMock {}