user.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
  55. if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil {
  56. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user response").SetInternal(err)
  57. }
  58. return nil
  59. })
  60. g.GET("/user", func(c echo.Context) error {
  61. ctx := c.Request().Context()
  62. userList, err := s.Store.FindUserList(ctx, &api.UserFind{})
  63. if err != nil {
  64. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user list").SetInternal(err)
  65. }
  66. for _, user := range userList {
  67. // data desensitize
  68. user.OpenID = ""
  69. user.Email = ""
  70. }
  71. c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
  72. if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(userList)); err != nil {
  73. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user list response").SetInternal(err)
  74. }
  75. return nil
  76. })
  77. // GET /api/user/me is used to check if the user is logged in.
  78. g.GET("/user/me", func(c echo.Context) error {
  79. ctx := c.Request().Context()
  80. userID, ok := c.Get(getUserIDContextKey()).(int)
  81. if !ok {
  82. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  83. }
  84. userFind := &api.UserFind{
  85. ID: &userID,
  86. }
  87. user, err := s.Store.FindUser(ctx, userFind)
  88. if err != nil {
  89. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  90. }
  91. userSettingList, err := s.Store.FindUserSettingList(ctx, &api.UserSettingFind{
  92. UserID: userID,
  93. })
  94. if err != nil {
  95. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  96. }
  97. user.UserSettingList = userSettingList
  98. c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
  99. if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil {
  100. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user response").SetInternal(err)
  101. }
  102. return nil
  103. })
  104. g.POST("/user/setting", func(c echo.Context) error {
  105. ctx := c.Request().Context()
  106. userID, ok := c.Get(getUserIDContextKey()).(int)
  107. if !ok {
  108. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  109. }
  110. userSettingUpsert := &api.UserSettingUpsert{}
  111. if err := json.NewDecoder(c.Request().Body).Decode(userSettingUpsert); err != nil {
  112. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user setting upsert request").SetInternal(err)
  113. }
  114. if err := userSettingUpsert.Validate(); err != nil {
  115. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user setting format").SetInternal(err)
  116. }
  117. userSettingUpsert.UserID = userID
  118. userSetting, err := s.Store.UpsertUserSetting(ctx, userSettingUpsert)
  119. if err != nil {
  120. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert user setting").SetInternal(err)
  121. }
  122. c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
  123. if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(userSetting)); err != nil {
  124. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user setting response").SetInternal(err)
  125. }
  126. return nil
  127. })
  128. g.GET("/user/:id", func(c echo.Context) error {
  129. ctx := c.Request().Context()
  130. id, err := strconv.Atoi(c.Param("id"))
  131. if err != nil {
  132. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err)
  133. }
  134. user, err := s.Store.FindUser(ctx, &api.UserFind{
  135. ID: &id,
  136. })
  137. if err != nil {
  138. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user").SetInternal(err)
  139. }
  140. if user != nil {
  141. // data desensitize
  142. user.OpenID = ""
  143. user.Email = ""
  144. }
  145. c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
  146. if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil {
  147. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user response").SetInternal(err)
  148. }
  149. return nil
  150. })
  151. g.PATCH("/user/:id", func(c echo.Context) error {
  152. ctx := c.Request().Context()
  153. userID, err := strconv.Atoi(c.Param("id"))
  154. if err != nil {
  155. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
  156. }
  157. currentUserID, ok := c.Get(getUserIDContextKey()).(int)
  158. if !ok {
  159. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  160. }
  161. currentUser, err := s.Store.FindUser(ctx, &api.UserFind{
  162. ID: &currentUserID,
  163. })
  164. if err != nil {
  165. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  166. }
  167. if currentUser == nil {
  168. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
  169. } else if currentUser.Role != api.Host && currentUserID != userID {
  170. return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
  171. }
  172. currentTs := time.Now().Unix()
  173. userPatch := &api.UserPatch{
  174. UpdatedTs: &currentTs,
  175. }
  176. if err := json.NewDecoder(c.Request().Body).Decode(userPatch); err != nil {
  177. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
  178. }
  179. userPatch.ID = userID
  180. if err := userPatch.Validate(); err != nil {
  181. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format").SetInternal(err)
  182. }
  183. if userPatch.Password != nil && *userPatch.Password != "" {
  184. passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost)
  185. if err != nil {
  186. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
  187. }
  188. passwordHashStr := string(passwordHash)
  189. userPatch.PasswordHash = &passwordHashStr
  190. }
  191. if userPatch.ResetOpenID != nil && *userPatch.ResetOpenID {
  192. openID := common.GenUUID()
  193. userPatch.OpenID = &openID
  194. }
  195. user, err := s.Store.PatchUser(ctx, userPatch)
  196. if err != nil {
  197. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
  198. }
  199. userSettingList, err := s.Store.FindUserSettingList(ctx, &api.UserSettingFind{
  200. UserID: userID,
  201. })
  202. if err != nil {
  203. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  204. }
  205. user.UserSettingList = userSettingList
  206. c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
  207. if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user)); err != nil {
  208. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user response").SetInternal(err)
  209. }
  210. return nil
  211. })
  212. g.DELETE("/user/:id", func(c echo.Context) error {
  213. ctx := c.Request().Context()
  214. currentUserID, ok := c.Get(getUserIDContextKey()).(int)
  215. if !ok {
  216. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  217. }
  218. currentUser, err := s.Store.FindUser(ctx, &api.UserFind{
  219. ID: &currentUserID,
  220. })
  221. if err != nil {
  222. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  223. }
  224. if currentUser == nil {
  225. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
  226. } else if currentUser.Role != api.Host {
  227. return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
  228. }
  229. userID, err := strconv.Atoi(c.Param("id"))
  230. if err != nil {
  231. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
  232. }
  233. userDelete := &api.UserDelete{
  234. ID: userID,
  235. }
  236. if err := s.Store.DeleteUser(ctx, userDelete); err != nil {
  237. if common.ErrorCode(err) == common.NotFound {
  238. return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("User ID not found: %d", userID))
  239. }
  240. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err)
  241. }
  242. return c.JSON(http.StatusOK, true)
  243. })
  244. }
  245. func (s *Server) createUserCreateActivity(c echo.Context, user *api.User) error {
  246. ctx := c.Request().Context()
  247. payload := api.ActivityUserCreatePayload{
  248. UserID: user.ID,
  249. Username: user.Username,
  250. Role: user.Role,
  251. }
  252. payloadStr, err := json.Marshal(payload)
  253. if err != nil {
  254. return errors.Wrap(err, "failed to marshal activity payload")
  255. }
  256. activity, err := s.Store.CreateActivity(ctx, &api.ActivityCreate{
  257. CreatorID: user.ID,
  258. Type: api.ActivityUserCreate,
  259. Level: api.ActivityInfo,
  260. Payload: string(payloadStr),
  261. })
  262. if err != nil || activity == nil {
  263. return errors.Wrap(err, "failed to create activity")
  264. }
  265. s.Collector.Collect(ctx, &metric.Metric{
  266. Name: string(activity.Type),
  267. })
  268. return err
  269. }