1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Error sanitization layer — separates user-facing messages from internal details.
//!
//! Authentication errors often contain internal information that must never reach API
//! clients (database query details, key material references, stack traces). This module
//! provides [`SanitizedError`] and [`AuthErrorSanitizer`] to ensure only safe, generic
//! messages are returned in API responses while full details are retained for server-side
//! logging.
use std::fmt;
/// A sanitizable error that separates user-facing and internal messages
#[derive(Debug, Clone)]
pub struct SanitizedError {
/// User-facing message (safe for API responses)
user_message: String,
/// Internal message (for logging only)
internal_message: String,
}
impl SanitizedError {
/// Create a new sanitized error
pub fn new(user_message: impl Into<String>, internal_message: impl Into<String>) -> Self {
Self {
user_message: user_message.into(),
internal_message: internal_message.into(),
}
}
/// Get the user-facing message (safe for API responses)
#[must_use]
pub fn user_facing(&self) -> &str {
&self.user_message
}
/// Get the internal message (for logging only)
#[must_use]
pub fn internal(&self) -> &str {
&self.internal_message
}
}
impl fmt::Display for SanitizedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Display uses user-facing message (safe for logs)
write!(f, "{}", self.user_message)
}
}
impl std::error::Error for SanitizedError {}
/// Helper trait for creating sanitized errors from standard error types.
///
/// The method `.sanitized(msg)` converts any `Display` type into a [`SanitizedError`],
/// keeping the original message for server-side logging while returning a safe message
/// in the API response.
pub trait Sanitize {
/// Convert to a sanitized error
fn sanitized(self, user_message: &str) -> SanitizedError;
}
impl<E: fmt::Display> Sanitize for E {
fn sanitized(self, user_message: &str) -> SanitizedError {
SanitizedError::new(user_message, self.to_string())
}
}
/// Pre-defined error messages for common authentication scenarios
pub mod messages {
/// Generic authentication failure message
pub const AUTH_FAILED: &str = "Authentication failed";
/// Generic permission denied message
pub const PERMISSION_DENIED: &str = "Permission denied";
/// Generic service error message
pub const SERVICE_UNAVAILABLE: &str = "Service temporarily unavailable";
/// Generic request error message
pub const REQUEST_FAILED: &str = "Request failed";
/// Invalid state (CSRF token)
pub const INVALID_STATE: &str = "Authentication failed";
/// Token expired
pub const TOKEN_EXPIRED: &str = "Authentication failed";
/// Invalid signature
pub const INVALID_SIGNATURE: &str = "Authentication failed";
/// Session expired
pub const SESSION_EXPIRED: &str = "Authentication failed";
/// Session revoked
pub const SESSION_REVOKED: &str = "Authentication failed";
}
/// Error sanitization for authentication errors
pub struct AuthErrorSanitizer;
impl AuthErrorSanitizer {
/// Sanitize JWT validation error
#[must_use]
pub fn jwt_validation_error(internal_error: &str) -> SanitizedError {
SanitizedError::new(messages::AUTH_FAILED, internal_error)
}
/// Sanitize OIDC provider error
#[must_use]
pub fn oidc_provider_error(internal_error: &str) -> SanitizedError {
SanitizedError::new(messages::AUTH_FAILED, internal_error)
}
/// Sanitize session token error
#[must_use]
pub fn session_token_error(internal_error: &str) -> SanitizedError {
SanitizedError::new(messages::AUTH_FAILED, internal_error)
}
/// Sanitize CSRF state error
#[must_use]
pub fn csrf_state_error(internal_error: &str) -> SanitizedError {
SanitizedError::new(messages::INVALID_STATE, internal_error)
}
/// Sanitize permission/authorization error
#[must_use]
pub fn permission_error(internal_error: &str) -> SanitizedError {
SanitizedError::new(messages::PERMISSION_DENIED, internal_error)
}
/// Sanitize database error
#[must_use]
pub fn database_error(internal_error: &str) -> SanitizedError {
SanitizedError::new(messages::SERVICE_UNAVAILABLE, internal_error)
}
}