feat: trace httpapi.{Read,Write} (#4134)

This commit is contained in:
Colin Adler
2022-09-21 17:07:00 -05:00
committed by GitHub
parent 1bf2dc0cc3
commit 5de6f86959
45 changed files with 919 additions and 774 deletions
+133 -114
View File
@@ -37,9 +37,10 @@ import (
// Returns whether the initial user has been created or not.
func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) {
userCount, err := api.Database.GetUserCount(r.Context())
ctx := r.Context()
userCount, err := api.Database.GetUserCount(ctx)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user count.",
Detail: err.Error(),
})
@@ -47,28 +48,29 @@ func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) {
}
if userCount == 0 {
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: "The initial user has not been created!",
})
return
}
httpapi.Write(rw, http.StatusOK, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
Message: "The initial user has already been created!",
})
}
// Creates the initial user for a Coder deployment.
func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var createUser codersdk.CreateFirstUserRequest
if !httpapi.Read(rw, r, &createUser) {
if !httpapi.Read(ctx, rw, r, &createUser) {
return
}
// This should only function for the first user.
userCount, err := api.Database.GetUserCount(r.Context())
userCount, err := api.Database.GetUserCount(ctx)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user count.",
Detail: err.Error(),
})
@@ -77,13 +79,13 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
// If a user already exists, the initial admin user no longer can be created.
if userCount != 0 {
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
Message: "The initial user has already been created.",
})
return
}
user, organizationID, err := api.CreateUser(r.Context(), api.Database, CreateUserRequest{
user, organizationID, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
CreateUserRequest: codersdk.CreateUserRequest{
Email: createUser.Email,
Username: createUser.Username,
@@ -94,7 +96,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
LoginType: database.LoginTypePassword,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error creating user.",
Detail: err.Error(),
})
@@ -112,12 +114,12 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
// the user. Maybe I add this ability to grant roles in the createUser api
// and add some rbac bypass when calling api functions this way??
// Add the admin role to this first user.
_, err = api.Database.UpdateUserRoles(r.Context(), database.UpdateUserRolesParams{
_, err = api.Database.UpdateUserRoles(ctx, database.UpdateUserRolesParams{
GrantedRoles: []string{rbac.RoleOwner()},
ID: user.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user's roles.",
Detail: err.Error(),
})
@@ -128,7 +130,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
for _, template := range api.AutoImportTemplates {
archive, err := examples.Archive(string(template))
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error importing template.",
Detail: xerrors.Errorf("load template archive for %q: %w", template, err).Error(),
})
@@ -152,14 +154,14 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
}
default:
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error importing template.",
Detail: fmt.Sprintf("cannot auto-import %q template", template),
})
return
}
tpl, err := api.autoImportTemplate(r.Context(), autoImportTemplateOpts{
tpl, err := api.autoImportTemplate(ctx, autoImportTemplateOpts{
name: string(template),
archive: archive,
params: parameters,
@@ -167,28 +169,29 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
orgID: organizationID,
})
if err != nil {
api.Logger.Warn(r.Context(), "failed to auto-import template", slog.F("template", template), slog.F("parameters", parameters), slog.Error(err))
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
api.Logger.Warn(ctx, "failed to auto-import template", slog.F("template", template), slog.F("parameters", parameters), slog.Error(err))
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error importing template.",
Detail: xerrors.Errorf("failed to import template %q: %w", template, err).Error(),
})
return
}
api.Logger.Info(r.Context(), "auto-imported template", slog.F("id", tpl.ID), slog.F("template", template), slog.F("parameters", parameters))
api.Logger.Info(ctx, "auto-imported template", slog.F("id", tpl.ID), slog.F("template", template), slog.F("parameters", parameters))
}
httpapi.Write(rw, http.StatusCreated, codersdk.CreateFirstUserResponse{
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateFirstUserResponse{
UserID: user.ID,
OrganizationID: organizationID,
})
}
func (api *API) users(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
query := r.URL.Query().Get("q")
params, errs := userSearchQuery(query)
if len(errs) > 0 {
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid user search query.",
Validations: errs,
})
@@ -200,7 +203,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
return
}
users, err := api.Database.GetUsers(r.Context(), database.GetUsersParams{
users, err := api.Database.GetUsers(ctx, database.GetUsersParams{
AfterID: paginationParams.AfterID,
OffsetOpt: int32(paginationParams.Offset),
LimitOpt: int32(paginationParams.Limit),
@@ -209,11 +212,11 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
RbacRole: params.RbacRole,
})
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusOK, []codersdk.User{})
httpapi.Write(ctx, rw, http.StatusOK, []codersdk.User{})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching users.",
Detail: err.Error(),
})
@@ -222,7 +225,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
users, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, users)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching users.",
Detail: err.Error(),
})
@@ -233,12 +236,12 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
for _, user := range users {
userIDs = append(userIDs, user.ID)
}
organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(r.Context(), userIDs)
organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(ctx, userIDs)
if xerrors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's organizations.",
Detail: err.Error(),
})
@@ -255,6 +258,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
// Creates a new user.
func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
auditor := *api.Auditor.Load()
aReq, commitAudit := audit.InitRequest[database.User](rw, &audit.RequestParams{
Audit: auditor,
@@ -271,7 +275,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
}
var req codersdk.CreateUserRequest
if !httpapi.Read(rw, r, &req) {
if !httpapi.Read(ctx, rw, r, &req) {
return
}
@@ -284,45 +288,45 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
// TODO: @emyrk Authorize the organization create if the createUser will do that.
_, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
_, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
Username: req.Username,
Email: req.Email,
})
if err == nil {
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
Message: "User already exists.",
})
return
}
if !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user.",
Detail: err.Error(),
})
return
}
_, err = api.Database.GetOrganizationByID(r.Context(), req.OrganizationID)
_, err = api.Database.GetOrganizationByID(ctx, req.OrganizationID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", req.OrganizationID),
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching organization.",
Detail: err.Error(),
})
return
}
user, _, err := api.CreateUser(r.Context(), api.Database, CreateUserRequest{
user, _, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
CreateUserRequest: req,
LoginType: database.LoginTypePassword,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error creating user.",
Detail: err.Error(),
})
@@ -336,10 +340,11 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
Users: []telemetry.User{telemetry.ConvertUser(user)},
})
httpapi.Write(rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID}))
httpapi.Write(ctx, rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID}))
}
func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
auditor := *api.Auditor.Load()
user := httpmw.UserParam(r)
aReq, commitAudit := audit.InitRequest[database.User](rw, &audit.RequestParams{
@@ -356,29 +361,29 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
return
}
workspaces, err := api.Database.GetWorkspaces(r.Context(), database.GetWorkspacesParams{
workspaces, err := api.Database.GetWorkspaces(ctx, database.GetWorkspacesParams{
OwnerID: user.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching workspaces.",
Detail: err.Error(),
})
return
}
if len(workspaces) > 0 {
httpapi.Write(rw, http.StatusExpectationFailed, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusExpectationFailed, codersdk.Response{
Message: "You cannot delete a user that has workspaces. Delete their workspaces and try again!",
})
return
}
err = api.Database.UpdateUserDeletedByID(r.Context(), database.UpdateUserDeletedByIDParams{
err = api.Database.UpdateUserDeletedByID(ctx, database.UpdateUserDeletedByIDParams{
ID: user.ID,
Deleted: true,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error deleting user.",
Detail: err.Error(),
})
@@ -386,7 +391,7 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
}
user.Deleted = true
aReq.New = user
httpapi.Write(rw, http.StatusOK, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
Message: "User has been deleted!",
})
}
@@ -394,8 +399,9 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
// Returns the parameterized user requested. All validation
// is completed in the middleware for this route.
func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := httpmw.UserParam(r)
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
organizationIDs, err := userOrganizationIDs(ctx, api, user)
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceUser) {
httpapi.ResourceNotFound(rw)
@@ -403,18 +409,19 @@ func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's organizations.",
Detail: err.Error(),
})
return
}
httpapi.Write(rw, http.StatusOK, convertUser(user, organizationIDs))
httpapi.Write(ctx, rw, http.StatusOK, convertUser(user, organizationIDs))
}
func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
user = httpmw.UserParam(r)
auditor = *api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.User](rw, &audit.RequestParams{
@@ -433,10 +440,10 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
}
var params codersdk.UpdateUserProfileRequest
if !httpapi.Read(rw, r, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
return
}
existentUser, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
existentUser, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
Username: params.Username,
})
isDifferentUser := existentUser.ID != user.ID
@@ -449,21 +456,21 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
Detail: "this value is already in use and should be unique",
})
}
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
Message: "User already exists.",
Validations: responseErrors,
})
return
}
if !errors.Is(err, sql.ErrNoRows) && isDifferentUser {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user.",
Detail: err.Error(),
})
return
}
updatedUserProfile, err := api.Database.UpdateUserProfile(r.Context(), database.UpdateUserProfileParams{
updatedUserProfile, err := api.Database.UpdateUserProfile(ctx, database.UpdateUserProfileParams{
ID: user.ID,
Email: user.Email,
AvatarURL: user.AvatarURL,
@@ -473,28 +480,29 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
aReq.New = updatedUserProfile
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user.",
Detail: err.Error(),
})
return
}
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
organizationIDs, err := userOrganizationIDs(ctx, api, user)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's organizations.",
Detail: err.Error(),
})
return
}
httpapi.Write(rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs))
httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs))
}
func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseWriter, r *http.Request) {
return func(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
user = httpmw.UserParam(r)
apiKey = httpmw.APIKey(r)
auditor = *api.Auditor.Load()
@@ -520,26 +528,26 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
case user.ID == apiKey.UserID:
// Suspending yourself is not allowed, as you can lock yourself
// out of the system.
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "You cannot suspend yourself.",
})
return
case slice.Contains(user.RBACRoles, rbac.RoleOwner()):
// You may not suspend an owner
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("You cannot suspend a user with the %q role. You must remove the role first.", rbac.RoleOwner()),
})
return
}
}
suspendedUser, err := api.Database.UpdateUserStatus(r.Context(), database.UpdateUserStatusParams{
suspendedUser, err := api.Database.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
ID: user.ID,
Status: status,
UpdatedAt: database.Now(),
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: fmt.Sprintf("Internal error updating user's status to %q.", status),
Detail: err.Error(),
})
@@ -547,21 +555,22 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
}
aReq.New = suspendedUser
organizations, err := userOrganizationIDs(r.Context(), api, user)
organizations, err := userOrganizationIDs(ctx, api, user)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's organizations.",
Detail: err.Error(),
})
return
}
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations))
httpapi.Write(ctx, rw, http.StatusOK, convertUser(suspendedUser, organizations))
}
}
func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
user = httpmw.UserParam(r)
params codersdk.UpdateUserPasswordRequest
auditor = *api.Auditor.Load()
@@ -580,13 +589,13 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
return
}
if !httpapi.Read(rw, r, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
return
}
err := userpassword.Validate(params.Password)
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid password.",
Validations: []codersdk.ValidationError{
{
@@ -608,14 +617,14 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
// if they send something let's validate it
ok, err := userpassword.Compare(string(user.HashedPassword), params.OldPassword)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error with passwords.",
Detail: err.Error(),
})
return
}
if !ok {
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Old password is incorrect.",
Validations: []codersdk.ValidationError{
{
@@ -630,18 +639,18 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
hashedPassword, err := userpassword.Hash(params.Password)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error hashing new password.",
Detail: err.Error(),
})
return
}
err = api.Database.UpdateUserHashedPassword(r.Context(), database.UpdateUserHashedPasswordParams{
err = api.Database.UpdateUserHashedPassword(ctx, database.UpdateUserHashedPasswordParams{
ID: user.ID,
HashedPassword: []byte(hashedPassword),
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user's password.",
Detail: err.Error(),
})
@@ -652,10 +661,11 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
newUser.HashedPassword = []byte(hashedPassword)
aReq.New = newUser
httpapi.Write(rw, http.StatusNoContent, nil)
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
}
func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := httpmw.UserParam(r)
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceUserData.WithOwner(user.ID.String())) {
@@ -668,9 +678,9 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
OrganizationRoles: make(map[uuid.UUID][]string),
}
memberships, err := api.Database.GetOrganizationMembershipsByUserID(r.Context(), user.ID)
memberships, err := api.Database.GetOrganizationMembershipsByUserID(ctx, user.ID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's organization memberships.",
Detail: err.Error(),
})
@@ -680,7 +690,7 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
// Only include ones we can read from RBAC.
memberships, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, memberships)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching memberships.",
Detail: err.Error(),
})
@@ -694,11 +704,12 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
}
}
httpapi.Write(rw, http.StatusOK, resp)
httpapi.Write(ctx, rw, http.StatusOK, resp)
}
func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
// User is the user to modify.
user = httpmw.UserParam(r)
actorRoles = httpmw.UserAuthorization(r)
@@ -715,14 +726,14 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
aReq.Old = user
if apiKey.UserID == user.ID {
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "You cannot change your own roles.",
})
return
}
var params codersdk.UpdateRoles
if !httpapi.Read(rw, r, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
return
}
@@ -755,28 +766,28 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
}
}
updatedUser, err := api.updateSiteUserRoles(r.Context(), database.UpdateUserRolesParams{
updatedUser, err := api.updateSiteUserRoles(ctx, database.UpdateUserRolesParams{
GrantedRoles: params.Roles,
ID: user.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: err.Error(),
})
return
}
aReq.New = updatedUser
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
organizationIDs, err := userOrganizationIDs(ctx, api, user)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's organizations.",
Detail: err.Error(),
})
return
}
httpapi.Write(rw, http.StatusOK, convertUser(updatedUser, organizationIDs))
httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUser, organizationIDs))
}
// updateSiteUserRoles will ensure only site wide roles are passed in as arguments.
@@ -802,15 +813,16 @@ func (api *API) updateSiteUserRoles(ctx context.Context, args database.UpdateUse
// Returns organizations the parameterized user has access to.
func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := httpmw.UserParam(r)
organizations, err := api.Database.GetOrganizationsByUserID(r.Context(), user.ID)
organizations, err := api.Database.GetOrganizationsByUserID(ctx, user.ID)
if errors.Is(err, sql.ErrNoRows) {
err = nil
organizations = []database.Organization{}
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's organizations.",
Detail: err.Error(),
})
@@ -820,7 +832,7 @@ func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
// Only return orgs the user can read.
organizations, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, organizations)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching organizations.",
Detail: err.Error(),
})
@@ -832,18 +844,19 @@ func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
publicOrganizations = append(publicOrganizations, convertOrganization(organization))
}
httpapi.Write(rw, http.StatusOK, publicOrganizations)
httpapi.Write(ctx, rw, http.StatusOK, publicOrganizations)
}
func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organizationName := chi.URLParam(r, "organizationname")
organization, err := api.Database.GetOrganizationByName(r.Context(), organizationName)
organization, err := api.Database.GetOrganizationByName(ctx, organizationName)
if errors.Is(err, sql.ErrNoRows) {
httpapi.ResourceNotFound(rw)
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching organization.",
Detail: err.Error(),
})
@@ -857,21 +870,22 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques
return
}
httpapi.Write(rw, http.StatusOK, convertOrganization(organization))
httpapi.Write(ctx, rw, http.StatusOK, convertOrganization(organization))
}
// Authenticates the user with an email and password.
func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var loginWithPassword codersdk.LoginWithPasswordRequest
if !httpapi.Read(rw, r, &loginWithPassword) {
if !httpapi.Read(ctx, rw, r, &loginWithPassword) {
return
}
user, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
user, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
Email: loginWithPassword.Email,
})
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error.",
})
return
@@ -880,7 +894,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
// If the user doesn't exist, it will be a default struct.
equal, err := userpassword.Compare(string(user.HashedPassword), loginWithPassword.Password)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error.",
})
return
@@ -888,14 +902,14 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
if !equal {
// This message is the same as above to remove ease in detecting whether
// users are registered or not. Attackers still could with a timing attack.
httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
Message: "Incorrect email or password.",
})
return
}
if user.LoginType != database.LoginTypePassword {
httpapi.Write(rw, http.StatusForbidden, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Incorrect login type, attempting to use %q but user is of login type %q", database.LoginTypePassword, user.LoginType),
})
return
@@ -903,18 +917,19 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
// If the user logged into a suspended account, reject the login request.
if user.Status != database.UserStatusActive {
httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
Message: "Your account is suspended. Contact an admin to reactivate your account.",
})
return
}
cookie, err := api.createAPIKey(r, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to create API key.",
Detail: err.Error(),
})
@@ -923,13 +938,14 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
api.setAuthCookie(rw, cookie)
httpapi.Write(rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{
SessionToken: cookie.Value,
})
}
// Creates a new session key, used for logging in via the CLI.
func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := httpmw.UserParam(r)
if !api.Authorize(r, rbac.ActionCreate, rbac.ResourceAPIKey.WithOwner(user.ID.String())) {
@@ -938,16 +954,17 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
}
lifeTime := time.Hour * 24 * 7
cookie, err := api.createAPIKey(r, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
// All api generated keys will last 1 week. Browser login tokens have
// a shorter life.
ExpiresAt: database.Now().Add(lifeTime),
LifetimeSeconds: int64(lifeTime.Seconds()),
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to create API key.",
Detail: err.Error(),
})
@@ -958,7 +975,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
// Setting the cookie will couple the browser sesion to the API
// key we return here, meaning logging out of the website would
// invalid your CLI key.
httpapi.Write(rw, http.StatusCreated, codersdk.GenerateAPIKeyResponse{Key: cookie.Value})
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.GenerateAPIKeyResponse{Key: cookie.Value})
}
func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) {
@@ -979,18 +996,19 @@ func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) {
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching API key.",
Detail: err.Error(),
})
return
}
httpapi.Write(rw, http.StatusOK, convertAPIKey(key))
httpapi.Write(ctx, rw, http.StatusOK, convertAPIKey(key))
}
// Clear the user's session cookie.
func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Get a blank token cookie.
cookie := &http.Cookie{
// MaxAge < 0 means to delete the cookie now.
@@ -1002,16 +1020,16 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
// Delete the session token from database.
apiKey := httpmw.APIKey(r)
err := api.Database.DeleteAPIKeyByID(r.Context(), apiKey.ID)
err := api.Database.DeleteAPIKeyByID(ctx, apiKey.ID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error deleting API key.",
Detail: err.Error(),
})
return
}
httpapi.Write(rw, http.StatusOK, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
Message: "Logged out!",
})
}
@@ -1032,15 +1050,16 @@ func generateAPIKeyIDSecret() (id string, secret string, err error) {
}
type createAPIKeyParams struct {
UserID uuid.UUID
LoginType database.LoginType
UserID uuid.UUID
RemoteAddr string
LoginType database.LoginType
// Optional.
ExpiresAt time.Time
LifetimeSeconds int64
}
func (api *API) createAPIKey(r *http.Request, params createAPIKeyParams) (*http.Cookie, error) {
func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*http.Cookie, error) {
keyID, keySecret, err := generateAPIKeyIDSecret()
if err != nil {
return nil, xerrors.Errorf("generate API key: %w", err)
@@ -1056,13 +1075,13 @@ func (api *API) createAPIKey(r *http.Request, params createAPIKeyParams) (*http.
}
}
host, _, _ := net.SplitHostPort(r.RemoteAddr)
host, _, _ := net.SplitHostPort(params.RemoteAddr)
ip := net.ParseIP(host)
if ip == nil {
ip = net.IPv4(0, 0, 0, 0)
}
bitlen := len(ip) * 8
key, err := api.Database.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{
key, err := api.Database.InsertAPIKey(ctx, database.InsertAPIKeyParams{
ID: keyID,
UserID: params.UserID,
LifetimeSeconds: params.LifetimeSeconds,