@@ -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 {