actix_cloud/
security.rs

1use std::fmt::Display;
2
3use actix_web::middleware;
4
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6#[derive(Clone, Debug)]
7pub enum RefererPolicy {
8    NoReferrer,
9    NoReferrerWhenDowngrade,
10    Origin,
11    OriginWhenCrossOrigin,
12    SameOrigin,
13    StrictOrigin,
14    StrictOriginWhenCrossOrigin,
15    UnsafeUrl,
16}
17
18impl Display for RefererPolicy {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.write_str(match self {
21            RefererPolicy::NoReferrer => "no-referrer",
22            RefererPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
23            RefererPolicy::Origin => "origin",
24            RefererPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
25            RefererPolicy::SameOrigin => "same-origin",
26            RefererPolicy::StrictOrigin => "strict-origin",
27            RefererPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
28            RefererPolicy::UnsafeUrl => "unsafe-url",
29        })
30    }
31}
32
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[derive(Clone, Debug)]
35pub enum XFrameOptions {
36    Deny,
37    SameOrigin,
38}
39
40impl Display for XFrameOptions {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.write_str(match self {
43            XFrameOptions::Deny => "DENY",
44            XFrameOptions::SameOrigin => "SAMEORIGIN",
45        })
46    }
47}
48
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50#[derive(Clone, Debug)]
51pub enum XXSSProtection {
52    Disable,
53    Enable,
54    EnableBlock,
55    EnableReport(String),
56}
57
58impl Display for XXSSProtection {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            XXSSProtection::Disable => f.write_str("0"),
62            XXSSProtection::Enable => f.write_str("1"),
63            XXSSProtection::EnableBlock => f.write_str("1; mode=block"),
64            XXSSProtection::EnableReport(x) => f.write_str(&format!("1; report={}", x)),
65        }
66    }
67}
68
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70#[derive(Clone, Debug)]
71pub enum CrossOriginOpenerPolicy {
72    UnsafeNone,
73    SameOriginAllowPopups,
74    SameOrigin,
75}
76
77impl Display for CrossOriginOpenerPolicy {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.write_str(match self {
80            CrossOriginOpenerPolicy::UnsafeNone => "unsafe-none",
81            CrossOriginOpenerPolicy::SameOriginAllowPopups => "same-origin-allow-popups",
82            CrossOriginOpenerPolicy::SameOrigin => "same-origin",
83        })
84    }
85}
86
87#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88#[derive(Clone, Debug)]
89pub enum StrictTransportSecurity {
90    MaxAge(u32),
91    IncludeSubDomains(u32),
92    Preload(u32),
93}
94
95impl Display for StrictTransportSecurity {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            StrictTransportSecurity::MaxAge(x) => f.write_str(&format!("max-age={}", x)),
99            StrictTransportSecurity::IncludeSubDomains(x) => {
100                f.write_str(&format!("max-age={}; includeSubDomains", x))
101            }
102            StrictTransportSecurity::Preload(x) => {
103                f.write_str(&format!("max-age={}; includeSubDomains; preload", x))
104            }
105        }
106    }
107}
108
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[derive(Clone, Debug)]
111pub struct SecurityHeader {
112    pub referer_policy: RefererPolicy,
113    pub x_frame_options: XFrameOptions,
114    pub x_xss_protection: XXSSProtection,
115    pub cross_origin_opener_policy: CrossOriginOpenerPolicy,
116    pub content_security_policy: String,
117    pub strict_transport_security: Option<StrictTransportSecurity>,
118}
119
120impl Default for SecurityHeader {
121    fn default() -> Self {
122        Self {
123            referer_policy: RefererPolicy::StrictOriginWhenCrossOrigin,
124            x_frame_options: XFrameOptions::Deny,
125            x_xss_protection: XXSSProtection::EnableBlock,
126            cross_origin_opener_policy: CrossOriginOpenerPolicy::SameOrigin,
127            content_security_policy: String::from("default-src 'none'; script-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"),
128            strict_transport_security: None,
129        }
130    }
131}
132
133impl SecurityHeader {
134    /// Set default HSTS to 1 year, includeSubDomains and preload.
135    ///
136    /// `max-age=31536000; includeSubDomains; preload`
137    pub fn set_default_hsts(&mut self) {
138        self.strict_transport_security = Some(StrictTransportSecurity::Preload(31536000));
139    }
140
141    pub fn build(self) -> middleware::DefaultHeaders {
142        let mut ret = middleware::DefaultHeaders::new()
143            .add(("X-Content-Type-Options", "nosniff"))
144            .add(("Referrer-Policy", self.referer_policy.to_string()))
145            .add(("X-Frame-Options", self.x_frame_options.to_string()))
146            .add(("X-XSS-Protection", self.x_xss_protection.to_string()))
147            .add((
148                "Cross-Origin-Opener-Policy",
149                self.cross_origin_opener_policy.to_string(),
150            ))
151            .add(("Content-Security-Policy", self.content_security_policy));
152        if let Some(hsts) = self.strict_transport_security {
153            ret = ret.add(("Strict-Transport-Security", hsts.to_string()));
154        }
155        ret
156    }
157}