CVE-2022-4804 (published 2022-12-28T14:15Z)

Improper Authorization in GitHub repository usememos/memos prior to 0.9.1.

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)

Weakness information:
cwe_id: CWE-285
cwe_name: Improper Authorization
description: The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.
extended_description: 
            Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.
            When access control checks are not applied consistently - or not at all - users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.
         
         

Fix information:
repo_url: https://github.com/usememos/memos
commit_details: repo_url: https://github.com/usememos/memos
author: boojack
author_date: 2022-12-28 20:22:52+08:00
msg: fix: access control (#870)
repository: repo_url: https://github.com/usememos/memos
repo_name: usememos/memos
description: An open source, lightweight note-taking service. Easily capture and share your great thoughts.
repo_language: Go
owner: usememos
file_changes: filename: memo.go
change_type: ModificationType.MODIFY
diff: @@ -46,7 +46,7 @@ type Memo struct {
 
 type MemoCreate struct {
 	// Standard fields
-	CreatorID int
+	CreatorID int `json:"-"`
 
 	// Domain specific fields
 	Visibility Visibility `json:"visibility"`
@@ -73,11 +73,11 @@ type MemoPatch struct {
 }
 
 type MemoFind struct {
-	ID *int `json:"id"`
+	ID *int
 
 	// Standard fields
-	RowStatus *RowStatus `json:"rowStatus"`
-	CreatorID *int       `json:"creatorId"`
+	RowStatus *RowStatus
+	CreatorID *int
 
 	// Domain specific fields
 	Pinned         *bool

programming_language: Go
filename: memo_organizer.go
change_type: ModificationType.MODIFY
diff: @@ -9,15 +9,15 @@ type MemoOrganizer struct {
 	Pinned bool
 }
 
-type MemoOrganizerFind struct {
-	MemoID int
-	UserID int
+type MemoOrganizerUpsert struct {
+	MemoID int  `json:"-"`
+	UserID int  `json:"-"`
+	Pinned bool `json:"pinned"`
 }
 
-type MemoOrganizerUpsert struct {
+type MemoOrganizerFind struct {
 	MemoID int
 	UserID int
-	Pinned bool `json:"pinned"`
 }
 
 type MemoOrganizerDelete struct {

programming_language: Go
filename: memo_resource.go
change_type: ModificationType.MODIFY
diff: @@ -8,7 +8,7 @@ type MemoResource struct {
 }
 
 type MemoResourceUpsert struct {
-	MemoID     int
+	MemoID     int `json:"-"`
 	ResourceID int
 	UpdatedTs  *int64
 }

programming_language: Go
filename: resource.go
change_type: ModificationType.MODIFY
diff: @@ -20,7 +20,7 @@ type Resource struct {
 
 type ResourceCreate struct {
 	// Standard fields
-	CreatorID int
+	CreatorID int `json:"-"`
 
 	// Domain specific fields
 	Filename string `json:"filename"`

programming_language: Go
filename: shortcut.go
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,7 @@ type Shortcut struct {
 
 type ShortcutCreate struct {
 	// Standard fields
-	CreatorID int
+	CreatorID int `json:"-"`
 
 	// Domain specific fields
 	Title   string `json:"title"`

programming_language: Go
filename: tag.go
change_type: ModificationType.MODIFY
diff: @@ -7,7 +7,7 @@ type Tag struct {
 
 type TagUpsert struct {
 	Name      string
-	CreatorID int
+	CreatorID int `json:"-"`
 }
 
 type TagFind struct {

programming_language: Go
filename: user_setting.go
change_type: ModificationType.MODIFY
diff: @@ -50,7 +50,7 @@ type UserSetting struct {
 }
 
 type UserSettingUpsert struct {
-	UserID int
+	UserID int            `json:"-"`
 	Key    UserSettingKey `json:"key"`
 	Value  string         `json:"value"`
 }

programming_language: Go
filename: auth.go
change_type: ModificationType.MODIFY
diff: @@ -84,7 +84,7 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find host user").SetInternal(err)
 		}
 		if signup.Role == api.Host && hostUser != nil {
-			return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly.").SetInternal(err)
+			return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly").SetInternal(err)
 		}
 
 		systemSettingAllowSignUpName := api.SystemSettingAllowSignUpName
@@ -103,7 +103,7 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
 			}
 		}
 		if !allowSignUpSettingValue && hostUser != nil {
-			return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly.").SetInternal(err)
+			return echo.NewHTTPError(http.StatusUnauthorized, "Site Host existed, please contact the site host to signin account firstly").SetInternal(err)
 		}
 
 		userCreate := &api.UserCreate{
@@ -114,7 +114,7 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
 			OpenID:   common.GenUUID(),
 		}
 		if err := userCreate.Validate(); err != nil {
-			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format.").SetInternal(err)
+			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
 		}
 
 		passwordHash, err := bcrypt.GenerateFromPassword([]byte(signup.Password), bcrypt.DefaultCost)

programming_language: Go
filename: memo.go
change_type: ModificationType.MODIFY
diff: @@ -24,9 +24,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
 		}
 
-		memoCreate := &api.MemoCreate{
-			CreatorID: userID,
-		}
+		memoCreate := &api.MemoCreate{}
 		if err := json.NewDecoder(c.Request().Body).Decode(memoCreate); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo request").SetInternal(err)
 		}
@@ -57,6 +55,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			}
 		}
 
+		memoCreate.CreatorID = userID
 		memo, err := s.Store.CreateMemo(ctx, memoCreate)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create memo").SetInternal(err)
@@ -98,13 +97,15 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
 		}
 
-		memoFind := &api.MemoFind{
-			ID:        &memoID,
-			CreatorID: &userID,
-		}
-		if _, err := s.Store.FindMemo(ctx, memoFind); err != nil {
+		memo, err := s.Store.FindMemo(ctx, &api.MemoFind{
+			ID: &memoID,
+		})
+		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
 		}
+		if memo.CreatorID != userID {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
+		}
 
 		currentTs := time.Now().Unix()
 		memoPatch := &api.MemoPatch{
@@ -115,7 +116,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch memo request").SetInternal(err)
 		}
 
-		memo, err := s.Store.PatchMemo(ctx, memoPatch)
+		memo, err = s.Store.PatchMemo(ctx, memoPatch)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch memo").SetInternal(err)
 		}
@@ -173,7 +174,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 		}
 		tag := c.QueryParam("tag")
 		if tag != "" {
-			contentSearch := "#" + tag + " "
+			contentSearch := "#" + tag
 			memoFind.ContentSearch = &contentSearch
 		}
 		visibilityListStr := c.QueryParam("visibility")
@@ -229,129 +230,6 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 		return nil
 	})
 
-	g.GET("/memo/amount", func(c echo.Context) error {
-		ctx := c.Request().Context()
-		normalRowStatus := api.Normal
-		memoFind := &api.MemoFind{
-			RowStatus: &normalRowStatus,
-		}
-		if userID, err := strconv.Atoi(c.QueryParam("userId")); err == nil {
-			memoFind.CreatorID = &userID
-		}
-
-		memoList, err := s.Store.FindMemoList(ctx, memoFind)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
-		}
-
-		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
-		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(len(memoList))); err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo amount").SetInternal(err)
-		}
-		return nil
-	})
-
-	g.GET("/memo/stats", func(c echo.Context) error {
-		ctx := c.Request().Context()
-		normalStatus := api.Normal
-		memoFind := &api.MemoFind{
-			RowStatus: &normalStatus,
-		}
-		if creatorID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
-			memoFind.CreatorID = &creatorID
-		}
-		if memoFind.CreatorID == nil {
-			return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
-		}
-
-		currentUserID, ok := c.Get(getUserIDContextKey()).(int)
-		if !ok {
-			memoFind.VisibilityList = []api.Visibility{api.Public}
-		} else {
-			if *memoFind.CreatorID != currentUserID {
-				memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
-			} else {
-				memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected, api.Private}
-			}
-		}
-
-		list, err := s.Store.FindMemoList(ctx, memoFind)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err)
-		}
-
-		displayTsList := []int64{}
-		for _, memo := range list {
-			displayTsList = append(displayTsList, memo.DisplayTs)
-		}
-
-		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
-		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(displayTsList)); err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo stats response").SetInternal(err)
-		}
-		return nil
-	})
-
-	g.GET("/memo/all", func(c echo.Context) error {
-		ctx := c.Request().Context()
-		memoFind := &api.MemoFind{}
-
-		_, ok := c.Get(getUserIDContextKey()).(int)
-		if !ok {
-			memoFind.VisibilityList = []api.Visibility{api.Public}
-		} else {
-			memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
-		}
-
-		pinnedStr := c.QueryParam("pinned")
-		if pinnedStr != "" {
-			pinned := pinnedStr == "true"
-			memoFind.Pinned = &pinned
-		}
-		tag := c.QueryParam("tag")
-		if tag != "" {
-			contentSearch := "#" + tag + " "
-			memoFind.ContentSearch = &contentSearch
-		}
-		visibilityListStr := c.QueryParam("visibility")
-		if visibilityListStr != "" {
-			visibilityList := []api.Visibility{}
-			for _, visibility := range strings.Split(visibilityListStr, ",") {
-				visibilityList = append(visibilityList, api.Visibility(visibility))
-			}
-			memoFind.VisibilityList = visibilityList
-		}
-		if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
-			memoFind.Limit = limit
-		}
-		if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
-			memoFind.Offset = offset
-		}
-
-		// Only fetch normal status memos.
-		normalStatus := api.Normal
-		memoFind.RowStatus = &normalStatus
-
-		list, err := s.Store.FindMemoList(ctx, memoFind)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch all memo list").SetInternal(err)
-		}
-
-		sort.Slice(list, func(i, j int) bool {
-			return list[i].DisplayTs > list[j].DisplayTs
-		})
-
-		if memoFind.Limit != 0 {
-			list = list[memoFind.Offset:common.Min(len(list), memoFind.Offset+memoFind.Limit)]
-		}
-
-		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
-		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(list)); err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode all memo list response").SetInternal(err)
-		}
-		return nil
-	})
-
 	g.GET("/memo/:memoId", func(c echo.Context) error {
 		ctx := c.Request().Context()
 		memoID, err := strconv.Atoi(c.Param("memoId"))
@@ -400,13 +278,12 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 		if !ok {
 			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
 		}
-		memoOrganizerUpsert := &api.MemoOrganizerUpsert{
-			MemoID: memoID,
-			UserID: userID,
-		}
+		memoOrganizerUpsert := &api.MemoOrganizerUpsert{}
 		if err := json.NewDecoder(c.Request().Body).Decode(memoOrganizerUpsert); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo organizer request").SetInternal(err)
 		}
+		memoOrganizerUpsert.MemoID = memoID
+		memoOrganizerUpsert.UserID = userID
 
 		err = s.Store.UpsertMemoOrganizer(ctx, memoOrganizerUpsert)
 		if err != nil {
@@ -440,12 +317,12 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 
 		currentTs := time.Now().Unix()
 		memoResourceUpsert := &api.MemoResourceUpsert{
-			MemoID:    memoID,
 			UpdatedTs: &currentTs,
 		}
 		if err := json.NewDecoder(c.Request().Body).Decode(memoResourceUpsert); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo resource request").SetInternal(err)
 		}
+		memoResourceUpsert.MemoID = memoID
 
 		if _, err := s.Store.UpsertMemoResource(ctx, memoResourceUpsert); err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo resource").SetInternal(err)
@@ -488,26 +365,127 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 		return nil
 	})
 
