types.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. package server
  2. import (
  3. "heckel.io/ntfy/log"
  4. "heckel.io/ntfy/user"
  5. "net/http"
  6. "net/netip"
  7. "time"
  8. "heckel.io/ntfy/util"
  9. )
  10. // List of possible events
  11. const (
  12. openEvent = "open"
  13. keepaliveEvent = "keepalive"
  14. messageEvent = "message"
  15. pollRequestEvent = "poll_request"
  16. )
  17. const (
  18. messageIDLength = 12
  19. )
  20. // message represents a message published to a topic
  21. type message struct {
  22. ID string `json:"id"` // Random message ID
  23. Time int64 `json:"time"` // Unix time in seconds
  24. Expires int64 `json:"expires,omitempty"` // Unix time in seconds (not required for open/keepalive)
  25. Event string `json:"event"` // One of the above
  26. Topic string `json:"topic"`
  27. Title string `json:"title,omitempty"`
  28. Message string `json:"message,omitempty"`
  29. Priority int `json:"priority,omitempty"`
  30. Tags []string `json:"tags,omitempty"`
  31. Click string `json:"click,omitempty"`
  32. Icon string `json:"icon,omitempty"`
  33. Actions []*action `json:"actions,omitempty"`
  34. Attachment *attachment `json:"attachment,omitempty"`
  35. PollID string `json:"poll_id,omitempty"`
  36. Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
  37. Sender netip.Addr `json:"-"` // IP address of uploader, used for rate limiting
  38. User string `json:"-"` // Username of the uploader, used to associated attachments
  39. }
  40. func (m *message) Context() log.Context {
  41. fields := map[string]any{
  42. "topic": m.Topic,
  43. "message_id": m.ID,
  44. "message_time": m.Time,
  45. "message_event": m.Event,
  46. "message_body_size": len(m.Message),
  47. }
  48. if m.Sender.IsValid() {
  49. fields["message_sender"] = m.Sender.String()
  50. }
  51. if m.User != "" {
  52. fields["message_user"] = m.User
  53. }
  54. return fields
  55. }
  56. type attachment struct {
  57. Name string `json:"name"`
  58. Type string `json:"type,omitempty"`
  59. Size int64 `json:"size,omitempty"`
  60. Expires int64 `json:"expires,omitempty"`
  61. URL string `json:"url"`
  62. }
  63. type action struct {
  64. ID string `json:"id"`
  65. Action string `json:"action"` // "view", "broadcast", or "http"
  66. Label string `json:"label"` // action button label
  67. Clear bool `json:"clear"` // clear notification after successful execution
  68. URL string `json:"url,omitempty"` // used in "view" and "http" actions
  69. Method string `json:"method,omitempty"` // used in "http" action, default is POST (!)
  70. Headers map[string]string `json:"headers,omitempty"` // used in "http" action
  71. Body string `json:"body,omitempty"` // used in "http" action
  72. Intent string `json:"intent,omitempty"` // used in "broadcast" action
  73. Extras map[string]string `json:"extras,omitempty"` // used in "broadcast" action
  74. }
  75. func newAction() *action {
  76. return &action{
  77. Headers: make(map[string]string),
  78. Extras: make(map[string]string),
  79. }
  80. }
  81. // publishMessage is used as input when publishing as JSON
  82. type publishMessage struct {
  83. Topic string `json:"topic"`
  84. Title string `json:"title"`
  85. Message string `json:"message"`
  86. Priority int `json:"priority"`
  87. Tags []string `json:"tags"`
  88. Click string `json:"click"`
  89. Icon string `json:"icon"`
  90. Actions []action `json:"actions"`
  91. Attach string `json:"attach"`
  92. Filename string `json:"filename"`
  93. Email string `json:"email"`
  94. Delay string `json:"delay"`
  95. }
  96. // messageEncoder is a function that knows how to encode a message
  97. type messageEncoder func(msg *message) (string, error)
  98. // newMessage creates a new message with the current timestamp
  99. func newMessage(event, topic, msg string) *message {
  100. return &message{
  101. ID: util.RandomString(messageIDLength),
  102. Time: time.Now().Unix(),
  103. Event: event,
  104. Topic: topic,
  105. Message: msg,
  106. }
  107. }
  108. // newOpenMessage is a convenience method to create an open message
  109. func newOpenMessage(topic string) *message {
  110. return newMessage(openEvent, topic, "")
  111. }
  112. // newKeepaliveMessage is a convenience method to create a keepalive message
  113. func newKeepaliveMessage(topic string) *message {
  114. return newMessage(keepaliveEvent, topic, "")
  115. }
  116. // newDefaultMessage is a convenience method to create a notification message
  117. func newDefaultMessage(topic, msg string) *message {
  118. return newMessage(messageEvent, topic, msg)
  119. }
  120. // newPollRequestMessage is a convenience method to create a poll request message
  121. func newPollRequestMessage(topic, pollID string) *message {
  122. m := newMessage(pollRequestEvent, topic, newMessageBody)
  123. m.PollID = pollID
  124. return m
  125. }
  126. func validMessageID(s string) bool {
  127. return util.ValidRandomString(s, messageIDLength)
  128. }
  129. type sinceMarker struct {
  130. time time.Time
  131. id string
  132. }
  133. func newSinceTime(timestamp int64) sinceMarker {
  134. return sinceMarker{time.Unix(timestamp, 0), ""}
  135. }
  136. func newSinceID(id string) sinceMarker {
  137. return sinceMarker{time.Unix(0, 0), id}
  138. }
  139. func (t sinceMarker) IsAll() bool {
  140. return t == sinceAllMessages
  141. }
  142. func (t sinceMarker) IsNone() bool {
  143. return t == sinceNoMessages
  144. }
  145. func (t sinceMarker) IsID() bool {
  146. return t.id != ""
  147. }
  148. func (t sinceMarker) Time() time.Time {
  149. return t.time
  150. }
  151. func (t sinceMarker) ID() string {
  152. return t.id
  153. }
  154. var (
  155. sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
  156. sinceNoMessages = sinceMarker{time.Unix(1, 0), ""}
  157. )
  158. type queryFilter struct {
  159. ID string
  160. Message string
  161. Title string
  162. Tags []string
  163. Priority []int
  164. }
  165. func parseQueryFilters(r *http.Request) (*queryFilter, error) {
  166. idFilter := readParam(r, "x-id", "id")
  167. messageFilter := readParam(r, "x-message", "message", "m")
  168. titleFilter := readParam(r, "x-title", "title", "t")
  169. tagsFilter := util.SplitNoEmpty(readParam(r, "x-tags", "tags", "tag", "ta"), ",")
  170. priorityFilter := make([]int, 0)
  171. for _, p := range util.SplitNoEmpty(readParam(r, "x-priority", "priority", "prio", "p"), ",") {
  172. priority, err := util.ParsePriority(p)
  173. if err != nil {
  174. return nil, errHTTPBadRequestPriorityInvalid
  175. }
  176. priorityFilter = append(priorityFilter, priority)
  177. }
  178. return &queryFilter{
  179. ID: idFilter,
  180. Message: messageFilter,
  181. Title: titleFilter,
  182. Tags: tagsFilter,
  183. Priority: priorityFilter,
  184. }, nil
  185. }
  186. func (q *queryFilter) Pass(msg *message) bool {
  187. if msg.Event != messageEvent {
  188. return true // filters only apply to messages
  189. } else if q.ID != "" && msg.ID != q.ID {
  190. return false
  191. } else if q.Message != "" && msg.Message != q.Message {
  192. return false
  193. } else if q.Title != "" && msg.Title != q.Title {
  194. return false
  195. }
  196. messagePriority := msg.Priority
  197. if messagePriority == 0 {
  198. messagePriority = 3 // For query filters, default priority (3) is the same as "not set" (0)
  199. }
  200. if len(q.Priority) > 0 && !util.Contains(q.Priority, messagePriority) {
  201. return false
  202. }
  203. if len(q.Tags) > 0 && !util.ContainsAll(msg.Tags, q.Tags) {
  204. return false
  205. }
  206. return true
  207. }
  208. type apiHealthResponse struct {
  209. Healthy bool `json:"healthy"`
  210. }
  211. type apiStatsResponse struct {
  212. Messages int64 `json:"messages"`
  213. MessagesRate float64 `json:"messages_rate"` // Average number of messages per second
  214. }
  215. type apiUserAddRequest struct {
  216. Username string `json:"username"`
  217. Password string `json:"password"`
  218. Tier string `json:"tier"`
  219. // Do not add 'role' here. We don't want to add admins via the API.
  220. }
  221. type apiUserDeleteRequest struct {
  222. Username string `json:"username"`
  223. }
  224. type apiAccessAllowRequest struct {
  225. Username string `json:"username"`
  226. Topic string `json:"topic"`
  227. Permission string `json:"permission"`
  228. }
  229. type apiAccessResetRequest struct {
  230. Username string `json:"username"`
  231. Topic string `json:"topic"`
  232. }
  233. type apiAccountCreateRequest struct {
  234. Username string `json:"username"`
  235. Password string `json:"password"`
  236. }
  237. type apiAccountPasswordChangeRequest struct {
  238. Password string `json:"password"`
  239. NewPassword string `json:"new_password"`
  240. }
  241. type apiAccountDeleteRequest struct {
  242. Password string `json:"password"`
  243. }
  244. type apiAccountTokenIssueRequest struct {
  245. Label *string `json:"label"`
  246. Expires *int64 `json:"expires"` // Unix timestamp
  247. }
  248. type apiAccountTokenUpdateRequest struct {
  249. Token string `json:"token"`
  250. Label *string `json:"label"`
  251. Expires *int64 `json:"expires"` // Unix timestamp
  252. }
  253. type apiAccountTokenResponse struct {
  254. Token string `json:"token"`
  255. Label string `json:"label,omitempty"`
  256. LastAccess int64 `json:"last_access,omitempty"`
  257. LastOrigin string `json:"last_origin,omitempty"`
  258. Expires int64 `json:"expires,omitempty"` // Unix timestamp
  259. }
  260. type apiAccountTier struct {
  261. Code string `json:"code"`
  262. Name string `json:"name"`
  263. }
  264. type apiAccountLimits struct {
  265. Basis string `json:"basis,omitempty"` // "ip" or "tier"
  266. Messages int64 `json:"messages"`
  267. MessagesExpiryDuration int64 `json:"messages_expiry_duration"`
  268. Emails int64 `json:"emails"`
  269. Reservations int64 `json:"reservations"`
  270. AttachmentTotalSize int64 `json:"attachment_total_size"`
  271. AttachmentFileSize int64 `json:"attachment_file_size"`
  272. AttachmentExpiryDuration int64 `json:"attachment_expiry_duration"`
  273. AttachmentBandwidth int64 `json:"attachment_bandwidth"`
  274. }
  275. type apiAccountStats struct {
  276. Messages int64 `json:"messages"`
  277. MessagesRemaining int64 `json:"messages_remaining"`
  278. Emails int64 `json:"emails"`
  279. EmailsRemaining int64 `json:"emails_remaining"`
  280. Reservations int64 `json:"reservations"`
  281. ReservationsRemaining int64 `json:"reservations_remaining"`
  282. AttachmentTotalSize int64 `json:"attachment_total_size"`
  283. AttachmentTotalSizeRemaining int64 `json:"attachment_total_size_remaining"`
  284. }
  285. type apiAccountReservation struct {
  286. Topic string `json:"topic"`
  287. Everyone string `json:"everyone"`
  288. }
  289. type apiAccountBilling struct {
  290. Customer bool `json:"customer"`
  291. Subscription bool `json:"subscription"`
  292. Status string `json:"status,omitempty"`
  293. Interval string `json:"interval,omitempty"`
  294. PaidUntil int64 `json:"paid_until,omitempty"`
  295. CancelAt int64 `json:"cancel_at,omitempty"`
  296. }
  297. type apiAccountResponse struct {
  298. Username string `json:"username"`
  299. Role string `json:"role,omitempty"`
  300. SyncTopic string `json:"sync_topic,omitempty"`
  301. Language string `json:"language,omitempty"`
  302. Notification *user.NotificationPrefs `json:"notification,omitempty"`
  303. Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
  304. Reservations []*apiAccountReservation `json:"reservations,omitempty"`
  305. Tokens []*apiAccountTokenResponse `json:"tokens,omitempty"`
  306. Tier *apiAccountTier `json:"tier,omitempty"`
  307. Limits *apiAccountLimits `json:"limits,omitempty"`
  308. Stats *apiAccountStats `json:"stats,omitempty"`
  309. Billing *apiAccountBilling `json:"billing,omitempty"`
  310. }
  311. type apiAccountReservationRequest struct {
  312. Topic string `json:"topic"`
  313. Everyone string `json:"everyone"`
  314. }
  315. type apiConfigResponse struct {
  316. BaseURL string `json:"base_url"`
  317. AppRoot string `json:"app_root"`
  318. EnableLogin bool `json:"enable_login"`
  319. EnableSignup bool `json:"enable_signup"`
  320. EnablePayments bool `json:"enable_payments"`
  321. EnableReservations bool `json:"enable_reservations"`
  322. BillingContact string `json:"billing_contact"`
  323. DisallowedTopics []string `json:"disallowed_topics"`
  324. }
  325. type apiAccountBillingPrices struct {
  326. Month int64 `json:"month"`
  327. Year int64 `json:"year"`
  328. }
  329. type apiAccountBillingTier struct {
  330. Code string `json:"code,omitempty"`
  331. Name string `json:"name,omitempty"`
  332. Prices *apiAccountBillingPrices `json:"prices,omitempty"`
  333. Limits *apiAccountLimits `json:"limits"`
  334. }
  335. type apiAccountBillingSubscriptionCreateResponse struct {
  336. RedirectURL string `json:"redirect_url"`
  337. }
  338. type apiAccountBillingSubscriptionChangeRequest struct {
  339. Tier string `json:"tier"`
  340. Interval string `json:"interval"`
  341. }
  342. type apiAccountBillingPortalRedirectResponse struct {
  343. RedirectURL string `json:"redirect_url"`
  344. }
  345. type apiAccountSyncTopicResponse struct {
  346. Event string `json:"event"`
  347. }
  348. type apiSuccessResponse struct {
  349. Success bool `json:"success"`
  350. }
  351. func newSuccessResponse() *apiSuccessResponse {
  352. return &apiSuccessResponse{
  353. Success: true,
  354. }
  355. }
  356. type apiStripeSubscriptionUpdatedEvent struct {
  357. ID string `json:"id"`
  358. Customer string `json:"customer"`
  359. Status string `json:"status"`
  360. CurrentPeriodEnd int64 `json:"current_period_end"`
  361. CancelAt int64 `json:"cancel_at"`
  362. Items *struct {
  363. Data []*struct {
  364. Price *struct {
  365. ID string `json:"id"`
  366. Recurring *struct {
  367. Interval string `json:"interval"`
  368. } `json:"recurring"`
  369. } `json:"price"`
  370. } `json:"data"`
  371. } `json:"items"`
  372. }
  373. type apiStripeSubscriptionDeletedEvent struct {
  374. ID string `json:"id"`
  375. Customer string `json:"customer"`
  376. }