use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use super::helpers::{
cookie_header, json_request, router, secret, session, set_cookie_values, signed_session_cookie,
user, AdapterSeed, TestAdapter,
};
use http::Method;
use rustauth_core::context::create_auth_context;
use rustauth_core::cookies::{set_session_cookie, SessionCookieOptions};
use rustauth_core::options::{CookieCacheOptions, RustAuthOptions, SessionOptions};
use rustauth_plugins::custom_session::{custom_session, CustomSessionOptions};
use serde_json::{json, Value};
use time::{Duration, OffsetDateTime};
#[tokio::test]
async fn get_session_preserves_set_cookie_headers_individually(
) -> Result<(), Box<dyn std::error::Error>> {
let adapter = Arc::new(TestAdapter::default());
let now = OffsetDateTime::now_utc();
adapter.insert_user(user(now)).await;
adapter
.insert_session(session(now, now + Duration::hours(1)))
.await;
let cookie = signed_session_cookie("token_1")?;
let plugin = custom_session(CustomSessionOptions::default(), |input, _context| {
Box::pin(async move { Ok(json!({ "user": input.user, "session": input.session })) })
});
let router = router(
adapter,
plugin,
RustAuthOptions {
session: SessionOptions {
cookie_cache: CookieCacheOptions {
enabled: true,
max_age: Some(time::Duration::seconds(300)),
..CookieCacheOptions::default()
},
..SessionOptions::default()
},
..RustAuthOptions::default()
},
)?;
let response = router
.handle_async(json_request(
Method::GET,
"/api/auth/get-session",
Some(&cookie),
)?)
.await?;
let set_cookies = set_cookie_values(&response);
assert!(set_cookies.len() >= 2);
assert!(set_cookies
.iter()
.any(|value| value.starts_with("rustauth.session_token=")));
assert!(set_cookies
.iter()
.any(|value| value.starts_with("rustauth.session_data=")));
assert!(set_cookies
.iter()
.filter(|value| value.starts_with("better-auth."))
.all(|value| value.matches("better-auth.").count() == 1));
Ok(())
}
#[tokio::test]
async fn get_session_refresh_cookies_keep_individual_max_age_and_partitioned(
) -> Result<(), Box<dyn std::error::Error>> {
let adapter = Arc::new(TestAdapter::default());
let now = OffsetDateTime::now_utc();
adapter.insert_user(user(now)).await;
adapter
.insert_session(session(now, now + Duration::hours(1)))
.await;
let plugin = custom_session(CustomSessionOptions::default(), |input, _context| {
Box::pin(async move { Ok(json!({ "user": input.user, "session": input.session })) })
});
let options = RustAuthOptions {
session: SessionOptions {
expires_in: Some(time::Duration::seconds(60 * 60 * 24)),
update_age: Some(time::Duration::seconds(0)),
cookie_cache: CookieCacheOptions {
enabled: true,
max_age: Some(time::Duration::seconds(300)),
..CookieCacheOptions::default()
},
..SessionOptions::default()
},
advanced: rustauth_core::options::AdvancedOptions {
default_cookie_attributes: rustauth_core::options::CookieAttributesOverride {
secure: Some(true),
same_site: Some("none".to_owned()),
partitioned: Some(true),
..Default::default()
},
..Default::default()
},
..RustAuthOptions::default()
};
let context = create_auth_context(RustAuthOptions {
secret: Some(secret().to_owned()),
..options.clone()
})?;
let cookie = cookie_header(&set_session_cookie(
&context.auth_cookies,
&context.secret,
"token_1",
SessionCookieOptions::default(),
)?);
let router = router(adapter, plugin, options)?;
let response = router
.handle_async(json_request(
Method::GET,
"/api/auth/get-session",
Some(&cookie),
)?)
.await?;
let set_cookies = set_cookie_values(&response);
let session_cookie = set_cookies
.iter()
.find(|value| value.contains("session_token="))
.ok_or("missing refreshed session cookie")?;
let cache_cookie = set_cookies
.iter()
.find(|value| value.contains("session_data="))
.ok_or("missing session data cookie")?;
assert!(session_cookie.contains("Max-Age=86400") || session_cookie.contains("Max-Age=86399"));
assert!(cache_cookie.contains("Max-Age=300"));
assert!(session_cookie.contains("Partitioned"));
assert!(cache_cookie.contains("Partitioned"));
Ok(())
}
#[tokio::test]
async fn get_session_does_not_double_encode_session_token_after_refresh(
) -> Result<(), Box<dyn std::error::Error>> {
let adapter = Arc::new(TestAdapter::default());
let now = OffsetDateTime::now_utc();
adapter.insert_user(user(now)).await;
adapter
.insert_session(session(now, now + Duration::hours(1)))
.await;
let cookie = signed_session_cookie("token_1")?;
let plugin = custom_session(CustomSessionOptions::default(), |input, _context| {
Box::pin(async move { Ok(json!({ "user": input.user, "session": input.session })) })
});
let router = router(adapter, plugin, RustAuthOptions::default())?;
let response = router
.handle_async(json_request(
Method::GET,
"/api/auth/get-session",
Some(&cookie),
)?)
.await?;
let original_value = cookie
.strip_prefix("rustauth.session_token=")
.ok_or("unexpected session cookie name")?;
let refreshed = set_cookie_values(&response)
.into_iter()
.find(|value| value.starts_with("rustauth.session_token="))
.ok_or("missing refreshed session cookie")?;
let refreshed_value = refreshed
.trim_start_matches("rustauth.session_token=")
.split(';')
.next()
.ok_or("missing refreshed value")?;
assert_eq!(refreshed_value, original_value);
assert!(!refreshed_value.contains("%25"));
Ok(())
}
#[tokio::test]
async fn get_session_null_preserves_delete_set_cookie_headers(
) -> Result<(), Box<dyn std::error::Error>> {
let adapter = Arc::new(TestAdapter::default());
let cookie = signed_session_cookie("missing_token")?;
let calls = Arc::new(AtomicUsize::new(0));
let calls_for_handler = Arc::clone(&calls);
let plugin = custom_session(CustomSessionOptions::default(), move |_input, _context| {
let calls = Arc::clone(&calls_for_handler);
Box::pin(async move {
calls.fetch_add(1, Ordering::SeqCst);
Ok(json!({ "unexpected": true }))
})
});
let router = router(adapter, plugin, RustAuthOptions::default())?;
let response = router
.handle_async(json_request(
Method::GET,
"/api/auth/get-session",
Some(&cookie),
)?)
.await?;
let body: Value = serde_json::from_slice(response.body())?;
assert!(body.is_null());
let set_cookies = set_cookie_values(&response);
assert!(set_cookies
.iter()
.any(|value| value.starts_with("rustauth.session_token=;") && value.contains("Max-Age=0")));
assert!(set_cookies
.iter()
.any(|value| value.starts_with("rustauth.session_data=;") && value.contains("Max-Age=0")));
assert_eq!(calls.load(Ordering::SeqCst), 0);
Ok(())
}