CVE-2022-4809: Improper Access Control (CWE-284)
value: Improper Access Control in GitHub repository usememos/memos prior to 0.9.1.
CWE description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
CVSS v3 base score: 8.8
Published: 2022-12-28T14:15Z
Repository: https://github.com/usememos/memos
Language: Go
Files changed: api/memo.go, api/memo_organizer.go, api/memo_resource.go, api/resource.go, api/shortcut.go, api/tag.go, api/user_setting.go, server/auth.go, server/memo.go, server/resource.go, server/shortcut.go, server/system.go, server/tag.go, server/user.go
Commit date: 2022-12-28 20:22:52 +0800
Commit message: fix: access control (#870)


Diff: diff --git a/api/memo.go b/api/memo.go
index 01aa42f..32e5383 100644
--- a/api/memo.go
+++ b/api/memo.go
@@ -44,11 +44,11 @@ type Memo struct {
 	ResourceList []*Resource `json:"resourceList"`
 }
 
 type MemoCreate struct {
 	// Standard fields
-	CreatorID int
+	CreatorID int `json:"-"`
 
 	// Domain specific fields
 	Visibility Visibility `json:"visibility"`
 	Content    string     `json:"content"`
 
@@ -71,15 +71,15 @@ type MemoPatch struct {
 	// Related fields
 	ResourceIDList []int `json:"resourceIdList"`
 }
 
 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
 	ContentSearch  *string
 	VisibilityList []Visibility
diff --git a/api/memo_organizer.go b/api/memo_organizer.go
index 7c99280..10cc83b 100644
--- a/api/memo_organizer.go
+++ b/api/memo_organizer.go
@@ -7,19 +7,19 @@ type MemoOrganizer struct {
 	MemoID int
 	UserID int
 	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 {
 	MemoID *int
 	UserID *int
diff --git a/api/memo_resource.go b/api/memo_resource.go
index f2ceacf..3c0b82c 100644
--- a/api/memo_resource.go
+++ b/api/memo_resource.go
@@ -6,11 +6,11 @@ type MemoResource struct {
 	CreatedTs  int64
 	UpdatedTs  int64
 }
 
 type MemoResourceUpsert struct {
-	MemoID     int
+	MemoID     int `json:"-"`
 	ResourceID int
 	UpdatedTs  *int64
 }
 
 type MemoResourceFind struct {
diff --git a/api/resource.go b/api/resource.go
index 90e47c7..d08ca07 100644
--- a/api/resource.go
+++ b/api/resource.go
@@ -18,11 +18,11 @@ type Resource struct {
 	LinkedMemoAmount int `json:"linkedMemoAmount"`
 }
 
 type ResourceCreate struct {
 	// Standard fields
-	CreatorID int
+	CreatorID int `json:"-"`
 
 	// Domain specific fields
 	Filename string `json:"filename"`
 	Blob     []byte `json:"blob"`
 	Type     string `json:"type"`
diff --git a/api/shortcut.go b/api/shortcut.go
index 5eaedd3..ce5dfff 100644
--- a/api/shortcut.go
+++ b/api/shortcut.go
@@ -14,11 +14,11 @@ type Shortcut struct {
 	Payload string `json:"payload"`
 }
 
 type ShortcutCreate struct {
 	// Standard fields
-	CreatorID int
+	CreatorID int `json:"-"`
 
 	// Domain specific fields
 	Title   string `json:"title"`
 	Payload string `json:"payload"`
 }
diff --git a/api/tag.go b/api/tag.go
index 77d6403..50e6b5e 100644
--- a/api/tag.go
+++ b/api/tag.go
@@ -5,11 +5,11 @@ type Tag struct {
 	CreatorID int
 }
 
 type TagUpsert struct {
 	Name      string
-	CreatorID int
+	CreatorID int `json:"-"`
 }
 
 type TagFind struct {
 	CreatorID int
 }
diff --git a/api/user_setting.go b/api/user_setting.go
index 6c7ff69..f3b272f 100644
--- a/api/user_setting.go
+++ b/api/user_setting.go
@@ -48,11 +48,11 @@ type UserSetting struct {
 	// Value is a JSON string with basic value
 	Value string `json:"value"`
 }
 
 type UserSettingUpsert struct {
-	UserID int
+	UserID int            `json:"-"`
 	Key    UserSettingKey `json:"key"`
 	Value  string         `json:"value"`
 }
 
 func (upsert UserSettingUpsert) Validate() error {
diff --git a/server/auth.go b/server/auth.go
index 15ea385..3224e5f 100644
--- a/server/auth.go
+++ b/server/auth.go
@@ -82,11 +82,11 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
 		hostUser, err := s.Store.FindUser(ctx, &hostUserFind)
 		if err != nil && common.ErrorCode(err) != common.NotFound {
 			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
 		allowSignUpSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{
 			Name: &systemSettingAllowSignUpName,
@@ -101,22 +101,22 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
 			if err != nil {
 				return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting allow signup").SetInternal(err)
 			}
 		}
 		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{
 			Username: signup.Username,
 			Role:     api.Role(signup.Role),
 			Nickname: signup.Username,
 			Password: signup.Password,
 			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)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
diff --git a/server/memo.go b/server/memo.go
index a435d65..d22f398 100644
--- a/server/memo.go
+++ b/server/memo.go
@@ -22,13 +22,11 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 		userID, ok := c.Get(getUserIDContextKey()).(int)
 		if !ok {
 			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)
 		}
 		if memoCreate.Content == "" {
 			return echo.NewHTTPError(http.StatusBadRequest, "Memo content shouldn't be empty")
@@ -55,10 +53,11 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 				// Private is the default memo visibility.
 				memoCreate.Visibility = api.Private
 			}
 		}
 
+		memoCreate.CreatorID = userID
 		memo, err := s.Store.CreateMemo(ctx, memoCreate)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create memo").SetInternal(err)
 		}
 		s.Collector.Collect(ctx, &metric.Metric{
@@ -96,28 +95,30 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 		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")
+		}
 
 		currentTs := time.Now().Unix()
 		memoPatch := &api.MemoPatch{
 			ID:        memoID,
 			UpdatedTs: &currentTs,
 		}
 		if err := json.NewDecoder(c.Request().Body).Decode(memoPatch); err != nil {
 			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)
 		}
 
 		for _, resourceID := range memoPatch.ResourceIDList {
@@ -171,11 +172,11 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			pinned := pinnedStr == "true"
 			memoFind.Pinned = &pinned
 		}
 		tag := c.QueryParam("tag")
 		if tag != "" {
-			contentSearch := "#" + tag + " "
+			contentSearch := "#" + tag
 			memoFind.ContentSearch = &contentSearch
 		}
 		visibilityListStr := c.QueryParam("visibility")
 		if visibilityListStr != "" {
 			visibilityList := []api.Visibility{}
@@ -227,133 +228,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo list response").SetInternal(err)
 		}
 		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"))
 		if err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
@@ -398,17 +276,16 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 
 		userID, ok := c.Get(getUserIDContextKey()).(int)
 		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 {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo organizer").SetInternal(err)
 		}
@@ -438,16 +315,16 @@ 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)
 		}
 
 		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)
 		}
 
@@ -486,51 +363,153 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode resource list response").SetInternal(err)
 		}
 		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 {
 		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("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,
 		}
 		if err := s.Store.DeleteMemo(ctx, memoDelete); err != nil {
@@ -540,6 +519,42 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete memo ID: %v", memoID)).SetInternal(err)
 		}
 
 		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)
