mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat!: add --default-token-lifetime (#14631)
This commit is contained in:
Generated
+4
-1
@@ -12416,7 +12416,10 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default_duration": {
|
||||
"description": "DefaultDuration is for api keys, not tokens.",
|
||||
"description": "DefaultDuration is only for browser, workspace app and oauth sessions.",
|
||||
"type": "integer"
|
||||
},
|
||||
"default_token_lifetime": {
|
||||
"type": "integer"
|
||||
},
|
||||
"disable_expiry_refresh": {
|
||||
|
||||
Generated
+4
-1
@@ -11240,7 +11240,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default_duration": {
|
||||
"description": "DefaultDuration is for api keys, not tokens.",
|
||||
"description": "DefaultDuration is only for browser, workspace app and oauth sessions.",
|
||||
"type": "integer"
|
||||
},
|
||||
"default_token_lifetime": {
|
||||
"type": "integer"
|
||||
},
|
||||
"disable_expiry_refresh": {
|
||||
|
||||
+20
-27
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
// Creates a new token API key that effectively doesn't expire.
|
||||
// Creates a new token API key with the given scope and lifetime.
|
||||
//
|
||||
// @Summary Create token API key
|
||||
// @ID create-token-api-key
|
||||
@@ -60,36 +60,34 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
|
||||
scope = database.APIKeyScope(createToken.Scope)
|
||||
}
|
||||
|
||||
// default lifetime is 30 days
|
||||
lifeTime := 30 * 24 * time.Hour
|
||||
if createToken.Lifetime != 0 {
|
||||
lifeTime = createToken.Lifetime
|
||||
}
|
||||
|
||||
tokenName := namesgenerator.GetRandomName(1)
|
||||
|
||||
if len(createToken.TokenName) != 0 {
|
||||
tokenName = createToken.TokenName
|
||||
}
|
||||
|
||||
err := api.validateAPIKeyLifetime(lifeTime)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Failed to validate create API key request.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cookie, key, err := api.createAPIKey(ctx, apikey.CreateParams{
|
||||
params := apikey.CreateParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypeToken,
|
||||
DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(),
|
||||
ExpiresAt: dbtime.Now().Add(lifeTime),
|
||||
DefaultLifetime: api.DeploymentValues.Sessions.DefaultTokenDuration.Value(),
|
||||
Scope: scope,
|
||||
LifetimeSeconds: int64(lifeTime.Seconds()),
|
||||
TokenName: tokenName,
|
||||
})
|
||||
}
|
||||
|
||||
if createToken.Lifetime != 0 {
|
||||
err := api.validateAPIKeyLifetime(createToken.Lifetime)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Failed to validate create API key request.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
params.ExpiresAt = dbtime.Now().Add(createToken.Lifetime)
|
||||
params.LifetimeSeconds = int64(createToken.Lifetime.Seconds())
|
||||
}
|
||||
|
||||
cookie, key, err := api.createAPIKey(ctx, params)
|
||||
if err != nil {
|
||||
if database.IsUniqueViolation(err, database.UniqueIndexAPIKeyName) {
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
@@ -125,16 +123,11 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpmw.UserParam(r)
|
||||
|
||||
lifeTime := time.Hour * 24 * 7
|
||||
cookie, _, err := api.createAPIKey(ctx, apikey.CreateParams{
|
||||
UserID: user.ID,
|
||||
DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(),
|
||||
DefaultLifetime: api.DeploymentValues.Sessions.DefaultTokenDuration.Value(),
|
||||
LoginType: database.LoginTypePassword,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
// All api generated keys will last 1 week. Browser login tokens have
|
||||
// a shorter life.
|
||||
ExpiresAt: dbtime.Now().Add(lifeTime),
|
||||
LifetimeSeconds: int64(lifeTime.Seconds()),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
|
||||
+88
-4
@@ -45,8 +45,8 @@ func TestTokenCRUD(t *testing.T) {
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
// expires_at should default to 30 days
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8))
|
||||
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
|
||||
|
||||
// no update
|
||||
@@ -115,8 +115,8 @@ func TestDefaultTokenDuration(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8))
|
||||
}
|
||||
|
||||
func TestTokenUserSetMaxLifetime(t *testing.T) {
|
||||
@@ -144,6 +144,27 @@ func TestTokenUserSetMaxLifetime(t *testing.T) {
|
||||
require.ErrorContains(t, err, "lifetime must be less")
|
||||
}
|
||||
|
||||
func TestTokenCustomDefaultLifetime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
dc := coderdtest.DeploymentValues(t)
|
||||
dc.Sessions.DefaultTokenDuration = serpent.Duration(time.Hour * 12)
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
DeploymentValues: dc,
|
||||
})
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tokens, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tokens, 1)
|
||||
require.EqualValues(t, dc.Sessions.DefaultTokenDuration.Value().Seconds(), tokens[0].LifetimeSeconds)
|
||||
}
|
||||
|
||||
func TestSessionExpiry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -224,3 +245,66 @@ func TestAPIKey_Deleted(t *testing.T) {
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
||||
}
|
||||
|
||||
func TestAPIKey_Refresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, pubsub := dbtestutil.NewDB(t)
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
Database: db,
|
||||
Pubsub: pubsub,
|
||||
})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
token, err := client.CreateAPIKey(ctx, owner.UserID.String())
|
||||
require.NoError(t, err)
|
||||
split := strings.Split(token.Key, "-")
|
||||
apiKey1, err := db.GetAPIKeyByID(ctx, split[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(604800), apiKey1.LifetimeSeconds, "default should be 7 days")
|
||||
|
||||
err = db.UpdateAPIKeyByID(ctx, database.UpdateAPIKeyByIDParams{
|
||||
ID: apiKey1.ID,
|
||||
LastUsed: apiKey1.LastUsed,
|
||||
// Cross the no-refresh threshold
|
||||
ExpiresAt: apiKey1.ExpiresAt.Add(time.Hour * -2),
|
||||
IPAddress: apiKey1.IPAddress,
|
||||
})
|
||||
require.NoError(t, err, "update login key")
|
||||
|
||||
// Refresh the token
|
||||
client.SetSessionToken(token.Key)
|
||||
_, err = client.User(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
|
||||
apiKey2, err := client.APIKeyByID(ctx, owner.UserID.String(), split[0])
|
||||
require.NoError(t, err)
|
||||
require.True(t, apiKey2.ExpiresAt.After(apiKey1.ExpiresAt), "token should have a later expiry")
|
||||
}
|
||||
|
||||
func TestAPIKey_SetDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, pubsub := dbtestutil.NewDB(t)
|
||||
dc := coderdtest.DeploymentValues(t)
|
||||
dc.Sessions.DefaultTokenDuration = serpent.Duration(time.Hour * 12)
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
Database: db,
|
||||
Pubsub: pubsub,
|
||||
DeploymentValues: dc,
|
||||
})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
token, err := client.CreateAPIKey(ctx, owner.UserID.String())
|
||||
require.NoError(t, err)
|
||||
split := strings.Split(token.Key, "-")
|
||||
apiKey1, err := db.GetAPIKeyByID(ctx, split[0])
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, dc.Sessions.DefaultTokenDuration.Value().Seconds(), apiKey1.LifetimeSeconds)
|
||||
}
|
||||
|
||||
@@ -1958,7 +1958,7 @@ func (s *server) regenerateSessionToken(ctx context.Context, user database.User,
|
||||
UserID: user.ID,
|
||||
LoginType: user.LoginType,
|
||||
TokenName: workspaceSessionTokenName(workspace),
|
||||
DefaultLifetime: s.DeploymentValues.Sessions.DefaultDuration.Value(),
|
||||
DefaultLifetime: s.DeploymentValues.Sessions.DefaultTokenDuration.Value(),
|
||||
LifetimeSeconds: int64(s.DeploymentValues.Sessions.MaximumTokenDuration.Value().Seconds()),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -299,8 +299,8 @@ func TestPostLogin(t *testing.T) {
|
||||
apiKey, err := client.APIKeyByID(ctx, owner.UserID.String(), split[0])
|
||||
require.NoError(t, err, "fetch api key")
|
||||
|
||||
require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*24*29)), "default tokens lasts more than 29 days")
|
||||
require.True(t, apiKey.ExpiresAt.Before(time.Now().Add(time.Hour*24*31)), "default tokens lasts less than 31 days")
|
||||
require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*24*6)), "default tokens lasts more than 6 days")
|
||||
require.True(t, apiKey.ExpiresAt.Before(time.Now().Add(time.Hour*24*8)), "default tokens lasts less than 8 days")
|
||||
require.Greater(t, apiKey.LifetimeSeconds, key.LifetimeSeconds, "token should have longer lifetime")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user