Skip to content

Commit 66176f7

Browse files
committed
add max lifetime flag
1 parent 2f5e228 commit 66176f7

File tree

4 files changed

+114
-84
lines changed

4 files changed

+114
-84
lines changed

cli/deployment/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,12 @@ func newConfig() *codersdk.DeploymentConfig {
424424
Flag: "update-check",
425425
Default: flag.Lookup("test.v") == nil && !buildinfo.IsDev(),
426426
},
427+
MaxTokenLifetime: &codersdk.DeploymentConfigField[time.Duration]{
428+
Name: "Max Token Lifetime",
429+
Usage: "The maximum lifetime duration for any user creating a token.",
430+
Flag: "max-token-lifetime",
431+
Default: 24 * 30 * time.Hour,
432+
},
427433
}
428434
}
429435

coderd/apikey.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
5050
lifeTime = createToken.Lifetime
5151
}
5252

53+
err := api.validateAPIKeyLifetime(lifeTime)
54+
if err != nil {
55+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
56+
Message: "Failed to validate create API key request.",
57+
Detail: err.Error(),
58+
})
59+
return
60+
}
61+
5362
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
5463
UserID: user.ID,
5564
LoginType: database.LoginTypeToken,
@@ -69,7 +78,6 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
6978
}
7079

7180
// Creates a new session key, used for logging in via the CLI.
72-
// DEPRECATED: use postToken instead.
7381
func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
7482
ctx := r.Context()
7583
user := httpmw.UserParam(r)
@@ -218,6 +226,18 @@ type createAPIKeyParams struct {
218226
Scope database.APIKeyScope
219227
}
220228