+	})
 }
diff --git a/server/resource.go b/server/resource.go
index 9958e5f..09e3124 100644
--- a/server/resource.go
+++ b/server/resource.go
@@ -54,17 +54,16 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read file").SetInternal(err)
 		}
 
 		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)
 		}
 		s.Collector.Collect(ctx, &metric.Metric{
@@ -156,10 +155,11 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource").SetInternal(err)
 		}
 
 		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)
 		}
 		return nil
 	})
@@ -175,27 +175,30 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 		if err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
 		}
 
 		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)
 		}
 
 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
@@ -222,12 +225,12 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
 			CreatorID: &userID,
 		})
 		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{
 			ID: resourceID,
 		}
diff --git a/server/shortcut.go b/server/shortcut.go
index 11f4949..23b875a 100644
--- a/server/shortcut.go
+++ b/server/shortcut.go
@@ -19,17 +19,16 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 		ctx := c.Request().Context()
 		userID, ok := c.Get(getUserIDContextKey()).(int)
 		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)
 		}
 		s.Collector.Collect(ctx, &metric.Metric{
@@ -43,25 +42,40 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 		return nil
 	})
 
 	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)
 		}
 
 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
@@ -71,21 +85,16 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 		return nil
 	})
 
 	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)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch shortcut list").SetInternal(err)
