@@ -2,6 +2,7 @@ package coderd
2
2
3
3
import (
4
4
"context"
5
+ "database/sql"
5
6
"fmt"
6
7
"net/http"
7
8
"strconv"
@@ -75,8 +76,7 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
75
76
}
76
77
77
78
if createToken .Lifetime != 0 {
78
- err := api .validateAPIKeyLifetime (createToken .Lifetime )
79
- if err != nil {
79
+ if err := api .validateAPIKeyLifetime (ctx ,createToken .Lifetime ,user .ID );err != nil {
80
80
httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
81
81
Message :"Failed to validate create API key request." ,
82
82
Detail :err .Error (),
@@ -338,35 +338,104 @@ func (api *API) deleteAPIKey(rw http.ResponseWriter, r *http.Request) {
338
338
// @Success 200 {object} codersdk.TokenConfig
339
339
// @Router /users/{user}/keys/tokens/tokenconfig [get]
340
340
func (api * API )tokenConfig (rw http.ResponseWriter ,r * http.Request ) {
341
- values ,err := api .DeploymentValues .WithoutSecrets ()
342
- if err != nil {
343
- httpapi .InternalServerError (rw ,err )
344
- return
341
+ ctx := r .Context ()
342
+ // Prefer httpmw.UserParam(r) as it's consistently used in this file (e.g., postToken, postAPIKey).
343
+ // The user object from UserParam should contain the ID.
344
+ user := httpmw .UserParam (r )
345
+
346
+ var userRoles []string
347
+
348
+ // Check if user has a valid ID.
349
+ if user .ID != uuid .Nil {
350
+ authzInfo ,err := api .Database .GetAuthorizationUserRoles (ctx ,user .ID )
351
+ if err != nil {
352
+ api .Logger .Error (ctx ,"failed to get authorization roles for token config" ,"user_id" ,user .ID .String (),"error" ,err )
353
+ // Fallback to global max if roles can't be fetched
354
+ userRoles = []string {}
355
+ }else {
356
+ userRoles = authzInfo .Roles
357
+ }
358
+ }else {
359
+ // This case implies an issue with user retrieval (e.g. UserParam returning a zero User struct)
360
+ // or an unauthenticated request reaching this point.
361
+ // Logging as Warn because it's unexpected for an authenticated endpoint.
362
+ api .Logger .Warn (ctx ,"user ID is nil in token config request context" )
363
+ // Fallback to global max, implying default/maximum permissions.
364
+ userRoles = []string {}
345
365
}
346
366
367
+ maxLifetime := api .getMaxTokenLifetimeForUserRoles (userRoles )
368
+
347
369
httpapi .Write (
348
- r . Context () ,rw ,http .StatusOK ,
370
+ ctx ,rw ,http .StatusOK ,
349
371
codersdk.TokenConfig {
350
- MaxTokenLifetime :values . Sessions . MaximumTokenDuration . Value () ,
372
+ MaxTokenLifetime :maxLifetime ,
351
373
},
352
374
)
353
375
}
354
376
355
- func (api * API )validateAPIKeyLifetime (lifetime time.Duration )error {
377
+ func (api * API )validateAPIKeyLifetime (ctx context. Context , lifetime time.Duration , userID uuid. UUID )error {
356
378
if lifetime <= 0 {
357
379
return xerrors .New ("lifetime must be positive number greater than 0" )
358
380
}
359
381
360
- if lifetime > api .DeploymentValues .Sessions .MaximumTokenDuration .Value () {
382
+ authzInfo ,err := api .Database .GetAuthorizationUserRoles (ctx ,userID )
383
+ if err != nil {
384
+ api .Logger .Error (ctx ,"failed to get user authorization info during token validation" ,"user_id" ,userID .String (),"error" ,err )
385
+ if xerrors .Is (err ,sql .ErrNoRows ) {
386
+ return xerrors .Errorf ("user %s not found" ,userID )
387
+ }
388
+ return xerrors .Errorf ("internal server error validating token lifetime for user %s" ,userID )
389
+ }
390
+
391
+ // Check if user is suspended
392
+ if authzInfo .Status == database .UserStatusSuspended {
393
+ return xerrors .Errorf ("user %s is suspended and cannot create tokens" ,userID )
394
+ }
395
+
396
+ maxAllowedLifetime := api .getMaxTokenLifetimeForUserRoles (authzInfo .Roles )
397
+
398
+ if lifetime > maxAllowedLifetime {
361
399
return xerrors .Errorf (
362
- "lifetime must be less than %v" ,
363
- api .DeploymentValues .Sessions .MaximumTokenDuration ,
400
+ "requested lifetime of %v exceeds the maximum allowed %v based on your roles" ,
401
+ lifetime ,
402
+ maxAllowedLifetime ,
364
403
)
365
404
}
366
-
367
405
return nil
368
406
}
369
407
408
+ // getMaxTokenLifetimeForUserRoles determines the most generous token lifetime a user is entitled to
409
+ // based on their roles and the CODER_ROLE_TOKEN_LIFETIMES configuration.
410
+ // Roles are expected in the internal format ("rolename" or "rolename:org_id").
411
+ func (api * API )getMaxTokenLifetimeForUserRoles (roles []string ) time.Duration {
412
+ globalMaxDefault := api .DeploymentValues .Sessions .MaximumTokenDuration .Value ()
413
+
414
+ // Early return for empty config
415
+ if api .DeploymentValues .Sessions .RoleTokenLifetimes .Value ()== "" ||
416
+ api .DeploymentValues .Sessions .RoleTokenLifetimes .Value ()== "{}" {
417
+ return globalMaxDefault
418
+ }
419
+
420
+ // Early return for no roles
421
+ if len (roles )== 0 {
422
+ return globalMaxDefault
423
+ }
424
+
425
+ // Find the maximum lifetime among all roles
426
+ // This includes both role-specific lifetimes and the global default
427
+ maxLifetime := globalMaxDefault
428
+
429
+ for _ ,roleStr := range roles {
430
+ roleDuration := api .DeploymentValues .Sessions .MaxTokenLifetimeForRole (roleStr )
431
+ if roleDuration > maxLifetime {
432
+ maxLifetime = roleDuration
433
+ }
434
+ }
435
+
436
+ return maxLifetime
437
+ }
438
+
370
439
func (api * API )createAPIKey (ctx context.Context ,params apikey.CreateParams ) (* http.Cookie ,* database.APIKey ,error ) {
371
440
key ,sessionToken ,err := apikey .Generate (params )
372
441
if err != nil {