Skip to content

Commit d9b404d

Browse files
committed
feat: end-to-end workspace application authentication
1 parent b5d2be3 commit d9b404d

File tree

6 files changed

+426
-116
lines changed

6 files changed

+426
-116
lines changed

coderd/httpmw/apikey.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func ExtractAPIKey(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
154154
return
155155
}
156156

157-
keyID, keySecret, err := splitAPIToken(token)
157+
keyID, keySecret, err := SplitAPIToken(token)
158158
if err != nil {
159159
optionalWrite(http.StatusUnauthorized, codersdk.Response{
160160
Message: signedOutErrorMessage,
@@ -377,11 +377,11 @@ func apiTokenFromRequest(r *http.Request) string {
377377
return ""
378378
}
379379

380-
// splitAPIToken verifies the format of an API key and returns the split ID and
380+
// SplitAPIToken verifies the format of an API key and returns the split ID and
381381
// secret.
382382
//
383383
// APIKeys are formatted: ${ID}-${SECRET}
384-
func splitAPIToken(token string) (id string, secret string, err error) {
384+
func SplitAPIToken(token string) (id string, secret string, err error) {
385385
parts := strings.Split(token, "-")
386386
if len(parts) != 2 {
387387
return "", "", xerrors.Errorf("incorrect amount of API key parts, expected 2 got %d", len(parts))

coderd/userauth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
162162
return
163163
}
164164

165-
api.setAuthCookie(rw, cookie)
165+
http.SetCookie(rw, cookie)
166166

167167
redirect := state.Redirect
168168
if redirect == "" {
@@ -296,7 +296,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
296296
return
297297
}
298298

299-
api.setAuthCookie(rw, cookie)
299+
http.SetCookie(rw, cookie)
300300

301301
redirect := state.Redirect
302302
if redirect == "" {

coderd/users.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
921921
return
922922
}
923923

924-
api.setAuthCookie(rw, cookie)
924+
http.SetCookie(rw, cookie)
925925

926926
httpapi.Write(rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{
927927
SessionToken: cookie.Value,
@@ -998,7 +998,7 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
998998
Name: codersdk.SessionTokenKey,
999999
Path: "/",
10001000
}
1001-
api.setAuthCookie(rw, cookie)
1001+
http.SetCookie(rw, cookie)
10021002

10031003
// Delete the session token from database.
10041004
apiKey := httpmw.APIKey(r)
@@ -1186,15 +1186,6 @@ func (api *API) createUser(ctx context.Context, store database.Store, req create
11861186
})
11871187
}
11881188

1189-
func (api *API) setAuthCookie(rw http.ResponseWriter, cookie *http.Cookie) {
1190-
http.SetCookie(rw, cookie)
1191-
1192-
appCookie := api.applicationCookie(cookie)
1193-
if appCookie != nil {
1194-
http.SetCookie(rw, appCookie)
1195-
}
1196-
}
1197-
11981189
func convertUser(user database.User, organizationIDs []uuid.UUID) codersdk.User {
11991190
convertedUser := codersdk.User{
12001191
ID: user.ID,
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package coderd
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/coderd/database"
12+
"github.com/coder/coder/coderd/database/databasefake"
13+
"github.com/coder/coder/testutil"
14+
)
15+
16+
func TestAPIKeyEncryption(t *testing.T) {
17+
t.Parallel()
18+
19+
generateAPIKey := func(t *testing.T, db database.Store) (keyID, keySecret string, hashedSecret []byte, data encryptedAPIKeyPayload) {
20+
keyID, keySecret, err := generateAPIKeyIDSecret()
21+
require.NoError(t, err)
22+
23+
hashedSecretArray := sha256.Sum256([]byte(keySecret))
24+
data = encryptedAPIKeyPayload{
25+
APIKey: keyID + "-" + keySecret,
26+
ExpiresAt: database.Now().Add(24 * time.Hour),
27+
}
28+
29+
return keyID, keySecret, hashedSecretArray[:], data
30+
}
31+
insertAPIKey := func(t *testing.T, db database.Store, keyID string, hashedSecret []byte) {
32+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
33+
defer cancel()
34+
35+
_, err := db.InsertAPIKey(ctx, database.InsertAPIKeyParams{
36+
ID: keyID,
37+
HashedSecret: hashedSecret,
38+
})
39+
require.NoError(t, err)
40+
}
41+
42+
t.Run("OK", func(t *testing.T) {
43+
t.Parallel()
44+
db := databasefake.New()
45+
keyID, _, hashedSecret, data := generateAPIKey(t, db)
46+
insertAPIKey(t, db, keyID, hashedSecret)
47+
48+
encrypted, err := encryptAPIKey(data)
49+
require.NoError(t, err)
50+
51+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
52+
defer cancel()
53+
54+
key, token, err := decryptAPIKey(ctx, db, encrypted)
55+
require.NoError(t, err)
56+
require.Equal(t, keyID, key.ID)
57+
require.Equal(t, hashedSecret[:], key.HashedSecret)
58+
require.Equal(t, data.APIKey, token)
59+
})
60+
61+
t.Run("Verifies", func(t *testing.T) {
62+
t.Parallel()
63+
64+
t.Run("Expiry", func(t *testing.T) {
65+
t.Parallel()
66+
db := databasefake.New()
67+
keyID, _, hashedSecret, data := generateAPIKey(t, db)
68+
insertAPIKey(t, db, keyID, hashedSecret)
69+
70+
data.ExpiresAt = database.Now().Add(-1 * time.Hour)
71+
encrypted, err := encryptAPIKey(data)
72+
require.NoError(t, err)
73+
74+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
75+
defer cancel()
76+
77+
_, _, err = decryptAPIKey(ctx, db, encrypted)
78+
require.Error(t, err)
79+
require.ErrorContains(t, err, "expired")
80+
})
81+
82+
t.Run("KeyMatches", func(t *testing.T) {
83+
t.Parallel()
84+
db := databasefake.New()
85+
keyID, _, _, data := generateAPIKey(t, db)
86+
hashedSecret := sha256.Sum256([]byte("wrong"))
87+
insertAPIKey(t, db, keyID, hashedSecret[:])
88+
89+
encrypted, err := encryptAPIKey(data)
90+
require.NoError(t, err)
91+
92+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
93+
defer cancel()
94+
95+
_, _, err = decryptAPIKey(ctx, db, encrypted)
96+
require.Error(t, err)
97+
require.ErrorContains(t, err, "error in crypto")
98+
})
99+
})
100+
}

0 commit comments

Comments
 (0)