@@ -120,15 +129,30 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
 		return nil
 	})
 
 	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,
 		}
 		if err := s.Store.DeleteShortcut(ctx, shortcutDelete); err != nil {
 			if common.ErrorCode(err) == common.NotFound {
diff --git a/server/system.go b/server/system.go
index 90d522a..74a3768 100644
--- a/server/system.go
+++ b/server/system.go
@@ -74,17 +74,28 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
 				systemStatus.AdditionalStyle = value.(string)
 			} else if systemSetting.Name == api.SystemSettingAdditionalScriptName {
 				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)
 				}
 			}
 		}
 
 		userID, ok := c.Get(getUserIDContextKey()).(int)
@@ -123,13 +134,11 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
 			ID: &userID,
 		})
 		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")
 		}
 
 		systemSettingUpsert := &api.SystemSettingUpsert{}
 		if err := json.NewDecoder(c.Request().Body).Decode(systemSettingUpsert); err != nil {
diff --git a/server/tag.go b/server/tag.go
index 5b139db..fc47e7a 100644
--- a/server/tag.go
+++ b/server/tag.go
@@ -21,20 +21,19 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
 		userID, ok := c.Get(getUserIDContextKey()).(int)
 		if !ok {
 			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)
 		}
 		if tagUpsert.Name == "" {
 			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)
 		}
 		s.Collector.Collect(ctx, &metric.Metric{
@@ -80,35 +79,22 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
 		return nil
 	})
 
 	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)
 		}
 
diff --git a/server/user.go b/server/user.go
index 76d7658..8b608b8 100644
--- a/server/user.go
+++ b/server/user.go
@@ -27,22 +27,24 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 		})
 		if err != nil {
 			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)
 		if err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
@@ -72,10 +74,11 @@ 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)
 		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(userList)); err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user list response").SetInternal(err)
@@ -157,10 +160,11 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 		}
 
 		if user != nil {
 			// data desensitize
 			user.OpenID = ""
+			user.Email = ""
 		}
 
 		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
 		if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user response").SetInternal(err)
