user.go 10 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. apiv1 "github.com/usememos/memos/api/v1"
  11. "github.com/usememos/memos/common"
  12. "github.com/usememos/memos/store"
  13. "github.com/labstack/echo/v4"
  14. "golang.org/x/crypto/bcrypt"
  15. )
  16. func (s *Server) registerUserRoutes(g *echo.Group) {
  17. g.POST("/user", func(c echo.Context) error {
  18. ctx := c.Request().Context()
  19. userID, ok := c.Get(getUserIDContextKey()).(int)
  20. if !ok {
  21. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  22. }
  23. currentUser, err := s.Store.FindUser(ctx, &api.UserFind{
  24. ID: &userID,
  25. })
  26. if err != nil {
  27. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
  28. }
  29. if currentUser.Role != api.Host {
  30. return echo.NewHTTPError(http.StatusUnauthorized, "Only Host user can create member")
  31. }
  32. userCreate := &api.UserCreate{}
  33. if err := json.NewDecoder(c.Request().Body).Decode(userCreate); err != nil {
  34. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user request").SetInternal(err)
  35. }
  36. if userCreate.Role == api.Host {
  37. return echo.NewHTTPError(http.StatusForbidden, "Could not create host user")
  38. }
  39. userCreate.OpenID = common.GenUUID()
  40. if err := userCreate.Validate(); err != nil {
  41. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
  42. }
  43. passwordHash, err := bcrypt.GenerateFromPassword([]byte(userCreate.Password), bcrypt.DefaultCost)
  44. if err != nil {
  45. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
  46. }
  47. userCreate.PasswordHash = string(passwordHash)
  48. user, err := s.Store.CreateUser(ctx, userCreate)
  49. if err != nil {
  50. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
  51. }
  52. if err := s.createUserCreateActivity(c, user); err != nil {
  53. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
  54. }
  55. return c.JSON(http.StatusOK, composeResponse(user))
  56. })
  57. g.GET("/user", func(c echo.Context) error {
  58. ctx := c.Request().Context()
  59. userList, err := s.Store.FindUserList(ctx, &api.UserFind{})
  60. if err != nil {
  61. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user list").SetInternal(err)
  62. }
  63. for _, user := range userList {
  64. // data desensitize
  65. user.OpenID = ""
  66. user.Email = ""
  67. }
  68. return c.JSON(http.StatusOK, composeResponse(userList))
  69. })
  70. g.POST("/user/setting", func(c echo.Context) error {
  71. ctx := c.Request().Context()
  72. userID, ok := c.Get(getUserIDContextKey()).(int)
  73. if !ok {
  74. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  75. }
  76. userSettingUpsert := &apiv1.UserSettingUpsert{}
  77. if err := json.NewDecoder(c.Request().Body).Decode(userSettingUpsert); err != nil {
  78. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user setting upsert request").SetInternal(err)
  79. }
  80. if err := userSettingUpsert.Validate(); err != nil {
  81. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user setting format").SetInternal(err)
  82. }
  83. userSettingUpsert.UserID = userID
  84. userSetting, err := s.Store.UpsertUserSetting(ctx, &store.UserSetting{
  85. UserID: userID,
  86. Key: userSettingUpsert.Key.String(),
  87. Value: userSettingUpsert.Value,
  88. })
  89. if err != nil {
  90. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert user setting").SetInternal(err)
  91. }
  92. userSettingMessage := convertUserSettingFromStore(userSetting)
  93. return c.JSON(http.StatusOK, composeResponse(userSettingMessage))
  94. })
  95. // GET /api/user/me is used to check if the user is logged in.
  96. g.GET("/user/me", func(c echo.Context) error {
  97. ctx := c.Request().Context()
  98. userID, ok := c.Get(getUserIDContextKey()).(int)
  99. if !ok {
  100. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  101. }
  102. userFind := &api.UserFind{
  103. ID: &userID,
  104. }
  105. user, err := s.Store.FindUser(ctx, userFind)
  106. if err != nil {
  107. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  108. }
  109. list, err := s.Store.ListUserSettings(ctx, &store.FindUserSetting{
  110. UserID: &userID,
  111. })
  112. if err != nil {
  113. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  114. }
  115. userSettingList := []*api.UserSetting{}
  116. for _, item := range list {
  117. userSetting := convertUserSettingFromStore(item)
  118. userSettingList = append(userSettingList, &api.UserSetting{
  119. UserID: userSetting.UserID,
  120. Key: api.UserSettingKey(userSetting.Key),
  121. Value: userSetting.Value,
  122. })
  123. }
  124. user.UserSettingList = userSettingList
  125. return c.JSON(http.StatusOK, composeResponse(user))
  126. })
  127. g.GET("/user/:id", func(c echo.Context) error {
  128. ctx := c.Request().Context()
  129. id, err := strconv.Atoi(c.Param("id"))
  130. if err != nil {
  131. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err)
  132. }
  133. user, err := s.Store.FindUser(ctx, &api.UserFind{
  134. ID: &id,
  135. })
  136. if err != nil {
  137. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user").SetInternal(err)
  138. }
  139. if user != nil {
  140. // data desensitize
  141. user.OpenID = ""
  142. user.Email = ""
  143. }
  144. return c.JSON(http.StatusOK, composeResponse(user))
  145. })
  146. g.PATCH("/user/:id", func(c echo.Context) error {
  147. ctx := c.Request().Context()
  148. userID, err := strconv.Atoi(c.Param("id"))
  149. if err != nil {
  150. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
  151. }
  152. currentUserID, ok := c.Get(getUserIDContextKey()).(int)
  153. if !ok {
  154. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  155. }
  156. currentUser, err := s.Store.FindUser(ctx, &api.UserFind{
  157. ID: &currentUserID,
  158. })
  159. if err != nil {
  160. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  161. }
  162. if currentUser == nil {
  163. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
  164. } else if currentUser.Role != api.Host && currentUserID != userID {
  165. return echo.NewHTTPError(http.StatusForbidden, "Access forbidden for current session user").SetInternal(err)
  166. }
  167. currentTs := time.Now().Unix()
  168. userPatch := &api.UserPatch{
  169. UpdatedTs: &currentTs,
  170. }
  171. if err := json.NewDecoder(c.Request().Body).Decode(userPatch); err != nil {
  172. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
  173. }
  174. userPatch.ID = userID
  175. if userPatch.Password != nil && *userPatch.Password != "" {
  176. passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost)
  177. if err != nil {
  178. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
  179. }
  180. passwordHashStr := string(passwordHash)
  181. userPatch.PasswordHash = &passwordHashStr
  182. }
  183. if userPatch.ResetOpenID != nil && *userPatch.ResetOpenID {
  184. openID := common.GenUUID()
  185. userPatch.OpenID = &openID
  186. }
  187. if err := userPatch.Validate(); err != nil {
  188. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format").SetInternal(err)
  189. }
  190. user, err := s.Store.PatchUser(ctx, userPatch)
  191. if err != nil {
  192. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
  193. }
  194. list, err := s.Store.ListUserSettings(ctx, &store.FindUserSetting{
  195. UserID: &userID,
  196. })
  197. if err != nil {
  198. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  199. }
  200. userSettingList := []*api.UserSetting{}
  201. for _, item := range list {
  202. userSetting := convertUserSettingFromStore(item)
  203. userSettingList = append(userSettingList, &api.UserSetting{
  204. UserID: userSetting.UserID,
  205. Key: api.UserSettingKey(userSetting.Key),
  206. Value: userSetting.Value,
  207. })
  208. }
  209. user.UserSettingList = userSettingList
  210. return c.JSON(http.StatusOK, composeResponse(user))
  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. payloadBytes, 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(payloadBytes),
  261. })
  262. if err != nil || activity == nil {
  263. return errors.Wrap(err, "failed to create activity")
  264. }
  265. return err
  266. }
  267. func convertUserSettingFromStore(userSetting *store.UserSetting) *apiv1.UserSetting {
  268. return &apiv1.UserSetting{
  269. UserID: userSetting.UserID,
  270. Key: apiv1.UserSettingKey(userSetting.Key),
  271. Value: userSetting.Value,
  272. }
  273. }