server_account.go 8.2 KB

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