Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
305f696
fix: use unique ID for linked accounts
sreya Aug 9, 2022
b4ab301
fixup a bunch of stuff
sreya Aug 9, 2022
dd2df9c
gofmt
sreya Aug 9, 2022
0356f46
make fake db happy
sreya Aug 9, 2022
6b1b900
make audit happy
sreya Aug 9, 2022
8f63d5c
fix some tests
sreya Aug 10, 2022
de7db33
make gen
sreya Aug 10, 2022
5fdf899
fix tests
sreya Aug 10, 2022
3a4d049
fmt
sreya Aug 10, 2022
4108ece
begin refactoring PR
sreya Aug 11, 2022
14b5382
finish migration
sreya Aug 12, 2022
8553501
use main sql.dump
sreya Aug 12, 2022
f748d3d
lift error
sreya Aug 12, 2022
c1b9871
new migration
sreya Aug 12, 2022
e41c103
more rewriting
sreya Aug 12, 2022
bb9b777
even more rewriting
sreya Aug 12, 2022
d940dae
finish up some test fixing
sreya Aug 12, 2022
c97d572
typos
sreya Aug 12, 2022
10bfe77
Merge branch 'main' into jon/userauth
sreya Aug 12, 2022
28a37f1
fix some remaining tests
sreya Aug 12, 2022
c889bf0
fix a gnarly bug
sreya Aug 12, 2022
0196a49
add a down migration
sreya Aug 12, 2022
b5dc95b
add fkey on user_links, fix tests, add comments
sreya Aug 12, 2022
f2f76e9
add login_type to users table
sreya Aug 12, 2022
940ced4
Merge branch 'main' into jon/userauth
sreya Aug 12, 2022
eb266db
fix login_type query
sreya Aug 13, 2022
4671bf6
fix tests
sreya Aug 13, 2022
c41f4e6
fix audit
sreya Aug 13, 2022
f3d8392
fix down
sreya Aug 13, 2022
cc8400b
fix one more test
sreya Aug 13, 2022
5c7cbae
Merge branch 'main' into jon/userauth
sreya Aug 17, 2022
083d256
pr comments
sreya Aug 17, 2022
92c185d
fix conflicting migration file
sreya Aug 17, 2022
05595d8
generate.sh
sreya Aug 17, 2022
aa90148
butcher the english language to appease colin
sreya Aug 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
finish up some test fixing
  • Loading branch information
