@@ -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"
@@ -18,6 +19,7 @@ import (
18
19
"github.com/coder/coder/v2/coderd/database/dbtime"
19
20
"github.com/coder/coder/v2/coderd/httpapi"
20
21
"github.com/coder/coder/v2/coderd/httpmw"
22
+ "github.com/coder/coder/v2/coderd/rbac"
21
23
"github.com/coder/coder/v2/coderd/rbac/policy"
22
24
"github.com/coder/coder/v2/coderd/telemetry"
23
25
"github.com/coder/coder/v2/codersdk"
@@ -75,8 +77,7 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
75
77
}
76
78
77
79
if createToken .Lifetime != 0 {
78
- err := api .validateAPIKeyLifetime (createToken .Lifetime )
79
- if err != nil {
80
+ if err := api .validateAPIKeyLifetime (ctx , createToken .Lifetime , user .ID ); err != nil {
80
81
httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
81
82
Message : "Failed to validate create API key request." ,
82
83
Detail : err .Error (),
@@ -338,35 +339,101 @@ func (api *API) deleteAPIKey(rw http.ResponseWriter, r *http.Request) {
338
339
// @Success 200 {object} codersdk.TokenConfig
339
340
// @Router /users/{user}/keys/tokens/tokenconfig [get]
340
341
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
342
+ ctx := r .Context ()
343
+ user := httpmw .UserParam (r )
344
+
345
+ var roleIdentifiers []rbac.RoleIdentifier
346
+
347
+ if user .ID != uuid .Nil {
348
+ subject , userStatus , err := httpmw .UserRBACSubject (ctx , api .Database , user .ID , rbac .ScopeAll )
349
+ switch {
350
+ case err != nil :
351
+ api .Logger .Error (ctx , "failed to get user RBAC subject for token config" , "user_id" , user .ID .String (), "error" , err )
352
+ roleIdentifiers = []rbac.RoleIdentifier {}
353
+ case userStatus == database .UserStatusSuspended :
354
+ roleIdentifiers = []rbac.RoleIdentifier {}
355
+ default :
356
+ // Extract role names from the RBAC subject and convert to internal format
357
+ roleIdentifiers = subject .Roles .Names ()
358
+ }
359
+ } else {
360
+ api .Logger .Warn (ctx , "user ID is nil in token config request context" )
361
+ roleIdentifiers = []rbac.RoleIdentifier {}
345
362
}
346
363
364
+ maxLifetime := api .getMaxTokenLifetimeForUserRoles (roleIdentifiers )
365
+
347
366
httpapi .Write (
348
- r . Context () , rw , http .StatusOK ,
367
+ ctx , rw , http .StatusOK ,
349
368
codersdk.TokenConfig {
350
- MaxTokenLifetime : values . Sessions . MaximumTokenDuration . Value () ,
369
+ MaxTokenLifetime : maxLifetime ,
351
370
},
352
371
)
353
372
}
354
373
355
- func (api * API ) validateAPIKeyLifetime (lifetime time.Duration ) error {
374
+ func (api * API ) validateAPIKeyLifetime (ctx context. Context , lifetime time.Duration , userID uuid. UUID ) error {
356
375
if lifetime <= 0 {
357
376
return xerrors .New ("lifetime must be positive number greater than 0" )
358
377
}
359
378
360
- if lifetime > api .DeploymentValues .Sessions .MaximumTokenDuration .Value () {
379
+ subject , userStatus , err := httpmw .UserRBACSubject (ctx , api .Database , userID , rbac .ScopeAll )
380
+ if err != nil {
381
+ api .Logger .Error (ctx , "failed to get user RBAC subject during token validation" , "user_id" , userID .String (), "error" , err )
382
+ if xerrors .Is (err , sql .ErrNoRows ) {
383
+ return xerrors .Errorf ("user %s not found" , userID )
384
+ }
385
+ return xerrors .Errorf ("internal server error validating token lifetime for user %s" , userID )
386
+ }
387
+
388
+ if userStatus == database .UserStatusSuspended {
389
+ return xerrors .Errorf ("user %s is suspended and cannot create tokens" , userID )
390
+ }
391
+
392
+ // Extract role names from the RBAC subject and convert to internal format
393
+ roleIdentifiers := subject .Roles .Names ()
394
+ maxAllowedLifetime := api .getMaxTokenLifetimeForUserRoles (roleIdentifiers )
395
+
396
+ if lifetime > maxAllowedLifetime {
361
397
return xerrors .Errorf (
362
- "lifetime must be less than %v" ,
363
- api .DeploymentValues .Sessions .MaximumTokenDuration ,
398
+ "requested lifetime of %v exceeds the maximum allowed %v based on your roles" ,
399
+ lifetime ,
400
+ maxAllowedLifetime ,
364
401
)
365
402
}
366
-
367
403
return nil
368
404
}
369
405
406
+ // getMaxTokenLifetimeForUserRoles determines the most generous token lifetime a user is entitled to
407
+ // based on their roles and the CODER_ROLE_TOKEN_LIFETIMES configuration.
408
+ // Roles are expected in the internal format ("rolename" or "rolename:org_id").
409
+ func (api * API ) getMaxTokenLifetimeForUserRoles (roles []rbac.RoleIdentifier ) time.Duration {
410
+ globalMaxDefault := api .DeploymentValues .Sessions .MaximumTokenDuration .Value ()
411
+
412
+ // Early return for empty config
413
+ if api .DeploymentValues .Sessions .RoleTokenLifetimes .Value () == "" ||
414
+ api .DeploymentValues .Sessions .RoleTokenLifetimes .Value () == "{}" {
415
+ return globalMaxDefault
416
+ }
417
+
418
+ // Early return for no roles
419
+ if len (roles ) == 0 {
420
+ return globalMaxDefault
421
+ }
422
+
423
+ // Find the maximum lifetime among all roles
424
+ // This includes both role-specific lifetimes and the global default
425
+ maxLifetime := globalMaxDefault
426
+
427
+ for _ , role := range roles {
428
+ roleDuration := api .DeploymentValues .Sessions .MaxTokenLifetimeForRole (role )
429
+ if roleDuration > maxLifetime {
430
+ maxLifetime = roleDuration
431
+ }
432
+ }
433
+
434
+ return maxLifetime
435
+ }
436
+
370
437
func (api * API ) createAPIKey (ctx context.Context , params apikey.CreateParams ) (* http.Cookie , * database.APIKey , error ) {
371
438
key , sessionToken , err := apikey .Generate (params )
372
439
if err != nil {
0 commit comments