/* * Copyright (C) 2026 Mateusz GruszczyƄski @linuxiarz.pl * Source-Available Code / Dual-Licensed. * * Free for non-commercial and evaluation use under terms of BSL/GPLv3. * Commercial or production use requires a valid paid license. * See LICENSE file in repository root for details. */ use super::*; fn websocket_headers(origin: &'static str, host: &'static str) -> HeaderMap { let mut headers = HeaderMap::new(); headers.insert(header::ORIGIN, HeaderValue::from_static(origin)); headers.insert(header::HOST, HeaderValue::from_static(host)); headers } #[test] fn account_sessions_are_cookie_only() { let mut headers = HeaderMap::new(); headers.insert( header::AUTHORIZATION, HeaderValue::from_static("Bearer legacy-account-token"), ); assert_eq!(session_token(&headers), None); assert_eq!(bearer_token(&headers), Some("legacy-account-token")); headers.insert( header::COOKIE, HeaderValue::from_static("__Host-rustpad_session=cookie-token"), ); assert_eq!(session_token(&headers), Some("cookie-token")); } #[test] fn prefers_proxy_controlled_real_ip() { let mut headers = HeaderMap::new(); headers.insert( axum::http::HeaderName::from_static("x-forwarded-for"), HeaderValue::from_static("203.0.113.10"), ); headers.insert( axum::http::HeaderName::from_static("x-real-ip"), HeaderValue::from_static("198.51.100.20"), ); assert_eq!(client_key(&headers), "ip:198.51.100.20"); } #[test] fn accepts_same_origin_websocket() { let headers = websocket_headers("https://pad.example.com", "pad.example.com"); assert!(websocket_origin_allowed(&headers)); } #[test] fn rejects_cross_origin_websocket() { let headers = websocket_headers("https://evil.example", "pad.example.com"); assert!(!websocket_origin_allowed(&headers)); } #[test] fn does_not_trust_forwarded_host_for_websocket_origin() { let mut headers = websocket_headers("https://evil.example", "pad.example.com"); headers.insert( axum::http::HeaderName::from_static("x-forwarded-host"), HeaderValue::from_static("evil.example"), ); assert!(!websocket_origin_allowed(&headers)); } #[test] fn rejects_origin_with_path() { let headers = websocket_headers("https://pad.example.com/other", "pad.example.com"); assert!(!websocket_origin_allowed(&headers)); } #[test] fn rejects_missing_websocket_origin() { let mut headers = HeaderMap::new(); headers.insert(header::HOST, HeaderValue::from_static("pad.example.com")); assert!(!websocket_origin_allowed(&headers)); } #[test] fn secure_cookies_are_not_script_readable() { let value = session_cookie("abc123", 7).to_str().unwrap(); assert!(value.contains("HttpOnly")); assert!(value.contains("Secure")); assert!(value.contains("SameSite=Lax")); assert!(value.starts_with("__Host-rustpad_session=abc123;")); } #[test] fn share_sessions_use_separate_scoped_opaque_cookies() { let value = share_session_cookie("workspace", "private-space", "opaque", 600) .to_str() .unwrap(); assert!(value.starts_with("__Host-rustpad_share_")); assert!(value.contains("=opaque;")); assert!(value.contains("Max-Age=600")); assert!(value.contains("HttpOnly")); assert!(value.contains("Secure")); assert!(value.contains("SameSite=Lax")); } #[test] fn csrf_requires_matching_cookie_and_header() { let token = "a".repeat(CSRF_TOKEN_BYTES * 2); let mut headers = HeaderMap::new(); headers.insert( header::COOKIE, HeaderValue::from_str(&format!("{CSRF_COOKIE}={token}")).unwrap(), ); headers.insert( axum::http::HeaderName::from_static(CSRF_HEADER), HeaderValue::from_str(&token).unwrap(), ); assert!(csrf_request_is_valid(&headers)); headers.insert( axum::http::HeaderName::from_static(CSRF_HEADER), HeaderValue::from_static( "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ), ); assert!(!csrf_request_is_valid(&headers)); } #[test] fn csrf_cookie_is_strict_and_script_readable() { let token = "a".repeat(CSRF_TOKEN_BYTES * 2); let value = csrf_cookie(&token).to_str().unwrap(); assert!(value.contains("Secure")); assert!(value.contains("SameSite=Strict")); assert!(!value.contains("HttpOnly")); }