Skip to main content

better_auth_api/plugins/test_utils/
cookies.rs

1use better_auth_core::utils::cookie_utils::sign_cookie_value_raw;
2use better_auth_core::{AuthContext, AuthSchema, CookieAttributes, SameSite};
3use chrono::Utc;
4use serde::Serialize;
5use std::collections::HashMap;
6
7/// Browser-cookie input compatible with Playwright's `addCookies` shape.
8#[derive(Debug, Clone, Serialize)]
9#[serde(rename_all = "camelCase")]
10pub struct TestCookie {
11    /// Configured wire cookie name.
12    pub name: String,
13    /// Raw signed cookie value.
14    pub value: String,
15    /// Base URL hostname or explicit browser domain override.
16    pub domain: String,
17    /// Configured cookie path.
18    pub path: String,
19    /// Whether browser scripts can read the cookie.
20    pub http_only: bool,
21    /// Whether the browser requires HTTPS.
22    pub secure: bool,
23    /// Browser API spelling: `Strict`, `Lax`, or `None`.
24    pub same_site: &'static str,
25    /// Expiry in Unix seconds; absent for a session cookie.
26    #[serde(
27        skip_serializing_if = "Option::is_none",
28        serialize_with = "better_auth_core::wire::serialize_optional_number"
29    )]
30    pub expires: Option<f64>,
31}
32fn signed<S: AuthSchema>(
33    auth: &AuthContext<S>,
34    token: &str,
35) -> (better_auth_core::request_runtime::ResolvedCookie, String) {
36    (
37        auth.config.auth_cookie(
38            "session_token",
39            CookieAttributes {
40                max_age: Some(auth.config.session.expires_in().as_seconds_f64()),
41                ..Default::default()
42            },
43        ),
44        sign_cookie_value_raw(token, &auth.config.secret),
45    )
46}
47pub(super) fn headers<S: AuthSchema>(
48    auth: &AuthContext<S>,
49    token: &str,
50) -> HashMap<String, String> {
51    let (cookie, value) = signed(auth, token);
52    [("cookie".into(), format!("{}={value}", cookie.name))].into()
53}
54pub(super) fn cookies<S: AuthSchema>(
55    auth: &AuthContext<S>,
56    token: &str,
57    domain: Option<&str>,
58) -> Vec<TestCookie> {
59    let (cookie, value) = signed(auth, token);
60    let attributes = cookie.attributes;
61    let domain = domain
62        .filter(|value| !value.is_empty())
63        .map(str::to_owned)
64        .unwrap_or_else(|| {
65            url::Url::parse(auth.base_url())
66                .ok()
67                .and_then(|url| url.host_str().map(str::to_owned))
68                .unwrap_or_else(|| "localhost".into())
69        });
70    vec![TestCookie {
71        name: cookie.name,
72        value,
73        domain,
74        path: attributes
75            .path
76            .filter(|value| !value.is_empty())
77            .unwrap_or_else(|| "/".into()),
78        http_only: attributes.http_only.unwrap_or(true),
79        secure: attributes.secure.unwrap_or(false),
80        same_site: match attributes.same_site {
81            Some(SameSite::Strict) => "Strict",
82            Some(SameSite::None) => "None",
83            _ => "Lax",
84        },
85        expires: attributes
86            .max_age
87            .filter(|value| *value != 0.0 && !value.is_nan())
88            .map(|age| Utc::now().timestamp() as f64 + age),
89    }]
90}