Skip to main content

webgates_secrets/
errors.rs

1//! Error types for secret-related operations.
2//!
3//! This module contains the structured error type used when secret creation or
4//! verification fails in `webgates-secrets`.
5//!
6//! [`SecretError`] adds caller-safe messaging around hashing failures that
7//! happen at the `Secret` value-object boundary.
8//!
9//! # Examples
10//!
11//! Create a secret error with algorithm context:
12//! ```rust
13//! use webgates_secrets::errors::SecretError;
14//! use webgates_secrets::hashing::errors::HashingOperation;
15//!
16//! let err = SecretError::hashing_with_algorithm(
17//!     HashingOperation::Verify,
18//!     "password verification failed",
19//!     "argon2id",
20//! );
21//! ```
22
23use crate::hashing::errors::HashingOperation;
24use std::collections::hash_map::DefaultHasher;
25use std::hash::{Hash, Hasher};
26use thiserror::Error;
27use webgates_core::errors_core::{ErrorSeverity, UserFriendlyError};
28
29/// Error returned when secret creation or verification fails.
30///
31/// Use this type when failures occur at the `Secret` value-object boundary.
32#[derive(Debug, Error)]
33#[non_exhaustive]
34pub enum SecretError {
35    /// Hashing/verification error in secret flow.
36    #[error("Secret hashing error: {operation} - {message}")]
37    Hashing {
38        /// The hashing operation that failed.
39        operation: HashingOperation,
40        /// Description of the failure (non-sensitive).
41        message: String,
42        /// The hashing algorithm used (e.g., `argon2id`) if known.
43        algorithm: Option<String>,
44        /// Expected hash format (sanitized) if relevant.
45        expected_format: Option<String>,
46    },
47}
48
49impl SecretError {
50    /// Creates a hashing error with algorithm context.
51    pub fn hashing_with_algorithm(
52        operation: HashingOperation,
53        message: impl Into<String>,
54        algorithm: impl Into<String>,
55    ) -> Self {
56        SecretError::Hashing {
57            operation,
58            message: message.into(),
59            algorithm: Some(algorithm.into()),
60            expected_format: None,
61        }
62    }
63
64    /// Creates a hashing error with full context.
65    pub fn hashing_with_context(
66        operation: HashingOperation,
67        message: impl Into<String>,
68        algorithm: Option<String>,
69        expected_format: Option<String>,
70    ) -> Self {
71        SecretError::Hashing {
72            operation,
73            message: message.into(),
74            algorithm,
75            expected_format,
76        }
77    }
78
79    fn support_code_inner(&self) -> String {
80        let mut hasher = DefaultHasher::new();
81        match self {
82            SecretError::Hashing {
83                operation,
84                algorithm,
85                ..
86            } => {
87                format!("SECR-HASH-{}-{:X}", operation.to_string().to_uppercase(), {
88                    format!("{:?}{:?}", operation, algorithm).hash(&mut hasher);
89                    hasher.finish() % 10000
90                })
91            }
92        }
93    }
94}
95
96impl UserFriendlyError for SecretError {
97    fn user_message(&self) -> String {
98        match self {
99            SecretError::Hashing { operation, .. } => match operation {
100                HashingOperation::Hash => {
101                    "There's an issue with the security processing system. Please try again in a moment.".to_string()
102                }
103                HashingOperation::Verify => {
104                    "We couldn't verify your credentials due to a technical issue. Please try signing in again.".to_string()
105                }
106            },
107        }
108    }
109
110    fn developer_message(&self) -> String {
111        match self {
112            SecretError::Hashing {
113                operation,
114                message,
115                algorithm,
116                expected_format,
117            } => {
118                let algo_s = algorithm
119                    .as_ref()
120                    .map(|a| format!(" [Algorithm: {}]", a))
121                    .unwrap_or_default();
122                let exp_s = expected_format
123                    .as_ref()
124                    .map(|e| format!(" [Expected: {}]", e))
125                    .unwrap_or_default();
126                format!(
127                    "Secret hashing {} failed: {}{}{}",
128                    operation, message, algo_s, exp_s
129                )
130            }
131        }
132    }
133
134    fn support_code(&self) -> String {
135        self.support_code_inner()
136    }
137
138    fn severity(&self) -> ErrorSeverity {
139        match self {
140            SecretError::Hashing { .. } => ErrorSeverity::Critical,
141        }
142    }
143
144    fn suggested_actions(&self) -> Vec<String> {
145        match self {
146            SecretError::Hashing { operation, .. } => match operation {
147                HashingOperation::Hash => vec![
148                    "This is a critical security system error".to_string(),
149                    "Contact our support team immediately".to_string(),
150                    "Do not retry operations that involve password or secret changes".to_string(),
151                ],
152                HashingOperation::Verify => vec![
153                    "Double-check your password for typos".to_string(),
154                    "Ensure Caps Lock is not accidentally enabled".to_string(),
155                    "If you're certain your password is correct, contact support".to_string(),
156                    "Try using password recovery if verification continues to fail".to_string(),
157                ],
158            },
159        }
160    }
161
162    fn is_retryable(&self) -> bool {
163        match self {
164            SecretError::Hashing { operation, .. } => match operation {
165                HashingOperation::Hash => false,
166                HashingOperation::Verify => true,
167            },
168        }
169    }
170}