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}