user.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. package server
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "time"
  8. "github.com/pkg/errors"
  9. "github.com/usememos/memos/api"
  10. "github.com/usememos/memos/common"
  11. metric "github.com/usememos/memos/plugin/metrics"
  12. "github.com/labstack/echo/v4"
  13. "golang.org/x/crypto/bcrypt"
  14. )
  15. func (s *Server) registerUserRoutes(g *echo.Group) {
  16. g.POST("/user", func(c echo.Context) error {
  17. ctx := c.Request().Context()
  18. userID, ok := c.Get(getUserIDContextKey()).(int)
  19. if !ok {
  20. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  21. }
  22. currentUser, err := s.Store.FindUser(ctx, &api.UserFind{
  23. ID: &userID,
  24. })
  25. if err != nil {
  26. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
  27. }
  28. if currentUser.Role != api.Host {
  29. return echo.NewHTTPError(http.StatusUnauthorized, "Only Host user can create member")
  30. }
  31. userCreate := &api.UserCreate{}
  32. if err := json.NewDecoder(c.Request().Body).Decode(userCreate); err != nil {
  33. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user request").SetInternal(err)
  34. }
  35. if userCreate.Role == api.Host {
  36. return echo.NewHTTPError(http.StatusForbidden, "Could not create host user")
  37. }
  38. userCreate.OpenID = common.GenUUID()
  39. if err := userCreate.Validate(); err != nil {
  40. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
  41. }
  42. passwordHash, err := bcrypt.GenerateFromPassword([]byte(userCreate.Password), bcrypt.DefaultCost)
  43. if err != nil {
  44. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
  45. }
  46. userCreate.PasswordHash = string(passwordHash)
  47. user, err := s.Store.CreateUser(ctx, userCreate)
  48. if err != nil {
  49. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
  50. }
  51. if err := s.createUserCreateActivity(c, user); err != nil {
  52. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
  53. }
  54. return c.JSON(http.StatusOK, composeResponse(user))
  55. })
  56. g.GET("/user", func(c echo.Context) error {
  57. ctx := c.Request().Context()
  58. userList, err := s.Store.FindUserList(ctx, &api.UserFind{})
  59. if err != nil {
  60. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user list").SetInternal(err)
  61. }
  62. for _, user := range userList {
  63. // data desensitize
  64. user.OpenID = ""
  65. user.Email = ""
  66. }
  67. return c.JSON(http.StatusOK, composeResponse(userList))
  68. })
  69. g.POST("/user/setting", func(c echo.Context) error {
  70. ctx := c.Request().Context()
  71. userID, ok := c.Get(getUserIDContextKey()).(int)
  72. if !ok {
  73. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  74. }
  75. userSettingUpsert := &api.UserSettingUpsert{}
  76. if err := json.NewDecoder(c.Request().Body).Decode(userSettingUpsert); err != nil {
  77. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user setting upsert request").SetInternal(err)
  78. }
  79. if err := userSettingUpsert.Validate(); err != nil {
  80. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user setting format").SetInternal(err)
  81. }
  82. userSettingUpsert.UserID = userID
  83. userSetting, err := s.Store.UpsertUserSetting(ctx, userSettingUpsert)
  84. if err != nil {
  85. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert user setting").SetInternal(err)
  86. }
  87. return c.JSON(http.StatusOK, composeResponse(userSetting))
  88. })
  89. // GET /api/user/me is used to check if the user is logged in.
  90. g.GET("/user/me", func(c echo.Context) error {
  91. ctx := c.Request().Context()
  92. userID, ok := c.Get(getUserIDContextKey()).(int)
  93. if !ok {
  94. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  95. }
  96. userFind := &api.UserFind{
  97. ID: &userID,
  98. }
  99. user, err := s.Store.FindUser(ctx, userFind)
  100. if err != nil {
  101. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  102. }
  103. userSettingList, err := s.Store.FindUserSettingList(ctx, &api.UserSettingFind{
  104. UserID: userID,
  105. })
  106. if err != nil {
  107. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  108. }
  109. user.UserSettingList = userSettingList
  110. return c.JSON(http.StatusOK, composeResponse(user))
  111. })
  112. g.GET("/user/:id", func(c echo.Context) error {
  113. ctx := c.Request().Context()
  114. id, err := strconv.Atoi(c.Param("id"))
  115. if err != nil {
  116. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err)
  117. }
  118. user, err := s.Store.FindUser(ctx, &api.UserFind{
  119. ID: &id,
  120. })
  121. if err != nil {
  122. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user").SetInternal(err)
  123. }
  124. if user != nil {
  125. // data desensitize
  126. user.OpenID = ""
  127. user.Email = ""
  128. }
  129. return c.JSON(http.StatusOK, composeResponse(user))
  130. })
  131. g.PATCH("/user/:id", func(c echo.Context) error {
  132. ctx := c.Request().Context()
  133. userID, err := strconv.Atoi(c.Param("id"))
  134. if err != nil {
  135. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
  136. }
  137. currentUserID, ok := c.Get(getUserIDContextKey()).(int)
  138. if !ok {
  139. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  140. }
  141. currentUser, err := s.Store.FindUser(ctx, &api.UserFind{
  142. ID: &currentUserID,
  143. })
  144. if err != nil {
  145. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  146. }
  147. if currentUser == nil {
  148. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
  149. } else if currentUser.Role != api.Host && currentUserID != userID {
  150. return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
  151. }
  152. currentTs := time.Now().Unix()
  153. userPatch := &api.UserPatch{
  154. UpdatedTs: &currentTs,
  155. }
  156. if err := json.NewDecoder(c.Request().Body).Decode(userPatch); err != nil {
  157. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
  158. }
  159. userPatch.ID = userID
  160. if err := userPatch.Validate(); err != nil {
  161. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format").SetInternal(err)
  162. }
  163. if userPatch.Password != nil && *userPatch.Password != "" {
  164. passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost)
  165. if err != nil {
  166. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
  167. }
  168. passwordHashStr := string(passwordHash)
  169. userPatch.PasswordHash = &passwordHashStr
  170. }
  171. if userPatch.ResetOpenID != nil && *userPatch.ResetOpenID {
  172. openID := common.GenUUID()
  173. userPatch.OpenID = &openID
  174. }
  175. user, err := s.Store.PatchUser(ctx, userPatch)
  176. if err != nil {
  177. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
  178. }
  179. userSettingList, err := s.Store.FindUserSettingList(ctx, &api.UserSettingFind{
  180. UserID: userID,
  181. })
  182. if err != nil {
  183. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  184. }
  185. user.UserSettingList = userSettingList
  186. return c.JSON(http.StatusOK, composeResponse(user))
  187. })
  188. g.DELETE("/user/:id", func(c echo.Context) error {
  189. ctx := c.Request().Context()
  190. currentUserID, ok := c.Get(getUserIDContextKey()).(int)
  191. if !ok {
  192. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  193. }
  194. currentUser, err := s.Store.FindUser(ctx, &api.UserFind{
  195. ID: &currentUserID,
  196. })
  197. if err != nil {
  198. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  199. }
  200. if currentUser == nil {
  201. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
  202. } else if currentUser.Role != api.Host {
  203. return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
  204. }
  205. userID, err := strconv.Atoi(c.Param("id"))
  206. if err != nil {
  207. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
  208. }
  209. userDelete := &api.UserDelete{
  210. ID: userID,
  211. }
  212. if err := s.Store.DeleteUser(ctx, userDelete); err != nil {
  213. if common.ErrorCode(err) == common.NotFound {
  214. return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("User ID not found: %d", userID))
  215. }
  216. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err)
  217. }
  218. return c.JSON(http.StatusOK, true)
  219. })
  220. }
  221. func (s *Server) createUserCreateActivity(c echo.Context, user *api.User) error {
  222. ctx := c.Request().Context()
  223. payload := api.ActivityUserCreatePayload{
  224. UserID: user.ID,
  225. Username: user.Username,
  226. Role: user.Role,
  227. }
  228. payloadBytes, err := json.Marshal(payload)
  229. if err != nil {
  230. return errors.Wrap(err, "failed to marshal activity payload")
  231. }
  232. activity, err := s.Store.CreateActivity(ctx, &api.ActivityCreate{
  233. CreatorID: user.ID,
  234. Type: api.ActivityUserCreate,
  235. Level: api.ActivityInfo,
  236. Payload: string(payloadBytes),
  237. })
  238. if err != nil || activity == nil {
  239. return errors.Wrap(err, "failed to create activity")
  240. }
  241. s.Collector.Collect(ctx, &metric.Metric{
  242. Name: string(activity.Type),
  243. })
  244. return err
  245. }