mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: trace httpapi.{Read,Write} (#4134)
This commit is contained in:
+133
-114
@@ -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, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
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, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
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, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user