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

Insufficient Granularity of Access Control in GitHub repository usememos/memos prior to 0.9.1.

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

Weakness information:
cwe_id: CWE-1220
cwe_name: Insufficient Granularity of Access Control
description: The product implements access controls via a policy or other feature with the intention to disable or restrict accesses (reads and/or writes) to assets in a system from untrusted agents. However, implemented access controls lack required granularity, which renders the control policy too broad because it allows accesses from unauthorized agents to the security-sensitive assets.
extended_description: 
                Integrated circuits and hardware engines can expose accesses to assets (device configuration, keys, etc.) to trusted firmware or a software module (commonly set by BIOS/bootloader). This access is typically access-controlled. Upon a power reset, the hardware or system usually starts with default values in registers, and the trusted firmware (Boot firmware) configures the necessary access-control protection.
                A common weakness that can exist in such protection schemes is that access controls or policies are not granular enough. This condition allows agents beyond trusted agents to access assets and could lead to a loss of functionality or the ability to set up the device securely. This further results in security risks from leaked, sensitive, key material to modification of device configuration.
            
			

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