@@ -433,6 +433,63 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
433433 assert_eq ! ( settings. device_token_max_ttl_seconds, None ) ;
434434}
435435
436+ // lets me stick whatever I want in this thing to be URL-encoded
437+ #[ derive( serde:: Serialize ) ]
438+ struct BadAuthReq {
439+ client_id : String ,
440+ ttl_seconds : String ,
441+ }
442+
443+ /// Test that 0 and negative values for ttl_seconds give immediate 400s
444+ #[ nexus_test]
445+ async fn test_device_token_request_ttl_invalid (
446+ cptestctx : & ControlPlaneTestContext ,
447+ ) {
448+ let testctx = & cptestctx. external_client ;
449+
450+ let auth_response = NexusRequest :: new (
451+ RequestBuilder :: new ( testctx, Method :: POST , "/device/auth" )
452+ . allow_non_dropshot_errors ( )
453+ . body_urlencoded ( Some ( & BadAuthReq {
454+ client_id : Uuid :: new_v4 ( ) . to_string ( ) ,
455+ ttl_seconds : "0" . to_string ( ) ,
456+ } ) )
457+ . expect_status ( Some ( StatusCode :: BAD_REQUEST ) ) ,
458+ )
459+ . execute ( )
460+ // .execute_and_parse_unwrap::<DeviceAuthResponse>()
461+ . await
462+ . expect ( "expected an Ok(TestResponse)" ) ;
463+
464+ let error_body: serde_json:: Value =
465+ serde_json:: from_slice ( & auth_response. body ) . unwrap ( ) ;
466+ assert_eq ! (
467+ error_body. get( "message" ) . unwrap( ) . to_string( ) ,
468+ "\" unable to parse URL-encoded body: ttl_seconds: invalid value: integer `0`, expected a nonzero u32\" "
469+ ) ;
470+
471+ let auth_response = NexusRequest :: new (
472+ RequestBuilder :: new ( testctx, Method :: POST , "/device/auth" )
473+ . allow_non_dropshot_errors ( )
474+ . body_urlencoded ( Some ( & BadAuthReq {
475+ client_id : Uuid :: new_v4 ( ) . to_string ( ) ,
476+ ttl_seconds : "-3" . to_string ( ) ,
477+ } ) )
478+ . expect_status ( Some ( StatusCode :: BAD_REQUEST ) ) ,
479+ )
480+ . execute ( )
481+ // .execute_and_parse_unwrap::<DeviceAuthResponse>()
482+ . await
483+ . expect ( "expected an Ok(TestResponse)" ) ;
484+
485+ let error_body: serde_json:: Value =
486+ serde_json:: from_slice ( & auth_response. body ) . unwrap ( ) ;
487+ assert_eq ! (
488+ error_body. get( "message" ) . unwrap( ) . to_string( ) ,
489+ "\" unable to parse URL-encoded body: ttl_seconds: invalid digit found in string\" "
490+ ) ;
491+ }
492+
436493#[ nexus_test]
437494async fn test_device_token_request_ttl ( cptestctx : & ControlPlaneTestContext ) {
438495 let testctx = & cptestctx. external_client ;
@@ -444,10 +501,10 @@ async fn test_device_token_request_ttl(cptestctx: &ControlPlaneTestContext) {
444501 let _: views:: SiloAuthSettings =
445502 object_put ( testctx, "/v1/auth-settings" , & settings) . await ;
446503
447- // Test 1: Request TTL above the max should fail at verification time
504+ // Request TTL above the max should fail at verification time
448505 let invalid_ttl = DeviceAuthRequest {
449506 client_id : Uuid :: new_v4 ( ) ,
450- ttl_seconds : Some ( 20 ) , // Above the 10 second max
507+ ttl_seconds : NonZeroU32 :: new ( 20 ) , // Above the 10 second max
451508 } ;
452509
453510 let auth_response = NexusRequest :: new (
@@ -478,10 +535,10 @@ async fn test_device_token_request_ttl(cptestctx: &ControlPlaneTestContext) {
478535 "Requested TTL 20 seconds exceeds maximum allowed TTL 10 seconds for this silo"
479536 ) ;
480537
481- // Test 2: Request TTL below the max should succeed and be used
538+ // Request TTL below the max should succeed and be used
482539 let valid_ttl = DeviceAuthRequest {
483540 client_id : Uuid :: new_v4 ( ) ,
484- ttl_seconds : Some ( 3 ) , // Below the 10 second max
541+ ttl_seconds : NonZeroU32 :: new ( 3 ) , // Below the 10 second max
485542 } ;
486543
487544 let auth_response = NexusRequest :: new (
0 commit comments