@@ -8,6 +8,7 @@ use dropshot::ResultsPage;
88use dropshot:: test_util:: ClientTestContext ;
99use http:: header:: HeaderName ;
1010use http:: { StatusCode , header, method:: Method } ;
11+ use nexus_auth:: context:: OpContext ;
1112use std:: env:: current_dir;
1213
1314use crate :: integration_tests:: saml:: SAML_RESPONSE_IDP_DESCRIPTOR ;
@@ -30,7 +31,7 @@ use nexus_test_utils_macros::nexus_test;
3031use nexus_types:: external_api:: params:: { self , ProjectCreate } ;
3132use nexus_types:: external_api:: shared:: { SiloIdentityMode , SiloRole } ;
3233use nexus_types:: external_api:: { shared, views} ;
33- use omicron_common:: api:: external:: IdentityMetadataCreateParams ;
34+ use omicron_common:: api:: external:: { Error , IdentityMetadataCreateParams } ;
3435use omicron_sled_agent:: sim;
3536use omicron_test_utils:: dev:: poll:: { CondCheckError , wait_for_condition} ;
3637
@@ -918,3 +919,60 @@ async fn expect_redirect(testctx: &ClientTestContext, from: &str, to: &str) {
918919 . await
919920 . expect ( "did not find expected redirect" ) ;
920921}
922+
923+ /// Make sure an expired session gets deleted when you try to use it
924+ ///
925+ /// This is not the best kind of test because it breaks the API abstraction
926+ /// boundary, but in this case it's necessary because by design we do not
927+ /// expose through the API why authn failed, i.e., whether it's because
928+ /// the session was found but is expired. vs not found at all
929+ #[ tokio:: test]
930+ async fn test_session_idle_timeout_deletes_session ( ) {
931+ // set idle timeout to 1 second so we can test expiration
932+ let mut config = load_test_config ( ) ;
933+ config. pkg . console . session_idle_timeout_minutes = 0 ;
934+ let cptestctx = test_setup_with_config :: < omicron_nexus:: Server > (
935+ "test_session_idle_timeout_deletes_session" ,
936+ & mut config,
937+ sim:: SimMode :: Explicit ,
938+ None ,
939+ 0 ,
940+ )
941+ . await ;
942+ let testctx = & cptestctx. external_client ;
943+
944+ // Start session
945+ let session_cookie = log_in_and_extract_token ( & cptestctx) . await ;
946+
947+ // sleep here not necessary given TTL of 0
948+
949+ // Make a request with the expired session cookie
950+ let me_response = RequestBuilder :: new ( testctx, Method :: GET , "/v1/me" )
951+ . header ( header:: COOKIE , & session_cookie)
952+ . expect_status ( Some ( StatusCode :: UNAUTHORIZED ) )
953+ . execute ( )
954+ . await
955+ . expect ( "first request with expired cookie did not 401" ) ;
956+
957+ let error_body: dropshot:: HttpErrorResponseBody =
958+ me_response. parsed_body ( ) . unwrap ( ) ;
959+ assert_eq ! ( error_body. message, "credentials missing or invalid" ) ;
960+
961+ // check that the session actually got deleted
962+
963+ let nexus = & cptestctx. server . server_context ( ) . nexus ;
964+ let datastore = nexus. datastore ( ) ;
965+ let opctx =
966+ OpContext :: for_tests ( cptestctx. logctx . log . new ( o ! ( ) ) , datastore. clone ( ) ) ;
967+
968+ let token = session_cookie. strip_prefix ( "session=" ) . unwrap ( ) ;
969+ let db_token_error = nexus
970+ . datastore ( )
971+ . session_lookup_by_token ( & opctx, token. to_string ( ) )
972+ . await
973+ . expect_err ( "session should be deleted" ) ;
974+ assert_matches:: assert_matches!(
975+ db_token_error,
976+ Error :: ObjectNotFound { .. }
977+ ) ;
978+ }
0 commit comments