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:
MythEclipse
2025-09-26 23:15:33 +07:00
parent c12da948aa
commit 5859af5294
32 changed files with 164 additions and 141 deletions
+20 -26
View File
@@ -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))
}
+10 -19
View File
@@ -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
}
+38 -6
View File
@@ -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
}
}
+8 -1
View File
@@ -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
View File
@@ -1 +0,0 @@
+24 -17
View File
@@ -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 {
+13 -7
View File
@@ -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()