Skip to content

Commit 007a825

Browse files
committed
use NonZeroU32 for ttl_seconds, test it
1 parent 6bb39cb commit 007a825

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::typed_uuid::DbTypedUuid;
@@ -101,7 +102,10 @@ fn generate_user_code() -> String {
101102
}
102103

103104
impl DeviceAuthRequest {
104-
pub fn new(client_id: Uuid, requested_ttl_seconds: Option<u32>) -> Self {
105+
pub fn new(
106+
client_id: Uuid,
107+
requested_ttl_seconds: Option<NonZeroU32>,
108+
) -> Self {
105109
let now = Utc::now();
106110
Self {
107111
client_id,
@@ -110,7 +114,8 @@ impl DeviceAuthRequest {
110114
time_created: now,
111115
time_expires: now
112116
+ Duration::seconds(CLIENT_AUTHENTICATION_TIMEOUT),
113-
token_ttl_seconds: requested_ttl_seconds.map(i64::from),
117+
token_ttl_seconds: requested_ttl_seconds
118+
.map(|ttl| ttl.get().into()),
114119
}
115120
}
116121

nexus/tests/integration_tests/device_auth.rs

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

nexus/types/src/external_api/params.rs

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

24762476
#[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)