44
55use std:: num:: NonZeroU32 ;
66
7+ use chrono:: Utc ;
8+ use dropshot:: ResultsPage ;
79use dropshot:: test_util:: ClientTestContext ;
810use nexus_auth:: authn:: USER_TEST_UNPRIVILEGED ;
911use nexus_db_queries:: db:: fixed_data:: silo:: DEFAULT_SILO ;
@@ -138,6 +140,10 @@ async fn test_device_auth_flow(cptestctx: &ControlPlaneTestContext) {
138140 . expect ( "failed to deserialize OAuth error" ) ;
139141 assert_eq ! ( & error. error, "authorization_pending" ) ;
140142
143+ // Check tokens before creating the device token
144+ assert_eq ! ( get_tokens_priv( testctx) . await . len( ) , 0 ) ;
145+ assert_eq ! ( get_tokens_unpriv( testctx) . await . len( ) , 0 ) ;
146+
141147 // Authenticated confirmation should succeed.
142148 NexusRequest :: new (
143149 RequestBuilder :: new ( testctx, Method :: POST , "/device/confirm" )
@@ -162,10 +168,17 @@ async fn test_device_auth_flow(cptestctx: &ControlPlaneTestContext) {
162168 . expect ( "failed to get token" )
163169 . parsed_body ( )
164170 . expect ( "failed to deserialize token response" ) ;
171+
165172 assert_eq ! ( token. token_type, DeviceAccessTokenType :: Bearer ) ;
166173 assert_eq ! ( token. access_token. len( ) , 52 ) ;
167174 assert ! ( token. access_token. starts_with( "oxide-token-" ) ) ;
168175
176+ // Check token list endpoints after creating the device token
177+ assert_eq ! ( get_tokens_priv( testctx) . await . len( ) , 0 ) ;
178+ let tokens_unpriv_after = get_tokens_unpriv ( testctx) . await ;
179+ assert_eq ! ( tokens_unpriv_after. len( ) , 1 ) ;
180+ assert_eq ! ( tokens_unpriv_after[ 0 ] . time_expires, None ) ;
181+
169182 // now make a request with the token. it 403s because unpriv user has no roles
170183 project_list ( & testctx, & token. access_token , StatusCode :: FORBIDDEN )
171184 . await
@@ -257,10 +270,18 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
257270 object_get ( testctx, "/v1/settings" ) . await ;
258271 assert_eq ! ( settings. device_token_max_ttl_seconds, None ) ;
259272
273+ // no tokens in the list
274+ assert_eq ! ( get_tokens_priv( testctx) . await . len( ) , 0 ) ;
275+
260276 // get a token for the privileged user. default silo max token expiration
261277 // is null, so tokens don't expire
262278 let initial_token = get_device_token ( testctx) . await ;
263279
280+ // now there is a token in the list
281+ let tokens = get_tokens_priv ( testctx) . await ;
282+ assert_eq ! ( tokens. len( ) , 1 ) ;
283+ assert_eq ! ( tokens[ 0 ] . time_expires, None ) ;
284+
264285 // test token works on project list
265286 project_list ( & testctx, & initial_token, StatusCode :: OK )
266287 . await
@@ -299,6 +320,21 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
299320 // create token again (this one will have the 3-second expiration)
300321 let expiring_token = get_device_token ( testctx) . await ;
301322
323+ // use a block so we don't touch expiring_token
324+ {
325+ // now there are two tokens in the list
326+ let tokens = get_tokens_priv ( testctx) . await ;
327+ assert_eq ! ( tokens. len( ) , 2 ) ;
328+
329+ let permanent_token =
330+ tokens. iter ( ) . find ( |t| t. time_expires . is_none ( ) ) . unwrap ( ) ;
331+ let expiring_token =
332+ tokens. iter ( ) . find ( |t| t. time_expires . is_some ( ) ) . unwrap ( ) ;
333+
334+ assert_eq ! ( permanent_token. time_expires, None ) ;
335+ assert ! ( expiring_token. time_expires. unwrap( ) > Utc :: now( ) ) ;
336+ }
337+
302338 // immediately use token, it should work
303339 project_list ( & testctx, & expiring_token, StatusCode :: OK )
304340 . await
@@ -316,6 +352,31 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
316352 project_list ( & testctx, & initial_token, StatusCode :: OK )
317353 . await
318354 . expect ( "initial token should still work" ) ;
355+
356+ // back down to one non-expiring token
357+ let tokens = get_tokens_priv ( testctx) . await ;
358+ assert_eq ! ( tokens. len( ) , 1 ) ;
359+ assert_eq ! ( tokens[ 0 ] . time_expires, None ) ;
360+ }
361+
362+ async fn get_tokens_priv (
363+ testctx : & ClientTestContext ,
364+ ) -> Vec < views:: DeviceAccessToken > {
365+ NexusRequest :: object_get ( testctx, "/v1/me/tokens" )
366+ . authn_as ( AuthnMode :: PrivilegedUser )
367+ . execute_and_parse_unwrap :: < ResultsPage < views:: DeviceAccessToken > > ( )
368+ . await
369+ . items
370+ }
371+
372+ async fn get_tokens_unpriv (
373+ testctx : & ClientTestContext ,
374+ ) -> Vec < views:: DeviceAccessToken > {
375+ NexusRequest :: object_get ( testctx, "/v1/me/tokens" )
376+ . authn_as ( AuthnMode :: UnprivilegedUser )
377+ . execute_and_parse_unwrap :: < ResultsPage < views:: DeviceAccessToken > > ( )
378+ . await
379+ . items
319380}
320381
321382async fn project_list (
0 commit comments