Skip to main content

dioxus_cookie/
types.rs

1use std::time::Duration;
2
3/// Cross-site request cookie policy (SameSite attribute).
4///
5/// Controls when cookies are sent with cross-site requests, providing
6/// protection against CSRF attacks.
7///
8/// # Variants
9///
10/// - `Strict` — Cookie only sent with same-site requests
11/// - `Lax` — Cookie sent with same-site requests and top-level navigation (default)
12/// - `None` — Cookie sent with all requests (requires `Secure` flag)
13#[derive(Clone, Debug, Default)]
14pub enum SameSite {
15    /// Cookie only sent with same-site requests.
16    ///
17    /// Most restrictive. Use for sensitive operations like banking or account changes.
18    Strict,
19    /// Cookie sent with same-site requests and top-level navigation.
20    ///
21    /// Balanced protection. Good for most session cookies.
22    #[default]
23    Lax,
24    /// Cookie sent with all requests, including cross-site.
25    ///
26    /// Requires `Secure` flag. Use only when cross-site access is necessary
27    /// (e.g., embedded widgets, third-party integrations).
28    None,
29}
30
31impl SameSite {
32    /// Returns the string representation for use in cookie headers.
33    pub fn as_str(&self) -> &'static str {
34        match self {
35            SameSite::Strict => "Strict",
36            SameSite::Lax => "Lax",
37            SameSite::None => "None",
38        }
39    }
40}
41
42/// Configuration options for setting cookies.
43///
44/// The default options prioritize security:
45/// - `http_only: true` — prevents JavaScript access
46/// - `secure: true` — HTTPS only
47/// - `same_site: Lax` — CSRF protection
48/// - `path: "/"` — available to all routes
49///
50/// # Example
51///
52/// ```rust,ignore
53/// use dioxus_cookie::{CookieOptions, SameSite};
54/// use std::time::Duration;
55///
56/// let options = CookieOptions {
57///     max_age: Some(Duration::from_secs(86400 * 7)), // 7 days
58///     http_only: true,
59///     secure: true,
60///     same_site: SameSite::Strict,
61///     path: "/".to_string(),
62/// };
63/// ```
64#[derive(Clone, Debug)]
65pub struct CookieOptions {
66    /// Cookie lifetime. `None` = session cookie (deleted when browser closes).
67    pub max_age: Option<Duration>,
68    /// If `true`, cookie is inaccessible to JavaScript/WASM. Default: `true`.
69    ///
70    /// Always use `true` for session tokens to prevent XSS attacks.
71    pub http_only: bool,
72    /// If `true`, cookie is only sent over HTTPS. Default: `true`.
73    pub secure: bool,
74    /// Cross-site request policy. Default: [`SameSite::Lax`].
75    pub same_site: SameSite,
76    /// URL path scope for the cookie. Default: `"/"`.
77    pub path: String,
78}
79
80impl Default for CookieOptions {
81    fn default() -> Self {
82        Self {
83            max_age: None,
84            http_only: true,
85            secure: true,
86            same_site: SameSite::Lax,
87            path: "/".to_string(),
88        }
89    }
90}
91
92impl CookieOptions {
93    /// Builds a `Set-Cookie` header string from the options.
94    ///
95    /// The value is URL-encoded automatically.
96    pub fn build_header(&self, name: &str, value: &str) -> String {
97        let mut s = format!("{}={}", name, urlencoding::encode(value));
98
99        if self.http_only {
100            s.push_str("; HttpOnly");
101        }
102        if self.secure {
103            s.push_str("; Secure");
104        }
105        s.push_str("; SameSite=");
106        s.push_str(self.same_site.as_str());
107        s.push_str("; Path=");
108        s.push_str(&self.path);
109
110        if let Some(max_age) = self.max_age {
111            s.push_str("; Max-Age=");
112            s.push_str(&max_age.as_secs().to_string());
113        }
114
115        s
116    }
117}
118
119/// Error type for cookie operations.
120///
121/// On the server, this automatically converts to `ServerFnError`
122/// for seamless error propagation from `#[server]` functions.
123#[derive(Debug, Clone, thiserror::Error)]
124#[error("CookieError: {message}")]
125pub struct CookieError {
126    /// Human-readable error description.
127    pub message: String,
128}
129
130impl CookieError {
131    /// Creates a new cookie error with the given message.
132    pub fn new(message: impl Into<String>) -> Self {
133        Self {
134            message: message.into(),
135        }
136    }
137}
138
139#[cfg(feature = "server")]
140impl From<CookieError> for dioxus::prelude::ServerFnError {
141    fn from(err: CookieError) -> Self {
142        dioxus::prelude::ServerFnError::new(err.message)
143    }
144}