@@ -27,8 +27,11 @@ impl<C: CredentialRegistry> RouteHandler for StsHandler<C> {
2727
2828/// Extension trait for registering STS routes on a [`Router`].
2929pub trait StsRouterExt {
30- /// Register a catch-all STS handler that intercepts
31- /// `AssumeRoleWithWebIdentity` requests on any path.
30+ /// Register the STS handler on the root path (`/`).
31+ ///
32+ /// STS requests are identified by query parameters
33+ /// (`Action=AssumeRoleWithWebIdentity`), not by path, and clients
34+ /// always send them to `/`.
3235 fn with_sts < C : CredentialRegistry + ' static > (
3336 self ,
3437 config : C ,
@@ -44,6 +47,68 @@ impl StsRouterExt for Router {
4447 cache : JwksCache ,
4548 key : Option < TokenKey > ,
4649 ) -> Self {
47- self . route ( "/{*path}" , StsHandler { config, cache, key } )
50+ self . route ( "/" , StsHandler { config, cache, key } )
51+ }
52+ }
53+
54+ #[ cfg( test) ]
55+ mod tests {
56+ use super :: * ;
57+ use multistore:: error:: ProxyError ;
58+ use multistore:: types:: { RoleConfig , StoredCredential } ;
59+
60+ /// Minimal stub that satisfies `CredentialRegistry` without real data.
61+ #[ derive( Clone ) ]
62+ struct EmptyRegistry ;
63+
64+ impl CredentialRegistry for EmptyRegistry {
65+ async fn get_credential (
66+ & self ,
67+ _access_key_id : & str ,
68+ ) -> Result < Option < StoredCredential > , ProxyError > {
69+ Ok ( None )
70+ }
71+ async fn get_role ( & self , _role_id : & str ) -> Result < Option < RoleConfig > , ProxyError > {
72+ Ok ( None )
73+ }
74+ }
75+
76+ fn test_router ( ) -> Router {
77+ let cache = JwksCache :: new ( reqwest:: Client :: new ( ) , std:: time:: Duration :: from_secs ( 60 ) ) ;
78+ Router :: new ( ) . with_sts ( EmptyRegistry , cache, None )
79+ }
80+
81+ #[ tokio:: test]
82+ async fn sts_query_on_root_path_is_handled ( ) {
83+ let router = test_router ( ) ;
84+ let headers = http:: HeaderMap :: new ( ) ;
85+ let req = RequestInfo {
86+ method : & http:: Method :: GET ,
87+ path : "/" ,
88+ query : Some ( "Action=AssumeRoleWithWebIdentity&RoleArn=test&WebIdentityToken=tok" ) ,
89+ headers : & headers,
90+ params : Default :: default ( ) ,
91+ } ;
92+ assert ! (
93+ router. dispatch( & req) . await . is_some( ) ,
94+ "STS request to / must be intercepted by the router"
95+ ) ;
96+ }
97+
98+ #[ tokio:: test]
99+ async fn non_sts_query_on_root_path_falls_through ( ) {
100+ let router = test_router ( ) ;
101+ let headers = http:: HeaderMap :: new ( ) ;
102+ let req = RequestInfo {
103+ method : & http:: Method :: GET ,
104+ path : "/" ,
105+ query : Some ( "prefix=foo/" ) ,
106+ headers : & headers,
107+ params : Default :: default ( ) ,
108+ } ;
109+ assert ! (
110+ router. dispatch( & req) . await . is_none( ) ,
111+ "non-STS request to / must fall through"
112+ ) ;
48113 }
49114}
0 commit comments