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

Skip to content

Commit 75e26af

Browse files
committed
feat: machine keys
1 parent b65c555 commit 75e26af

File tree

9 files changed

+80
-76
lines changed

9 files changed

+80
-76
lines changed

coderd/coderd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ func New(options *Options) *API {
399399
r.Get("/roles", api.userRoles)
400400

401401
r.Route("/keys", func(r chi.Router) {
402-
r.Post("/", api.postAPIKey)
402+
r.Post("/machine", api.postMachineAPIKey)
403403
r.Get("/{keyid}", api.apiKey)
404404
})
405405

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TYPE old_login_type AS ENUM (
2+
'password',
3+
'github',
4+
'oidc'
5+
);
6+
ALTER TABLE api_keys ALTER COLUMN login_type TYPE old_login_type USING (login_type::text::old_login_type);
7+
DROP TYPE login_type;
8+
ALTER TYPE old_login_type RENAME TO login_type;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TYPE login_type ADD VALUE IF NOT EXISTS 'machine';

coderd/database/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/users.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -943,8 +943,8 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
943943
})
944944
}
945945

946-
// Creates a new session key, used for logging in via the CLI.
947-
func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
946+
// Creates a new machine session key that effectively doesn't expire.
947+
func (api *API) postMachineAPIKey(rw http.ResponseWriter, r *http.Request) {
948948
ctx := r.Context()
949949
user := httpmw.UserParam(r)
950950

@@ -953,7 +953,8 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
953953
return
954954
}
955955

956-
lifeTime := time.Hour * 24 * 7
956+
// machine keys last 100 years
957+
lifeTime := time.Hour * 876000
957958
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
958959
UserID: user.ID,
959960
LoginType: database.LoginTypePassword,

coderd/users_test.go

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -285,15 +285,14 @@ func TestPostLogin(t *testing.T) {
285285
require.NoError(t, err, "fetch login key")
286286
require.Equal(t, int64(86400), key.LifetimeSeconds, "default should be 86400")
287287

288-
// Generated tokens have a longer life
289-
token, err := client.CreateAPIKey(ctx, admin.UserID.String())
290-
require.NoError(t, err, "make new api key")
288+
// Machine tokens have a longer life
289+
token, err := client.CreateMachineKey(ctx)
290+
require.NoError(t, err, "make new machine api key")
291291
split = strings.Split(token.Key, "-")
292292
apiKey, err := client.GetAPIKey(ctx, admin.UserID.String(), split[0])
293293
require.NoError(t, err, "fetch api key")
294294

295-
require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*24*6)), "api key lasts more than 6 days")
296-
require.True(t, apiKey.ExpiresAt.After(key.ExpiresAt.Add(time.Hour)), "api key should be longer expires")
295+
require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*438300)), "api key lasts more than 50 years")
297296
require.Greater(t, apiKey.LifetimeSeconds, key.LifetimeSeconds, "api key should have longer lifetime")
298297
})
299298
}
@@ -1195,36 +1194,18 @@ func TestGetUsers(t *testing.T) {
11951194
})
11961195
}
11971196