-	g.DELETE("/memo/:memoId/resource/:resourceId", func(c echo.Context) error {
+	g.GET("/memo/amount", func(c echo.Context) error {
 		ctx := c.Request().Context()
-		memoID, err := strconv.Atoi(c.Param("memoId"))
+		normalRowStatus := api.Normal
+		memoFind := &api.MemoFind{
+			RowStatus: &normalRowStatus,
+		}
+		if userID, err := strconv.Atoi(c.QueryParam("userId")); err == nil {
+			memoFind.CreatorID = &userID
+		}
+
+		memoList, err := s.Store.FindMemoList(ctx, memoFind)
 		if err != nil {
-			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Memo ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
 		}
-		resourceID, err := strconv.Atoi(c.Param("resourceId"))
+
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
+		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(len(memoList))); err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo amount").SetInternal(err)
+		}
+		return nil
+	})
+
+	g.GET("/memo/stats", func(c echo.Context) error {
+		ctx := c.Request().Context()
+		normalStatus := api.Normal
+		memoFind := &api.MemoFind{
+			RowStatus: &normalStatus,
+		}
+		if creatorID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
+			memoFind.CreatorID = &creatorID
+		}
+		if memoFind.CreatorID == nil {
+			return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
+		}
+
+		currentUserID, ok := c.Get(getUserIDContextKey()).(int)
+		if !ok {
+			memoFind.VisibilityList = []api.Visibility{api.Public}
+		} else {
+			if *memoFind.CreatorID != currentUserID {
+				memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
+			} else {
+				memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected, api.Private}
+			}
+		}
+
+		list, err := s.Store.FindMemoList(ctx, memoFind)
 		if err != nil {
-			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Resource ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err)
 		}
 
