Skip to main content

shared/config/configuration/
validate.rs

1use crate::config::{
2    AuthSecurity, AuthStrategy, Authentication, PasswordConfig, PasswordRequirements,
3    SameSiteConfig, Security, SessionConfig,
4};
5use crate::error::{CoreError, InternalError};
6
7pub trait Validate {
8    fn validate(&self) -> Result<(), Vec<CoreError>>;
9}
10
11// =============================================================================
12// Security Configuration - REQUIRED
13// =============================================================================
14impl Validate for Security {
15    fn validate(&self) -> Result<(), Vec<CoreError>> {
16        let mut errors = vec![];
17
18        if self.secret_key.len() < 32 {
19            errors.push(CoreError::Internal(InternalError::MissingField {
20                field: "security.secret_key".into(),
21                reason: "must be at least 32 characters".into(),
22            }));
23        }
24        if let Err(e) = self.auth.validate() {
25            errors.extend(e);
26        }
27
28        if errors.is_empty() {
29            Ok(())
30        } else {
31            Err(errors)
32        }
33    }
34}
35impl Validate for AuthSecurity {
36    fn validate(&self) -> Result<(), Vec<CoreError>> {
37        let mut errors = vec![];
38
39        if self.max_failed_attempts == 0 {
40            errors.push(CoreError::Internal(InternalError::MissingField {
41                field: "auth.password.security.max_failed_attempts".into(),
42                reason: "must be greater than 0".into(),
43            }));
44        }
45        if self.lockout_duration <= 0 {
46            errors.push(CoreError::Internal(InternalError::MissingField {
47                field: "auth.password.security.lockout_duration".into(),
48                reason: "must be greater than 0".into(),
49            }));
50        }
51
52        if errors.is_empty() {
53            Ok(())
54        } else {
55            Err(errors)
56        }
57    }
58}
59
60// =============================================================================
61// Authentication Configuration - Optional
62// =============================================================================
63
64impl Validate for Authentication {
65    fn validate(&self) -> Result<(), Vec<CoreError>> {
66        let mut errors = vec![];
67
68        if let AuthStrategy::Jwt(jwt) = &self.strategy {
69            if jwt.issuer.is_empty() {
70                errors.push(CoreError::Internal(InternalError::MissingField {
71                    field: "auth.jwt.issuer".into(),
72                    reason: "must be set".into(),
73                }));
74            }
75            if jwt.audience.is_empty() {
76                errors.push(CoreError::Internal(InternalError::MissingField {
77                    field: "auth.jwt.audience".into(),
78                    reason: "must be set".into(),
79                }));
80            }
81        }
82
83        if let AuthStrategy::Session(session) = &self.strategy
84            && let Err(e) = session.validate()
85        {
86            errors.extend(e);
87        }
88
89        // Collect child errors with context
90        if let Err(e) = self.password.validate() {
91            errors.extend(e);
92        }
93
94        if errors.is_empty() {
95            Ok(())
96        } else {
97            Err(errors)
98        }
99    }
100}
101impl Validate for SessionConfig {
102    fn validate(&self) -> Result<(), Vec<CoreError>> {
103        let mut errors = vec![];
104
105        if self.name.is_empty() {
106            errors.push(CoreError::Internal(InternalError::MissingField {
107                field: "auth.session.name".into(),
108                reason: "must not be empty".into(),
109            }));
110        }
111        if self.max_age == 0 {
112            errors.push(CoreError::Internal(InternalError::MissingField {
113                field: "auth.session.max_age".into(),
114                reason: "must be greater than 0".into(),
115            }));
116        }
117        if let SameSiteConfig::None = self.same_site
118            && !self.secure
119        {
120            errors.push(CoreError::Internal(InternalError::MissingField {
121                field: "auth.session.secure".into(),
122                reason: "must be true when same_site is None".into(),
123            }));
124        }
125
126        if errors.is_empty() {
127            Ok(())
128        } else {
129            Err(errors)
130        }
131    }
132}
133
134// PasswordConfiguration
135// ------------------------------------------------------------
136impl Validate for PasswordConfig {
137    fn validate(&self) -> Result<(), Vec<CoreError>> {
138        let mut errors = vec![];
139
140        if let Err(e) = self.requirements.validate() {
141            errors.extend(e);
142        }
143
144        if errors.is_empty() {
145            Ok(())
146        } else {
147            Err(errors)
148        }
149    }
150}
151// ------------------------------------------------------------
152impl Validate for PasswordRequirements {
153    fn validate(&self) -> Result<(), Vec<CoreError>> {
154        let mut errors = vec![];
155
156        if self.min_length == 0 {
157            errors.push(CoreError::Internal(InternalError::MissingField {
158                field: "auth.password.requirements.min_length".into(),
159                reason: "must be greater than 0".into(),
160            }));
161        }
162        if self.max_length < self.min_length {
163            errors.push(CoreError::Internal(InternalError::MissingField {
164                field: "auth.password.requirements.max_length".into(),
165                reason: "must be greater than or equal to min_length".into(),
166            }));
167        }
168
169        if errors.is_empty() {
170            Ok(())
171        } else {
172            Err(errors)
173        }
174    }
175}