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

Incorrect Use of Privileged APIs in GitHub repository usememos/memos prior to 0.9.1.

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

Weakness information:
cwe_id: CWE-648
cwe_name: Incorrect Use of Privileged APIs
description: The product does not conform to the API requirements for a function call that requires extra privileges. This could allow attackers to gain privileges by causing the function to be called incorrectly.
extended_description: 
            When a product contains certain functions that perform operations requiring an elevated level of privilege, the caller of a privileged API must be careful to:
               
                  ensure that assumptions made by the APIs are valid, such as validity of arguments
                  account for known weaknesses in the design/implementation of the API
                  call the API from a safe context
               
            If the caller of the API does not follow these requirements, then it may allow a malicious user or process to elevate their privilege, hijack the process, or steal sensitive data.
            For instance, it is important to know if privileged APIs do not shed their privileges before returning to the caller or if the privileged function might make certain assumptions about the data, context or state information passed to it by the caller. It is important to always know when and how privileged APIs can be called in order to ensure that their elevated level of privilege cannot be exploited.
         
         

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