server_account.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. package server
  2. import (
  3. "encoding/json"
  4. "heckel.io/ntfy/log"
  5. "heckel.io/ntfy/user"
  6. "heckel.io/ntfy/util"
  7. "net/http"
  8. "net/netip"
  9. "strings"
  10. "time"
  11. )
  12. const (
  13. subscriptionIDLength = 16
  14. subscriptionIDPrefix = "su_"
  15. syncTopicAccountSyncEvent = "sync"
  16. tokenExpiryDuration = 72 * time.Hour // Extend tokens by this much
  17. )
  18. func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error {
  19. u := v.User()
  20. admin := u != nil && u.Role == user.RoleAdmin
  21. if !admin {
  22. if !s.config.EnableSignup {
  23. return errHTTPBadRequestSignupNotEnabled
  24. } else if u != nil {
  25. return errHTTPUnauthorized // Cannot create account from user context
  26. }
  27. if !v.AccountCreationAllowed() {
  28. return errHTTPTooManyRequestsLimitAccountCreation
  29. }
  30. }
  31. newAccount, err := readJSONWithLimit[apiAccountCreateRequest](r.Body, jsonBodyBytesLimit, false)
  32. if err != nil {
  33. return err
  34. }
  35. if existingUser, _ := s.userManager.User(newAccount.Username); existingUser != nil {
  36. return errHTTPConflictUserExists
  37. }
  38. if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil { // TODO this should return a User
  39. return err
  40. }
  41. return s.writeJSON(w, newSuccessResponse())
  42. }
  43. func (s *Server) handleAccountGet(w http.ResponseWriter, _ *http.Request, v *visitor) error {
  44. info, err := v.Info()
  45. if err != nil {
  46. return err
  47. }
  48. limits, stats := info.Limits, info.Stats
  49. response := &apiAccountResponse{
  50. Limits: &apiAccountLimits{
  51. Basis: string(limits.Basis),
  52. Messages: limits.MessageLimit,
  53. MessagesExpiryDuration: int64(limits.MessageExpiryDuration.Seconds()),
  54. Emails: limits.EmailLimit,
  55. Reservations: limits.ReservationsLimit,
  56. AttachmentTotalSize: limits.AttachmentTotalSizeLimit,
  57. AttachmentFileSize: limits.AttachmentFileSizeLimit,
  58. AttachmentExpiryDuration: int64(limits.AttachmentExpiryDuration.Seconds()),
  59. AttachmentBandwidth: limits.AttachmentBandwidthLimit,
  60. },
  61. Stats: &apiAccountStats{
  62. Messages: stats.Messages,
  63. MessagesRemaining: stats.MessagesRemaining,
  64. Emails: stats.Emails,
  65. EmailsRemaining: stats.EmailsRemaining,
  66. Reservations: stats.Reservations,
  67. ReservationsRemaining: stats.ReservationsRemaining,
  68. AttachmentTotalSize: stats.AttachmentTotalSize,
  69. AttachmentTotalSizeRemaining: stats.AttachmentTotalSizeRemaining,
  70. },
  71. }
  72. u := v.User()
  73. if u != nil {
  74. response.Username = u.Name
  75. response.Role = string(u.Role)
  76. response.SyncTopic = u.SyncTopic
  77. if u.Prefs != nil {
  78. if u.Prefs.Language != nil {
  79. response.Language = *u.Prefs.Language
  80. }
  81. if u.Prefs.Notification != nil {
  82. response.Notification = u.Prefs.Notification
  83. }
  84. if u.Prefs.Subscriptions != nil {
  85. response.Subscriptions = u.Prefs.Subscriptions
  86. }
  87. }
  88. if u.Tier != nil {
  89. response.Tier = &apiAccountTier{
  90. Code: u.Tier.Code,
  91. Name: u.Tier.Name,
  92. }
  93. }
  94. if u.Billing.StripeCustomerID != "" {
  95. response.Billing = &apiAccountBilling{
  96. Customer: true,
  97. Subscription: u.Billing.StripeSubscriptionID != "",
  98. Status: string(u.Billing.StripeSubscriptionStatus),
  99. PaidUntil: u.Billing.StripeSubscriptionPaidUntil.Unix(),
  100. CancelAt: u.Billing.StripeSubscriptionCancelAt.Unix(),
  101. }
  102. }
  103. reservations, err := s.userManager.Reservations(u.Name)
  104. if err != nil {
  105. return err
  106. }
  107. if len(reservations) > 0 {
  108. response.Reservations = make([]*apiAccountReservation, 0)
  109. for _, r := range reservations {
  110. response.Reservations = append(response.Reservations, &apiAccountReservation{
  111. Topic: r.Topic,
  112. Everyone: r.Everyone.String(),
  113. })
  114. }
  115. }
  116. tokens, err := s.userManager.Tokens(u.ID)
  117. if err != nil {
  118. return err
  119. }
  120. if len(tokens) > 0 {
  121. response.Tokens = make([]*apiAccountTokenResponse, 0)
  122. for _, t := range tokens {
  123. var lastOrigin string
  124. if t.LastOrigin != netip.IPv4Unspecified() {
  125. lastOrigin = t.LastOrigin.String()
  126. }
  127. response.Tokens = append(response.Tokens, &apiAccountTokenResponse{
  128. Token: t.Value,
  129. Label: t.Label,
  130. LastAccess: t.LastAccess.Unix(),
  131. LastOrigin: lastOrigin,
  132. Expires: t.Expires.Unix(),
  133. })
  134. }
  135. }
  136. } else {
  137. response.Username = user.Everyone
  138. response.Role = string(user.RoleAnonymous)
  139. }
  140. return s.writeJSON(w, response)
  141. }
  142. func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
  143. req, err := readJSONWithLimit[apiAccountDeleteRequest](r.Body, jsonBodyBytesLimit, false)
  144. if err != nil {
  145. return err
  146. } else if req.Password == "" {
  147. return errHTTPBadRequest
  148. }
  149. u := v.User()
  150. if _, err := s.userManager.Authenticate(u.Name, req.Password); err != nil {
  151. return errHTTPBadRequestIncorrectPasswordConfirmation
  152. }
  153. if u.Billing.StripeSubscriptionID != "" {
  154. log.Info("%s Canceling billing subscription %s", logHTTPPrefix(v, r), u.Billing.StripeSubscriptionID)
  155. if _, err := s.stripe.CancelSubscription(u.Billing.StripeSubscriptionID); err != nil {
  156. return err
  157. }
  158. }
  159. if err := s.maybeRemoveMessagesAndExcessReservations(logHTTPPrefix(v, r), u, 0); err != nil {
  160. return err
  161. }
  162. log.Info("%s Marking user %s as deleted", logHTTPPrefix(v, r), u.Name)
  163. if err := s.userManager.MarkUserRemoved(u); err != nil {
  164. return err
  165. }
  166. return s.writeJSON(w, newSuccessResponse())
  167. }
  168. func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
  169. req, err := readJSONWithLimit[apiAccountPasswordChangeRequest](r.Body, jsonBodyBytesLimit, false)
  170. if err != nil {
  171. return err
  172. } else if req.Password == "" || req.NewPassword == "" {
  173. return errHTTPBadRequest
  174. }
  175. u := v.User()
  176. if _, err := s.userManager.Authenticate(u.Name, req.Password); err != nil {
  177. return errHTTPBadRequestIncorrectPasswordConfirmation
  178. }
  179. if err := s.userManager.ChangePassword(u.Name, req.NewPassword); err != nil {
  180. return err
  181. }
  182. return s.writeJSON(w, newSuccessResponse())
  183. }
  184. func (s *Server) handleAccountTokenCreate(w http.ResponseWriter, r *http.Request, v *visitor) error {
  185. // TODO rate limit
  186. req, err := readJSONWithLimit[apiAccountTokenIssueRequest](r.Body, jsonBodyBytesLimit, true) // Allow empty body!
  187. if err != nil {
  188. return err
  189. }
  190. var label string
  191. if req.Label != nil {
  192. label = *req.Label
  193. }
  194. expires := time.Now().Add(tokenExpiryDuration)
  195. if req.Expires != nil {
  196. expires = time.Unix(*req.Expires, 0)
  197. }
  198. token, err := s.userManager.CreateToken(v.User().ID, label, expires, v.IP())
  199. if err != nil {
  200. return err
  201. }
  202. response := &apiAccountTokenResponse{
  203. Token: token.Value,
  204. Label: token.Label,
  205. LastAccess: token.LastAccess.Unix(),
  206. LastOrigin: token.LastOrigin.String(),
  207. Expires: token.Expires.Unix(),
  208. }
  209. return s.writeJSON(w, response)
  210. }
  211. func (s *Server) handleAccountTokenUpdate(w http.ResponseWriter, r *http.Request, v *visitor) error {
  212. // TODO rate limit
  213. u := v.User()
  214. req, err := readJSONWithLimit[apiAccountTokenUpdateRequest](r.Body, jsonBodyBytesLimit, true) // Allow empty body!
  215. if err != nil {
  216. return err
  217. } else if req.Token == "" {
  218. req.Token = u.Token
  219. if req.Token == "" {
  220. return errHTTPBadRequestNoTokenProvided
  221. }
  222. }
  223. var expires *time.Time
  224. if req.Expires != nil {
  225. expires = util.Time(time.Unix(*req.Expires, 0))
  226. } else if req.Label == nil {
  227. // If label and expires are not set, simply extend the token by 72 hours
  228. expires = util.Time(time.Now().Add(tokenExpiryDuration))
  229. }
  230. token, err := s.userManager.ChangeToken(u.ID, req.Token, req.Label, expires)
  231. if err != nil {
  232. return err
  233. }
  234. response := &apiAccountTokenResponse{
  235. Token: token.Value,
  236. Label: token.Label,
  237. LastAccess: token.LastAccess.Unix(),
  238. LastOrigin: token.LastOrigin.String(),
  239. Expires: token.Expires.Unix(),
  240. }
  241. return s.writeJSON(w, response)
  242. }
  243. func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
  244. // TODO rate limit
  245. u := v.User()
  246. token := readParam(r, "X-Token", "Token") // DELETEs cannot have a body, and we don't want it in the path
  247. if token == "" {
  248. token = u.Token
  249. if token == "" {
  250. return errHTTPBadRequestNoTokenProvided
  251. }
  252. }
  253. if err := s.userManager.RemoveToken(u.ID, token); err != nil {
  254. return err
  255. }
  256. return s.writeJSON(w, newSuccessResponse())
  257. }
  258. func (s *Server) handleAccountSettingsChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
  259. newPrefs, err := readJSONWithLimit[user.Prefs](r.Body, jsonBodyBytesLimit, false)
  260. if err != nil {
  261. return err
  262. }
  263. u := v.User()
  264. if u.Prefs == nil {
  265. u.Prefs = &user.Prefs{}
  266. }
  267. prefs := u.Prefs
  268. if newPrefs.Language != nil {
  269. prefs.Language = newPrefs.Language
  270. }
  271. if newPrefs.Notification != nil {
  272. if prefs.Notification == nil {
  273. prefs.Notification = &user.NotificationPrefs{}
  274. }
  275. if newPrefs.Notification.DeleteAfter != nil {
  276. prefs.Notification.DeleteAfter = newPrefs.Notification.DeleteAfter
  277. }
  278. if newPrefs.Notification.Sound != nil {
  279. prefs.Notification.Sound = newPrefs.Notification.Sound
  280. }
  281. if newPrefs.Notification.MinPriority != nil {
  282. prefs.Notification.MinPriority = newPrefs.Notification.MinPriority
  283. }
  284. }
  285. if err := s.userManager.ChangeSettings(u); err != nil {
  286. return err
  287. }
  288. return s.writeJSON(w, newSuccessResponse())
  289. }
  290. func (s *Server) handleAccountSubscriptionAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
  291. newSubscription, err := readJSONWithLimit[user.Subscription](r.Body, jsonBodyBytesLimit, false)
  292. if err != nil {
  293. return err
  294. }
  295. u := v.User()
  296. if u.Prefs == nil {
  297. u.Prefs = &user.Prefs{}
  298. }
  299. newSubscription.ID = "" // Client cannot set ID
  300. for _, subscription := range u.Prefs.Subscriptions {
  301. if newSubscription.BaseURL == subscription.BaseURL && newSubscription.Topic == subscription.Topic {
  302. newSubscription = subscription
  303. break
  304. }
  305. }
  306. if newSubscription.ID == "" {
  307. newSubscription.ID = util.RandomStringPrefix(subscriptionIDPrefix, subscriptionIDLength)
  308. u.Prefs.Subscriptions = append(u.Prefs.Subscriptions, newSubscription)
  309. if err := s.userManager.ChangeSettings(u); err != nil {
  310. return err
  311. }
  312. }
  313. return s.writeJSON(w, newSubscription)
  314. }
  315. func (s *Server) handleAccountSubscriptionChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
  316. matches := apiAccountSubscriptionSingleRegex.FindStringSubmatch(r.URL.Path)
  317. if len(matches) != 2 {
  318. return errHTTPInternalErrorInvalidPath
  319. }
  320. subscriptionID := matches[1]
  321. updatedSubscription, err := readJSONWithLimit[user.Subscription](r.Body, jsonBodyBytesLimit, false)
  322. if err != nil {
  323. return err
  324. }
  325. u := v.User()
  326. if u.Prefs == nil || u.Prefs.Subscriptions == nil {
  327. return errHTTPNotFound
  328. }
  329. var subscription *user.Subscription
  330. for _, sub := range u.Prefs.Subscriptions {
  331. if sub.ID == subscriptionID {
  332. sub.DisplayName = updatedSubscription.DisplayName
  333. subscription = sub
  334. break
  335. }
  336. }
  337. if subscription == nil {
  338. return errHTTPNotFound
  339. }
  340. if err := s.userManager.ChangeSettings(u); err != nil {
  341. return err
  342. }
  343. return s.writeJSON(w, subscription)
  344. }
  345. func (s *Server) handleAccountSubscriptionDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
  346. matches := apiAccountSubscriptionSingleRegex.FindStringSubmatch(r.URL.Path)
  347. if len(matches) != 2 {
  348. return errHTTPInternalErrorInvalidPath
  349. }
  350. subscriptionID := matches[1]
  351. u := v.User()
  352. if u.Prefs == nil || u.Prefs.Subscriptions == nil {
  353. return nil
  354. }
  355. newSubscriptions := make([]*user.Subscription, 0)
  356. for _, subscription := range u.Prefs.Subscriptions {
  357. if subscription.ID != subscriptionID {
  358. newSubscriptions = append(newSubscriptions, subscription)
  359. }
  360. }
  361. if len(newSubscriptions) < len(u.Prefs.Subscriptions) {
  362. u.Prefs.Subscriptions = newSubscriptions
  363. if err := s.userManager.ChangeSettings(u); err != nil {
  364. return err
  365. }
  366. }
  367. return s.writeJSON(w, newSuccessResponse())
  368. }
  369. func (s *Server) handleAccountReservationAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
  370. u := v.User()
  371. if u != nil && u.Role == user.RoleAdmin {
  372. return errHTTPBadRequestMakesNoSenseForAdmin
  373. }
  374. req, err := readJSONWithLimit[apiAccountReservationRequest](r.Body, jsonBodyBytesLimit, false)
  375. if err != nil {
  376. return err
  377. }
  378. if !topicRegex.MatchString(req.Topic) {
  379. return errHTTPBadRequestTopicInvalid
  380. }
  381. everyone, err := user.ParsePermission(req.Everyone)
  382. if err != nil {
  383. return errHTTPBadRequestPermissionInvalid
  384. }
  385. if u.Tier == nil {
  386. return errHTTPUnauthorized
  387. }
  388. // CHeck if we are allowed to reserve this topic
  389. if err := s.userManager.CheckAllowAccess(u.Name, req.Topic); err != nil {
  390. return errHTTPConflictTopicReserved
  391. }
  392. hasReservation, err := s.userManager.HasReservation(u.Name, req.Topic)
  393. if err != nil {
  394. return err
  395. }
  396. if !hasReservation {
  397. reservations, err := s.userManager.ReservationsCount(u.Name)
  398. if err != nil {
  399. return err
  400. } else if reservations >= u.Tier.ReservationLimit {
  401. return errHTTPTooManyRequestsLimitReservations
  402. }
  403. }
  404. // Actually add the reservation
  405. if err := s.userManager.AddReservation(u.Name, req.Topic, everyone); err != nil {
  406. return err
  407. }
  408. // Kill existing subscribers
  409. t, err := s.topicFromID(req.Topic)
  410. if err != nil {
  411. return err
  412. }
  413. t.CancelSubscribers(u.ID)
  414. return s.writeJSON(w, newSuccessResponse())
  415. }
  416. func (s *Server) handleAccountReservationDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
  417. matches := apiAccountReservationSingleRegex.FindStringSubmatch(r.URL.Path)
  418. if len(matches) != 2 {
  419. return errHTTPInternalErrorInvalidPath
  420. }
  421. topic := matches[1]
  422. if !topicRegex.MatchString(topic) {
  423. return errHTTPBadRequestTopicInvalid
  424. }
  425. u := v.User()
  426. authorized, err := s.userManager.HasReservation(u.Name, topic)
  427. if err != nil {
  428. return err
  429. } else if !authorized {
  430. return errHTTPUnauthorized
  431. }
  432. if err := s.userManager.RemoveReservations(u.Name, topic); err != nil {
  433. return err
  434. }
  435. deleteMessages := readBoolParam(r, false, "X-Delete-Messages", "Delete-Messages")
  436. if deleteMessages {
  437. if err := s.messageCache.ExpireMessages(topic); err != nil {
  438. return err
  439. }
  440. }
  441. return s.writeJSON(w, newSuccessResponse())
  442. }
  443. // maybeRemoveMessagesAndExcessReservations deletes topic reservations for the given user (if too many for tier),
  444. // and marks associated messages for the topics as deleted. This also eventually deletes attachments.
  445. // The process relies on the manager to perform the actual deletions (see runManager).
  446. func (s *Server) maybeRemoveMessagesAndExcessReservations(logPrefix string, u *user.User, reservationsLimit int64) error {
  447. reservations, err := s.userManager.Reservations(u.Name)
  448. if err != nil {
  449. return err
  450. } else if int64(len(reservations)) <= reservationsLimit {
  451. return nil
  452. }
  453. topics := make([]string, 0)
  454. for i := int64(len(reservations)) - 1; i >= reservationsLimit; i-- {
  455. topics = append(topics, reservations[i].Topic)
  456. }
  457. log.Info("%s Removing excess reservations for topics %s", logPrefix, strings.Join(topics, ", "))
  458. if err := s.userManager.RemoveReservations(u.Name, topics...); err != nil {
  459. return err
  460. }
  461. if err := s.messageCache.ExpireMessages(topics...); err != nil {
  462. return err
  463. }
  464. return nil
  465. }
  466. // publishSyncEventAsync kicks of a Go routine to publish a sync message to the user's sync topic
  467. func (s *Server) publishSyncEventAsync(v *visitor) {
  468. go func() {
  469. if err := s.publishSyncEvent(v); err != nil {
  470. log.Trace("%s Error publishing to user's sync topic: %s", v.String(), err.Error())
  471. }
  472. }()
  473. }
  474. // publishSyncEvent publishes a sync message to the user's sync topic
  475. func (s *Server) publishSyncEvent(v *visitor) error {
  476. u := v.User()
  477. if u == nil || u.SyncTopic == "" {
  478. return nil
  479. }
  480. log.Trace("Publishing sync event to user %s's sync topic %s", u.Name, u.SyncTopic)
  481. syncTopic, err := s.topicFromID(u.SyncTopic)
  482. if err != nil {
  483. return err
  484. }
  485. messageBytes, err := json.Marshal(&apiAccountSyncTopicResponse{Event: syncTopicAccountSyncEvent})
  486. if err != nil {
  487. return err
  488. }
  489. m := newDefaultMessage(syncTopic.ID, string(messageBytes))
  490. if err := syncTopic.Publish(v, m); err != nil {
  491. return err
  492. }
  493. return nil
  494. }