-		memoResourceDelete := &api.MemoResourceDelete{
-			MemoID:     &memoID,
-			ResourceID: &resourceID,
+		displayTsList := []int64{}
+		for _, memo := range list {
+			displayTsList = append(displayTsList, memo.DisplayTs)
 		}
-		if err := s.Store.DeleteMemoResource(ctx, memoResourceDelete); err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
+
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
+		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(displayTsList)); err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo stats response").SetInternal(err)
 		}
+		return nil
+	})
 
-		return c.JSON(http.StatusOK, true)
+	g.GET("/memo/all", func(c echo.Context) error {
+		ctx := c.Request().Context()
+		memoFind := &api.MemoFind{}
+
+		_, ok := c.Get(getUserIDContextKey()).(int)
+		if !ok {
+			memoFind.VisibilityList = []api.Visibility{api.Public}
+		} else {
+			memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
+		}
+
+		pinnedStr := c.QueryParam("pinned")
+		if pinnedStr != "" {
+			pinned := pinnedStr == "true"
+			memoFind.Pinned = &pinned
+		}
+		tag := c.QueryParam("tag")
+		if tag != "" {
+			contentSearch := "#" + tag + " "
+			memoFind.ContentSearch = &contentSearch
+		}
+		visibilityListStr := c.QueryParam("visibility")
+		if visibilityListStr != "" {
+			visibilityList := []api.Visibility{}
+			for _, visibility := range strings.Split(visibilityListStr, ",") {
+				visibilityList = append(visibilityList, api.Visibility(visibility))
+			}
+			memoFind.VisibilityList = visibilityList
+		}
+		if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
+			memoFind.Limit = limit
+		}
+		if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
+			memoFind.Offset = offset
+		}
+
+		// Only fetch normal status memos.
+		normalStatus := api.Normal
+		memoFind.RowStatus = &normalStatus
+
+		list, err := s.Store.FindMemoList(ctx, memoFind)
+		if err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch all memo list").SetInternal(err)
+		}
+
+		sort.Slice(list, func(i, j int) bool {
+			return list[i].DisplayTs > list[j].DisplayTs
+		})
+
+		if memoFind.Limit != 0 {
+			list = list[memoFind.Offset:common.Min(len(list), memoFind.Offset+memoFind.Limit)]
+		}
+
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
+		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(list)); err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode all memo list response").SetInternal(err)
+		}
+		return nil
 	})
 
 	g.DELETE("/memo/:memoId", func(c echo.Context) error {
@@ -516,19 +494,20 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 		if !ok {
 			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
 		}
-
 		memoID, err := strconv.Atoi(c.Param("memoId"))
 		if err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
 		}
 
-		memoFind := &api.MemoFind{
-			ID:        &memoID,
-			CreatorID: &userID,
-		}
-		if _, err := s.Store.FindMemo(ctx, memoFind); err != nil {
+		memo, err := s.Store.FindMemo(ctx, &api.MemoFind{
+			ID: &memoID,
+		})
+		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
 		}
+		if memo.CreatorID != userID {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
+		}
 
 		memoDelete := &api.MemoDelete{
 			ID: memoID,
@@ -542,4 +521,40 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 
 		return c.JSON(http.StatusOK, true)
 	})
