@@ -423,6 +423,63 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
423423 assert_eq ! ( settings. device_token_max_ttl_seconds, None ) ;
424424}
425425
426+ // lets me stick whatever I want in this thing to be URL-encoded
427+ #[ derive( serde:: Serialize ) ]
428+ struct BadAuthReq {
429+ client_id : String ,
430+ ttl_seconds : String ,
431+ }
432+
433+ /// Test that 0 and negative values for ttl_seconds give immediate 400s
434+ #[ nexus_test]
435+ async fn test_device_token_request_ttl_invalid (
436+ cptestctx : & ControlPlaneTestContext ,
437+ ) {
438+ let testctx = & cptestctx. external_client ;
439+
440+ let auth_response = NexusRequest :: new (
441+ RequestBuilder :: new ( testctx, Method :: POST , "/device/auth" )
442+ . allow_non_dropshot_errors ( )
443+ . body_urlencoded ( Some ( & BadAuthReq {
444+ client_id : Uuid :: new_v4 ( ) . to_string ( ) ,
445+ ttl_seconds : "0" . to_string ( ) ,
446+ } ) )
447+ . expect_status ( Some ( StatusCode :: BAD_REQUEST ) ) ,
448+ )
449+ . execute ( )
450+ // .execute_and_parse_unwrap::<DeviceAuthResponse>()
451+ . await
452+ . expect ( "expected an Ok(TestResponse)" ) ;
453+
454+ let error_body: serde_json:: Value =
455+ serde_json:: from_slice ( & auth_response. body ) . unwrap ( ) ;
456+ assert_eq ! (
457+ error_body. get( "message" ) . unwrap( ) . to_string( ) ,
458+ "\" unable to parse URL-encoded body: ttl_seconds: invalid value: integer `0`, expected a nonzero u32\" "
459+ ) ;
460+
461+ let auth_response = NexusRequest :: new (
462+ RequestBuilder :: new ( testctx, Method :: POST , "/device/auth" )
463+ . allow_non_dropshot_errors ( )
464+ . body_urlencoded ( Some ( & BadAuthReq {
465+ client_id : Uuid :: new_v4 ( ) . to_string ( ) ,
466+ ttl_seconds : "-3" . to_string ( ) ,
467+ } ) )
468+ . expect_status ( Some ( StatusCode :: BAD_REQUEST ) ) ,
469+ )
470+ . execute ( )
471+ // .execute_and_parse_unwrap::<DeviceAuthResponse>()
472+ . await
473+ . expect ( "expected an Ok(TestResponse)" ) ;
474+
475+ let error_body: serde_json:: Value =
476+ serde_json:: from_slice ( & auth_response. body ) . unwrap ( ) ;
477+ assert_eq ! (
478+ error_body. get( "message" ) . unwrap( ) . to_string( ) ,
479+ "\" unable to parse URL-encoded body: ttl_seconds: invalid digit found in string\" "
480+ ) ;
481+ }
482+
426483#[ nexus_test]
427484async fn test_device_token_request_ttl ( cptestctx : & ControlPlaneTestContext ) {
428485 let testctx = & cptestctx. external_client ;
@@ -434,10 +491,10 @@ async fn test_device_token_request_ttl(cptestctx: &ControlPlaneTestContext) {
434491 let _: views:: SiloAuthSettings =
435492 object_put ( testctx, "/v1/auth-settings" , & settings) . await ;
436493
437- // Test 1: Request TTL above the max should fail at verification time
494+ // Request TTL above the max should fail at verification time
438495 let invalid_ttl = DeviceAuthRequest {
439496 client_id : Uuid :: new_v4 ( ) ,
440- ttl_seconds : Some ( 20 ) , // Above the 10 second max
497+ ttl_seconds : NonZeroU32 :: new ( 20 ) , // Above the 10 second max
441498 } ;
442499
443500 let auth_response = NexusRequest :: new (
@@ -468,10 +525,10 @@ async fn test_device_token_request_ttl(cptestctx: &ControlPlaneTestContext) {
468525 "Requested TTL 20 seconds exceeds maximum allowed TTL 10 seconds for this silo"
469526 ) ;
470527
471- // Test 2: Request TTL below the max should succeed and be used
528+ // Request TTL below the max should succeed and be used
472529 let valid_ttl = DeviceAuthRequest {
473530 client_id : Uuid :: new_v4 ( ) ,
474- ttl_seconds : Some ( 3 ) , // Below the 10 second max
531+ ttl_seconds : NonZeroU32 :: new ( 3 ) , // Below the 10 second max
475532 } ;
476533
477534 let auth_response = NexusRequest :: new (
0 commit comments