@@ -22,13 +22,20 @@ use std::{
2222 result:: Result ,
2323 str:: { from_utf8, Split } ,
2424 string:: ToString ,
25+ sync:: atomic:: { AtomicBool , Ordering as AtomicOrdering } ,
2526 sync:: Arc ,
2627} ;
2728use time:: OffsetDateTime ;
2829use tokio:: net:: TcpListener ;
2930
3031use crate :: dbg_msg;
3132
33+ /// Set to `true` at startup (by `main`) when the server is running behind HTTPS
34+ /// (i.e. when `--hsts` is passed). Controls the `Secure` attribute on all
35+ /// `Set-Cookie` headers: cookies with `Secure` are silently dropped by browsers
36+ /// on plain-HTTP origins, so we only set it when we know HTTPS is in use.
37+ pub static COOKIE_SECURE : AtomicBool = AtomicBool :: new ( false ) ;
38+
3239/// The unified body type used for all responses.
3340pub type Body = BoxBody < Bytes , Infallible > ;
3441
@@ -275,13 +282,46 @@ impl ResponseExt for Response<Body> {
275282 }
276283
277284 fn remove_cookie ( & mut self , name : String ) {
278- let removal_cookie = Cookie :: build ( name) . path ( "/" ) . http_only ( true ) . expires ( OffsetDateTime :: now_utc ( ) ) ;
285+ let removal_cookie = Cookie :: build ( name)
286+ . path ( "/" )
287+ . http_only ( true )
288+ . secure ( COOKIE_SECURE . load ( AtomicOrdering :: Relaxed ) )
289+ . same_site ( cookie:: SameSite :: Lax )
290+ . expires ( OffsetDateTime :: now_utc ( ) ) ;
279291 if let Ok ( val) = header:: HeaderValue :: from_str ( & removal_cookie. to_string ( ) ) {
280292 self . headers_mut ( ) . append ( "Set-Cookie" , val) ;
281293 }
282294 }
283295}
284296
297+ /// Build a persistent [`Cookie`] with standardised privacy/security attributes.
298+ ///
299+ /// Sets `Path=/`, `HttpOnly`, `SameSite=Lax`, and `Secure` only when
300+ /// [`COOKIE_SECURE`] is `true` (i.e. when `--hsts` was passed at startup).
301+ pub fn build_cookie ( name : impl Into < String > , value : impl Into < String > , expires : OffsetDateTime ) -> Cookie < ' static > {
302+ Cookie :: build ( ( name. into ( ) , value. into ( ) ) )
303+ . path ( "/" )
304+ . http_only ( true )
305+ . secure ( COOKIE_SECURE . load ( AtomicOrdering :: Relaxed ) )
306+ . same_site ( cookie:: SameSite :: Lax )
307+ . expires ( expires)
308+ . build ( )
309+ }
310+
311+ /// Build a session-scoped [`Cookie`] with standardised privacy/security attributes.
312+ ///
313+ /// Sets `Path=/`, `HttpOnly`, `SameSite=Lax`, and `Secure` only when
314+ /// [`COOKIE_SECURE`] is `true` (i.e. when `--hsts` was passed at startup).
315+ pub fn build_session_cookie ( name : impl Into < String > , value : impl Into < String > ) -> Cookie < ' static > {
316+ Cookie :: build ( ( name. into ( ) , value. into ( ) ) )
317+ . path ( "/" )
318+ . http_only ( true )
319+ . secure ( COOKIE_SECURE . load ( AtomicOrdering :: Relaxed ) )
320+ . same_site ( cookie:: SameSite :: Lax )
321+ . build ( )
322+ }
323+
324+
285325impl Route < ' _ > {
286326 fn method ( & mut self , method : & Method , dest : fn ( Request < Body > ) -> BoxResponse ) -> & mut Self {
287327 self . router . add ( & format ! ( "/{}{}" , method. as_str( ) , self . path) , dest) ;
0 commit comments