+
+	g.DELETE("/memo/:memoId/resource/:resourceId", func(c echo.Context) error {
+		ctx := c.Request().Context()
+		userID, ok := c.Get(getUserIDContextKey()).(int)
+		if !ok {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
+		}
+		memoID, err := strconv.Atoi(c.Param("memoId"))
+		if err != nil {
+			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Memo ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
+		}
+		resourceID, err := strconv.Atoi(c.Param("resourceId"))
+		if err != nil {
+			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Resource ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
+		}
+
+		memo, err := s.Store.FindMemo(ctx, &api.MemoFind{
+			ID: &memoID,
+		})
+		if err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
+		}
+		if memo.CreatorID != userID {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
+		}
+
+		memoResourceDelete := &api.MemoResourceDelete{
+			MemoID:     &memoID,
+			ResourceID: &resourceID,
+		}
+		if err := s.Store.DeleteMemoResource(ctx, memoResourceDelete); err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
+		}
+
+		return c.JSON(http.StatusOK, true)
+	})
 }

programming_language: Go
filename: resource.go
change_type: ModificationType.MODIFY
diff: @@ -56,13 +56,12 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 		}
 
 		resourceCreate := &api.ResourceCreate{
+			CreatorID: userID,
 			Filename:  filename,
 			Type:      filetype,
 			Size:      size,
 			Blob:      fileBytes,
-			CreatorID: userID,
 		}
