1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! Defines the configuration for the cookie belonging to the session.
use time::{Duration, OffsetDateTime};
use tower_cookies::{cookie::SameSite, Cookie};

use crate::Session;

/// Defines the configuration for the cookie belonging to the session.
#[derive(Debug, Clone)]
pub struct CookieConfig {
    /// The name of the cookie.
    pub name: String,

    /// Specifies the SameSite attribute of the cookie.
    ///
    /// The SameSite attribute restricts when cookies are sent to the server,
    /// helping to protect against certain types of cross-site request
    /// forgery (CSRF) attacks.
    ///
    /// - `SameSite::Strict`: The cookie is only sent when making a "same-site"
    ///   request, which includes requests originating from the same site as the
    ///   cookie.
    /// - `SameSite::Lax`: The cookie is sent on "same-site" requests as well as
    ///   during safe "cross-site" top-level navigations (e.g., clicking a
    ///   link).
    /// - `SameSite::None`: The cookie is sent on all requests, regardless of
    ///   origin.
    pub same_site: SameSite,

    /// Specifies the maximum age of the cookie.
    ///
    /// This field represents the duration for which the cookie is considered
    /// valid before it expires. If set to `None`, the cookie will be
    /// treated as a session cookie and will expire when the browser is
    /// closed.
    pub max_age: Option<Duration>,

    /// Indicates whether the cookie should only be transmitted over secure
    /// (HTTPS) connections.
    ///
    /// If `true`, the cookie will only be sent to the server over secure
    /// connections. If `false`, the cookie may be sent over both secure and
    /// non-secure connections.
    pub secure: bool,

    /// Specifies the path for which the cookie is valid.
    ///
    /// The cookie will only be sent to the server when the request path matches
    /// or is a subpath of the `path` specified here.
    pub path: String,

    /// Specifies the domain for which the cookie is valid.
    ///
    /// If `Some`, the cookie is only sent to the server when the request domain
    /// matches the specified domain. If `None`, the cookie is valid for the
    /// current domain.
    pub domain: Option<String>,
}

impl CookieConfig {
    /// Create a `Cookie` from the provided `Session`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use tower_sessions::{CookieConfig, Session};
    /// let session = Session::default();
    /// let cookie_config = CookieConfig::default();
    /// let cookie = cookie_config.build_cookie(&session);
    /// assert_eq!(cookie.value(), session.id().to_string());
    /// ```
    pub fn build_cookie<'c>(&self, session: &Session) -> Cookie<'c> {
        let mut cookie_builder = Cookie::build(self.name.clone(), session.id().to_string())
            .http_only(true)
            .same_site(self.same_site)
            .secure(self.secure)
            .path(self.path.clone());

        if let Some(max_age) = session
            .expiration_time()
            .map(|et| et - OffsetDateTime::now_utc())
        {
            cookie_builder = cookie_builder.max_age(max_age);
        }

        if let Some(domain) = &self.domain {
            cookie_builder = cookie_builder.domain(domain.clone());
        }

        cookie_builder.finish()
    }
}

impl Default for CookieConfig {
    fn default() -> Self {
        Self {
            name: String::from("tower.sid"),
            same_site: SameSite::Strict,
            max_age: None, // TODO: Is `Max-Age: "Session"` the right default?
            secure: false,
            path: String::from("/"),
            domain: None,
        }
    }
}