Skip to main content

webgates_secrets/hashing/
errors.rs

1//! Error types for hashing and verification.
2//!
3//! This module defines the structured error types used by hashing
4//! implementations and secret verification flows.
5//!
6//! [`HashingError`] describes caller-visible hashing failures, and
7//! [`HashingOperation`] identifies which operation failed.
8//!
9//! # Examples
10//!
11//! Construct a hashing error with algorithm context:
12//! ```rust
13//! use webgates_core::errors_core::UserFriendlyError;
14//! use webgates_secrets::hashing::errors::{HashingError, HashingOperation};
15//!
16//! let err = HashingError::with_algorithm(
17//!     HashingOperation::Hash,
18//!     "argon2 hashing failed",
19//!     "argon2id",
20//! );
21//!
22//! assert!(err.user_message().contains("security processing system"));
23//! assert!(err.developer_message().contains("Hash operation hash failed"));
24//! assert!(err.support_code().starts_with("HASH-HASH"));
25//! ```
26//!
27//! Construct a verification failure where retry is possible:
28//! ```rust
29//! use webgates_core::errors_core::UserFriendlyError;
30//! use webgates_secrets::hashing::errors::{HashingError, HashingOperation};
31//!
32//! let err = HashingError::with_context(
33//!     HashingOperation::Verify,
34//!     "password verification failed",
35//!     Some("argon2id".into()),
36//!     Some("$argon2id$v=19$...".into()),
37//! );
38//! assert!(err.is_retryable());
39//! ```
40
41use std::collections::hash_map::DefaultHasher;
42use std::fmt;
43use std::hash::{Hash, Hasher};
44use thiserror::Error;
45use webgates_core::errors_core::{ErrorSeverity, UserFriendlyError};
46
47/// Hashing operation identifiers used for structured error reporting.
48#[derive(Debug, Clone)]
49pub enum HashingOperation {
50    /// Compute a new hash for a provided plaintext value.
51    Hash,
52    /// Verify a plaintext value against an existing hash.
53    Verify,
54}
55
56impl fmt::Display for HashingOperation {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            HashingOperation::Hash => write!(f, "hash"),
60            HashingOperation::Verify => write!(f, "verify"),
61        }
62    }
63}
64
65/// Error returned by hashing services and verification flows.
66///
67/// Use this type when hashing or verification cannot be completed safely.
68#[derive(Debug, Error)]
69#[non_exhaustive]
70pub enum HashingError {
71    /// A hashing-related operation failed.
72    #[error("Hashing error: {operation} - {message}")]
73    Operation {
74        /// The hashing operation that failed.
75        operation: HashingOperation,
76        /// Description of the error (non-sensitive).
77        message: String,
78        /// The hashing algorithm used (e.g., `argon2id`, `bcrypt`) if known.
79        algorithm: Option<String>,
80        /// Expected hash format (sanitized) if relevant.
81        expected_format: Option<String>,
82    },
83}
84
85impl HashingError {
86    /// Creates a hashing error without algorithm or format context.
87    ///
88    /// # Examples
89    /// ```rust
90    /// use webgates_secrets::hashing::errors::{HashingError, HashingOperation};
91    /// let _err = HashingError::new(HashingOperation::Hash, "failed to hash value");
92    /// ```
93    pub fn new(operation: HashingOperation, message: impl Into<String>) -> Self {
94        HashingError::Operation {
95            operation,
96            message: message.into(),
97            algorithm: None,
98            expected_format: None,
99        }
100    }
101
102    /// Creates a hashing error with algorithm context.
103    ///
104    /// # Examples
105    /// ```rust
106    /// use webgates_secrets::hashing::errors::{HashingError, HashingOperation};
107    /// let _err = HashingError::with_algorithm(HashingOperation::Verify, "verification failed", "argon2id");
108    /// ```
109    pub fn with_algorithm(
110        operation: HashingOperation,
111        message: impl Into<String>,
112        algorithm: impl Into<String>,
113    ) -> Self {
114        HashingError::Operation {
115            operation,
116            message: message.into(),
117            algorithm: Some(algorithm.into()),
118            expected_format: None,
119        }
120    }
121
122    /// Creates a hashing error with full context.
123    ///
124    /// # Examples
125    /// ```rust
126    /// use webgates_secrets::hashing::errors::{HashingError, HashingOperation};
127    /// let _err = HashingError::with_context(
128    ///     HashingOperation::Verify,
129    ///     "verification failed",
130    ///     Some("argon2id".into()),
131    ///     Some("$argon2id$v=19$...".into()),
132    /// );
133    /// ```
134    pub fn with_context(
135        operation: HashingOperation,
136        message: impl Into<String>,
137        algorithm: Option<String>,
138        expected_format: Option<String>,
139    ) -> Self {
140        HashingError::Operation {
141            operation,
142            message: message.into(),
143            algorithm,
144            expected_format,
145        }
146    }
147
148    /// Deterministic, category-specific support code for this error.
149    fn support_code_inner(&self) -> String {
150        let mut hasher = DefaultHasher::new();
151        match self {
152            HashingError::Operation {
153                operation,
154                algorithm,
155                ..
156            } => {
157                format!("HASH-{}-{:X}", operation.to_string().to_uppercase(), {
158                    format!("{:?}{:?}", operation, algorithm).hash(&mut hasher);
159                    hasher.finish() % 10000
160                })
161            }
162        }
163    }
164}
165
166impl UserFriendlyError for HashingError {
167    fn user_message(&self) -> String {
168        match self {
169            HashingError::Operation { operation, .. } => match operation {
170                HashingOperation::Hash => {
171                    "There's an issue with the security processing system. Please try again in a moment."
172                        .to_string()
173                }
174                HashingOperation::Verify => {
175                    "We couldn't verify your credentials due to a technical issue. Please try signing in again."
176                        .to_string()
177                }
178            },
179        }
180    }
181
182    fn developer_message(&self) -> String {
183        match self {
184            HashingError::Operation {
185                operation,
186                message,
187                algorithm,
188                expected_format,
189            } => {
190                let algorithm_context = algorithm
191                    .as_ref()
192                    .map(|a| format!(" [Algorithm: {}]", a))
193                    .unwrap_or_default();
194                let format_context = expected_format
195                    .as_ref()
196                    .map(|ef| format!(" [Expected: {}]", ef))
197                    .unwrap_or_default();
198                format!(
199                    "Hash operation {} failed: {}{}{}",
200                    operation, message, algorithm_context, format_context
201                )
202            }
203        }
204    }
205
206    fn support_code(&self) -> String {
207        self.support_code_inner()
208    }
209
210    fn severity(&self) -> ErrorSeverity {
211        match self {
212            HashingError::Operation { operation, .. } => match operation {
213                HashingOperation::Hash => ErrorSeverity::Critical,
214                HashingOperation::Verify => ErrorSeverity::Critical,
215            },
216        }
217    }
218
219    fn suggested_actions(&self) -> Vec<String> {
220        match self {
221            HashingError::Operation { operation, .. } => match operation {
222                HashingOperation::Hash => vec![
223                    "This is a critical security system error".to_string(),
224                    "Contact our support team immediately".to_string(),
225                    "Do not retry operations that involve password or secret changes".to_string(),
226                    "Use secure communication when reporting this issue".to_string(),
227                ],
228                HashingOperation::Verify => vec![
229                    "Double-check your password for typos".to_string(),
230                    "Ensure Caps Lock is not accidentally enabled".to_string(),
231                    "If you're certain your password is correct, contact support".to_string(),
232                    "Try using password recovery if verification continues to fail".to_string(),
233                ],
234            },
235        }
236    }
237
238    fn is_retryable(&self) -> bool {
239        match self {
240            HashingError::Operation { operation, .. } => match operation {
241                HashingOperation::Hash => false,  // critical system condition
242                HashingOperation::Verify => true, // user can retry with correct credentials
243            },
244        }
245    }
246}