Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c969613

Browse files
committed
feat!: add --default-token-lifetime
1 parent 7ea8a22 commit c969613

File tree

14 files changed

+165
-38
lines changed

14 files changed

+165
-38
lines changed

cli/testdata/coder_server_--help.golden

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ OPTIONS:
2525
systemd. This directory is NOT safe to be configured as a shared
2626
directory across coderd/provisionerd replicas.
2727

28+
--default-token-lifetime duration, $CODER_DEFAULT_TOKEN_LIFETIME (default: 168h0m0s)
29+
The default lifetime duration for API tokens. This value is used when
30+
creating a token without specifying a duration, such as when
31+
authenticating the CLI or an IDE plugin.
32+
2833
--disable-owner-workspace-access bool, $CODER_DISABLE_OWNER_WORKSPACE_ACCESS
2934
Remove the permission for the 'owner' role to have workspace execution
3035
on all workspaces. This prevents the 'owner' from ssh, apps, and

cli/testdata/server-config.yaml.golden

+5
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,11 @@ experiments: []
423423
# performed once per day.
424424
# (default: false, type: bool)
425425
updateCheck: false
426+
# The default lifetime duration for API tokens. This value is used when creating a
427+
# token without specifying a duration, such as when authenticating the CLI or an
428+
# IDE plugin.
429+
# (default: 168h0m0s, type: duration)
430+
defaultTokenLifetime: 168h0m0s
426431
# Expose the swagger endpoint via /swagger.
427432
# (default: <unset>, type: bool)
428433
enableSwagger: false

coderd/apidoc/docs.go

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apikey.go

+20-27
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"github.com/coder/coder/v2/codersdk"
2424
)
2525

26-
// Creates a new token API key that effectively doesn't expire.
26+
// Creates a new token API key with the given scope and lifetime.
2727
//
2828
// @Summary Create token API key
2929
// @ID create-token-api-key
@@ -60,36 +60,34 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
6060
scope = database.APIKeyScope(createToken.Scope)
6161
}
6262

63-
// default lifetime is 30 days
64-
lifeTime := 30 * 24 * time.Hour
65-
if createToken.Lifetime != 0 {
66-
lifeTime = createToken.Lifetime
67-
}
68-
6963
tokenName := namesgenerator.GetRandomName(1)
7064

7165
if len(createToken.TokenName) != 0 {
7266
tokenName = createToken.TokenName
7367
}
7468

75-
err := api.validateAPIKeyLifetime(lifeTime)
76-
if err != nil {
77-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
78-
Message: "Failed to validate create API key request.",
79-
Detail: err.Error(),
80-
})
81-
return
82-
}
83-
84-
cookie, key, err := api.createAPIKey(ctx, apikey.CreateParams{
69+
params := apikey.CreateParams{
8570
UserID: user.ID,
8671
LoginType: database.LoginTypeToken,
87-
DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(),
88-
ExpiresAt: dbtime.Now().Add(lifeTime),
72+
DefaultLifetime: api.DeploymentValues.Sessions.DefaultTokenDuration.Value(),
8973
Scope: scope,
90-
LifetimeSeconds: int64(lifeTime.Seconds()),
9174
TokenName: tokenName,
92-
})
75+
}
76+
77+
if createToken.Lifetime != 0 {
78+
err := api.validateAPIKeyLifetime(createToken.Lifetime)
79+
if err != nil {
80+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
81+
Message: "Failed to validate create API key request.",
82+
Detail: err.Error(),
83+
})
84+
return
85+
}
86+
params.ExpiresAt = dbtime.Now().Add(createToken.Lifetime)
87+
params.LifetimeSeconds = int64(createToken.Lifetime.Seconds())
88+
}
89+
90+
cookie, key, err := api.createAPIKey(ctx, params)
9391
if err != nil {
9492
if database.IsUniqueViolation(err, database.UniqueIndexAPIKeyName) {
9593
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
@@ -125,16 +123,11 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
125123
ctx := r.Context()
126124
user := httpmw.UserParam(r)
127125

128-
lifeTime := time.Hour * 24 * 7
129126
cookie, _, err := api.createAPIKey(ctx, apikey.CreateParams{
130127
UserID: user.ID,
131-
DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(),
128+
DefaultLifetime: api.DeploymentValues.Sessions.DefaultTokenDuration.Value(),
132129
LoginType: database.LoginTypePassword,
133130
RemoteAddr: r.RemoteAddr,
134-
// All api generated keys will last 1 week. Browser login tokens have
135-
// a shorter life.
136-
ExpiresAt: dbtime.Now().Add(lifeTime),
137-
LifetimeSeconds: int64(lifeTime.Seconds()),
138131
})
139132
if err != nil {
140133
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{

coderd/apikey_test.go

+88-4
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ func TestTokenCRUD(t *testing.T) {
4545
require.EqualValues(t, len(keys), 1)
4646
require.Contains(t, res.Key, keys[0].ID)
4747
// expires_at should default to 30 days
48-
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
49-
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
48+
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6))
49+
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8))
5050
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
5151

5252
// no update
@@ -115,8 +115,8 @@ func TestDefaultTokenDuration(t *testing.T) {
115115
require.NoError(t, err)
116116
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
117117
require.NoError(t, err)
118-
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
119-
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
118+
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6))
119+
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8))
120120
}
121121