sreya committed Aug 12, 2022
commit d940daedb37e4b26fc08c176cfe421329a07d031
2 changes: 0 additions & 2 deletions coderd/audit/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ var AuditableResources = auditMap(map[any]map[string]Action{
"updated_at": ActionIgnore, // Changes, but is implicit and not helpful in a diff.
"status": ActionTrack,
"rbac_roles": ActionTrack,
"login_type": ActionTrack,
"linked_id": ActionTrack,
},
&database.Workspace{}: {
"id": ActionTrack,
Expand Down
15 changes: 8 additions & 7 deletions coderd/database/databasefake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -2278,17 +2278,18 @@ func (q *fakeQuerier) GetUserLinkByUserIDLoginType(_ context.Context, params dat
return database.UserLink{}, sql.ErrNoRows
}

func (q *fakeQuerier) InsertUserLink(_ context.Context, params database.InsertUserLinkParams) (database.UserLink, error) {
func (q *fakeQuerier) InsertUserLink(_ context.Context, args database.InsertUserLinkParams) (database.UserLink, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()

//nolint:gosimple
link := database.UserLink{
UserID: params.UserID,
LoginType: params.LoginType,
LinkedID: params.LinkedID,
OAuthAccessToken: params.OAuthAccessToken,
OAuthRefreshToken: params.OAuthRefreshToken,
OAuthExpiry: params.OAuthExpiry,
UserID: args.UserID,
LoginType: args.LoginType,
LinkedID: args.LinkedID,
OAuthAccessToken: args.OAuthAccessToken,
OAuthRefreshToken: args.OAuthRefreshToken,
OAuthExpiry: args.OAuthExpiry,
}

q.userLinks = append(q.userLinks, link)
Expand Down
10 changes: 10 additions & 0 deletions coderd/database/migrations/000034_linked_user_id.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ CREATE TABLE IF NOT EXISTS user_links (
UNIQUE(user_id, login_type)
);

-- This migrates columns on api_keys to the new user_links table.
-- It does this by finding all the API keys for each user, choosing
-- the most recently updated for each one and then assigning its relevant
-- values to the user_links table.
-- A user should at most have a row for an OIDC account and a Github account.
-- 'password' login types are ignored.

INSERT INTO user_links
(
user_id,
Expand All @@ -34,6 +41,9 @@ FROM
) as keys
WHERE x=1 AND keys.login_type != 'password';

-- Drop columns that have been migrated to user_links.
-- It appears the 'oauth_id_token' was unused and so it has
-- been dropped here as well to avoid future confusion.
ALTER TABLE api_keys
DROP COLUMN oauth_access_token,
DROP COLUMN oauth_refresh_token,
Expand Down
25 changes: 11 additions & 14 deletions coderd/database/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,27 @@ func Open() (string, func(), error) {
return "", nil, xerrors.Errorf("expire resource: %w", err)
}

pool.MaxWait = 15 * time.Second
var retryErr error
pool.MaxWait = 120 * time.Second
err = pool.Retry(func() error {
var db *sql.DB
db, retryErr := sql.Open("postgres", dbURL)
if retryErr != nil {
return xerrors.Errorf("open postgres: %w", retryErr)
db, err := sql.Open("postgres", dbURL)
if err != nil {
return xerrors.Errorf("open postgres: %w", err)
}
defer db.Close()

retryErr = db.Ping()
if retryErr != nil {
return xerrors.Errorf("ping postgres: %w", retryErr)
err = db.Ping()
if err != nil {
return xerrors.Errorf("ping postgres: %w", err)
}
retryErr = database.MigrateUp(db)
if retryErr != nil {
fmt.Printf("err: %v\n", retryErr)
return xerrors.Errorf("migrate db: %w", retryErr)
err = database.MigrateUp(db)
if err != nil {
return xerrors.Errorf("migrate db: %w", err)
}

return nil
})
if err != nil {
return "", nil, retryErr
return "", nil, err
}
return dbURL, func() {
_ = pool.Purge(resource)
Expand Down
3 changes: 2 additions & 1 deletion coderd/httpmw/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
if err != nil {
write(http.StatusInternalServerError, codersdk.Response{
Message: "A database error occurred",
Detail: err.Error(),
Detail: fmt.Sprintf("get user link by user ID and login type: %s", err.Error()),
})
return
}
Expand Down Expand Up @@ -250,6 +250,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
if link.UserID != uuid.Nil {
link, err = db.UpdateUserLink(r.Context(), database.UpdateUserLinkParams{
UserID: link.UserID,
LoginType: link.LoginType,
OAuthAccessToken: link.OAuthAccessToken,
OAuthRefreshToken: link.OAuthRefreshToken,
OAuthExpiry: link.OAuthExpiry,
Expand Down
18 changes: 12 additions & 6 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
user, link, err := findLinkedUser(ctx, api.Database, githubLinkedID(ghUser), verifiedEmails...)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to find user.",
Message: "An internal error occurred.",
Detail: err.Error(),
})
return
}
Expand Down Expand Up @@ -195,7 +196,6 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
})
return
}

}

// This can happen if a user is a built-in user but is signing in
Expand All @@ -220,7 +220,9 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {

// LEGACY: Remove 10/2022.
// We started tracking linked IDs later so it's possible for a user to be a
// pre-existing Github user and not have a linked ID.
// pre-existing Github user and not have a linked ID. The migration
// to user_links did not populate this field as it requires calling out
// to Github to query the user's ID.
if link.LinkedID == "" {
link, err = api.Database.UpdateUserLinkedID(ctx, database.UpdateUserLinkedIDParams{
UserID: user.ID,
Expand Down Expand Up @@ -387,7 +389,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
if link.UserID == uuid.Nil {
link, err = api.Database.InsertUserLink(ctx, database.InsertUserLinkParams{
UserID: user.ID,
LoginType: database.LoginTypeGithub,
LoginType: database.LoginTypeOIDC,
LinkedID: oidcLinkedID(idToken),
OAuthAccessToken: state.Token.AccessToken,
OAuthRefreshToken: state.Token.RefreshToken,
Expand All @@ -400,12 +402,13 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
})
return
}

}

// LEGACY: Remove 10/2022.
// We started tracking linked IDs later so it's possible for a user to be a
// pre-existing OIDC user and not have a linked ID.
// The migration that added the user_links table could not populate
// the 'linked_id' field since it requires fields off the access token.
if link.LinkedID == "" {
link, err = api.Database.UpdateUserLinkedID(ctx, database.UpdateUserLinkedIDParams{
UserID: user.ID,
Expand Down Expand Up @@ -440,7 +443,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to update user profile.",
Detail: err.Error(),
Detail: fmt.Sprintf("udpate user profile: %s", err.Error()),
})
return
}
Expand Down Expand Up @@ -486,6 +489,9 @@ func findLinkedUser(ctx context.Context, db database.Store, linkedID string, ema

if err == nil {
user, err = db.GetUserByID(ctx, link.UserID)
if err != nil {
return database.User{}, database.UserLink{}, xerrors.Errorf("get user by id: %w", err)
}
return user, link, nil
}

Expand Down
59 changes: 28 additions & 31 deletions coderd/userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,34 @@ func TestUserOAuth2Github(t *testing.T) {
resp := oauth2Callback(t, client)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("Login", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
GithubOAuth2Config: &coderd.GithubOAuth2Config{
OAuth2Config: &oauth2Config{},
AllowOrganizations: []string{"coder"},
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
return []*github.Membership{{
Organization: &github.Organization{
Login: github.String("coder"),
},
}}, nil
},
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
return &github.User{}, nil
},
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
return []*github.UserEmail{{
Email: github.String("testuser@coder.com"),
Verified: github.Bool(true),
}}, nil
},
},
})
_ = coderdtest.CreateFirstUser(t, client)
resp := oauth2Callback(t, client)
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
})
t.Run("Signup", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
Expand Down Expand Up @@ -210,7 +238,6 @@ func TestUserOAuth2Github(t *testing.T) {
client.SessionToken = resp.Cookies()[0].Value
user, err := client.User(context.Background(), "me")
require.NoError(t, err)
require.Equal(t, "1234", user.LinkedID)
require.Equal(t, "kyle@coder.com", user.Email)
require.Equal(t, "kyle", user.Username)
})
Expand Down Expand Up @@ -341,7 +368,6 @@ func TestUserOIDC(t *testing.T) {
user, err := client.User(ctx, "me")
require.NoError(t, err)
require.Equal(t, tc.Username, user.Username)
require.Equal(t, "https://coder.com||hello", user.LinkedID)
}
})
}
Expand Down Expand Up @@ -385,35 +411,6 @@ func TestUserOIDC(t *testing.T) {
resp := oidcCallback(t, client)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
})

