Skip to content

Commit d3d449c

Browse files
authored
Add token_id and time_expires to access token grant response (#8280)
Closes #8279. It seems the response schemas I changed are not in the OpenAPI output, so there are no changes there.
1 parent b08db01 commit d3d449c

File tree

4 files changed

+36
-12
lines changed

4 files changed

+36
-12
lines changed

nexus/db-model/src/device_auth.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ impl From<DeviceAccessToken> for views::DeviceAccessTokenGrant {
179179
Self {
180180
access_token: format!("oxide-token-{}", access_token.token),
181181
token_type: views::DeviceAccessTokenType::Bearer,
182+
token_id: access_token.id.into_untyped_uuid(),
183+
time_expires: access_token.time_expires,
182184
}
183185
}
184186
}

nexus/tests/integration_tests/device_auth.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ async fn test_device_auth_flow(cptestctx: &ControlPlaneTestContext) {
179179
assert_eq!(get_tokens_priv(testctx).await.len(), 0);
180180
let tokens_unpriv_after = get_tokens_unpriv(testctx).await;
181181
assert_eq!(tokens_unpriv_after.len(), 1);
182+
assert_eq!(tokens_unpriv_after[0].id, token.token_id);
183+
assert_eq!(token.time_expires, None);
182184
assert_eq!(tokens_unpriv_after[0].time_expires, None);
183185

184186
// now make a request with the token. it 403s because unpriv user has no
@@ -241,7 +243,9 @@ async fn test_device_auth_flow(cptestctx: &ControlPlaneTestContext) {
241243

242244
/// Helper to make the test cute. Goes through the whole flow, returns the token
243245
/// as a string
244-
async fn get_device_token(testctx: &ClientTestContext) -> String {
246+
async fn get_device_token(
247+
testctx: &ClientTestContext,
248+
) -> DeviceAccessTokenGrant {
245249
let client_id = Uuid::new_v4();
246250
let authn_params = DeviceAuthRequest { client_id, ttl_seconds: None };
247251

@@ -279,8 +283,8 @@ async fn get_device_token(testctx: &ClientTestContext) -> String {
279283
client_id,
280284
};
281285

282-
// Get the token
283-
let token: DeviceAccessTokenGrant = NexusRequest::new(
286+
// Get the token and return it
287+
NexusRequest::new(
284288
RequestBuilder::new(testctx, Method::POST, "/device/token")
285289
.allow_non_dropshot_errors()
286290
.body_urlencoded(Some(&token_params))
@@ -291,9 +295,7 @@ async fn get_device_token(testctx: &ClientTestContext) -> String {
291295
.await
292296
.expect("failed to get token")
293297
.parsed_body()
294-
.expect("failed to deserialize token response");
295-
296-
token.access_token
298+
.expect("failed to deserialize token response")
297299
}
298300

299301
#[nexus_test]
@@ -309,12 +311,14 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
309311

310312
// get a token for the privileged user. default silo max token expiration
311313
// is null, so tokens don't expire
312-
let initial_token = get_device_token(testctx).await;
314+
let initial_token_grant = get_device_token(testctx).await;
315+
let initial_token = initial_token_grant.access_token;
313316

314317
// now there is a token in the list
315318
let tokens = get_tokens_priv(testctx).await;
316319
assert_eq!(tokens.len(), 1);
317320
assert_eq!(tokens[0].time_expires, None);
321+
assert_eq!(tokens[0].id, initial_token_grant.token_id);
318322

319323
// test token works on project list
320324
project_list(&testctx, &initial_token, StatusCode::OK)
@@ -377,7 +381,16 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
377381
assert_eq!(settings.device_token_max_ttl_seconds, Some(3));
378382

379383
// create token again (this one will have the 3-second expiration)
380-
let expiring_token = get_device_token(testctx).await;
384+
let expiring_token_grant = get_device_token(testctx).await;
385+
386+
// check that expiration time is there and in the right range
387+
let exp = expiring_token_grant
388+
.time_expires
389+
.expect("Expiring token should have an expiration time");
390+
let exp = (exp - Utc::now()).num_seconds();
391+
assert!(exp > 0 && exp < 5, "should be around 3 seconds from now");
392+
393+
let expiring_token = expiring_token_grant.access_token;
381394

382395
// use a block so we don't touch expiring_token
383396
{

nexus/types/src/external_api/views.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,8 @@ pub struct DeviceAccessToken {
997997
/// "oxide-token-"
998998
pub id: Uuid,
999999
pub time_created: DateTime<Utc>,
1000+
1001+
/// Expiration timestamp. A null value means the token does not automatically expire.
10001002
pub time_expires: Option<DateTime<Utc>>,
10011003
}
10021004

@@ -1012,29 +1014,35 @@ impl SimpleIdentity for DeviceAccessToken {
10121014
/// See RFC 8628 §3.2 (Device Authorization Response).
10131015
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
10141016
pub struct DeviceAuthResponse {
1015-
/// The device verification code.
1017+
/// The device verification code
10161018
pub device_code: String,
10171019

1018-
/// The end-user verification code.
1020+
/// The end-user verification code
10191021
pub user_code: String,
10201022

10211023
/// The end-user verification URI on the authorization server.
10221024
/// The URI should be short and easy to remember as end users
10231025
/// may be asked to manually type it into their user agent.
10241026
pub verification_uri: String,
10251027

1026-
/// The lifetime in seconds of the `device_code` and `user_code`.
1028+
/// The lifetime in seconds of the `device_code` and `user_code`
10271029
pub expires_in: u16,
10281030
}
10291031

10301032
/// Successful access token grant. See RFC 6749 §5.1.
10311033
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
10321034
pub struct DeviceAccessTokenGrant {
1033-
/// The access token issued to the client.
1035+
/// The access token issued to the client
10341036
pub access_token: String,
10351037

10361038
/// The type of the token issued, as described in RFC 6749 §7.1.
10371039
pub token_type: DeviceAccessTokenType,
1040+
1041+
/// A unique, immutable, system-controlled identifier for the token
1042+
pub token_id: Uuid,
1043+
1044+
/// Expiration timestamp. A null value means the token does not automatically expire.
1045+
pub time_expires: Option<DateTime<Utc>>,
10381046
}
10391047

10401048
/// The kind of token granted.

openapi/nexus.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16620,6 +16620,7 @@
1662016620
},
1662116621
"time_expires": {
1662216622
"nullable": true,
16623+
"description": "Expiration timestamp. A null value means the token does not automatically expire.",
1662316624
"type": "string",
1662416625
"format": "date-time"
1662516626
}

0 commit comments

Comments
 (0)