@@ -123,20 +123,67 @@ impl super::Nexus {
123123 let silo_max_ttl = silo_auth_settings. device_token_max_ttl_seconds ;
124124 let requested_ttl = db_request. token_ttl_seconds ;
125125
126- // Validate the requested TTL against the silo's max TTL
127- if let ( Some ( requested) , Some ( max) ) = ( requested_ttl, silo_max_ttl) {
128- if requested > max. 0 . into ( ) {
129- return Err ( Error :: invalid_request ( & format ! (
130- "Requested TTL {} seconds exceeds maximum \
131- allowed TTL for this silo of {} seconds",
132- requested, max
133- ) ) ) ;
126+ // This logic is a bit gnarly, but we landed on it as the least bad
127+ // option. Error out if the user requests a token TTL that is longer
128+ // than allowed, i.e., either
129+ //
130+ // a) it is longer than the silo max TTL, or
131+ // b) this request was authenticated with a device token and the TTL
132+ // would produce an expiration later than the current token's.
133+ //
134+ // If the user does not request a specific TTL, we do not error out.
135+ // We calculate the token TTL as min(silo max TTL, current token TTL
136+ // if present). Token confirm requests authenticated with a console
137+ // session can get device tokens with TTLs up to the silo max.
138+
139+ let time_expires = if let Some ( requested_ttl) = requested_ttl {
140+ // If the user requested a TTL, validate it against the silo max
141+ // TTL as well as the expiration time of the token being used (if a
142+ // token is being used)
143+
144+ // Validate the requested TTL against the silo's max TTL
145+ if let Some ( max) = silo_max_ttl {
146+ if requested_ttl > max. 0 . into ( ) {
147+ return Err ( Error :: invalid_request ( & format ! (
148+ "Requested TTL {} seconds exceeds maximum allowed \
149+ TTL for this silo of {} seconds",
150+ requested_ttl, max
151+ ) ) ) ;
152+ }
153+ } ;
154+
155+ let requested_exp =
156+ Utc :: now ( ) + Duration :: seconds ( requested_ttl. 0 . into ( ) ) ;
157+
158+ // If currently authenticated via token, error if requested exceeds it
159+ if let Some ( auth_exp) = opctx. authn . device_token_expiration ( ) {
160+ if requested_exp > auth_exp {
161+ return Err ( Error :: invalid_request (
162+ "Requested token TTL would exceed the expiration time \
163+ of the token being used to authenticate the confirm \
164+ request. To get the full requested TTL, confirm \
165+ this token using a web console session. Alternatively, \
166+ omit requested TTL to get a token with the longest \
167+ allowed lifetime, determined by the lesser of the silo \
168+ max and the current token's expiration time.",
169+ ) ) ;
170+ }
134171 }
135- }
136172
137- let time_expires = requested_ttl
138- . or ( silo_max_ttl)
139- . map ( |ttl| Utc :: now ( ) + Duration :: seconds ( ttl. 0 . into ( ) ) ) ;
173+ Some ( requested_exp)
174+ } else {
175+ // No explicit TTL requested. Rather than erroring out if silo max
176+ // exceeds TTL exceeds expiration time of current token, just clamp.
177+ let silo_max_exp = silo_max_ttl
178+ . map ( |ttl| Utc :: now ( ) + Duration :: seconds ( ttl. 0 . into ( ) ) ) ;
179+ // a.min(b) doesn't do it because None is always less than Some(_)
180+ match ( silo_max_exp, opctx. authn . device_token_expiration ( ) ) {
181+ ( Some ( silo_exp) , Some ( token_exp) ) => {
182+ Some ( silo_exp. min ( token_exp) )
183+ }
184+ ( a, b) => a. or ( b) ,
185+ }
186+ } ;
140187
141188 let token = DeviceAccessToken :: new (
142189 db_request. client_id ,
@@ -193,11 +240,12 @@ impl super::Nexus {
193240
194241 /// Look up the actor for which a token was granted.
195242 /// Corresponds to a request *after* completing the flow above.
196- pub ( crate ) async fn device_access_token_actor (
243+ /// Returns the actor and the token's expiration time (if any).
244+ pub ( crate ) async fn authenticate_token (
197245 & self ,
198246 opctx : & OpContext ,
199247 token : String ,
200- ) -> Result < Actor , Reason > {
248+ ) -> Result < ( Actor , Option < chrono :: DateTime < Utc > > ) , Reason > {
201249 let ( .., db_access_token) = self
202250 . db_datastore
203251 . device_token_lookup_by_token ( opctx, token)
@@ -222,7 +270,9 @@ impl super::Nexus {
222270 } ) ?;
223271 let silo_id = db_silo_user. silo_id ;
224272
225- if let Some ( time_expires) = db_access_token. time_expires {
273+ let expiration = db_access_token. time_expires ;
274+
275+ if let Some ( time_expires) = expiration {
226276 let now = Utc :: now ( ) ;
227277 if time_expires < now {
228278 return Err ( Reason :: BadCredentials {
@@ -236,7 +286,7 @@ impl super::Nexus {
236286 }
237287 }
238288
239- Ok ( Actor :: SiloUser { silo_user_id, silo_id } )
289+ Ok ( ( Actor :: SiloUser { silo_user_id, silo_id } , expiration ) )
240290 }
241291
242292 pub ( crate ) async fn device_access_token (
0 commit comments