Refactor environment module: Rename enviroment to environment and consolidate environment configuration management
- Updated all references from `enviroment` to `environment` across the codebase. - Removed the old `enviroment` module and replaced it with a new `environment` module that includes centralized configuration management. - Enhanced OTP generation to include secure hashing and expiration handling. - Improved CSRF token generation and validation with better error handling. - Cleaned up logging statements in various modules for clarity and consistency. - Updated response formatting to include versioning from Cargo.toml. - Removed unused mock test module from utils.
This commit is contained in:
@@ -6,7 +6,7 @@ use surrealdb::{opt::auth::Root, sql::Thing, Uuid}; // Added Uuid
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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,
|
||||
|
||||
@@ -5,7 +5,7 @@ use surrealdb::sql::Thing;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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 {
|
||||
|
||||
@@ -5,7 +5,7 @@ use surrealdb::opt::auth::Root;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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 {
|
||||
|
||||
@@ -7,7 +7,7 @@ use surrealdb::opt::auth::Root;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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,
|
||||
|
||||
@@ -5,7 +5,7 @@ use surrealdb::engine::any;
|
||||
use surrealdb::opt::auth::Root;
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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,
|
||||
|
||||
@@ -5,7 +5,7 @@ use surrealdb::opt::auth::Root;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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,
|
||||
|
||||
@@ -5,7 +5,7 @@ use surrealdb::{opt::auth::Root, sql::Thing};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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 {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::error::Error;
|
||||
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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 {
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -32,7 +32,6 @@ pub use imphnen_utils::{
|
||||
get_id,
|
||||
logger,
|
||||
make_thing,
|
||||
mock_test,
|
||||
query_builder,
|
||||
query_list,
|
||||
response_format,
|
||||
|
||||
@@ -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<String> {
|
||||
let expires_at = Utc::now() + Duration::seconds(300);
|
||||
pub async fn query_store_otp(&self, email: String, otp: OtpData) -> Result<String> {
|
||||
let table: String = ResourceEnum::OtpCache.to_string();
|
||||
info!(query = %format!("CREATE {}:{}", table, email), "Executing SurrealDB query");
|
||||
let record: Option<AuthOtpSchema> = 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()),
|
||||
|
||||
@@ -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<Utc>,
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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<Message, Box<dyn Error>> {
|
||||
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<SmtpTransport, Box<dyn Error>> {
|
||||
fn create_smtp_transport(env: &crate::environment::Env) -> Result<SmtpTransport, Box<dyn Error>> {
|
||||
let credentials = Credentials::new(
|
||||
env.smtp_email.clone(),
|
||||
env.smtp_password.replace("-", " "), // Normalize password
|
||||
|
||||
+19
-2
@@ -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,
|
||||
|
||||
@@ -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::<Sha256>::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::<Sha256>::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={}",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<String, Error> {
|
||||
.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<St
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|_| Error::Auth("Failed to get timestamp".to_string()))?
|
||||
.as_secs();
|
||||
info!("OAuth CSRF Token Generation: Timestamp = {}", timestamp); // Log after definition
|
||||
|
||||
|
||||
let random = uuid::Uuid::new_v4().to_string();
|
||||
info!("OAuth CSRF Token Generation: Random string generated."); // Log after definition
|
||||
|
||||
|
||||
let payload = OAuthCsrfPayload {
|
||||
timestamp,
|
||||
random,
|
||||
pkce_verifier: pkce_verifier.to_string(),
|
||||
};
|
||||
info!("OAuth CSRF Token Generation: PKCE Verifier = {}", pkce_verifier); // Log after use in payload
|
||||
|
||||
|
||||
let payload_json = serde_json::to_string(&payload)
|
||||
.map_err(|e| { // Changed to capture error
|
||||
.map_err(|e| {
|
||||
error!("OAuth CSRF Token Generation: Failed to serialize payload: {:?}", e);
|
||||
Error::Auth("Failed to serialize OAuth CSRF payload".to_string())
|
||||
})?;
|
||||
info!("OAuth CSRF Token Generation: Payload JSON = {}", payload_json); // Log after definition
|
||||
|
||||
|
||||
let payload_b64 = URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
|
||||
info!("OAuth 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!("OAuth CSRF Token Generation: Signature = {}", signature); // Log after definition
|
||||
|
||||
|
||||
Ok(format!("{}.{}", payload_b64, signature))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use tracing::{info, error};
|
||||
//! Email extraction utilities from authentication tokens.
|
||||
//!
|
||||
//! This module provides functions to extract email addresses from JWT tokens
|
||||
//! and Google OAuth access tokens, supporting both synchronous and asynchronous
|
||||
//! validation methods.
|
||||
|
||||
use tracing::{error, info};
|
||||
use crate::decode_access_token;
|
||||
use axum::http::{HeaderMap, header::AUTHORIZATION};
|
||||
|
||||
/// Extracts the email from the Authorization header, if present and valid.
|
||||
/// Supports both our internal JWT tokens and Google access tokens.
|
||||
pub fn extract_email(headers: &HeaderMap) -> Option<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
|
||||
/// Async version that can handle Google access tokens
|
||||
pub async fn extract_email_async(headers: &HeaderMap) -> Option<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
/// 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<String> {
|
||||
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<String> {
|
||||
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
|
||||
}
|
||||
@@ -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<Utc>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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<String>,
|
||||
conditions: Vec<String>,
|
||||
bindings: Map<String, Value>,
|
||||
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<String>,
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<T: Serialize>(params: ResponseSuccessDto<T>) -> 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<T: Serialize>(
|
||||
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<T: Serialize>(params: ResponseSuccessDto<T>) ->
|
||||
StatusCode::CREATED,
|
||||
Json(json!({
|
||||
"data": params.data,
|
||||
"version": "0.1.0",
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
Reference in New Issue
Block a user