@@ -20,6 +20,7 @@ use tokio::net::TcpListener;
2020use tokio:: sync:: watch;
2121use tower_http:: set_header:: SetResponseHeaderLayer ;
2222use tower_http:: trace:: TraceLayer ;
23+ use cookie:: SameSite ;
2324use tracing:: { debug, info} ;
2425
2526use crate :: auth;
@@ -130,19 +131,30 @@ fn cookie_token(headers: &HeaderMap) -> Option<&str> {
130131/// same-site requests and top-level navigations), scoped to `Path=/`,
131132/// and expires after `AUTH_COOKIE_MAX_AGE` seconds. The `Secure` flag
132133/// is included unless `allow_insecure` is true (HTTP dev mode).
134+ ///
135+ /// Uses the `cookie` crate for correct value encoding, preventing
136+ /// injection of extra attributes via malformed token values.
133137fn build_auth_cookie ( token : & str , secure : bool ) -> String {
134- let mut cookie = format ! (
135- "{AUTH_COOKIE_NAME}={token}; HttpOnly; SameSite=Lax; Path=/; Max-Age={AUTH_COOKIE_MAX_AGE}"
136- ) ;
138+ let mut builder = cookie:: Cookie :: build ( ( AUTH_COOKIE_NAME , token) )
139+ . http_only ( true )
140+ . same_site ( SameSite :: Lax )
141+ . path ( "/" )
142+ . max_age ( time:: Duration :: seconds ( AUTH_COOKIE_MAX_AGE as i64 ) ) ;
137143 if secure {
138- cookie . push_str ( "; Secure" ) ;
144+ builder = builder . secure ( true ) ;
139145 }
140- cookie
146+ builder . build ( ) . to_string ( )
141147}
142148
143149/// Build a `Set-Cookie` header value that clears the auth cookie.
144150fn build_clear_cookie ( ) -> String {
145- format ! ( "{AUTH_COOKIE_NAME}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0" )
151+ cookie:: Cookie :: build ( ( AUTH_COOKIE_NAME , "" ) )
152+ . http_only ( true )
153+ . same_site ( SameSite :: Lax )
154+ . path ( "/" )
155+ . max_age ( time:: Duration :: ZERO )
156+ . build ( )
157+ . to_string ( )
146158}
147159
148160/// Verify that a state-mutating request includes the CSRF protection header.
0 commit comments