229+
func (api *API) validateAPIKeyLifetime(lifetime time.Duration) error {
230+
if lifetime <= 0 {
231+
return xerrors.New("lifetime must be positive number greater than 0")
232+
}
233+
234+
if lifetime > api.DeploymentConfig.MaxTokenLifetime.Value {
235+
return xerrors.Errorf("lifetime must be less than %s", api.DeploymentConfig.MaxTokenLifetime.Value)
236+
}
237+
238+
return nil
239+
}
240+
221241
func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*http.Cookie, error) {
222242
keyID, keySecret, err := generateAPIKeyIDSecret()
223243
if err != nil {

coderd/apikey_test.go

Lines changed: 86 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -12,98 +12,101 @@ import (
1212
"github.com/coder/coder/testutil"
1313
)
1414

15-
func TestTokens(t *testing.T) {
15+
func TestTokenCRUD(t *testing.T) {
1616
t.Parallel()
1717

18-
t.Run("CRUD", func(t *testing.T) {
19-
t.Parallel()
20-
21-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
22-
defer cancel()
23-
client := coderdtest.New(t, nil)
24-
_ = coderdtest.CreateFirstUser(t, client)
25-
keys, err := client.GetTokens(ctx, codersdk.Me)
26-
require.NoError(t, err)
27-
require.Empty(t, keys)
28-
29-
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
30-
require.NoError(t, err)
31-
require.Greater(t, len(res.Key), 2)
32-
33-
keys, err = client.GetTokens(ctx, codersdk.Me)
34-
require.NoError(t, err)
35-
require.EqualValues(t, len(keys), 1)
36-
require.Contains(t, res.Key, keys[0].ID)
37-
// expires_at should default to 30 days
38-
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
39-
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
40-
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
41-
42-
// no update
43-
44-
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
45-
require.NoError(t, err)
46-
keys, err = client.GetTokens(ctx, codersdk.Me)
47-
require.NoError(t, err)
48-
require.Empty(t, keys)
18+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
19+
defer cancel()
20+
client := coderdtest.New(t, nil)
21+
_ = coderdtest.CreateFirstUser(t, client)
22+
keys, err := client.GetTokens(ctx, codersdk.Me)
23+
require.NoError(t, err)
24+
require.Empty(t, keys)
25+
26+
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
27+
require.NoError(t, err)
28+
require.Greater(t, len(res.Key), 2)
29+
30+
keys, err = client.GetTokens(ctx, codersdk.Me)
31+
require.NoError(t, err)
32+
require.EqualValues(t, len(keys), 1)
33+
require.Contains(t, res.Key, keys[0].ID)
34+
// expires_at should default to 30 days
35+
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
36+
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
37+
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
38+
39+
// no update
40+
41+
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
42+
require.NoError(t, err)
43+
keys, err = client.GetTokens(ctx, codersdk.Me)
44+
require.NoError(t, err)
45+
require.Empty(t, keys)
46+
}
47+
48+
func TestTokenScoped(t *testing.T) {
49+
t.Parallel()
50+
51+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
52+
defer cancel()
53+
client := coderdtest.New(t, nil)
54+
_ = coderdtest.CreateFirstUser(t, client)
55+
56+
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
57+
Scope: codersdk.APIKeyScopeApplicationConnect,
4958
})
59+
require.NoError(t, err)
60+
require.Greater(t, len(res.Key), 2)
61+
62+
keys, err := client.GetTokens(ctx, codersdk.Me)
63+
require.NoError(t, err)
64+
require.EqualValues(t, len(keys), 1)
65+
require.Contains(t, res.Key, keys[0].ID)
66+
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
67+
}
68+
69+
func TestTokenDuration(t *testing.T) {
70+
t.Parallel()
5071

51-
t.Run("Scoped", func(t *testing.T) {
52-
t.Parallel()
53-
54-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
55-
defer cancel()
56-
client := coderdtest.New(t, nil)
57-
_ = coderdtest.CreateFirstUser(t, client)
58-
59-
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
60-
Scope: codersdk.APIKeyScopeApplicationConnect,
61-
})
62-
require.NoError(t, err)
63-
require.Greater(t, len(res.Key), 2)
64-
65-
keys, err := client.GetTokens(ctx, codersdk.Me)
66-
require.NoError(t, err)
67-
require.EqualValues(t, len(keys), 1)
68-
require.Contains(t, res.Key, keys[0].ID)
69-
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
72+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
73+
defer cancel()
74+
client := coderdtest.New(t, nil)
75+
_ = coderdtest.CreateFirstUser(t, client)
76+
77+
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
78+
Lifetime: time.Hour * 24 * 7,
7079
})
80+
require.NoError(t, err)
81+
keys, err := client.GetTokens(ctx, codersdk.Me)
82+
require.NoError(t, err)
83+
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
84+
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))
85+
}
86+
87+
func TestTokenMaxLifetime(t *testing.T) {
88+
t.Parallel()
89+
90+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
91+
defer cancel()
92+
dc := coderdtest.DeploymentConfig(t)
93+
dc.MaxTokenLifetime.Value = time.Hour * 24 * 7
94+
client := coderdtest.New(t, &coderdtest.Options{
95+
DeploymentConfig: dc,
96+
})
97+
_ = coderdtest.CreateFirstUser(t, client)
7198

72-
t.Run("Duration", func(t *testing.T) {
73-
t.Parallel()
74-
75-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
76-
defer cancel()
77-
client := coderdtest.New(t, nil)
78-
_ = coderdtest.CreateFirstUser(t, client)
79-
80-
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
81-
Lifetime: time.Hour * 24 * 7,
82-
})
83-
require.NoError(t, err)
84-
keys, err := client.GetTokens(ctx, codersdk.Me)
85-
require.NoError(t, err)
86-
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
87-
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))
99+
// success
100+
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
101+
Lifetime: time.Hour * 24 * 6,
88102
})
103+
require.NoError(t, err)
89104

90-
t.Run("MaxLifetime", func(t *testing.T) {
91-
t.Parallel()
92-
93-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
94-
defer cancel()
95-
client := coderdtest.New(t, nil)
96-
_ = coderdtest.CreateFirstUser(t, client)
97-
98-
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
99-
Lifetime: time.Hour * 24 * 7,
100-
})
101-
require.NoError(t, err)
102-
keys, err := client.GetTokens(ctx, codersdk.Me)
103-
require.NoError(t, err)
104-
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
105-
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))
105+
// fail
106+
_, err = client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
107+
Lifetime: time.Hour * 24 * 8,
106108
})
109+
require.ErrorContains(t, err, "lifetime must be less")
107110
}
108111

109112
func TestAPIKey(t *testing.T) {

codersdk/deploymentconfig.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type DeploymentConfig struct {
4242
APIRateLimit *DeploymentConfigField[int] `json:"api_rate_limit" typescript:",notnull"`
4343
Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"`
4444
UpdateCheck *DeploymentConfigField[bool] `json:"update_check" typescript:",notnull"`
45+
MaxTokenLifetime *DeploymentConfigField[time.Duration] `json:"max_token_lifetime" typescript:",notnull"`
4546
}
4647

4748
type DERP struct {

0 commit comments

Comments
 (0)