@@ -1681,6 +1681,18 @@ enum MfaBeginStepInput {
16811681 Continue ( String ) ,
16821682}
16831683
1684+ /// Returns the existing MFA session token only for multi-step-capable instances.
1685+ ///
1686+ /// Pre-2.2 sessions are bound to one method, so switching methods must start a
1687+ /// new session to obtain the selected method's challenge.
1688+ fn reusable_mfa_continuation_token (
1689+ state : & AppState ,
1690+ instance_id : Id ,
1691+ token : Option < String > ,
1692+ ) -> Option < String > {
1693+ token. filter ( |_| state. is_multi_step_mfa_capable ( instance_id) )
1694+ }
1695+
16841696async fn begin_mfa_step (
16851697 state : & AppState ,
16861698 instance_id : Id ,
@@ -1795,7 +1807,7 @@ pub async fn mfa_begin_step(
17951807 let proxy_url =
17961808 Url :: parse ( & instance. proxy_url ) . map_err ( |e| format ! ( "Invalid proxy URL: {e}" ) ) ?;
17971809
1798- let input = if let Some ( token) = token {
1810+ let input = if let Some ( token) = reusable_mfa_continuation_token ( & state , instance_id , token) {
17991811 MfaBeginStepInput :: Continue ( token)
18001812 } else {
18011813 let step_methods = step_plan
@@ -2369,6 +2381,19 @@ mod tests {
23692381 } ) )
23702382 }
23712383
2384+ #[ test]
2385+ fn test_reusable_mfa_continuation_token_requires_multi_step_capability ( ) {
2386+ let state = AppState :: new ( AppConfig :: default ( ) , None ) ;
2387+
2388+ assert ! ( reusable_mfa_continuation_token( & state, 1 , Some ( "token-1" . into( ) ) ) . is_none( ) ) ;
2389+
2390+ state. set_multi_step_mfa_capable ( 1 , true ) ;
2391+ assert_eq ! (
2392+ reusable_mfa_continuation_token( & state, 1 , Some ( "token-1" . into( ) ) ) . as_deref( ) ,
2393+ Some ( "token-1" )
2394+ ) ;
2395+ }
2396+
23722397 #[ tokio:: test]
23732398 async fn test_mfa_begin_step_starts_capable_step_zero ( ) {
23742399 let server = MockServer :: start ( ) . await ;
0 commit comments