server_account.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. package server
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "heckel.io/ntfy/user"
  6. "heckel.io/ntfy/util"
  7. "io"
  8. "net/http"
  9. )
  10. func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error {
  11. admin := v.user != nil && v.user.Role == user.RoleAdmin
  12. if !admin {
  13. if !s.config.EnableSignup {
  14. return errHTTPBadRequestSignupNotEnabled
  15. } else if v.user != nil {
  16. return errHTTPUnauthorized // Cannot create account from user context
  17. }
  18. }
  19. body, err := util.Peek(r.Body, 4096) // FIXME
  20. if err != nil {
  21. return err
  22. }
  23. defer r.Body.Close()
  24. var newAccount apiAccountCreateRequest
  25. if err := json.NewDecoder(body).Decode(&newAccount); err != nil {
  26. return err
  27. }
  28. if existingUser, _ := s.userManager.User(newAccount.Username); existingUser != nil {
  29. return errHTTPConflictUserExists
  30. }
  31. if v.accountLimiter != nil && !v.accountLimiter.Allow() {
  32. return errHTTPTooManyRequestsAccountCreateLimit
  33. }
  34. if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil { // TODO this should return a User
  35. return err
  36. }
  37. w.Header().Set("Content-Type", "application/json")
  38. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  39. // FIXME return something
  40. return nil
  41. }
  42. func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *visitor) error {
  43. w.Header().Set("Content-Type", "application/json")
  44. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  45. stats, err := v.Stats()
  46. if err != nil {
  47. return err
  48. }
  49. response := &apiAccountSettingsResponse{
  50. Stats: &apiAccountStats{
  51. Messages: stats.Messages,
  52. MessagesRemaining: stats.MessagesRemaining,
  53. Emails: stats.Emails,
  54. EmailsRemaining: stats.EmailsRemaining,
  55. AttachmentTotalSize: stats.AttachmentTotalSize,
  56. AttachmentTotalSizeRemaining: stats.AttachmentTotalSizeRemaining,
  57. },
  58. Limits: &apiAccountLimits{
  59. Basis: stats.Basis,
  60. Messages: stats.MessagesLimit,
  61. Emails: stats.EmailsLimit,
  62. AttachmentTotalSize: stats.AttachmentTotalSizeLimit,
  63. AttachmentFileSize: stats.AttachmentFileSizeLimit,
  64. },
  65. }
  66. if v.user != nil {
  67. response.Username = v.user.Name
  68. response.Role = string(v.user.Role)
  69. if v.user.Prefs != nil {
  70. if v.user.Prefs.Language != "" {
  71. response.Language = v.user.Prefs.Language
  72. }
  73. if v.user.Prefs.Notification != nil {
  74. response.Notification = v.user.Prefs.Notification
  75. }
  76. if v.user.Prefs.Subscriptions != nil {
  77. response.Subscriptions = v.user.Prefs.Subscriptions
  78. }
  79. }
  80. if v.user.Plan != nil {
  81. response.Plan = &apiAccountPlan{
  82. Code: v.user.Plan.Code,
  83. Upgradable: v.user.Plan.Upgradable,
  84. }
  85. } else if v.user.Role == user.RoleAdmin {
  86. response.Plan = &apiAccountPlan{
  87. Code: string(user.PlanUnlimited),
  88. Upgradable: false,
  89. }
  90. } else {
  91. response.Plan = &apiAccountPlan{
  92. Code: string(user.PlanDefault),
  93. Upgradable: true,
  94. }
  95. }
  96. } else {
  97. response.Username = user.Everyone
  98. response.Role = string(user.RoleAnonymous)
  99. response.Plan = &apiAccountPlan{
  100. Code: string(user.PlanNone),
  101. Upgradable: true,
  102. }
  103. }
  104. if err := json.NewEncoder(w).Encode(response); err != nil {
  105. return err
  106. }
  107. return nil
  108. }
  109. func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
  110. if v.user == nil {
  111. return errHTTPUnauthorized
  112. }
  113. if err := s.userManager.RemoveUser(v.user.Name); err != nil {
  114. return err
  115. }
  116. w.Header().Set("Content-Type", "application/json")
  117. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  118. // FIXME return something
  119. return nil
  120. }
  121. func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
  122. if v.user == nil {
  123. return errHTTPUnauthorized
  124. }
  125. body, err := util.Peek(r.Body, 4096) // FIXME
  126. if err != nil {
  127. return err
  128. }
  129. defer r.Body.Close()
  130. var newPassword apiAccountCreateRequest // Re-use!
  131. if err := json.NewDecoder(body).Decode(&newPassword); err != nil {
  132. return err
  133. }
  134. if err := s.userManager.ChangePassword(v.user.Name, newPassword.Password); err != nil {
  135. return err
  136. }
  137. w.Header().Set("Content-Type", "application/json")
  138. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  139. // FIXME return something
  140. return nil
  141. }
  142. func (s *Server) handleAccountTokenIssue(w http.ResponseWriter, r *http.Request, v *visitor) error {
  143. // TODO rate limit
  144. if v.user == nil {
  145. return errHTTPUnauthorized
  146. }
  147. token, err := s.userManager.CreateToken(v.user)
  148. if err != nil {
  149. return err
  150. }
  151. w.Header().Set("Content-Type", "application/json")
  152. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  153. response := &apiAccountTokenResponse{
  154. Token: token.Value,
  155. Expires: token.Expires,
  156. }
  157. if err := json.NewEncoder(w).Encode(response); err != nil {
  158. return err
  159. }
  160. return nil
  161. }
  162. func (s *Server) handleAccountTokenExtend(w http.ResponseWriter, r *http.Request, v *visitor) error {
  163. // TODO rate limit
  164. if v.user == nil {
  165. return errHTTPUnauthorized
  166. } else if v.user.Token == "" {
  167. return errHTTPBadRequestNoTokenProvided
  168. }
  169. token, err := s.userManager.ExtendToken(v.user)
  170. if err != nil {
  171. return err
  172. }
  173. w.Header().Set("Content-Type", "application/json")
  174. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  175. response := &apiAccountTokenResponse{
  176. Token: token.Value,
  177. Expires: token.Expires,
  178. }
  179. if err := json.NewEncoder(w).Encode(response); err != nil {
  180. return err
  181. }
  182. return nil
  183. }
  184. func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
  185. // TODO rate limit
  186. if v.user == nil || v.user.Token == "" {
  187. return errHTTPUnauthorized
  188. }
  189. if err := s.userManager.RemoveToken(v.user); err != nil {
  190. return err
  191. }
  192. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  193. return nil
  194. }
  195. func (s *Server) handleAccountSettingsChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
  196. if v.user == nil {
  197. return errors.New("no user")
  198. }
  199. w.Header().Set("Content-Type", "application/json")
  200. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  201. body, err := util.Peek(r.Body, 4096) // FIXME
  202. if err != nil {
  203. return err
  204. }
  205. defer r.Body.Close()
  206. var newPrefs user.Prefs
  207. if err := json.NewDecoder(body).Decode(&newPrefs); err != nil {
  208. return err
  209. }
  210. if v.user.Prefs == nil {
  211. v.user.Prefs = &user.Prefs{}
  212. }
  213. prefs := v.user.Prefs
  214. if newPrefs.Language != "" {
  215. prefs.Language = newPrefs.Language
  216. }
  217. if newPrefs.Notification != nil {
  218. if prefs.Notification == nil {
  219. prefs.Notification = &user.NotificationPrefs{}
  220. }
  221. if newPrefs.Notification.DeleteAfter > 0 {
  222. prefs.Notification.DeleteAfter = newPrefs.Notification.DeleteAfter
  223. }
  224. if newPrefs.Notification.Sound != "" {
  225. prefs.Notification.Sound = newPrefs.Notification.Sound
  226. }
  227. if newPrefs.Notification.MinPriority > 0 {
  228. prefs.Notification.MinPriority = newPrefs.Notification.MinPriority
  229. }
  230. }
  231. return s.userManager.ChangeSettings(v.user)
  232. }
  233. func (s *Server) handleAccountSubscriptionAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
  234. if v.user == nil {
  235. return errors.New("no user")
  236. }
  237. newSubscription, err := readJSONBody[user.Subscription](r.Body)
  238. if err != nil {
  239. return err
  240. }
  241. if v.user.Prefs == nil {
  242. v.user.Prefs = &user.Prefs{}
  243. }
  244. newSubscription.ID = "" // Client cannot set ID
  245. for _, subscription := range v.user.Prefs.Subscriptions {
  246. if newSubscription.BaseURL == subscription.BaseURL && newSubscription.Topic == subscription.Topic {
  247. newSubscription = subscription
  248. break
  249. }
  250. }
  251. if newSubscription.ID == "" {
  252. newSubscription.ID = util.RandomString(16)
  253. v.user.Prefs.Subscriptions = append(v.user.Prefs.Subscriptions, newSubscription)
  254. if err := s.userManager.ChangeSettings(v.user); err != nil {
  255. return err
  256. }
  257. }
  258. w.Header().Set("Content-Type", "application/json")
  259. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  260. if err := json.NewEncoder(w).Encode(newSubscription); err != nil {
  261. return err
  262. }
  263. return nil
  264. }
  265. func (s *Server) handleAccountSubscriptionChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
  266. if v.user == nil {
  267. return errors.New("no user") // FIXME s.ensureUser
  268. }
  269. w.Header().Set("Content-Type", "application/json")
  270. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  271. matches := accountSubscriptionSingleRegex.FindStringSubmatch(r.URL.Path)
  272. if len(matches) != 2 {
  273. return errHTTPInternalErrorInvalidFilePath // FIXME
  274. }
  275. updatedSubscription, err := readJSONBody[user.Subscription](r.Body)
  276. if err != nil {
  277. return err
  278. }
  279. subscriptionID := matches[1]
  280. if v.user.Prefs == nil || v.user.Prefs.Subscriptions == nil {
  281. return errHTTPNotFound
  282. }
  283. var subscription *user.Subscription
  284. for _, sub := range v.user.Prefs.Subscriptions {
  285. if sub.ID == subscriptionID {
  286. sub.DisplayName = updatedSubscription.DisplayName
  287. subscription = sub
  288. break
  289. }
  290. }
  291. if subscription == nil {
  292. return errHTTPNotFound
  293. }
  294. if err := s.userManager.ChangeSettings(v.user); err != nil {
  295. return err
  296. }
  297. w.Header().Set("Content-Type", "application/json")
  298. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  299. if err := json.NewEncoder(w).Encode(subscription); err != nil {
  300. return err
  301. }
  302. return nil
  303. }
  304. func (s *Server) handleAccountSubscriptionDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
  305. if v.user == nil {
  306. return errors.New("no user")
  307. }
  308. w.Header().Set("Content-Type", "application/json")
  309. w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
  310. matches := accountSubscriptionSingleRegex.FindStringSubmatch(r.URL.Path)
  311. if len(matches) != 2 {
  312. return errHTTPInternalErrorInvalidFilePath // FIXME
  313. }
  314. subscriptionID := matches[1]
  315. if v.user.Prefs == nil || v.user.Prefs.Subscriptions == nil {
  316. return nil
  317. }
  318. newSubscriptions := make([]*user.Subscription, 0)
  319. for _, subscription := range v.user.Prefs.Subscriptions {
  320. if subscription.ID != subscriptionID {
  321. newSubscriptions = append(newSubscriptions, subscription)
  322. }
  323. }
  324. if len(newSubscriptions) < len(v.user.Prefs.Subscriptions) {
  325. v.user.Prefs.Subscriptions = newSubscriptions
  326. if err := s.userManager.ChangeSettings(v.user); err != nil {
  327. return err
  328. }
  329. }
  330. return nil
  331. }
  332. func readJSONBody[T any](body io.ReadCloser) (*T, error) {
  333. body, err := util.Peek(body, 4096)
  334. if err != nil {
  335. return nil, err
  336. }
  337. defer body.Close()
  338. var obj T
  339. if err := json.NewDecoder(body).Decode(&obj); err != nil {
  340. return nil, err
  341. }
  342. return &obj, nil
  343. }