122122
func TestTokenUserSetMaxLifetime(t *testing.T) {
@@ -144,6 +144,27 @@ func TestTokenUserSetMaxLifetime(t *testing.T) {
144144
require.ErrorContains(t, err, "lifetime must be less")
145145
}
146146

147+
func TestTokenCustomDefaultLifetime(t *testing.T) {
148+
t.Parallel()
149+
150+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
151+
defer cancel()
152+
dc := coderdtest.DeploymentValues(t)
153+
dc.Sessions.DefaultTokenDuration = serpent.Duration(time.Hour * 12)
154+
client := coderdtest.New(t, &coderdtest.Options{
155+
DeploymentValues: dc,
156+
})
157+
_ = coderdtest.CreateFirstUser(t, client)
158+
159+
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
160+
require.NoError(t, err)
161+
162+
tokens, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
163+
require.NoError(t, err)
164+
require.Len(t, tokens, 1)
165+
require.EqualValues(t, dc.Sessions.DefaultTokenDuration.Value().Seconds(), tokens[0].LifetimeSeconds)
166+
}
167+
147168
func TestSessionExpiry(t *testing.T) {
148169
t.Parallel()
149170

@@ -224,3 +245,66 @@ func TestAPIKey_Deleted(t *testing.T) {
224245
require.ErrorAs(t, err, &apiErr)
225246
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
226247
}
248+
249+
func TestAPIKey_Refresh(t *testing.T) {
250+
t.Parallel()
251+
252+
db, pubsub := dbtestutil.NewDB(t)
253+
client := coderdtest.New(t, &coderdtest.Options{
254+
Database: db,
255+
Pubsub: pubsub,
256+
})
257+
owner := coderdtest.CreateFirstUser(t, client)
258+
259+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
260+
defer cancel()
261+
262+
token, err := client.CreateAPIKey(ctx, owner.UserID.String())
263+
require.NoError(t, err)
264+
split := strings.Split(token.Key, "-")
265+
apiKey1, err := db.GetAPIKeyByID(ctx, split[0])
266+
require.NoError(t, err)
267+
require.Equal(t, int64(604800), apiKey1.LifetimeSeconds, "default should be 7 days")
268+
269+
err = db.UpdateAPIKeyByID(ctx, database.UpdateAPIKeyByIDParams{
270+
ID: apiKey1.ID,
271+
LastUsed: apiKey1.LastUsed,
272+
// Cross the no-refresh threshold
273+
ExpiresAt: apiKey1.ExpiresAt.Add(time.Hour * -2),
274+
IPAddress: apiKey1.IPAddress,
275+
})
276+
require.NoError(t, err, "update login key")
277+
278+
// Refresh the token
279+
client.SetSessionToken(token.Key)
280+
_, err = client.User(ctx, codersdk.Me)
281+
require.NoError(t, err)
282+
283+
apiKey2, err := client.APIKeyByID(ctx, owner.UserID.String(), split[0])
284+
require.NoError(t, err)
285+
require.True(t, apiKey2.ExpiresAt.After(apiKey1.ExpiresAt), "token should have a later expiry")
286+
}
287+
288+
func TestAPIKey_SetDefault(t *testing.T) {
289+
t.Parallel()
290+
291+
db, pubsub := dbtestutil.NewDB(t)
292+
dc := coderdtest.DeploymentValues(t)
293+
dc.Sessions.DefaultTokenDuration = serpent.Duration(time.Hour * 12)
294+
client := coderdtest.New(t, &coderdtest.Options{
295+
Database: db,
296+
Pubsub: pubsub,
297+
DeploymentValues: dc,
298+
})
299+
owner := coderdtest.CreateFirstUser(t, client)
300+
301+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
302+
defer cancel()
303+
304+
token, err := client.CreateAPIKey(ctx, owner.UserID.String())
305+
require.NoError(t, err)
306+
split := strings.Split(token.Key, "-")
307+
apiKey1, err := db.GetAPIKeyByID(ctx, split[0])
308+
require.NoError(t, err)
309+
require.EqualValues(t, dc.Sessions.DefaultTokenDuration.Value().Seconds(), apiKey1.LifetimeSeconds)
310+
}

coderd/provisionerdserver/provisionerdserver.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1958,7 +1958,7 @@ func (s *server) regenerateSessionToken(ctx context.Context, user database.User,
19581958
UserID: user.ID,
19591959
LoginType: user.LoginType,
19601960
TokenName: workspaceSessionTokenName(workspace),
1961-
DefaultLifetime: s.DeploymentValues.Sessions.DefaultDuration.Value(),
1961+
DefaultLifetime: s.DeploymentValues.Sessions.DefaultTokenDuration.Value(),
19621962
LifetimeSeconds: int64(s.DeploymentValues.Sessions.MaximumTokenDuration.Value().Seconds()),
19631963
})
19641964
if err != nil {

coderd/users_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,8 @@ func TestPostLogin(t *testing.T) {
299299
apiKey, err := client.APIKeyByID(ctx, owner.UserID.String(), split[0])
300300
require.NoError(t, err, "fetch api key")
301301

302-
require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*24*29)), "default tokens lasts more than 29 days")
303-
require.True(t, apiKey.ExpiresAt.Before(time.Now().Add(time.Hour*24*31)), "default tokens lasts less than 31 days")
302+
require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*24*6)), "default tokens lasts more than 6 days")
303+
require.True(t, apiKey.ExpiresAt.Before(time.Now().Add(time.Hour*24*8)), "default tokens lasts less than 8 days")
304304
require.Greater(t, apiKey.LifetimeSeconds, key.LifetimeSeconds, "token should have longer lifetime")
305305
})
306306
}