// Test that we do not allow collisions with pre-existing accounts
// of differing login types.
t.Run("InvalidLoginType", func(t *testing.T) {
t.Parallel()
config := createOIDCConfig(t, jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": true,
"preferred_username": "kyle",
})

client := coderdtest.New(t, &coderdtest.Options{
OIDCConfig: config,
})

_, err := client.CreateFirstUser(context.Background(), codersdk.CreateFirstUserRequest{
Email: "kyle@kwc.io",
Username: "kyle",
Password: "yeah",
OrganizationName: "default",
})
require.NoError(t, err)

config.AllowSignups = true
config.EmailDomain = "kwc.io"

resp := oidcCallback(t, client)
assert.Equal(t, http.StatusConflict, resp.StatusCode)
})
}

// createOIDCConfig generates a new OIDCConfig that returns a static token
Expand Down
2 changes: 0 additions & 2 deletions codersdk/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ type User struct {
Status UserStatus `json:"status"`
OrganizationIDs []uuid.UUID `json:"organization_ids"`
Roles []Role `json:"roles"`
LoginType LoginType `json:"login_type"`
LinkedID string `json:"linked_id"`
}

type APIKey struct {
Expand Down
2 changes: 0 additions & 2 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,6 @@ export interface User {
readonly status: UserStatus
readonly organization_ids: string[]
readonly roles: Role[]
readonly login_type: LoginType
readonly linked_id: string
}

// From codersdk/users.go
Expand Down