-
 		resource, err := s.Store.CreateResource(ctx, resourceCreate)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
@@ -158,6 +157,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 
 		c.Response().Writer.WriteHeader(http.StatusOK)
 		c.Response().Writer.Header().Set("Content-Type", resource.Type)
+		c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'self'")
 		if _, err := c.Response().Writer.Write(resource.Blob); err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to write resource blob").SetInternal(err)
 		}
@@ -177,23 +177,26 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 		}
 
 		resourceFind := &api.ResourceFind{
-			ID:        &resourceID,
-			CreatorID: &userID,
+			ID: &resourceID,
 		}
-		if _, err := s.Store.FindResource(ctx, resourceFind); err != nil {
+		resource, err := s.Store.FindResource(ctx, resourceFind)
+		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
 		}
+		if resource.CreatorID != userID {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
+		}
 
 		currentTs := time.Now().Unix()
 		resourcePatch := &api.ResourcePatch{
-			ID:        resourceID,
 			UpdatedTs: &currentTs,
 		}
 		if err := json.NewDecoder(c.Request().Body).Decode(resourcePatch); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch resource request").SetInternal(err)
 		}
 
-		resource, err := s.Store.PatchResource(ctx, resourcePatch)
+		resource.ID = resourceID
+		resource, err = s.Store.PatchResource(ctx, resourcePatch)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch resource").SetInternal(err)
 		}
@@ -224,8 +227,8 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
 		}
-		if resource == nil {
-			return echo.NewHTTPError(http.StatusNotFound, "Not find resource").SetInternal(err)
+		if resource.CreatorID != userID {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
 		}
 
 		resourceDelete := &api.ResourceDelete{

programming_language: Go
filename: shortcut.go
change_type: ModificationType.MODIFY
diff: @@ -21,13 +21,12 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 		if !ok {
 			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
 		}
-		shortcutCreate := &api.ShortcutCreate{
-			CreatorID: userID,
-		}
+		shortcutCreate := &api.ShortcutCreate{}
 		if err := json.NewDecoder(c.Request().Body).Decode(shortcutCreate); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err)
 		}
 
+		shortcutCreate.CreatorID = userID
 		shortcut, err := s.Store.CreateShortcut(ctx, shortcutCreate)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err)
@@ -45,21 +44,36 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 
 	g.PATCH("/shortcut/:shortcutId", func(c echo.Context) error {
 		ctx := c.Request().Context()
+		userID, ok := c.Get(getUserIDContextKey()).(int)
+		if !ok {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
+		}
 		shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
 		if err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
 		}
 
+		shortcutFind := &api.ShortcutFind{
+			ID: &shortcutID,
+		}
+		shortcut, err := s.Store.FindShortcut(ctx, shortcutFind)
+		if err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
+		}
+		if shortcut.CreatorID != userID {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
+		}
+
 		currentTs := time.Now().Unix()
 		shortcutPatch := &api.ShortcutPatch{
-			ID:        shortcutID,
 			UpdatedTs: &currentTs,
 		}
 		if err := json.NewDecoder(c.Request().Body).Decode(shortcutPatch); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch shortcut request").SetInternal(err)
 		}
 
-		shortcut, err := s.Store.PatchShortcut(ctx, shortcutPatch)
+		shortcutPatch.ID = shortcutID
+		shortcut, err = s.Store.PatchShortcut(ctx, shortcutPatch)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch shortcut").SetInternal(err)
 		}
@@ -73,17 +87,12 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 
 	g.GET("/shortcut", func(c echo.Context) error {
 		ctx := c.Request().Context()
-		shortcutFind := &api.ShortcutFind{}
-
-		if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
-			shortcutFind.CreatorID = &userID
-		} else {
-			userID, ok := c.Get(getUserIDContextKey()).(int)
-			if !ok {
-				return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find shortcut")
-			}
-
-			shortcutFind.CreatorID = &userID
+		userID, ok := c.Get(getUserIDContextKey()).(int)
+		if !ok {
+			return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find shortcut")
+		}
+		shortcutFind := &api.ShortcutFind{
+			CreatorID: &userID,
 		}
 
 		list, err := s.Store.FindShortcutList(ctx, shortcutFind)
@@ -122,11 +131,26 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 
 	g.DELETE("/shortcut/:shortcutId", func(c echo.Context) error {
 		ctx := c.Request().Context()
+		userID, ok := c.Get(getUserIDContextKey()).(int)
+		if !ok {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
+		}
 		shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
 		if err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
 		}
 
+		shortcutFind := &api.ShortcutFind{
+			ID: &shortcutID,
+		}
+		shortcut, err := s.Store.FindShortcut(ctx, shortcutFind)
+		if err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
+		}
+		if shortcut.CreatorID != userID {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
+		}
+
 		shortcutDelete := &api.ShortcutDelete{
 			ID: &shortcutID,
 		}

programming_language: Go
filename: system.go
change_type: ModificationType.MODIFY
diff: @@ -76,13 +76,24 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
 				systemStatus.AdditionalScript = value.(string)
 			} else if systemSetting.Name == api.SystemSettingCustomizedProfileName {
 				valueMap := value.(map[string]interface{})
-				systemStatus.CustomizedProfile = api.CustomizedProfile{
-					Name:        valueMap["name"].(string),
-					LogoURL:     valueMap["logoUrl"].(string),
-					Description: valueMap["description"].(string),
-					Locale:      valueMap["locale"].(string),
-					Appearance:  valueMap["appearance"].(string),
-					ExternalURL: valueMap["externalUrl"].(string),
+				systemStatus.CustomizedProfile = api.CustomizedProfile{}
+				if v := valueMap["name"]; v != nil {
+					systemStatus.CustomizedProfile.Name = v.(string)
+				}
+				if v := valueMap["logoUrl"]; v != nil {
+					systemStatus.CustomizedProfile.LogoURL = v.(string)
+				}
+				if v := valueMap["description"]; v != nil {
+					systemStatus.CustomizedProfile.Description = v.(string)
+				}
+				if v := valueMap["locale"]; v != nil {
+					systemStatus.CustomizedProfile.Locale = v.(string)
+				}
+				if v := valueMap["appearance"]; v != nil {
+					systemStatus.CustomizedProfile.Appearance = v.(string)
+				}
+				if v := valueMap["externalUrl"]; v != nil {
+					systemStatus.CustomizedProfile.ExternalURL = v.(string)
 				}
 			}
 		}
@@ -125,9 +136,7 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
 		}