@@ -190,18 +194,18 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 			return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
 		}
 
 		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 != "" {
 			passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost)
 			if err != nil {

Vulnerable code: 	CreatorID int
	ID *int `json:"id"`
	RowStatus *RowStatus `json:"rowStatus"`
	CreatorID *int       `json:"creatorId"`
type MemoOrganizerFind struct {
	MemoID int
	UserID int
type MemoOrganizerUpsert struct {
	Pinned bool `json:"pinned"`
	MemoID     int
	CreatorID int
	CreatorID int
	CreatorID int
	UserID int
			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)
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format.").SetInternal(err)
		memoCreate := &api.MemoCreate{
			CreatorID: userID,
		}
		memoFind := &api.MemoFind{
			ID:        &memoID,
			CreatorID: &userID,
		}
		if _, err := s.Store.FindMemo(ctx, memoFind); err != nil {
		memo, err := s.Store.PatchMemo(ctx, memoPatch)
			contentSearch := "#" + tag + " "
	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
	})

		memoOrganizerUpsert := &api.MemoOrganizerUpsert{
			MemoID: memoID,
			UserID: userID,
		}
			MemoID:    memoID,
	g.DELETE("/memo/:memoId/resource/:resourceId", func(c echo.Context) error {
		memoID, err := strconv.Atoi(c.Param("memoId"))
			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"))
			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Resource ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
		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)

		memoFind := &api.MemoFind{
			ID:        &memoID,
			CreatorID: &userID,
		}
		if _, err := s.Store.FindMemo(ctx, memoFind); err != nil {
			CreatorID: userID,

			ID:        &resourceID,
			CreatorID: &userID,
		if _, err := s.Store.FindResource(ctx, resourceFind); err != nil {
			ID:        resourceID,
		resource, err := s.Store.PatchResource(ctx, resourcePatch)
		if resource == nil {
			return echo.NewHTTPError(http.StatusNotFound, "Not find resource").SetInternal(err)
		shortcutCreate := &api.ShortcutCreate{
			CreatorID: userID,
		}
			ID:        shortcutID,
		shortcut, err := s.Store.PatchShortcut(ctx, shortcutPatch)
		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
				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),
		if user == nil {
			return echo.NewHTTPError(http.StatusNotFound, "Current signin user not found")
		} else if user.Role != api.Host {
		tagUpsert := &api.TagUpsert{
			CreatorID: userID,
		}
		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}
			}
		}

			return echo.NewHTTPError(http.StatusUnauthorized, "Only Host user can create member.")
		userCreate := &api.UserCreate{
			OpenID: common.GenUUID(),
		}
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format.").SetInternal(err)
			ID:        userID,
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format.").SetInternal(err)
Fixed code: 	CreatorID int `json:"-"`
	ID *int
	RowStatus *RowStatus
	CreatorID *int
type MemoOrganizerUpsert struct {
	MemoID int  `json:"-"`
	UserID int  `json:"-"`
	Pinned bool `json:"pinned"`
type MemoOrganizerFind struct {
	MemoID     int `json:"-"`
	CreatorID int `json:"-"`
	CreatorID int `json:"-"`
	CreatorID int `json:"-"`
	UserID int            `json:"-"`
			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)
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
		memoCreate := &api.MemoCreate{}
		memoCreate.CreatorID = userID
		memo, err := s.Store.FindMemo(ctx, &api.MemoFind{
			ID: &memoID,
		})
		if err != nil {
		if memo.CreatorID != userID {
			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
		}
		memo, err = s.Store.PatchMemo(ctx, memoPatch)
			contentSearch := "#" + tag
		memoOrganizerUpsert := &api.MemoOrganizerUpsert{}
		memoOrganizerUpsert.MemoID = memoID
		memoOrganizerUpsert.UserID = userID
		memoResourceUpsert.MemoID = memoID
	g.GET("/memo/amount", func(c echo.Context) error {
		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)
			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)
			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
		memo, err := s.Store.FindMemo(ctx, &api.MemoFind{
			ID: &memoID,
		})
		if err != nil {
		if memo.CreatorID != userID {
			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
		}

	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)
	})
			CreatorID: userID,
		c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'self'")
			ID: &resourceID,
		resource, err := s.Store.FindResource(ctx, resourceFind)
		if err != nil {
		if resource.CreatorID != userID {
			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
		}
		resource.ID = resourceID
		resource, err = s.Store.PatchResource(ctx, resourcePatch)
		if resource.CreatorID != userID {
			return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
		shortcutCreate := &api.ShortcutCreate{}
		shortcutCreate.CreatorID = userID
		userID, ok := c.Get(getUserIDContextKey()).(int)
		if !ok {
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
		}
		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")
		}

		shortcutPatch.ID = shortcutID
		shortcut, err = s.Store.PatchShortcut(ctx, shortcutPatch)
		userID, ok := c.Get(getUserIDContextKey()).(int)
		if !ok {
			return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find shortcut")
		}
		shortcutFind := &api.ShortcutFind{
			CreatorID: &userID,
		userID, ok := c.Get(getUserIDContextKey()).(int)
		if !ok {
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
		}
		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")
		}

				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)
		if user == nil || user.Role != api.Host {
		tagUpsert := &api.TagUpsert{}
		tagUpsert.CreatorID = userID
		userID, ok := c.Get(getUserIDContextKey()).(int)
		if !ok {
			return echo.NewHTTPError(http.StatusBadRequest, "Missing user session")
		}
			CreatorID:     &userID,
			return echo.NewHTTPError(http.StatusUnauthorized, "Only Host user can create member")
		userCreate := &api.UserCreate{}
		if userCreate.Role == api.Host {
			return echo.NewHTTPError(http.StatusForbidden, "Could not create host user")
		}
		userCreate.OpenID = common.GenUUID()
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
			user.Email = ""
			user.Email = ""
		userPatch.ID = userID
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format").SetInternal(err)