codersdk/deployment.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -454,9 +454,11 @@ type SessionLifetime struct {
454454
// creation is the lifetime of the api key.
455455
DisableExpiryRefresh serpent.Bool `json:"disable_expiry_refresh,omitempty" typescript:",notnull"`
456456

457-
// DefaultDuration is for api keys, not tokens.
457+
// DefaultDuration is only for browser, workspace app and oauth sessions.
458458
DefaultDuration serpent.Duration `json:"default_duration" typescript:",notnull"`
459459

460+
DefaultTokenDuration serpent.Duration `json:"default_token_lifetime,omitempty" typescript:",notnull"`
461+
460462
MaximumTokenDuration serpent.Duration `json:"max_token_lifetime,omitempty" typescript:",notnull"`
461463
}
462464

@@ -1998,6 +2000,16 @@ when required by your organization's security policy.`,
19982000
YAML: "maxTokenLifetime",
19992001
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
20002002
},
2003+
{
2004+
Name: "Default Token Lifetime",
2005+
Description: "The default lifetime duration for API tokens. This value is used when creating a token without specifying a duration, such as when authenticating the CLI or an IDE plugin.",
2006+
Flag: "default-token-lifetime",
2007+
Env: "CODER_DEFAULT_TOKEN_LIFETIME",
2008+
Default: (7 * 24 * time.Hour).String(),
2009+
Value: &c.Sessions.DefaultTokenDuration,
2010+
YAML: "defaultTokenLifetime",
2011+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
2012+
},
20012013
{
20022014
Name: "Enable swagger endpoint",
20032015
Description: "Expose the swagger endpoint via /swagger.",

docs/reference/api/general.md

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/api/schemas.md

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/cli/server.md

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/cli/testdata/coder_server_--help.golden

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ OPTIONS:
2626
systemd. This directory is NOT safe to be configured as a shared
2727
directory across coderd/provisionerd replicas.
2828

29+
--default-token-lifetime duration, $CODER_DEFAULT_TOKEN_LIFETIME (default: 168h0m0s)
30+
The default lifetime duration for API tokens. This value is used when
31+
creating a token without specifying a duration, such as when
32+
authenticating the CLI or an IDE plugin.
33+
2934
--disable-owner-workspace-access bool, $CODER_DISABLE_OWNER_WORKSPACE_ACCESS
3035
Remove the permission for the 'owner' role to have workspace execution
3136
on all workspaces. This prevents the 'owner' from ssh, apps, and

site/src/api/typesGenerated.ts

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)