-		if user == nil {
-			return echo.NewHTTPError(http.StatusNotFound, "Current signin user not found")
-		} else if user.Role != api.Host {
+		if user == nil || user.Role != api.Host {
 			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
 		}
 

programming_language: Go
filename: tag.go
change_type: ModificationType.MODIFY
diff: @@ -23,9 +23,7 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
 		}
 
-		tagUpsert := &api.TagUpsert{
-			CreatorID: userID,
-		}
+		tagUpsert := &api.TagUpsert{}
 		if err := json.NewDecoder(c.Request().Body).Decode(tagUpsert); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post tag request").SetInternal(err)
 		}
@@ -33,6 +31,7 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusBadRequest, "Tag name shouldn't be empty")
 		}
 
+		tagUpsert.CreatorID = userID
 		tag, err := s.Store.UpsertTag(ctx, tagUpsert)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert tag").SetInternal(err)
@@ -82,31 +81,18 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
 
 	g.GET("/tag/suggestion", func(c echo.Context) error {
 		ctx := c.Request().Context()
+		userID, ok := c.Get(getUserIDContextKey()).(int)
+		if !ok {
+			return echo.NewHTTPError(http.StatusBadRequest, "Missing user session")
+		}
 		contentSearch := "#"
 		normalRowStatus := api.Normal
 		memoFind := api.MemoFind{
+			CreatorID:     &userID,
 			ContentSearch: &contentSearch,
 			RowStatus:     &normalRowStatus,
 		}
 
-		if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
-			memoFind.CreatorID = &userID
-		}
-
-		currentUserID, ok := c.Get(getUserIDContextKey()).(int)
-		if !ok {
-			if memoFind.CreatorID == nil {
-				return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
-			}
-			memoFind.VisibilityList = []api.Visibility{api.Public}
-		} else {
-			if memoFind.CreatorID == nil {
-				memoFind.CreatorID = &currentUserID
-			} else {
-				memoFind.VisibilityList = []api.Visibility{api.Public, api.Protected}
-			}
-		}
-
 		memoList, err := s.Store.FindMemoList(ctx, &memoFind)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)

programming_language: Go
filename: user.go
change_type: ModificationType.MODIFY
diff: @@ -29,18 +29,20 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
 		}
 		if currentUser.Role != api.Host {
-			return echo.NewHTTPError(http.StatusUnauthorized, "Only Host user can create member.")
+			return echo.NewHTTPError(http.StatusUnauthorized, "Only Host user can create member")
 		}
 
-		userCreate := &api.UserCreate{
-			OpenID: common.GenUUID(),
-		}
+		userCreate := &api.UserCreate{}
 		if err := json.NewDecoder(c.Request().Body).Decode(userCreate); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user request").SetInternal(err)
 		}
+		if userCreate.Role == api.Host {
+			return echo.NewHTTPError(http.StatusForbidden, "Could not create host user")
+		}
+		userCreate.OpenID = common.GenUUID()
 
 		if err := userCreate.Validate(); err != nil {
-			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format.").SetInternal(err)
+			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
 		}
 
 		passwordHash, err := bcrypt.GenerateFromPassword([]byte(userCreate.Password), bcrypt.DefaultCost)
@@ -74,6 +76,7 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 		for _, user := range userList {
 			// data desensitize
 			user.OpenID = ""
+			user.Email = ""
 		}
 
 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
@@ -159,6 +162,7 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 		if user != nil {
 			// data desensitize
 			user.OpenID = ""
+			user.Email = ""
 		}
 
 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
@@ -192,14 +196,14 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 
 		currentTs := time.Now().Unix()
 		userPatch := &api.UserPatch{
-			ID:        userID,
 			UpdatedTs: &currentTs,
 		}
 		if err := json.NewDecoder(c.Request().Body).Decode(userPatch); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
 		}
+		userPatch.ID = userID
 		if err := userPatch.Validate(); err != nil {
-			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format.").SetInternal(err)
+			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format").SetInternal(err)
 		}
 
 		if userPatch.Password != nil && *userPatch.Password != "" {

programming_language: Go