types.go 11 KB

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