Skip to content

Commit 148ee14

Browse files
committed
use NonZeroU32 for ttl_seconds, test it
1 parent 9dffe77 commit 148ee14

File tree

4 files changed

+70
-8
lines changed

4 files changed

+70
-8
lines changed

nexus/db-model/src/device_auth.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use chrono::{DateTime, Duration, Utc};
1313
use nexus_types::external_api::views;
1414
use omicron_uuid_kinds::{AccessTokenKind, GenericUuid, TypedUuid};
1515
use rand::{Rng, RngCore, SeedableRng, distributions::Slice, rngs::StdRng};
16+
use std::num::NonZeroU32;
1617
use uuid::Uuid;
1718

1819
use crate::SqlU32;
@@ -102,7 +103,10 @@ fn generate_user_code() -> String {
102103
}
103104

104105
impl DeviceAuthRequest {
105-
pub fn new(client_id: Uuid, requested_ttl_seconds: Option<u32>) -> Self {
106+
pub fn new(
107+
client_id: Uuid,
108+
requested_ttl_seconds: Option<NonZeroU32>,
109+
) -> Self {
106110
let now = Utc::now();
107111
Self {
108112
client_id,
@@ -111,7 +115,8 @@ impl DeviceAuthRequest {
111115
time_created: now,
112116
time_expires: now
113117
+ Duration::seconds(CLIENT_AUTHENTICATION_TIMEOUT),
114-
token_ttl_seconds: requested_ttl_seconds.map(SqlU32::from),
118+
token_ttl_seconds: requested_ttl_seconds
119+
.map(|ttl| ttl.get().into()),
115120
}
116121
}
117122

nexus/tests/integration_tests/device_auth.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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]
437494
async 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(

nexus/types/src/external_api/params.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2469,7 +2469,7 @@ pub struct DeviceAuthRequest {
24692469
pub client_id: Uuid,
24702470
/// Optional lifetime for the access token in seconds. If not specified, the
24712471
/// silo's max TTL will be used (if set).
2472-
pub ttl_seconds: Option<u32>,
2472+
pub ttl_seconds: Option<NonZeroU32>,
24732473
}
24742474

24752475
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]

openapi/nexus.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16682,7 +16682,7 @@
1668216682
"description": "Optional lifetime for the access token in seconds. If not specified, the silo's max TTL will be used (if set).",
1668316683
"type": "integer",
1668416684
"format": "uint32",
16685-
"minimum": 0
16685+
"minimum": 1
1668616686
}
1668716687
},
1668816688
"required": [

0 commit comments

Comments
 (0)