diff --git a/Taskfile.yml b/Taskfile.yml index 860ebb4..a3dbd81 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -2,14 +2,14 @@ version: "3" env: HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1 - + UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure" tasks: setup: desc: Install dependencies cmds: - - go install github.com/swaggo/swag/cmd/swag@latest - - cd backend && go mod tidy - - cd frontend && pnpm install --shamefully-hoist + - go install github.com/swaggo/swag/cmd/swag@latest + - cd backend && go mod tidy + - cd frontend && pnpm install --shamefully-hoist generate: desc: | Generates collateral files from the backend project diff --git a/backend/app/api/docs/docs.go b/backend/app/api/docs/docs.go index 707a3c4..2b6483d 100644 --- a/backend/app/api/docs/docs.go +++ b/backend/app/api/docs/docs.go @@ -1135,27 +1135,6 @@ const docTemplate = `{ } } } - }, - "/v1/users/self/password": { - "put": { - "security": [ - { - "Bearer": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "User" - ], - "summary": "Update the current user's password // TODO:", - "responses": { - "204": { - "description": "No Content" - } - } - } } }, "definitions": { diff --git a/backend/app/api/docs/swagger.json b/backend/app/api/docs/swagger.json index bfa404d..9aab46d 100644 --- a/backend/app/api/docs/swagger.json +++ b/backend/app/api/docs/swagger.json @@ -1127,27 +1127,6 @@ } } } - }, - "/v1/users/self/password": { - "put": { - "security": [ - { - "Bearer": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "User" - ], - "summary": "Update the current user's password // TODO:", - "responses": { - "204": { - "description": "No Content" - } - } - } } }, "definitions": { diff --git a/backend/app/api/docs/swagger.yaml b/backend/app/api/docs/swagger.yaml index 8ed1389..3892c77 100644 --- a/backend/app/api/docs/swagger.yaml +++ b/backend/app/api/docs/swagger.yaml @@ -1138,18 +1138,6 @@ paths: summary: Update the current user tags: - User - /v1/users/self/password: - put: - produces: - - application/json - responses: - "204": - description: No Content - security: - - Bearer: [] - summary: 'Update the current user''s password // TODO:' - tags: - - User securityDefinitions: Bearer: description: '"Type ''Bearer TOKEN'' to correctly set the API Key"' diff --git a/backend/app/api/middleware.go b/backend/app/api/middleware.go index ed92ff5..7db65aa 100644 --- a/backend/app/api/middleware.go +++ b/backend/app/api/middleware.go @@ -63,22 +63,6 @@ func (a *app) mwAuthToken(next http.Handler) http.Handler { }) } -// mwAdminOnly is a middleware that extends the mwAuthToken middleware to only allow -// requests from superusers. -// func (a *app) mwAdminOnly(next http.Handler) http.Handler { -// mw := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// usr := services.UseUserCtx(r.Context()) - -// if !usr.IsSuperuser { -// server.RespondUnauthorized(w) -// return -// } - -// next.ServeHTTP(w, r) -// }) -// return a.mwAuthToken(mw) -// } - // mqStripTrailingSlash is a middleware that will strip trailing slashes from the request path. func mwStripTrailingSlash(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -111,7 +95,6 @@ func (a *app) mwStructLogger(next http.Handler) http.Handler { log.Info(). Str("id", middleware.GetReqID(r.Context())). - Str("url", url). Str("method", r.Method). Str("remote_addr", r.RemoteAddr). Int("status", record.Status). diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 813ec26..b0a1f1c 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -65,7 +65,6 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux { r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf()) r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate()) r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete()) - r.Put(v1Base("/users/self/password"), v1Ctrl.HandleUserUpdatePassword()) r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout()) r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh()) r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword()) diff --git a/backend/app/api/v1/controller.go b/backend/app/api/v1/controller.go index 349daf5..ed1f3f7 100644 --- a/backend/app/api/v1/controller.go +++ b/backend/app/api/v1/controller.go @@ -33,6 +33,8 @@ type V1Controller struct { } type ( + ReadyFunc func() bool + Build struct { Version string `json:"version"` Commit string `json:"commit"` @@ -50,12 +52,9 @@ type ( ) func BaseUrlFunc(prefix string) func(s string) string { - v1Base := prefix + "/v1" - prefixFunc := func(s string) string { - return v1Base + s + return func(s string) string { + return prefix + "/v1" + s } - - return prefixFunc } func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller { @@ -71,8 +70,6 @@ func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) return ctrl } -type ReadyFunc func() bool - // HandleBase godoc // @Summary Retrieves the basic information about the API // @Tags Base diff --git a/backend/app/api/v1/partials.go b/backend/app/api/v1/partials.go index f655ed1..47249e6 100644 --- a/backend/app/api/v1/partials.go +++ b/backend/app/api/v1/partials.go @@ -5,30 +5,17 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/uuid" - "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/services" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) -/* -This is where we put partial snippets/functions for actions that are commonly -used within the controller class. This _hopefully_ helps with code duplication -and makes it a little more consistent when error handling and logging. -*/ - -// partialParseIdAndUser parses the ID from the requests URL and pulls the user -// from the context. If either of these fail, it will return an error. When an error -// occurs it will also write the error to the response. As such, if an error is returned -// from this function you can return immediately without writing to the response. -func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *repo.UserOut, error) { - uid, err := uuid.Parse(chi.URLParam(r, "id")) +func (ctrl *V1Controller) routeID(w http.ResponseWriter, r *http.Request) (uuid.UUID, error) { + ID, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { log.Err(err).Msg("failed to parse id") server.RespondError(w, http.StatusBadRequest, err) - return uuid.Nil, &repo.UserOut{}, err + return uuid.Nil, err } - user := services.UseUserCtx(r.Context()) - return uid, user, nil + return ID, nil } diff --git a/backend/app/api/v1/v1_ctrl_group.go b/backend/app/api/v1/v1_ctrl_group.go index e8bd796..fe87379 100644 --- a/backend/app/api/v1/v1_ctrl_group.go +++ b/backend/app/api/v1/v1_ctrl_group.go @@ -32,20 +32,7 @@ type ( // @Router /v1/groups [Get] // @Security Bearer func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx := services.NewContext(r.Context()) - - group, err := ctrl.svc.Group.Get(ctx) - if err != nil { - log.Err(err).Msg("failed to get group") - server.RespondError(w, http.StatusInternalServerError, err) - return - } - group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower caseÍ - - server.Respond(w, http.StatusOK, group) - - } + return ctrl.handleGroupGeneral() } // HandleGroupUpdate godoc @@ -57,24 +44,41 @@ func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc { // @Router /v1/groups [Put] // @Security Bearer func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc { + return ctrl.handleGroupGeneral() +} + +func (ctrl *V1Controller) handleGroupGeneral() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - data := repo.GroupUpdate{} - - if err := server.Decode(r, &data); err != nil { - server.RespondError(w, http.StatusBadRequest, err) - return - } - ctx := services.NewContext(r.Context()) - group, err := ctrl.svc.Group.UpdateGroup(ctx, data) - if err != nil { - log.Err(err).Msg("failed to update group") - server.RespondError(w, http.StatusInternalServerError, err) - return + switch r.Method { + case http.MethodGet: + group, err := ctrl.svc.Group.Get(ctx) + if err != nil { + log.Err(err).Msg("failed to get group") + server.RespondError(w, http.StatusInternalServerError, err) + return + } + + group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower caseÍ + server.Respond(w, http.StatusOK, group) + + case http.MethodPut: + data := repo.GroupUpdate{} + if err := server.Decode(r, &data); err != nil { + server.RespondError(w, http.StatusBadRequest, err) + return + } + + group, err := ctrl.svc.Group.UpdateGroup(ctx, data) + if err != nil { + log.Err(err).Msg("failed to update group") + server.RespondError(w, http.StatusInternalServerError, err) + return + } + group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower case + server.Respond(w, http.StatusOK, group) } - group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower case - server.Respond(w, http.StatusOK, group) } } @@ -89,7 +93,6 @@ func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { data := GroupInvitationCreate{} - if err := server.Decode(r, &data); err != nil { log.Err(err).Msg("failed to decode user registration data") server.RespondError(w, http.StatusBadRequest, err) diff --git a/backend/app/api/v1/v1_ctrl_items.go b/backend/app/api/v1/v1_ctrl_items.go index b515be9..49fe8b6 100644 --- a/backend/app/api/v1/v1_ctrl_items.go +++ b/backend/app/api/v1/v1_ctrl_items.go @@ -13,41 +13,6 @@ import ( "github.com/rs/zerolog/log" ) -func uuidList(params url.Values, key string) []uuid.UUID { - var ids []uuid.UUID - for _, id := range params[key] { - uid, err := uuid.Parse(id) - if err != nil { - continue - } - ids = append(ids, uid) - } - return ids -} - -func intOrNegativeOne(s string) int { - i, err := strconv.Atoi(s) - if err != nil { - return -1 - } - return i -} - -func extractQuery(r *http.Request) repo.ItemQuery { - params := r.URL.Query() - - page := intOrNegativeOne(params.Get("page")) - perPage := intOrNegativeOne(params.Get("perPage")) - - return repo.ItemQuery{ - Page: page, - PageSize: perPage, - Search: params.Get("q"), - LocationIDs: uuidList(params, "locations"), - LabelIDs: uuidList(params, "labels"), - } -} - // HandleItemsGetAll godoc // @Summary Get All Items // @Tags Items @@ -61,6 +26,38 @@ func extractQuery(r *http.Request) repo.ItemQuery { // @Router /v1/items [GET] // @Security Bearer func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc { + uuidList := func(params url.Values, key string) []uuid.UUID { + var ids []uuid.UUID + for _, id := range params[key] { + uid, err := uuid.Parse(id) + if err != nil { + continue + } + ids = append(ids, uid) + } + return ids + } + + intOrNegativeOne := func(s string) int { + i, err := strconv.Atoi(s) + if err != nil { + return -1 + } + return i + } + + extractQuery := func(r *http.Request) repo.ItemQuery { + params := r.URL.Query() + + return repo.ItemQuery{ + Page: intOrNegativeOne(params.Get("page")), + PageSize: intOrNegativeOne(params.Get("perPage")), + Search: params.Get("q"), + LocationIDs: uuidList(params, "locations"), + LabelIDs: uuidList(params, "labels"), + } + } + return func(w http.ResponseWriter, r *http.Request) { ctx := services.NewContext(r.Context()) items, err := ctrl.svc.Items.Query(ctx, extractQuery(r)) @@ -102,31 +99,6 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc { } } -// HandleItemDelete godocs -// @Summary deletes a item -// @Tags Items -// @Produce json -// @Param id path string true "Item ID" -// @Success 204 -// @Router /v1/items/{id} [DELETE] -// @Security Bearer -func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } - - err = ctrl.svc.Items.Delete(r.Context(), user.GroupID, uid) - if err != nil { - log.Err(err).Msg("failed to delete item") - server.RespondServerError(w) - return - } - server.Respond(w, http.StatusNoContent, nil) - } -} - // HandleItemGet godocs // @Summary Gets a item and fields // @Tags Items @@ -136,20 +108,19 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc { // @Router /v1/items/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } + return ctrl.handleItemsGeneral() +} - items, err := ctrl.svc.Items.GetOne(r.Context(), user.GroupID, uid) - if err != nil { - log.Err(err).Msg("failed to get item") - server.RespondServerError(w) - return - } - server.Respond(w, http.StatusOK, items) - } +// HandleItemDelete godocs +// @Summary deletes a item +// @Tags Items +// @Produce json +// @Param id path string true "Item ID" +// @Success 204 +// @Router /v1/items/{id} [DELETE] +// @Security Bearer +func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc { + return ctrl.handleItemsGeneral() } // HandleItemUpdate godocs @@ -162,26 +133,53 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc { // @Router /v1/items/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc { + return ctrl.handleItemsGeneral() +} + +func (ctrl *V1Controller) handleItemsGeneral() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - body := repo.ItemUpdate{} - if err := server.Decode(r, &body); err != nil { - log.Err(err).Msg("failed to decode request body") - server.RespondError(w, http.StatusInternalServerError, err) - return - } - uid, user, err := ctrl.partialParseIdAndUser(w, r) + ctx := services.NewContext(r.Context()) + ID, err := ctrl.routeID(w, r) if err != nil { return } - body.ID = uid - result, err := ctrl.svc.Items.Update(r.Context(), user.GroupID, body) - if err != nil { - log.Err(err).Msg("failed to update item") - server.RespondServerError(w) + switch r.Method { + case http.MethodGet: + items, err := ctrl.svc.Items.GetOne(r.Context(), ctx.GID, ID) + if err != nil { + log.Err(err).Msg("failed to get item") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusOK, items) return + case http.MethodDelete: + err = ctrl.svc.Items.Delete(r.Context(), ctx.GID, ID) + if err != nil { + log.Err(err).Msg("failed to delete item") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusNoContent, nil) + return + case http.MethodPut: + body := repo.ItemUpdate{} + if err := server.Decode(r, &body); err != nil { + log.Err(err).Msg("failed to decode request body") + server.RespondError(w, http.StatusInternalServerError, err) + return + } + body.ID = ID + result, err := ctrl.svc.Items.Update(r.Context(), ctx.GID, body) + if err != nil { + log.Err(err).Msg("failed to update item") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusOK, result) } - server.Respond(w, http.StatusOK, result) + } } diff --git a/backend/app/api/v1/v1_ctrl_items_attachments.go b/backend/app/api/v1/v1_ctrl_items_attachments.go index d246eb0..d4e2981 100644 --- a/backend/app/api/v1/v1_ctrl_items_attachments.go +++ b/backend/app/api/v1/v1_ctrl_items_attachments.go @@ -72,7 +72,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc { attachmentType = attachment.TypeAttachment.String() } - id, _, err := ctrl.partialParseIdAndUser(w, r) + id, err := ctrl.routeID(w, r) if err != nil { return } @@ -163,7 +163,7 @@ func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc { } func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) + ID, err := ctrl.routeID(w, r) if err != nil { return } @@ -181,7 +181,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r // Token Handler case http.MethodGet: - token, err := ctrl.svc.Items.AttachmentToken(ctx, uid, attachmentId) + token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentId) if err != nil { switch err { case services.ErrNotFound: @@ -210,7 +210,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r // Delete Attachment Handler case http.MethodDelete: - err = ctrl.svc.Items.AttachmentDelete(r.Context(), user.GroupID, uid, attachmentId) + err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentId) if err != nil { log.Err(err).Msg("failed to delete attachment") server.RespondServerError(w) @@ -230,7 +230,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r } attachment.ID = attachmentId - val, err := ctrl.svc.Items.AttachmentUpdate(ctx, uid, &attachment) + val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ID, &attachment) if err != nil { log.Err(err).Msg("failed to delete attachment") server.RespondServerError(w) diff --git a/backend/app/api/v1/v1_ctrl_labels.go b/backend/app/api/v1/v1_ctrl_labels.go index 8400748..7d3c00d 100644 --- a/backend/app/api/v1/v1_ctrl_labels.go +++ b/backend/app/api/v1/v1_ctrl_labels.go @@ -56,7 +56,6 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc { } server.Respond(w, http.StatusCreated, label) - } } @@ -69,20 +68,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc { // @Router /v1/labels/{id} [DELETE] // @Security Bearer func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } - - err = ctrl.svc.Labels.Delete(r.Context(), user.GroupID, uid) - if err != nil { - log.Err(err).Msg("error deleting label") - server.RespondServerError(w) - return - } - server.Respond(w, http.StatusNoContent, nil) - } + return ctrl.handleLabelsGeneral() } // HandleLabelGet godocs @@ -94,27 +80,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc { // @Router /v1/labels/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } - - labels, err := ctrl.svc.Labels.Get(r.Context(), user.GroupID, uid) - if err != nil { - if ent.IsNotFound(err) { - log.Err(err). - Str("id", uid.String()). - Msg("label not found") - server.RespondError(w, http.StatusNotFound, err) - return - } - log.Err(err).Msg("error getting label") - server.RespondServerError(w) - return - } - server.Respond(w, http.StatusOK, labels) - } + return ctrl.handleLabelsGeneral() } // HandleLabelUpdate godocs @@ -126,25 +92,59 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc { // @Router /v1/labels/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc { + return ctrl.handleLabelsGeneral() +} + +func (ctrl *V1Controller) handleLabelsGeneral() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - body := repo.LabelUpdate{} - if err := server.Decode(r, &body); err != nil { - log.Err(err).Msg("error decoding label update data") - server.RespondError(w, http.StatusInternalServerError, err) - return - } - uid, user, err := ctrl.partialParseIdAndUser(w, r) + ctx := services.NewContext(r.Context()) + ID, err := ctrl.routeID(w, r) if err != nil { return } - body.ID = uid - result, err := ctrl.svc.Labels.Update(r.Context(), user.GroupID, body) - if err != nil { - log.Err(err).Msg("error updating label") - server.RespondServerError(w) - return + switch r.Method { + case http.MethodGet: + labels, err := ctrl.svc.Labels.Get(r.Context(), ctx.GID, ID) + if err != nil { + if ent.IsNotFound(err) { + log.Err(err). + Str("id", ID.String()). + Msg("label not found") + server.RespondError(w, http.StatusNotFound, err) + return + } + log.Err(err).Msg("error getting label") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusOK, labels) + + case http.MethodDelete: + err = ctrl.svc.Labels.Delete(r.Context(), ctx.GID, ID) + if err != nil { + log.Err(err).Msg("error deleting label") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusNoContent, nil) + + case http.MethodPut: + body := repo.LabelUpdate{} + if err := server.Decode(r, &body); err != nil { + log.Err(err).Msg("error decoding label update data") + server.RespondError(w, http.StatusInternalServerError, err) + return + } + + body.ID = ID + result, err := ctrl.svc.Labels.Update(r.Context(), ctx.GID, body) + if err != nil { + log.Err(err).Msg("error updating label") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusOK, result) } - server.Respond(w, http.StatusOK, result) } } diff --git a/backend/app/api/v1/v1_ctrl_locations.go b/backend/app/api/v1/v1_ctrl_locations.go index 3505ff4..4b8bedb 100644 --- a/backend/app/api/v1/v1_ctrl_locations.go +++ b/backend/app/api/v1/v1_ctrl_locations.go @@ -69,20 +69,7 @@ func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc { // @Router /v1/locations/{id} [DELETE] // @Security Bearer func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } - - err = ctrl.svc.Location.Delete(r.Context(), user.GroupID, uid) - if err != nil { - log.Err(err).Msg("failed to delete location") - server.RespondServerError(w) - return - } - server.Respond(w, http.StatusNoContent, nil) - } + return ctrl.handleLocationGeneral() } // HandleLocationGet godocs @@ -94,32 +81,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc { // @Router /v1/locations/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - uid, user, err := ctrl.partialParseIdAndUser(w, r) - if err != nil { - return - } - - location, err := ctrl.svc.Location.GetOne(r.Context(), user.GroupID, uid) - if err != nil { - if ent.IsNotFound(err) { - log.Err(err). - Str("id", uid.String()). - Str("gid", user.GroupID.String()). - Msg("location not found") - server.RespondError(w, http.StatusNotFound, err) - return - } - - log.Err(err). - Str("id", uid.String()). - Str("gid", user.GroupID.String()). - Msg("failed to get location") - server.RespondServerError(w) - return - } - server.Respond(w, http.StatusOK, location) - } + return ctrl.handleLocationGeneral() } // HandleLocationUpdate godocs @@ -131,27 +93,61 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc { // @Router /v1/locations/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc { + return ctrl.handleLocationGeneral() +} + +func (ctrl *V1Controller) handleLocationGeneral() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - body := repo.LocationUpdate{} - if err := server.Decode(r, &body); err != nil { - log.Err(err).Msg("failed to decode location update data") - server.RespondError(w, http.StatusInternalServerError, err) - return - } - - uid, user, err := ctrl.partialParseIdAndUser(w, r) + ctx := services.NewContext(r.Context()) + ID, err := ctrl.routeID(w, r) if err != nil { return } - body.ID = uid + switch r.Method { + case http.MethodGet: + location, err := ctrl.svc.Location.GetOne(r.Context(), ctx.GID, ID) + if err != nil { + l := log.Err(err). + Str("ID", ID.String()). + Str("GID", ctx.GID.String()) - result, err := ctrl.svc.Location.Update(r.Context(), user.GroupID, body) - if err != nil { - log.Err(err).Msg("failed to update location") - server.RespondServerError(w) - return + if ent.IsNotFound(err) { + l.Msg("location not found") + server.RespondError(w, http.StatusNotFound, err) + return + } + + l.Msg("failed to get location") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusOK, location) + case http.MethodPut: + body := repo.LocationUpdate{} + if err := server.Decode(r, &body); err != nil { + log.Err(err).Msg("failed to decode location update data") + server.RespondError(w, http.StatusInternalServerError, err) + return + } + + body.ID = ID + + result, err := ctrl.svc.Location.Update(r.Context(), ctx.GID, body) + if err != nil { + log.Err(err).Msg("failed to update location") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusOK, result) + case http.MethodDelete: + err = ctrl.svc.Location.Delete(r.Context(), ctx.GID, ID) + if err != nil { + log.Err(err).Msg("failed to delete location") + server.RespondServerError(w) + return + } + server.Respond(w, http.StatusNoContent, nil) } - server.Respond(w, http.StatusOK, result) } } diff --git a/backend/app/api/v1/v1_ctrl_user.go b/backend/app/api/v1/v1_ctrl_user.go index 9ea4fae..3b8fd44 100644 --- a/backend/app/api/v1/v1_ctrl_user.go +++ b/backend/app/api/v1/v1_ctrl_user.go @@ -85,7 +85,6 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc { newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData) if err != nil { - server.RespondError(w, http.StatusInternalServerError, err) return } @@ -94,18 +93,6 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc { } } -// HandleUserUpdatePassword godoc -// @Summary Update the current user's password // TODO: -// @Tags User -// @Produce json -// @Success 204 -// @Router /v1/users/self/password [PUT] -// @Security Bearer -func (ctrl *V1Controller) HandleUserUpdatePassword() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - } -} - // HandleUserSelfDelete godoc // @Summary Deletes the user account // @Tags User diff --git a/backend/pkgs/hasher/password.go b/backend/pkgs/hasher/password.go index f7cca4d..64e88b2 100644 --- a/backend/pkgs/hasher/password.go +++ b/backend/pkgs/hasher/password.go @@ -1,13 +1,37 @@ package hasher -import "golang.org/x/crypto/bcrypt" +import ( + "fmt" + "os" + + "golang.org/x/crypto/bcrypt" +) + +var enabled = true + +func init() { + disableHas := os.Getenv("UNSAFE_DISABLE_PASSWORD_PROJECTION") == "yes_i_am_sure" + + if disableHas { + fmt.Println("WARNING: Password projection is disabled. This is unsafe in production.") + enabled = false + } +} func HashPassword(password string) (string, error) { + if !enabled { + return password, nil + } + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) return string(bytes), err } func CheckPasswordHash(password, hash string) bool { + if !enabled { + return password == hash + } + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil }