mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
feat: add flag for token lifetime (#5385)
This commit is contained in:
@ -44,8 +44,21 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
|
||||
scope = database.APIKeyScope(createToken.Scope)
|
||||
}
|
||||
|
||||
// tokens last 100 years
|
||||
lifeTime := time.Hour * 876000
|
||||
// default lifetime is 30 days
|
||||
lifeTime := 30 * 24 * time.Hour
|
||||
if createToken.Lifetime != 0 {
|
||||
lifeTime = createToken.Lifetime
|
||||
}
|
||||
|
||||
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, err := api.createAPIKey(ctx, createAPIKeyParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypeToken,
|
||||
@ -65,7 +78,6 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Creates a new session key, used for logging in via the CLI.
|
||||
// DEPRECATED: use postToken instead.
|
||||
func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpmw.UserParam(r)
|
||||
@ -214,6 +226,18 @@ type createAPIKeyParams struct {
|
||||
Scope database.APIKeyScope
|
||||
}
|
||||
|
||||
func (api *API) validateAPIKeyLifetime(lifetime time.Duration) error {
|
||||
if lifetime <= 0 {
|
||||
return xerrors.New("lifetime must be positive number greater than 0")
|
||||
}
|
||||
|
||||
if lifetime > api.DeploymentConfig.MaxTokenLifetime.Value {
|
||||
return xerrors.Errorf("lifetime must be less than %s", api.DeploymentConfig.MaxTokenLifetime.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*http.Cookie, error) {
|
||||
keyID, keySecret, err := generateAPIKeyIDSecret()
|
||||
if err != nil {
|
||||
|
@ -12,63 +12,101 @@ import (
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestTokens(t *testing.T) {
|
||||
func TestTokenCRUD(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("CRUD", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
keys, err := client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, keys)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
keys, err := client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, keys)
|
||||
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Key), 2)
|
||||
|
||||
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Key), 2)
|
||||
keys, err = client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
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.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
|
||||
|
||||
keys, err = client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
// expires_at must be greater than 50 years
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*438300))
|
||||
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
|
||||
// no update
|
||||
|
||||
// no update
|
||||
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
|
||||
require.NoError(t, err)
|
||||
keys, err = client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, keys)
|
||||
}
|
||||
|
||||
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
|
||||
require.NoError(t, err)
|
||||
keys, err = client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, keys)
|
||||
func TestTokenScoped(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
||||
Scope: codersdk.APIKeyScopeApplicationConnect,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Key), 2)
|
||||
|
||||
t.Run("Scoped", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
keys, err := client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
func TestTokenDuration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
||||
Scope: codersdk.APIKeyScopeApplicationConnect,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Key), 2)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
keys, err := client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
// expires_at must be greater than 50 years
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*438300))
|
||||
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
|
||||
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
||||
Lifetime: time.Hour * 24 * 7,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
keys, err := client.GetTokens(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))
|
||||
}
|
||||
|
||||
func TestTokenMaxLifetime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
dc := coderdtest.DeploymentConfig(t)
|
||||
dc.MaxTokenLifetime.Value = time.Hour * 24 * 7
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
DeploymentConfig: dc,
|
||||
})
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
// success
|
||||
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
||||
Lifetime: time.Hour * 24 * 6,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// fail
|
||||
_, err = client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
||||
Lifetime: time.Hour * 24 * 8,
|
||||
})
|
||||
require.ErrorContains(t, err, "lifetime must be less")
|
||||
}
|
||||
|
||||
func TestAPIKey(t *testing.T) {
|
||||
|
@ -313,7 +313,8 @@ func TestPostLogin(t *testing.T) {
|
||||
apiKey, err := client.GetAPIKey(ctx, admin.UserID.String(), split[0])
|
||||
require.NoError(t, err, "fetch api key")
|
||||
|
||||
require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*438300)), "tokens lasts more than 50 years")
|
||||
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.Greater(t, apiKey.LifetimeSeconds, key.LifetimeSeconds, "token should have longer lifetime")
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user