user.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package v1
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "github.com/labstack/echo/v4"
  8. "github.com/pkg/errors"
  9. "github.com/usememos/memos/api/auth"
  10. "github.com/usememos/memos/common/util"
  11. "github.com/usememos/memos/store"
  12. "golang.org/x/crypto/bcrypt"
  13. )
  14. // Role is the type of a role.
  15. type Role string
  16. const (
  17. // RoleHost is the HOST role.
  18. RoleHost Role = "HOST"
  19. // RoleAdmin is the ADMIN role.
  20. RoleAdmin Role = "ADMIN"
  21. // RoleUser is the USER role.
  22. RoleUser Role = "USER"
  23. )
  24. func (role Role) String() string {
  25. return string(role)
  26. }
  27. type User struct {
  28. ID int32 `json:"id"`
  29. // Standard fields
  30. RowStatus RowStatus `json:"rowStatus"`
  31. CreatedTs int64 `json:"createdTs"`
  32. UpdatedTs int64 `json:"updatedTs"`
  33. // Domain specific fields
  34. Username string `json:"username"`
  35. Role Role `json:"role"`
  36. Email string `json:"email"`
  37. Nickname string `json:"nickname"`
  38. PasswordHash string `json:"-"`
  39. OpenID string `json:"openId"`
  40. AvatarURL string `json:"avatarUrl"`
  41. UserSettingList []*UserSetting `json:"userSettingList"`
  42. }
  43. type CreateUserRequest struct {
  44. Username string `json:"username"`
  45. Role Role `json:"role"`
  46. Email string `json:"email"`
  47. Nickname string `json:"nickname"`
  48. Password string `json:"password"`
  49. }
  50. func (create CreateUserRequest) Validate() error {
  51. if len(create.Username) < 3 {
  52. return fmt.Errorf("username is too short, minimum length is 3")
  53. }
  54. if len(create.Username) > 32 {
  55. return fmt.Errorf("username is too long, maximum length is 32")
  56. }
  57. if len(create.Password) < 3 {
  58. return fmt.Errorf("password is too short, minimum length is 3")
  59. }
  60. if len(create.Password) > 512 {
  61. return fmt.Errorf("password is too long, maximum length is 512")
  62. }
  63. if len(create.Nickname) > 64 {
  64. return fmt.Errorf("nickname is too long, maximum length is 64")
  65. }
  66. if create.Email != "" {
  67. if len(create.Email) > 256 {
  68. return fmt.Errorf("email is too long, maximum length is 256")
  69. }
  70. if !util.ValidateEmail(create.Email) {
  71. return fmt.Errorf("invalid email format")
  72. }
  73. }
  74. return nil
  75. }
  76. type UpdateUserRequest struct {
  77. RowStatus *RowStatus `json:"rowStatus"`
  78. Username *string `json:"username"`
  79. Email *string `json:"email"`
  80. Nickname *string `json:"nickname"`
  81. Password *string `json:"password"`
  82. ResetOpenID *bool `json:"resetOpenId"`
  83. AvatarURL *string `json:"avatarUrl"`
  84. }
  85. func (update UpdateUserRequest) Validate() error {
  86. if update.Username != nil && len(*update.Username) < 3 {
  87. return fmt.Errorf("username is too short, minimum length is 3")
  88. }
  89. if update.Username != nil && len(*update.Username) > 32 {
  90. return fmt.Errorf("username is too long, maximum length is 32")
  91. }
  92. if update.Password != nil && len(*update.Password) < 3 {
  93. return fmt.Errorf("password is too short, minimum length is 3")
  94. }
  95. if update.Password != nil && len(*update.Password) > 512 {
  96. return fmt.Errorf("password is too long, maximum length is 512")
  97. }
  98. if update.Nickname != nil && len(*update.Nickname) > 64 {
  99. return fmt.Errorf("nickname is too long, maximum length is 64")
  100. }
  101. if update.AvatarURL != nil {
  102. if len(*update.AvatarURL) > 2<<20 {
  103. return fmt.Errorf("avatar is too large, maximum is 2MB")
  104. }
  105. }
  106. if update.Email != nil && *update.Email != "" {
  107. if len(*update.Email) > 256 {
  108. return fmt.Errorf("email is too long, maximum length is 256")
  109. }
  110. if !util.ValidateEmail(*update.Email) {
  111. return fmt.Errorf("invalid email format")
  112. }
  113. }
  114. return nil
  115. }
  116. func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
  117. // POST /user - Create a new user.
  118. g.POST("/user", func(c echo.Context) error {
  119. ctx := c.Request().Context()
  120. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  121. if !ok {
  122. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  123. }
  124. currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
  125. ID: &userID,
  126. })
  127. if err != nil {
  128. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
  129. }
  130. if currentUser == nil {
  131. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  132. }
  133. if currentUser.Role != store.RoleHost {
  134. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to create user")
  135. }
  136. userCreate := &CreateUserRequest{}
  137. if err := json.NewDecoder(c.Request().Body).Decode(userCreate); err != nil {
  138. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user request").SetInternal(err)
  139. }
  140. if err := userCreate.Validate(); err != nil {
  141. return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
  142. }
  143. // Disallow host user to be created.
  144. if userCreate.Role == RoleHost {
  145. return echo.NewHTTPError(http.StatusForbidden, "Could not create host user")
  146. }
  147. passwordHash, err := bcrypt.GenerateFromPassword([]byte(userCreate.Password), bcrypt.DefaultCost)
  148. if err != nil {
  149. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
  150. }
  151. user, err := s.Store.CreateUser(ctx, &store.User{
  152. Username: userCreate.Username,
  153. Role: store.Role(userCreate.Role),
  154. Email: userCreate.Email,
  155. Nickname: userCreate.Nickname,
  156. PasswordHash: string(passwordHash),
  157. OpenID: util.GenUUID(),
  158. })
  159. if err != nil {
  160. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
  161. }
  162. userMessage := convertUserFromStore(user)
  163. if err := s.createUserCreateActivity(c, userMessage); err != nil {
  164. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
  165. }
  166. return c.JSON(http.StatusOK, userMessage)
  167. })
  168. // GET /user - List all users.
  169. g.GET("/user", func(c echo.Context) error {
  170. ctx := c.Request().Context()
  171. list, err := s.Store.ListUsers(ctx, &store.FindUser{})
  172. if err != nil {
  173. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user list").SetInternal(err)
  174. }
  175. userMessageList := make([]*User, 0, len(list))
  176. for _, user := range list {
  177. userMessage := convertUserFromStore(user)
  178. // data desensitize
  179. userMessage.OpenID = ""
  180. userMessage.Email = ""
  181. userMessageList = append(userMessageList, userMessage)
  182. }
  183. return c.JSON(http.StatusOK, userMessageList)
  184. })
  185. // GET /user/me - Get current user.
  186. g.GET("/user/me", func(c echo.Context) error {
  187. ctx := c.Request().Context()
  188. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  189. if !ok {
  190. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  191. }
  192. user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &userID})
  193. if err != nil {
  194. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  195. }
  196. if user == nil {
  197. return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
  198. }
  199. list, err := s.Store.ListUserSettings(ctx, &store.FindUserSetting{
  200. UserID: &userID,
  201. })
  202. if err != nil {
  203. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  204. }
  205. userSettingList := []*UserSetting{}
  206. for _, userSetting := range list {
  207. userSettingList = append(userSettingList, convertUserSettingFromStore(userSetting))
  208. }
  209. userMessage := convertUserFromStore(user)
  210. userMessage.UserSettingList = userSettingList
  211. return c.JSON(http.StatusOK, userMessage)
  212. })
  213. // GET /user/:id - Get user by id.
  214. g.GET("/user/:id", func(c echo.Context) error {
  215. ctx := c.Request().Context()
  216. id, err := util.ConvertStringToInt32(c.Param("id"))
  217. if err != nil {
  218. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err)
  219. }
  220. user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &id})
  221. if err != nil {
  222. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  223. }
  224. if user == nil {
  225. return echo.NewHTTPError(http.StatusNotFound, "User not found")
  226. }
  227. userMessage := convertUserFromStore(user)
  228. // data desensitize
  229. userMessage.OpenID = ""
  230. userMessage.Email = ""
  231. return c.JSON(http.StatusOK, userMessage)
  232. })
  233. // GET /user/name/:username - Get user by username.
  234. // NOTE: This should be moved to /api/v2/user/:username
  235. g.GET("/user/name/:username", func(c echo.Context) error {
  236. ctx := c.Request().Context()
  237. username := c.Param("username")
  238. user, err := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
  239. if err != nil {
  240. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  241. }
  242. if user == nil {
  243. return echo.NewHTTPError(http.StatusNotFound, "User not found")
  244. }
  245. userMessage := convertUserFromStore(user)
  246. // data desensitize
  247. userMessage.OpenID = ""
  248. userMessage.Email = ""
  249. return c.JSON(http.StatusOK, userMessage)
  250. })
  251. // PUT /user/:id - Update user by id.
  252. g.PATCH("/user/:id", func(c echo.Context) error {
  253. ctx := c.Request().Context()
  254. userID, err := util.ConvertStringToInt32(c.Param("id"))
  255. if err != nil {
  256. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
  257. }
  258. currentUserID, ok := c.Get(auth.UserIDContextKey).(int32)
  259. if !ok {
  260. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  261. }
  262. currentUser, err := s.Store.GetUser(ctx, &store.FindUser{ID: &currentUserID})
  263. if err != nil {
  264. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  265. }
  266. if currentUser == nil {
  267. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
  268. } else if currentUser.Role != store.RoleHost && currentUserID != userID {
  269. return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to update user").SetInternal(err)
  270. }
  271. request := &UpdateUserRequest{}
  272. if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
  273. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
  274. }
  275. if err := request.Validate(); err != nil {
  276. return echo.NewHTTPError(http.StatusBadRequest, "Invalid update user request").SetInternal(err)
  277. }
  278. currentTs := time.Now().Unix()
  279. userUpdate := &store.UpdateUser{
  280. ID: userID,
  281. UpdatedTs: &currentTs,
  282. }
  283. if request.RowStatus != nil {
  284. rowStatus := store.RowStatus(request.RowStatus.String())
  285. userUpdate.RowStatus = &rowStatus
  286. }
  287. if request.Username != nil {
  288. userUpdate.Username = request.Username
  289. }
  290. if request.Email != nil {
  291. userUpdate.Email = request.Email
  292. }
  293. if request.Nickname != nil {
  294. userUpdate.Nickname = request.Nickname
  295. }
  296. if request.Password != nil {
  297. passwordHash, err := bcrypt.GenerateFromPassword([]byte(*request.Password), bcrypt.DefaultCost)
  298. if err != nil {
  299. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
  300. }
  301. passwordHashStr := string(passwordHash)
  302. userUpdate.PasswordHash = &passwordHashStr
  303. }
  304. if request.ResetOpenID != nil && *request.ResetOpenID {
  305. openID := util.GenUUID()
  306. userUpdate.OpenID = &openID
  307. }
  308. if request.AvatarURL != nil {
  309. userUpdate.AvatarURL = request.AvatarURL
  310. }
  311. user, err := s.Store.UpdateUser(ctx, userUpdate)
  312. if err != nil {
  313. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
  314. }
  315. list, err := s.Store.ListUserSettings(ctx, &store.FindUserSetting{
  316. UserID: &userID,
  317. })
  318. if err != nil {
  319. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find userSettingList").SetInternal(err)
  320. }
  321. userSettingList := []*UserSetting{}
  322. for _, userSetting := range list {
  323. userSettingList = append(userSettingList, convertUserSettingFromStore(userSetting))
  324. }
  325. userMessage := convertUserFromStore(user)
  326. userMessage.UserSettingList = userSettingList
  327. return c.JSON(http.StatusOK, userMessage)
  328. })
  329. // DELETE /user/:id - Delete user by id.
  330. g.DELETE("/user/:id", func(c echo.Context) error {
  331. ctx := c.Request().Context()
  332. currentUserID, ok := c.Get(auth.UserIDContextKey).(int32)
  333. if !ok {
  334. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  335. }
  336. currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
  337. ID: &currentUserID,
  338. })
  339. if err != nil {
  340. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  341. }
  342. if currentUser == nil {
  343. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
  344. } else if currentUser.Role != store.RoleHost {
  345. return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to delete user").SetInternal(err)
  346. }
  347. userID, err := util.ConvertStringToInt32(c.Param("id"))
  348. if err != nil {
  349. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
  350. }
  351. userDelete := &store.DeleteUser{
  352. ID: userID,
  353. }
  354. if err := s.Store.DeleteUser(ctx, userDelete); err != nil {
  355. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err)
  356. }
  357. return c.JSON(http.StatusOK, true)
  358. })
  359. }
  360. func (s *APIV1Service) createUserCreateActivity(c echo.Context, user *User) error {
  361. ctx := c.Request().Context()
  362. payload := ActivityUserCreatePayload{
  363. UserID: user.ID,
  364. Username: user.Username,
  365. Role: user.Role,
  366. }
  367. payloadBytes, err := json.Marshal(payload)
  368. if err != nil {
  369. return errors.Wrap(err, "failed to marshal activity payload")
  370. }
  371. activity, err := s.Store.CreateActivity(ctx, &store.Activity{
  372. CreatorID: user.ID,
  373. Type: ActivityUserCreate.String(),
  374. Level: ActivityInfo.String(),
  375. Payload: string(payloadBytes),
  376. })
  377. if err != nil || activity == nil {
  378. return errors.Wrap(err, "failed to create activity")
  379. }
  380. return err
  381. }
  382. func convertUserFromStore(user *store.User) *User {
  383. return &User{
  384. ID: user.ID,
  385. RowStatus: RowStatus(user.RowStatus),
  386. CreatedTs: user.CreatedTs,
  387. UpdatedTs: user.UpdatedTs,
  388. Username: user.Username,
  389. Role: Role(user.Role),
  390. Email: user.Email,
  391. Nickname: user.Nickname,
  392. PasswordHash: user.PasswordHash,
  393. OpenID: user.OpenID,
  394. AvatarURL: user.AvatarURL,
  395. }
  396. }