Skip to main content

leptos/
nonce.rs

1use crate::context::{provide_context, use_context};
2use base64::{
3    alphabet,
4    engine::{self, general_purpose},
5    Engine,
6};
7use rand::{rng, RngCore};
8use std::{fmt::Display, ops::Deref, sync::Arc};
9use tachys::html::attribute::AttributeValue;
10
11/// A cryptographic nonce ("number used once") which can be
12/// used by Content Security Policy to determine whether or not a given
13/// resource will be allowed to load.
14///
15/// When the `nonce` feature is enabled on one of the server integrations,
16/// a nonce is generated during server rendering and added to all inline
17/// scripts used for HTML streaming and resource loading.
18///
19/// The nonce being used during the current server response can be
20/// accessed using [`use_nonce`].
21///
22/// ```rust,ignore
23/// #[component]
24/// pub fn App() -> impl IntoView {
25///     provide_meta_context;
26///
27///     view! {
28///         // use `leptos_meta` to insert a <meta> tag with the CSP
29///         <Meta
30///             http_equiv="Content-Security-Policy"
31///             content=move || {
32///                 // this will insert the CSP with nonce on the server, be empty on client
33///                 use_nonce()
34///                     .map(|nonce| {
35///                         format!(
36///                             "default-src 'self'; script-src 'strict-dynamic' 'nonce-{nonce}' \
37///                             'wasm-unsafe-eval'; style-src 'nonce-{nonce}';"
38///                         )
39///                     })
40///                     .unwrap_or_default()
41///             }
42///         />
43///         // manually insert nonce during SSR on inline script
44///         <script nonce=use_nonce()>"console.log('Hello, world!');"</script>
45///         // leptos_meta <Style/> and <Script/> automatically insert the nonce
46///         <Style>"body { color: blue; }"</Style>
47///         <p>"Test"</p>
48///     }
49/// }
50/// ```
51#[derive(Clone, Debug, PartialEq, Eq, Hash)]
52pub struct Nonce(pub(crate) Arc<str>);
53
54impl Nonce {
55    /// Returns a reference to the inner reference-counted string slice representing the nonce.
56    pub fn as_inner(&self) -> &Arc<str> {
57        &self.0
58    }
59}
60
61impl Deref for Nonce {
62    type Target = str;
63
64    fn deref(&self) -> &Self::Target {
65        &self.0
66    }
67}
68
69impl Display for Nonce {
70    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71        write!(f, "{}", self.0)
72    }
73}
74
75impl AttributeValue for Nonce {
76    type AsyncOutput = Self;
77    type State = <Arc<str> as AttributeValue>::State;
78    type Cloneable = Self;
79    type CloneableOwned = Self;
80
81    fn html_len(&self) -> usize {
82        self.0.len()
83    }
84
85    fn to_html(self, key: &str, buf: &mut String) {
86        <Arc<str> as AttributeValue>::to_html(self.0, key, buf)
87    }
88
89    fn to_template(_key: &str, _buf: &mut String) {}
90
91    fn hydrate<const FROM_SERVER: bool>(
92        self,
93        key: &str,
94        el: &tachys::renderer::types::Element,
95    ) -> Self::State {
96        <Arc<str> as AttributeValue>::hydrate::<FROM_SERVER>(self.0, key, el)
97    }
98
99    fn build(
100        self,
101        el: &tachys::renderer::types::Element,
102        key: &str,
103    ) -> Self::State {
104        <Arc<str> as AttributeValue>::build(self.0, el, key)
105    }
106
107    fn rebuild(self, key: &str, state: &mut Self::State) {
108        <Arc<str> as AttributeValue>::rebuild(self.0, key, state)
109    }
110
111    fn into_cloneable(self) -> Self::Cloneable {
112        self
113    }
114
115    fn into_cloneable_owned(self) -> Self::CloneableOwned {
116        self
117    }
118
119    fn dry_resolve(&mut self) {}
120
121    async fn resolve(self) -> Self::AsyncOutput {
122        self
123    }
124}
125
126/// Accesses the nonce that has been generated during the current
127/// server response. This can be added to inline `<script>` and
128/// `<style>` tags for compatibility with a Content Security Policy.
129///
130/// ```rust,ignore
131/// #[component]
132/// pub fn App() -> impl IntoView {
133///     provide_meta_context;
134///
135///     view! {
136///         // use `leptos_meta` to insert a <meta> tag with the CSP
137///         <Meta
138///             http_equiv="Content-Security-Policy"
139///             content=move || {
140///                 // this will insert the CSP with nonce on the server, be empty on client
141///                 use_nonce()
142///                     .map(|nonce| {
143///                         format!(
144///                             "default-src 'self'; script-src 'strict-dynamic' 'nonce-{nonce}' \
145///                             'wasm-unsafe-eval'; style-src 'nonce-{nonce}';"
146///                         )
147///                     })
148///                     .unwrap_or_default()
149///             }
150///         />
151///         // manually insert nonce during SSR on inline script
152///         <script nonce=use_nonce()>"console.log('Hello, world!');"</script>
153///         // leptos_meta <Style/> and <Script/> automatically insert the nonce
154///         <Style>"body { color: blue; }"</Style>
155///         <p>"Test"</p>
156///     }
157/// }
158/// ```
159pub fn use_nonce() -> Option<Nonce> {
160    use_context::<Nonce>()
161}
162
163/// Generates a nonce and provides it via context.
164pub fn provide_nonce() {
165    provide_context(Nonce::new())
166}
167
168const NONCE_ENGINE: engine::GeneralPurpose =
169    engine::GeneralPurpose::new(&alphabet::URL_SAFE, general_purpose::NO_PAD);
170
171impl Nonce {
172    /// Generates a new nonce from 16 bytes (128 bits) of random data.
173    pub fn new() -> Self {
174        let mut rng = rng();
175        let mut bytes = [0; 16];
176        rng.fill_bytes(&mut bytes);
177        Nonce(NONCE_ENGINE.encode(bytes).into())
178    }
179}
180
181impl Default for Nonce {
182    fn default() -> Self {
183        Self::new()
184    }
185}