1198-
func TestPostAPIKey(t *testing.T) {
1197+
func TestPostMachineKey(t *testing.T) {
11991198
t.Parallel()
1200-
t.Run("InvalidUser", func(t *testing.T) {
1201-
t.Parallel()
1202-
client := coderdtest.New(t, nil)
1203-
_ = coderdtest.CreateFirstUser(t, client)
1204-
1205-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1206-
defer cancel()
1207-
1208-
client.SessionToken = ""
1209-
_, err := client.CreateAPIKey(ctx, codersdk.Me)
1210-
var apiErr *codersdk.Error
1211-
require.ErrorAs(t, err, &apiErr)
1212-
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
1213-
})
1214-
1215-
t.Run("Success", func(t *testing.T) {
1216-
t.Parallel()
1217-
client := coderdtest.New(t, nil)
1218-
_ = coderdtest.CreateFirstUser(t, client)
1199+
client := coderdtest.New(t, nil)
1200+
_ = coderdtest.CreateFirstUser(t, client)
12191201

1220-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1221-
defer cancel()
1202+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1203+
defer cancel()
12221204

1223-
apiKey, err := client.CreateAPIKey(ctx, codersdk.Me)
1224-
require.NotNil(t, apiKey)
1225-
require.GreaterOrEqual(t, len(apiKey.Key), 2)
1226-
require.NoError(t, err)
1227-
})
1205+
apiKey, err := client.CreateMachineKey(ctx)
1206+
require.NotNil(t, apiKey)
1207+
require.GreaterOrEqual(t, len(apiKey.Key), 2)
1208+
require.NoError(t, err)
12281209
}
12291210

12301211
func TestWorkspacesByUser(t *testing.T) {

codersdk/api_key.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package codersdk
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
10+
"github.com/google/uuid"
11+
)
12+
13+
type APIKey struct {
14+
ID string `json:"id" validate:"required"`
15+
// NOTE: do not ever return the HashedSecret
16+
UserID uuid.UUID `json:"user_id" validate:"required"`
17+
LastUsed time.Time `json:"last_used" validate:"required"`
18+
ExpiresAt time.Time `json:"expires_at" validate:"required"`
19+
CreatedAt time.Time `json:"created_at" validate:"required"`
20+
UpdatedAt time.Time `json:"updated_at" validate:"required"`
21+
LoginType LoginType `json:"login_type" validate:"required"`
22+
LifetimeSeconds int64 `json:"lifetime_seconds" validate:"required"`
23+
}
24+
25+
// CreateMachineKey generates an API key that doesn't expire.
26+
func (c *Client) CreateMachineKey(ctx context.Context) (*GenerateAPIKeyResponse, error) {
27+
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys/machine", Me), nil)
28+
if err != nil {
29+
return nil, err
30+
}
31+
defer res.Body.Close()
32+
if res.StatusCode > http.StatusCreated {
33+
return nil, readBodyAsError(res)
34+
}
35+
apiKey := &GenerateAPIKeyResponse{}
36+
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
37+
}
38+
39+
func (c *Client) GetAPIKey(ctx context.Context, user string, id string) (*APIKey, error) {
40+
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/%s", user, id), nil)
41+
if err != nil {
42+
return nil, err
43+
}
44+
defer res.Body.Close()
45+
if res.StatusCode > http.StatusCreated {
46+
return nil, readBodyAsError(res)
47+
}
48+
apiKey := &APIKey{}
49+
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
50+
}

codersdk/users.go

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,6 @@ type User struct {
5555
AvatarURL string `json:"avatar_url"`
5656
}
5757

58-
type APIKey struct {
59-
ID string `json:"id" validate:"required"`
60-
// NOTE: do not ever return the HashedSecret
61-
UserID uuid.UUID `json:"user_id" validate:"required"`
62-
LastUsed time.Time `json:"last_used" validate:"required"`
63-
ExpiresAt time.Time `json:"expires_at" validate:"required"`
64-
CreatedAt time.Time `json:"created_at" validate:"required"`
65-
UpdatedAt time.Time `json:"updated_at" validate:"required"`
66-
LoginType LoginType `json:"login_type" validate:"required"`
67-
LifetimeSeconds int64 `json:"lifetime_seconds" validate:"required"`
68-
}
69-
7058
type CreateFirstUserRequest struct {
7159
Email string `json:"email" validate:"required,email"`
7260
Username string `json:"username" validate:"required,username"`
@@ -287,33 +275,6 @@ func (c *Client) GetUserRoles(ctx context.Context, user string) (UserRoles, erro
287275
return roles, json.NewDecoder(res.Body).Decode(&roles)
288276
}
289277

290-
// CreateAPIKey generates an API key for the user ID provided.
291-
func (c *Client) CreateAPIKey(ctx context.Context, user string) (*GenerateAPIKeyResponse, error) {
292-
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", user), nil)
293-
if err != nil {
294-
return nil, err
295-
}
296-
defer res.Body.Close()
297-
if res.StatusCode > http.StatusCreated {
298-
return nil, readBodyAsError(res)
299-
}
300-
apiKey := &GenerateAPIKeyResponse{}
301-
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
302-
}
303-
304-
func (c *Client) GetAPIKey(ctx context.Context, user string, id string) (*APIKey, error) {
305-
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/keys/%s", user, id), nil)
306-
if err != nil {
307-
return nil, err
308-
}
309-
defer res.Body.Close()
310-
if res.StatusCode > http.StatusCreated {
311-
return nil, readBodyAsError(res)
312-
}
313-
apiKey := &APIKey{}
314-
return apiKey, json.NewDecoder(res.Body).Decode(apiKey)
315-
}
316-
317278
// LoginWithPassword creates a session token authenticating with an email and password.
318279
// Call `SetSessionToken()` to apply the newly acquired token to the client.
319280
func (c *Client) LoginWithPassword(ctx context.Context, req LoginWithPasswordRequest) (LoginWithPasswordResponse, error) {

0 commit comments

Comments
 (0)