http_security_headers/
preset.rs

1//! Preset security header configurations.
2//!
3//! This module provides pre-configured security header sets for common use cases.
4
5use crate::config::SecurityHeaders;
6use crate::policy::*;
7use std::time::Duration;
8
9/// Security preset levels.
10///
11/// # Examples
12///
13/// ```
14/// use http_security_headers::Preset;
15///
16/// let headers = Preset::Strict.build();
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Preset {
20    /// Strict security configuration.
21    ///
22    /// Recommended for applications that can enforce strict security policies.
23    /// May break functionality if not properly configured.
24    ///
25    /// Includes:
26    /// - CSP: `default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'`
27    /// - HSTS: 1 year, includeSubDomains
28    /// - X-Frame-Options: DENY
29    /// - X-Content-Type-Options: nosniff
30    /// - Referrer-Policy: no-referrer
31    /// - COOP: same-origin
32    /// - COEP: require-corp
33    /// - CORP: same-origin
34    Strict,
35
36    /// Balanced security configuration.
37    ///
38    /// Provides good security while maintaining compatibility with most applications.
39    ///
40    /// Includes:
41    /// - CSP: `default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'`
42    /// - HSTS: 1 year, includeSubDomains
43    /// - X-Frame-Options: SAMEORIGIN
44    /// - X-Content-Type-Options: nosniff
45    /// - Referrer-Policy: strict-origin-when-cross-origin
46    /// - COOP: same-origin-allow-popups
47    Balanced,
48
49    /// Relaxed security configuration.
50    ///
51    /// Provides baseline security with minimal restrictions.
52    /// Suitable for applications that need maximum compatibility.
53    ///
54    /// Includes:
55    /// - HSTS: 6 months
56    /// - X-Frame-Options: SAMEORIGIN
57    /// - X-Content-Type-Options: nosniff
58    /// - Referrer-Policy: strict-origin-when-cross-origin
59    Relaxed,
60}
61
62impl Preset {
63    /// Builds the SecurityHeaders for this preset.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use http_security_headers::Preset;
69    ///
70    /// let headers = Preset::Strict.build();
71    /// ```
72    pub fn build(self) -> SecurityHeaders {
73        match self {
74            Self::Strict => self.build_strict(),
75            Self::Balanced => self.build_balanced(),
76            Self::Relaxed => self.build_relaxed(),
77        }
78    }
79
80    fn build_strict(self) -> SecurityHeaders {
81        let csp = ContentSecurityPolicy::new()
82            .default_src(vec!["'self'"])
83            .object_src(vec!["'none'"])
84            .base_uri(vec!["'self'"])
85            .frame_ancestors(vec!["'none'"]);
86
87        SecurityHeaders::builder()
88            .content_security_policy(csp)
89            .strict_transport_security(Duration::from_secs(31536000), true, false)
90            .x_frame_options_deny()
91            .x_content_type_options_nosniff()
92            .referrer_policy_no_referrer()
93            .cross_origin_opener_policy(CrossOriginOpenerPolicy::SameOrigin)
94            .cross_origin_embedder_policy(CrossOriginEmbedderPolicy::RequireCorp)
95            .cross_origin_resource_policy(CrossOriginResourcePolicy::SameOrigin)
96            .build()
97            .expect("strict preset should always be valid")
98    }
99
100    fn build_balanced(self) -> SecurityHeaders {
101        let csp = ContentSecurityPolicy::new()
102            .default_src(vec!["'self'"])
103            .script_src(vec!["'self'", "'unsafe-inline'"])
104            .object_src(vec!["'none'"]);
105
106        SecurityHeaders::builder()
107            .content_security_policy(csp)
108            .strict_transport_security(Duration::from_secs(31536000), true, false)
109            .x_frame_options_sameorigin()
110            .x_content_type_options_nosniff()
111            .referrer_policy_strict_origin_when_cross_origin()
112            .cross_origin_opener_policy(CrossOriginOpenerPolicy::SameOriginAllowPopups)
113            .build()
114            .expect("balanced preset should always be valid")
115    }
116
117    fn build_relaxed(self) -> SecurityHeaders {
118        SecurityHeaders::builder()
119            .strict_transport_security(Duration::from_secs(15552000), false, false) // 6 months
120            .x_frame_options_sameorigin()
121            .x_content_type_options_nosniff()
122            .referrer_policy_strict_origin_when_cross_origin()
123            .build()
124            .expect("relaxed preset should always be valid")
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_strict_preset() {
134        let headers = Preset::Strict.build();
135
136        assert!(headers.content_security_policy().is_some());
137        assert!(headers.strict_transport_security().is_some());
138        assert_eq!(headers.x_frame_options(), Some(XFrameOptions::Deny));
139        assert!(headers.x_content_type_options_enabled());
140        assert_eq!(headers.referrer_policy(), Some(ReferrerPolicy::NoReferrer));
141        assert_eq!(
142            headers.cross_origin_opener_policy(),
143            Some(CrossOriginOpenerPolicy::SameOrigin)
144        );
145        assert_eq!(
146            headers.cross_origin_embedder_policy(),
147            Some(CrossOriginEmbedderPolicy::RequireCorp)
148        );
149        assert_eq!(
150            headers.cross_origin_resource_policy(),
151            Some(CrossOriginResourcePolicy::SameOrigin)
152        );
153    }
154
155    #[test]
156    fn test_balanced_preset() {
157        let headers = Preset::Balanced.build();
158
159        assert!(headers.content_security_policy().is_some());
160        assert!(headers.strict_transport_security().is_some());
161        assert_eq!(headers.x_frame_options(), Some(XFrameOptions::SameOrigin));
162        assert!(headers.x_content_type_options_enabled());
163        assert_eq!(
164            headers.referrer_policy(),
165            Some(ReferrerPolicy::StrictOriginWhenCrossOrigin)
166        );
167        assert_eq!(
168            headers.cross_origin_opener_policy(),
169            Some(CrossOriginOpenerPolicy::SameOriginAllowPopups)
170        );
171    }
172
173    #[test]
174    fn test_relaxed_preset() {
175        let headers = Preset::Relaxed.build();
176
177        assert!(headers.content_security_policy().is_none());
178        assert!(headers.strict_transport_security().is_some());
179        assert_eq!(headers.x_frame_options(), Some(XFrameOptions::SameOrigin));
180        assert!(headers.x_content_type_options_enabled());
181        assert_eq!(
182            headers.referrer_policy(),
183            Some(ReferrerPolicy::StrictOriginWhenCrossOrigin)
184        );
185
186        let hsts = headers.strict_transport_security().unwrap();
187        assert_eq!(hsts.max_age(), Duration::from_secs(15552000)); // 6 months
188    }
189}