@@ -1471,6 +1471,55 @@ async fn answer_one_proxy_request(listener: tokio::net::UnixListener, generation
14711471 writer. shutdown ( ) . await . expect ( "shutdown fake daemon" ) ;
14721472}
14731473
1474+ #[ cfg( unix) ]
1475+ async fn answer_one_authenticated_proxy_request (
1476+ listener : tokio:: net:: UnixListener ,
1477+ expected_token : & str ,
1478+ generation : u64 ,
1479+ ) {
1480+ let ( stream, _addr) = listener. accept ( ) . await . expect ( "accept proxied client" ) ;
1481+ let ( reader, mut writer) = stream. into_split ( ) ;
1482+ let mut lines = tokio:: io:: BufReader :: new ( reader) . lines ( ) ;
1483+ let auth_line = lines
1484+ . next_line ( )
1485+ . await
1486+ . expect ( "read auth preface" )
1487+ . expect ( "auth preface line" ) ;
1488+ let preface =
1489+ super :: transport:: DaemonAuthPreface :: from_line ( auth_line. trim ( ) ) . expect ( "auth preface" ) ;
1490+ assert ! (
1491+ preface. authenticate( expected_token) ,
1492+ "proxy must reload the current daemon authority token"
1493+ ) ;
1494+ let handshake_line = lines
1495+ . next_line ( )
1496+ . await
1497+ . expect ( "read handshake" )
1498+ . expect ( "handshake line" ) ;
1499+ DaemonHandshake :: from_line ( & handshake_line) . expect ( "parse handshake" ) ;
1500+ let request_line = lines
1501+ . next_line ( )
1502+ . await
1503+ . expect ( "read request" )
1504+ . expect ( "request line" ) ;
1505+ let request: Value = serde_json:: from_str ( & request_line) . expect ( "request json" ) ;
1506+ let response = json ! ( {
1507+ "jsonrpc" : "2.0" ,
1508+ "id" : request[ "id" ] ,
1509+ "result" : { "generation" : generation }
1510+ } ) ;
1511+ writer
1512+ . write_all (
1513+ serde_json:: to_string ( & response)
1514+ . expect ( "response json" )
1515+ . as_bytes ( ) ,
1516+ )
1517+ . await
1518+ . expect ( "write response" ) ;
1519+ writer. write_all ( b"\n " ) . await . expect ( "write newline" ) ;
1520+ writer. shutdown ( ) . await . expect ( "shutdown fake daemon" ) ;
1521+ }
1522+
14741523#[ cfg( unix) ]
14751524async fn daemon_round_trip (
14761525 engine : super :: DaemonEngine ,
@@ -2048,6 +2097,86 @@ async fn long_lived_proxy_reconnects_after_daemon_socket_rebind() {
20482097 await_test_task ( daemon, "daemon rebind task" ) . await ;
20492098}
20502099
2100+ #[ cfg( unix) ]
2101+ #[ tokio:: test]
2102+ async fn long_lived_proxy_reloads_rotated_auth_after_daemon_restart ( ) {
2103+ let dir = TempDir :: new ( ) . expect ( "temp dir" ) ;
2104+ let profile = dir. path ( ) . canonicalize ( ) . expect ( "canonical profile" ) ;
2105+ let socket = profile. join ( "daemon.sock" ) ;
2106+ let endpoint = super :: transport:: DaemonEndpoint :: Unix ( socket. clone ( ) ) ;
2107+ let first_listener = tokio:: net:: UnixListener :: bind ( & socket) . expect ( "bind first daemon socket" ) ;
2108+ let first_authority = super :: authority:: DaemonAuthority :: acquire ( & profile, & endpoint, "first" )
2109+ . expect ( "first daemon authority" ) ;
2110+ let first_token = first_authority. auth_token ( ) . to_string ( ) ;
2111+ let rebound_socket = socket. clone ( ) ;
2112+ let rebound_profile = profile. clone ( ) ;
2113+ let rebound_endpoint = endpoint. clone ( ) ;
2114+ let ( unbound_tx, unbound_rx) = tokio:: sync:: oneshot:: channel ( ) ;
2115+ let daemon = tokio:: spawn ( async move {
2116+ answer_one_authenticated_proxy_request ( first_listener, & first_token, 1 ) . await ;
2117+ drop ( first_authority) ;
2118+ std:: fs:: remove_file ( & rebound_socket) . expect ( "unlink first daemon socket" ) ;
2119+ unbound_tx. send ( ( ) ) . expect ( "notify daemon outage" ) ;
2120+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 100 ) ) . await ;
2121+
2122+ let second_listener =
2123+ tokio:: net:: UnixListener :: bind ( & rebound_socket) . expect ( "bind second daemon socket" ) ;
2124+ let second_authority = super :: authority:: DaemonAuthority :: acquire (
2125+ & rebound_profile,
2126+ & rebound_endpoint,
2127+ "second" ,
2128+ )
2129+ . expect ( "second daemon authority" ) ;
2130+ let second_token = second_authority. auth_token ( ) . to_string ( ) ;
2131+ assert_ne ! ( first_token, second_token, "daemon restart must rotate auth" ) ;
2132+ answer_one_authenticated_proxy_request ( second_listener, & second_token, 2 ) . await ;
2133+ drop ( second_authority) ;
2134+ } ) ;
2135+
2136+ let ( mut transport, sender, mut receiver) = crate :: mcp:: transport:: ChannelTransport :: new ( ) ;
2137+ let proxy_socket = socket. clone ( ) ;
2138+ let proxy = tokio:: spawn ( async move {
2139+ super :: proxy_transport_to_daemon (
2140+ & proxy_socket,
2141+ & test_handshake_defaults ( ) ,
2142+ None ,
2143+ & mut transport,
2144+ )
2145+ . await
2146+ } ) ;
2147+ let request = |id| {
2148+ serde_json:: to_string ( & json ! ( {
2149+ "jsonrpc" : "2.0" ,
2150+ "id" : id,
2151+ "method" : "tools/list"
2152+ } ) )
2153+ . expect ( "request json" )
2154+ } ;
2155+
2156+ sender. send ( request ( 1 ) ) . expect ( "send first request" ) ;
2157+ let first = tokio:: time:: timeout ( std:: time:: Duration :: from_secs ( 2 ) , receiver. recv ( ) )
2158+ . await
2159+ . expect ( "first response timed out" )
2160+ . expect ( "first response" ) ;
2161+ let first: Value = serde_json:: from_str ( first. trim ( ) ) . expect ( "first response json" ) ;
2162+ assert_eq ! ( first[ "result" ] [ "generation" ] , json!( 1 ) ) ;
2163+
2164+ unbound_rx. await . expect ( "first daemon should unlink socket" ) ;
2165+ sender. send ( request ( 2 ) ) . expect ( "send second request" ) ;
2166+ let second = tokio:: time:: timeout ( std:: time:: Duration :: from_secs ( 2 ) , receiver. recv ( ) )
2167+ . await
2168+ . expect ( "second response timed out" )
2169+ . expect ( "second response" ) ;
2170+ let second: Value = serde_json:: from_str ( second. trim ( ) ) . expect ( "second response json" ) ;
2171+ assert_eq ! ( second[ "result" ] [ "generation" ] , json!( 2 ) ) ;
2172+
2173+ drop ( sender) ;
2174+ await_test_task ( proxy, "rotating-auth proxy task" )
2175+ . await
2176+ . expect ( "proxy transport" ) ;
2177+ await_test_task ( daemon, "rotating-auth daemon task" ) . await ;
2178+ }
2179+
20512180#[ cfg( unix) ]
20522181#[ tokio:: test]
20532182async fn proxy_uses_daemon_initialize_route_without_registry_access ( ) {
0 commit comments