koprogo_api/infrastructure/web/
auth_cookie.rs1use actix_web::cookie::{time::Duration, Cookie, SameSite};
11
12pub const REFRESH_COOKIE_NAME: &str = "koprogo_refresh";
14
15pub const REFRESH_COOKIE_PATH: &str = "/api/v1/auth";
18
19const REFRESH_COOKIE_MAX_AGE_DAYS: i64 = 7;
22
23fn cookie_secure() -> bool {
27 std::env::var("COOKIE_SECURE")
28 .map(|v| v != "false" && v != "0")
29 .unwrap_or(true)
30}
31
32pub fn build_refresh_cookie(refresh_token: &str) -> Cookie<'static> {
36 Cookie::build(REFRESH_COOKIE_NAME, refresh_token.to_owned())
37 .http_only(true)
38 .secure(cookie_secure())
39 .same_site(SameSite::Strict)
40 .path(REFRESH_COOKIE_PATH)
41 .max_age(Duration::days(REFRESH_COOKIE_MAX_AGE_DAYS))
42 .finish()
43}
44
45pub fn build_clearing_cookie() -> Cookie<'static> {
48 Cookie::build(REFRESH_COOKIE_NAME, "")
49 .http_only(true)
50 .secure(cookie_secure())
51 .same_site(SameSite::Strict)
52 .path(REFRESH_COOKIE_PATH)
53 .max_age(Duration::ZERO)
54 .finish()
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
63 fn happy_refresh_cookie_has_security_attributes() {
64 let c = build_refresh_cookie("tok-abc");
65 assert_eq!(c.name(), REFRESH_COOKIE_NAME);
66 assert_eq!(c.value(), "tok-abc");
67 assert_eq!(c.http_only(), Some(true));
68 assert_eq!(c.same_site(), Some(SameSite::Strict));
69 assert_eq!(c.path(), Some(REFRESH_COOKIE_PATH));
70 assert_eq!(c.max_age(), Some(Duration::days(7)));
71 }
72
73 #[test]
76 fn security_cookie_secure_defaults_true() {
77 std::env::remove_var("COOKIE_SECURE");
78 assert!(cookie_secure());
79 assert_eq!(build_refresh_cookie("x").secure(), Some(true));
80 }
81
82 #[test]
85 fn edge_cookie_secure_opt_out_only_explicit_false() {
86 std::env::set_var("COOKIE_SECURE", "false");
87 assert!(!cookie_secure());
88 std::env::set_var("COOKIE_SECURE", "true");
89 assert!(cookie_secure());
90 std::env::set_var("COOKIE_SECURE", "yes-please");
91 assert!(cookie_secure());
92 std::env::remove_var("COOKIE_SECURE");
93 }
94
95 #[test]
98 fn negative_clearing_cookie_expires_immediately() {
99 let c = build_clearing_cookie();
100 assert_eq!(c.name(), REFRESH_COOKIE_NAME);
101 assert_eq!(c.value(), "");
102 assert_eq!(c.max_age(), Some(Duration::ZERO));
103 assert_eq!(c.http_only(), Some(true));
104 assert_eq!(c.path(), Some(REFRESH_COOKIE_PATH));
105 }
106}