Skip to content

Commit 01e3aac

Browse files
committed
feat: Allow using username in user queries
1 parent a3decc4 commit 01e3aac

File tree

3 files changed

+89
-21
lines changed

3 files changed

+89
-21
lines changed

coderd/httpmw/userparam.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,60 @@ func UserParam(r *http.Request) database.User {
2727
func ExtractUserParam(db database.Store) func(http.Handler) http.Handler {
2828
return func(next http.Handler) http.Handler {
2929
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
30-
var userID uuid.UUID
31-
if chi.URLParam(r, "user") == "me" {
32-
userID = APIKey(r).UserID
30+
var user database.User
31+
var err error
32+
33+
// userQuery is either a uuid, a username, or 'me'
34+
userQuery := chi.URLParam(r, "user")
35+
if userQuery == "" {
36+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
37+
Message: fmt.Sprintf("%q must be provided", "user"),
38+
})
39+
return
40+
}
41+
42+
if userQuery == "me" {
43+
user, err = db.GetUserByID(r.Context(), APIKey(r).UserID)
44+
if err != nil {
45+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
46+
Message: fmt.Sprintf("get user: %s", err.Error()),
47+
})
48+
return
49+
}
50+
} else if userID, err := uuid.Parse(userQuery); err == nil {
51+
// If the userQuery is a valid uuid
52+
user, err = db.GetUserByID(r.Context(), userID)
53+
if err != nil {
54+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
55+
Message: fmt.Sprintf("get user: %s", err.Error()),
56+
})
57+
return
58+
}
3359
} else {
34-
var ok bool
35-
userID, ok = parseUUID(rw, r, "user")
36-
if !ok {
60+
// Try as a username last
61+
user, err = db.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
62+
Username: userQuery,
63+
})
64+
if err != nil {
65+
// If the error is no rows, they might have inputted something
66+
// that is not a username or uuid. Regardless, let's not indicate if
67+
// the user exists or not. Just lump all these errors into
68+
// something generic.
69+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
70+
Message: fmt.Sprint("\"user\" must be a uuid or username"),
71+
})
3772
return
3873
}
3974
}
4075

4176
apiKey := APIKey(r)
42-
if apiKey.UserID != userID {
77+
if apiKey.UserID != user.ID {
4378
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
4479
Message: "getting non-personal users isn't supported yet",
4580
})
4681
return
4782
}
4883

49-
user, err := db.GetUserByID(r.Context(), userID)
50-
if err != nil {
51-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
52-
Message: fmt.Sprintf("get user: %s", err.Error()),
53-
})
54-
}
55-
5684
ctx := context.WithValue(r.Context(), userParamContextKey{}, user)
5785
next.ServeHTTP(rw, r.WithContext(ctx))
5886
})

coderd/users_test.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -431,14 +431,45 @@ func TestPutUserSuspend(t *testing.T) {
431431
})
432432
}
433433

434-
func TestUserByName(t *testing.T) {
434+
func TestGetUser(t *testing.T) {
435435
t.Parallel()
436-
client := coderdtest.New(t, nil)
437-
firstUser := coderdtest.CreateFirstUser(t, client)
438-
user, err := client.User(context.Background(), codersdk.Me)
439436

440-
require.NoError(t, err)
441-
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
437+
t.Run("ByMe", func(t *testing.T) {
438+
t.Parallel()
439+
440+
client := coderdtest.New(t, nil)
441+
firstUser := coderdtest.CreateFirstUser(t, client)
442+
443+
user, err := client.User(context.Background(), codersdk.Me)
444+
require.NoError(t, err)
445+
require.Equal(t, firstUser.UserID, user.ID)
446+
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
447+
})
448+
449+
t.Run("ByID", func(t *testing.T) {
450+
t.Parallel()
451+
452+
client := coderdtest.New(t, nil)
453+
firstUser := coderdtest.CreateFirstUser(t, client)
454+
455+
user, err := client.User(context.Background(), firstUser.UserID)
456+
require.NoError(t, err)
457+
require.Equal(t, firstUser.UserID, user.ID)
458+
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
459+
})
460+
461+
t.Run("ByUsername", func(t *testing.T) {
462+
t.Parallel()
463+
464+
client := coderdtest.New(t, nil)
465+
firstUser := coderdtest.CreateFirstUser(t, client)
466+
exp, err := client.User(context.Background(), firstUser.UserID)
467+
require.NoError(t, err)
468+
469+
user, err := client.UserByUsername(context.Background(), exp.Username)
470+
require.NoError(t, err)
471+
require.Equal(t, exp, user)
472+
})
442473
}
443474

444475
func TestGetUsers(t *testing.T) {

codersdk/users.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,16 @@ func (c *Client) Logout(ctx context.Context) error {
274274
// User returns a user for the ID provided.
275275
// If the uuid is nil, the current user will be returned.
276276
func (c *Client) User(ctx context.Context, id uuid.UUID) (User, error) {
277-
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", uuidOrMe(id)), nil)
277+
return c.userByIdentifier(ctx, uuidOrMe(id))
278+
}
279+
280+
// UserByUsername returns a user for the username provided.
281+
func (c *Client) UserByUsername(ctx context.Context, username string) (User, error) {
282+
return c.userByIdentifier(ctx, username)
283+
}
284+
285+
func (c *Client) userByIdentifier(ctx context.Context, ident string) (User, error) {
286+
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", ident), nil)
278287
if err != nil {
279288
return User{}, err
280289
}

0 commit comments

Comments
 (0)