use axum::http::HeaderMap;
pub const RISE_JWT_COOKIE_NAME: &str = "rise_jwt";
fn parse_cookies(cookie_header: &str) -> impl Iterator<Item = (&str, &str)> {
cookie_header.split(';').filter_map(|cookie| {
let cookie = cookie.trim();
cookie.split_once('=')
})
}
#[derive(Debug, Clone)]
pub struct CookieSettings {
pub domain: String,
pub secure: bool,
}
pub fn create_rise_jwt_cookie(
jwt: &str,
settings: &CookieSettings,
max_age_seconds: u64,
) -> String {
let mut cookie_parts = vec![
format!("{}={}", RISE_JWT_COOKIE_NAME, jwt),
format!("Max-Age={}", max_age_seconds),
"Path=/".to_string(),
"HttpOnly".to_string(),
"SameSite=Lax".to_string(),
];
if !settings.domain.is_empty() {
cookie_parts.push(format!("Domain={}", settings.domain));
}
if settings.secure {
cookie_parts.push("Secure".to_string());
}
cookie_parts.join("; ")
}
pub fn extract_rise_jwt_cookie(headers: &HeaderMap) -> Option<String> {
let cookie_header = headers.get("cookie")?.to_str().ok()?;
parse_cookies(cookie_header)
.find(|(name, _)| *name == RISE_JWT_COOKIE_NAME)
.map(|(_, value)| value.to_string())
}
#[allow(dead_code)]
pub fn clear_rise_jwt_cookie(settings: &CookieSettings) -> String {
let mut cookie_parts = vec![
format!("{}=", RISE_JWT_COOKIE_NAME),
"Max-Age=0".to_string(),
"Path=/".to_string(),
"HttpOnly".to_string(),
"SameSite=Lax".to_string(),
];
if !settings.domain.is_empty() {
cookie_parts.push(format!("Domain={}", settings.domain));
}
if settings.secure {
cookie_parts.push("Secure".to_string());
}
cookie_parts.join("; ")
}
#[cfg(test)]
mod tests {
use super::*;
use axum::http::HeaderValue;
#[test]
fn test_create_rise_jwt_cookie() {
let settings = CookieSettings {
domain: ".rise.dev".to_string(),
secure: true,
};
let cookie = create_rise_jwt_cookie("jwt_token_xyz", &settings, 3600);
assert!(cookie.contains("rise_jwt=jwt_token_xyz"));
assert!(cookie.contains("Max-Age=3600"));
assert!(cookie.contains("Path=/"));
assert!(cookie.contains("HttpOnly"));
assert!(cookie.contains("SameSite=Lax"));
assert!(cookie.contains("Domain=.rise.dev"));
assert!(cookie.contains("Secure"));
}
#[test]
fn test_extract_rise_jwt_cookie() {
let mut headers = HeaderMap::new();
headers.insert(
"cookie",
HeaderValue::from_static("rise_jwt=my_jwt; other_cookie=value"),
);
let jwt = extract_rise_jwt_cookie(&headers);
assert_eq!(jwt, Some("my_jwt".to_string()));
}
#[test]
fn test_extract_rise_jwt_cookie_not_present() {
let mut headers = HeaderMap::new();
headers.insert("cookie", HeaderValue::from_static("other_cookie=value"));
let jwt = extract_rise_jwt_cookie(&headers);
assert_eq!(jwt, None);
}
#[test]
fn test_clear_rise_jwt_cookie() {
let settings = CookieSettings {
domain: ".rise.dev".to_string(),
secure: true,
};
let cookie = clear_rise_jwt_cookie(&settings);
assert!(cookie.contains("rise_jwt="));
assert!(cookie.contains("Max-Age=0"));
assert!(cookie.contains("HttpOnly"));
assert!(cookie.contains("Domain=.rise.dev"));
assert!(cookie.contains("Secure"));
}
}