emailengine-app 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/.eslintrc +14 -0
  2. package/.github/CODE_OF_CONDUCT.md +76 -0
  3. package/.github/FUNDING.yml +4 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. package/.github/contributing.md +17 -0
  7. package/.ncurc.js +10 -0
  8. package/.prettierrc.js +8 -0
  9. package/Dockerfile +17 -0
  10. package/Gruntfile.js +16 -0
  11. package/LICENSE.txt +661 -0
  12. package/README.md +524 -0
  13. package/bin/emailengine.js +14 -0
  14. package/config/default.toml +39 -0
  15. package/docker-compose.yml +47 -0
  16. package/encrypt.js +179 -0
  17. package/examples/api.md +137 -0
  18. package/examples/auth-server.js +104 -0
  19. package/getswagger.sh +8 -0
  20. package/lib/account.js +562 -0
  21. package/lib/append-list.js +67 -0
  22. package/lib/bounce-detect.js +380 -0
  23. package/lib/connection.js +1753 -0
  24. package/lib/consts.js +22 -0
  25. package/lib/db.js +72 -0
  26. package/lib/encrypt.js +100 -0
  27. package/lib/enum-message-flags.js +6 -0
  28. package/lib/get-raw-email.js +292 -0
  29. package/lib/get-secret.js +83 -0
  30. package/lib/logger.js +35 -0
  31. package/lib/lua/s-list-accounts.lua +51 -0
  32. package/lib/lua/z-expunge.lua +20 -0
  33. package/lib/lua/z-get-by-uid.lua +16 -0
  34. package/lib/lua/z-get-mailbox-id.lua +15 -0
  35. package/lib/lua/z-get-mailbox-path.lua +4 -0
  36. package/lib/lua/z-get.lua +15 -0
  37. package/lib/lua/z-push.lua +14 -0
  38. package/lib/lua/z-set.lua +17 -0
  39. package/lib/mailbox.js +1545 -0
  40. package/lib/message-port-stream.js +79 -0
  41. package/lib/schemas.js +311 -0
  42. package/lib/settings.js +63 -0
  43. package/lib/tools.js +488 -0
  44. package/package.json +79 -0
  45. package/scan.js +111 -0
  46. package/server.js +672 -0
  47. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css +3872 -0
  48. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css.map +1 -0
  49. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.min.css +7 -0
  50. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.min.css.map +1 -0
  51. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css +325 -0
  52. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css.map +1 -0
  53. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css +8 -0
  54. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css.map +1 -0
  55. package/static/bootstrap-4.6.0-dist/css/bootstrap.css +10298 -0
  56. package/static/bootstrap-4.6.0-dist/css/bootstrap.css.map +1 -0
  57. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css +7 -0
  58. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css.map +1 -0
  59. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js +7045 -0
  60. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js.map +1 -0
  61. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js +7 -0
  62. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js.map +1 -0
  63. package/static/bootstrap-4.6.0-dist/js/bootstrap.js +4432 -0
  64. package/static/bootstrap-4.6.0-dist/js/bootstrap.js.map +1 -0
  65. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js +7 -0
  66. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js.map +1 -0
  67. package/static/css/callout.css +63 -0
  68. package/static/css/emailengine.css +33 -0
  69. package/static/favicon/android-chrome-192x192.png +0 -0
  70. package/static/favicon/android-chrome-512x512.png +0 -0
  71. package/static/favicon/apple-touch-icon.png +0 -0
  72. package/static/favicon/favicon-16x16.png +0 -0
  73. package/static/favicon/favicon-32x32.png +0 -0
  74. package/static/favicon/manifest.json +20 -0
  75. package/static/favicon.ico +0 -0
  76. package/static/icons/alarm-fill.svg +3 -0
  77. package/static/icons/alarm.svg +7 -0
  78. package/static/icons/alert-circle-fill.svg +3 -0
  79. package/static/icons/alert-circle.svg +4 -0
  80. package/static/icons/alert-octagon-fill.svg +3 -0
  81. package/static/icons/alert-octagon.svg +5 -0
  82. package/static/icons/alert-square-fill.svg +3 -0
  83. package/static/icons/alert-square.svg +5 -0
  84. package/static/icons/alert-triangle-fill.svg +3 -0
  85. package/static/icons/alert-triangle.svg +5 -0
  86. package/static/icons/archive-fill.svg +3 -0
  87. package/static/icons/archive.svg +4 -0
  88. package/static/icons/arrow-bar-bottom.svg +4 -0
  89. package/static/icons/arrow-bar-left.svg +4 -0
  90. package/static/icons/arrow-bar-right.svg +4 -0
  91. package/static/icons/arrow-bar-up.svg +4 -0
  92. package/static/icons/arrow-clockwise.svg +4 -0
  93. package/static/icons/arrow-counterclockwise.svg +4 -0
  94. package/static/icons/arrow-down-left.svg +4 -0
  95. package/static/icons/arrow-down-right.svg +4 -0
  96. package/static/icons/arrow-down-short.svg +4 -0
  97. package/static/icons/arrow-down.svg +4 -0
  98. package/static/icons/arrow-left-right.svg +5 -0
  99. package/static/icons/arrow-left-short.svg +4 -0
  100. package/static/icons/arrow-left.svg +4 -0
  101. package/static/icons/arrow-repeat.svg +5 -0
  102. package/static/icons/arrow-right-short.svg +4 -0
  103. package/static/icons/arrow-right.svg +4 -0
  104. package/static/icons/arrow-up-down.svg +5 -0
  105. package/static/icons/arrow-up-left.svg +4 -0
  106. package/static/icons/arrow-up-right.svg +4 -0
  107. package/static/icons/arrow-up-short.svg +4 -0
  108. package/static/icons/arrow-up.svg +4 -0
  109. package/static/icons/arrows-angle-contract.svg +5 -0
  110. package/static/icons/arrows-angle-expand.svg +5 -0
  111. package/static/icons/arrows-collapse.svg +5 -0
  112. package/static/icons/arrows-expand.svg +5 -0
  113. package/static/icons/arrows-fullscreen.svg +7 -0
  114. package/static/icons/at.svg +3 -0
  115. package/static/icons/award.svg +4 -0
  116. package/static/icons/backspace-fill.svg +3 -0
  117. package/static/icons/backspace-reverse-fill.svg +3 -0
  118. package/static/icons/backspace-reverse.svg +5 -0
  119. package/static/icons/backspace.svg +5 -0
  120. package/static/icons/bar-chart-fill.svg +5 -0
  121. package/static/icons/bar-chart.svg +3 -0
  122. package/static/icons/battery-charging.svg +5 -0
  123. package/static/icons/battery-full.svg +4 -0
  124. package/static/icons/battery.svg +4 -0
  125. package/static/icons/bell-fill.svg +3 -0
  126. package/static/icons/bell.svg +4 -0
  127. package/static/icons/blockquote-left.svg +4 -0
  128. package/static/icons/blockquote-right.svg +4 -0
  129. package/static/icons/book-half-fill.svg +4 -0
  130. package/static/icons/book.svg +4 -0
  131. package/static/icons/bookmark-fill.svg +3 -0
  132. package/static/icons/bookmark.svg +3 -0
  133. package/static/icons/bootstrap-fill.svg +3 -0
  134. package/static/icons/bootstrap-reboot.svg +3 -0
  135. package/static/icons/bootstrap.svg +4 -0
  136. package/static/icons/box-arrow-bottom-left.svg +4 -0
  137. package/static/icons/box-arrow-bottom-right.svg +4 -0
  138. package/static/icons/box-arrow-down.svg +5 -0
  139. package/static/icons/box-arrow-left.svg +5 -0
  140. package/static/icons/box-arrow-right.svg +5 -0
  141. package/static/icons/box-arrow-up-left.svg +4 -0
  142. package/static/icons/box-arrow-up-right.svg +4 -0
  143. package/static/icons/box-arrow-up.svg +5 -0
  144. package/static/icons/braces.svg +3 -0
  145. package/static/icons/brightness-fill-high.svg +4 -0
  146. package/static/icons/brightness-fill-low.svg +11 -0
  147. package/static/icons/brightness-high.svg +3 -0
  148. package/static/icons/brightness-low.svg +11 -0
  149. package/static/icons/brush.svg +4 -0
  150. package/static/icons/bucket-fill.svg +4 -0
  151. package/static/icons/bucket.svg +4 -0
  152. package/static/icons/building.svg +5 -0
  153. package/static/icons/bullseye.svg +6 -0
  154. package/static/icons/calendar-fill.svg +4 -0
  155. package/static/icons/calendar.svg +4 -0
  156. package/static/icons/camera-video-fill.svg +4 -0
  157. package/static/icons/camera-video.svg +4 -0
  158. package/static/icons/camera.svg +5 -0
  159. package/static/icons/capslock-fill.svg +3 -0
  160. package/static/icons/capslock.svg +3 -0
  161. package/static/icons/chat-fill.svg +3 -0
  162. package/static/icons/chat.svg +3 -0
  163. package/static/icons/check-box.svg +4 -0
  164. package/static/icons/check-circle.svg +4 -0
  165. package/static/icons/check.svg +3 -0
  166. package/static/icons/chevron-compact-down.svg +3 -0
  167. package/static/icons/chevron-compact-left.svg +3 -0
  168. package/static/icons/chevron-compact-right.svg +3 -0
  169. package/static/icons/chevron-compact-up.svg +3 -0
  170. package/static/icons/chevron-down.svg +3 -0
  171. package/static/icons/chevron-left.svg +3 -0
  172. package/static/icons/chevron-right.svg +3 -0
  173. package/static/icons/chevron-up.svg +3 -0
  174. package/static/icons/circle-fill.svg +3 -0
  175. package/static/icons/circle-half.svg +3 -0
  176. package/static/icons/circle-slash.svg +3 -0
  177. package/static/icons/circle.svg +3 -0
  178. package/static/icons/clock-fill.svg +3 -0
  179. package/static/icons/clock.svg +4 -0
  180. package/static/icons/cloud-download.svg +5 -0
  181. package/static/icons/cloud-fill.svg +3 -0
  182. package/static/icons/cloud-upload.svg +5 -0
  183. package/static/icons/cloud.svg +3 -0
  184. package/static/icons/code-slash.svg +3 -0
  185. package/static/icons/code.svg +3 -0
  186. package/static/icons/columns-gutters.svg +3 -0
  187. package/static/icons/columns.svg +4 -0
  188. package/static/icons/command.svg +4 -0
  189. package/static/icons/compass.svg +5 -0
  190. package/static/icons/cone-striped.svg +4 -0
  191. package/static/icons/cone.svg +4 -0
  192. package/static/icons/controller.svg +5 -0
  193. package/static/icons/credit-card.svg +5 -0
  194. package/static/icons/cursor-fill.svg +3 -0
  195. package/static/icons/cursor.svg +3 -0
  196. package/static/icons/dash.svg +3 -0
  197. package/static/icons/diamond-half.svg +3 -0
  198. package/static/icons/diamond.svg +3 -0
  199. package/static/icons/display-fill.svg +5 -0
  200. package/static/icons/display.svg +4 -0
  201. package/static/icons/document-code.svg +4 -0
  202. package/static/icons/document-diff.svg +5 -0
  203. package/static/icons/document-richtext.svg +4 -0
  204. package/static/icons/document-spreadsheet.svg +5 -0
  205. package/static/icons/document-text.svg +4 -0
  206. package/static/icons/document.svg +3 -0
  207. package/static/icons/documents-alt.svg +4 -0
  208. package/static/icons/documents.svg +4 -0
  209. package/static/icons/dot.svg +3 -0
  210. package/static/icons/download.svg +5 -0
  211. package/static/icons/egg-fried.svg +4 -0
  212. package/static/icons/eject-fill.svg +3 -0
  213. package/static/icons/eject.svg +3 -0
  214. package/static/icons/envelope-fill.svg +3 -0
  215. package/static/icons/envelope-open-fill.svg +3 -0
  216. package/static/icons/envelope-open.svg +5 -0
  217. package/static/icons/envelope.svg +4 -0
  218. package/static/icons/eye-fill.svg +4 -0
  219. package/static/icons/eye-slash-fill.svg +5 -0
  220. package/static/icons/eye-slash.svg +6 -0
  221. package/static/icons/eye.svg +4 -0
  222. package/static/icons/filter.svg +3 -0
  223. package/static/icons/flag-fill.svg +4 -0
  224. package/static/icons/flag.svg +4 -0
  225. package/static/icons/folder-fill.svg +3 -0
  226. package/static/icons/folder-symlink-fill.svg +3 -0
  227. package/static/icons/folder-symlink.svg +5 -0
  228. package/static/icons/folder.svg +4 -0
  229. package/static/icons/fonts.svg +3 -0
  230. package/static/icons/forward-fill.svg +3 -0
  231. package/static/icons/forward.svg +3 -0
  232. package/static/icons/gear-fill.svg +3 -0
  233. package/static/icons/gear-wide-connected.svg +4 -0
  234. package/static/icons/gear-wide.svg +3 -0
  235. package/static/icons/gear.svg +4 -0
  236. package/static/icons/geo.svg +5 -0
  237. package/static/icons/graph-down.svg +5 -0
  238. package/static/icons/graph-up.svg +5 -0
  239. package/static/icons/grid-fill.svg +6 -0
  240. package/static/icons/grid.svg +3 -0
  241. package/static/icons/hammer.svg +4 -0
  242. package/static/icons/hash.svg +3 -0
  243. package/static/icons/heart-fill.svg +3 -0
  244. package/static/icons/heart.svg +3 -0
  245. package/static/icons/house-fill.svg +4 -0
  246. package/static/icons/house.svg +4 -0
  247. package/static/icons/image-alt.svg +4 -0
  248. package/static/icons/image-fill.svg +3 -0
  249. package/static/icons/image.svg +5 -0
  250. package/static/icons/images.svg +5 -0
  251. package/static/icons/inbox-fill.svg +4 -0
  252. package/static/icons/inbox.svg +4 -0
  253. package/static/icons/inboxes-fill.svg +4 -0
  254. package/static/icons/inboxes.svg +4 -0
  255. package/static/icons/info-fill.svg +3 -0
  256. package/static/icons/info-square-fill.svg +3 -0
  257. package/static/icons/info-square.svg +5 -0
  258. package/static/icons/info.svg +5 -0
  259. package/static/icons/justify-left.svg +3 -0
  260. package/static/icons/justify-right.svg +3 -0
  261. package/static/icons/justify.svg +3 -0
  262. package/static/icons/kanban-fill.svg +3 -0
  263. package/static/icons/kanban.svg +6 -0
  264. package/static/icons/laptop.svg +4 -0
  265. package/static/icons/layout-sidebar-reverse.svg +4 -0
  266. package/static/icons/layout-sidebar.svg +4 -0
  267. package/static/icons/layout-split.svg +3 -0
  268. package/static/icons/list-check.svg +3 -0
  269. package/static/icons/list-ol.svg +4 -0
  270. package/static/icons/list-task.svg +5 -0
  271. package/static/icons/list-ul.svg +3 -0
  272. package/static/icons/list.svg +3 -0
  273. package/static/icons/lock-fill.svg +4 -0
  274. package/static/icons/lock.svg +3 -0
  275. package/static/icons/map.svg +3 -0
  276. package/static/icons/mic.svg +4 -0
  277. package/static/icons/moon.svg +3 -0
  278. package/static/icons/music-player-fill.svg +4 -0
  279. package/static/icons/music-player.svg +5 -0
  280. package/static/icons/option.svg +3 -0
  281. package/static/icons/outlet.svg +5 -0
  282. package/static/icons/pause-fill.svg +3 -0
  283. package/static/icons/pause.svg +3 -0
  284. package/static/icons/pen.svg +5 -0
  285. package/static/icons/pencil.svg +4 -0
  286. package/static/icons/people-fill.svg +3 -0
  287. package/static/icons/people.svg +3 -0
  288. package/static/icons/person-fill.svg +3 -0
  289. package/static/icons/person.svg +3 -0
  290. package/static/icons/phone-landscape.svg +4 -0
  291. package/static/icons/phone.svg +4 -0
  292. package/static/icons/pie-chart-fill.svg +3 -0
  293. package/static/icons/pie-chart.svg +4 -0
  294. package/static/icons/play-fill.svg +3 -0
  295. package/static/icons/play.svg +3 -0
  296. package/static/icons/plug.svg +4 -0
  297. package/static/icons/plus.svg +4 -0
  298. package/static/icons/power.svg +4 -0
  299. package/static/icons/question-fill.svg +3 -0
  300. package/static/icons/question-square-fill.svg +3 -0
  301. package/static/icons/question-square.svg +4 -0
  302. package/static/icons/question.svg +4 -0
  303. package/static/icons/reply-all-fill.svg +4 -0
  304. package/static/icons/reply-all.svg +4 -0
  305. package/static/icons/reply-fill.svg +3 -0
  306. package/static/icons/reply.svg +3 -0
  307. package/static/icons/screwdriver.svg +3 -0
  308. package/static/icons/search.svg +4 -0
  309. package/static/icons/shield-fill.svg +3 -0
  310. package/static/icons/shield-lock-fill.svg +3 -0
  311. package/static/icons/shield-lock.svg +5 -0
  312. package/static/icons/shield-shaded.svg +4 -0
  313. package/static/icons/shield.svg +3 -0
  314. package/static/icons/shift-fill.svg +3 -0
  315. package/static/icons/shift.svg +3 -0
  316. package/static/icons/skip-backward-fill.svg +4 -0
  317. package/static/icons/skip-backward.svg +3 -0
  318. package/static/icons/skip-end-fill.svg +5 -0
  319. package/static/icons/skip-end.svg +4 -0
  320. package/static/icons/skip-forward-fill.svg +5 -0
  321. package/static/icons/skip-forward.svg +3 -0
  322. package/static/icons/skip-start-fill.svg +4 -0
  323. package/static/icons/skip-start.svg +4 -0
  324. package/static/icons/speaker.svg +4 -0
  325. package/static/icons/square-fill.svg +3 -0
  326. package/static/icons/square-half.svg +3 -0
  327. package/static/icons/square.svg +3 -0
  328. package/static/icons/star-fill.svg +3 -0
  329. package/static/icons/star-half.svg +3 -0
  330. package/static/icons/star.svg +3 -0
  331. package/static/icons/stop-fill.svg +3 -0
  332. package/static/icons/stop.svg +3 -0
  333. package/static/icons/stopwatch-fill.svg +3 -0
  334. package/static/icons/stopwatch.svg +5 -0
  335. package/static/icons/sun.svg +4 -0
  336. package/static/icons/table.svg +7 -0
  337. package/static/icons/tablet-landscape.svg +4 -0
  338. package/static/icons/tablet.svg +4 -0
  339. package/static/icons/tag-fill.svg +3 -0
  340. package/static/icons/tag.svg +4 -0
  341. package/static/icons/terminal-fill.svg +3 -0
  342. package/static/icons/terminal.svg +4 -0
  343. package/static/icons/text-center.svg +3 -0
  344. package/static/icons/text-indent-left.svg +3 -0
  345. package/static/icons/text-indent-right.svg +3 -0
  346. package/static/icons/text-left.svg +3 -0
  347. package/static/icons/text-right.svg +3 -0
  348. package/static/icons/three-dots-vertical.svg +3 -0
  349. package/static/icons/three-dots.svg +3 -0
  350. package/static/icons/toggle-off.svg +3 -0
  351. package/static/icons/toggle-on.svg +3 -0
  352. package/static/icons/toggles.svg +4 -0
  353. package/static/icons/tools.svg +4 -0
  354. package/static/icons/trash-fill.svg +3 -0
  355. package/static/icons/trash.svg +4 -0
  356. package/static/icons/triangle-fill.svg +3 -0
  357. package/static/icons/triangle-half.svg +3 -0
  358. package/static/icons/triangle.svg +3 -0
  359. package/static/icons/trophy.svg +6 -0
  360. package/static/icons/tv-fill.svg +3 -0
  361. package/static/icons/tv.svg +3 -0
  362. package/static/icons/type-bold.svg +3 -0
  363. package/static/icons/type-h1.svg +3 -0
  364. package/static/icons/type-h2.svg +3 -0
  365. package/static/icons/type-h3.svg +3 -0
  366. package/static/icons/type-italic.svg +3 -0
  367. package/static/icons/type-strikethrough.svg +4 -0
  368. package/static/icons/type-underline.svg +4 -0
  369. package/static/icons/type.svg +3 -0
  370. package/static/icons/unlock-fill.svg +4 -0
  371. package/static/icons/unlock.svg +3 -0
  372. package/static/icons/upload.svg +4 -0
  373. package/static/icons/volume-down-fill.svg +4 -0
  374. package/static/icons/volume-down.svg +4 -0
  375. package/static/icons/volume-mute-fill.svg +4 -0
  376. package/static/icons/volume-mute.svg +4 -0
  377. package/static/icons/volume-up-fill.svg +6 -0
  378. package/static/icons/volume-up.svg +6 -0
  379. package/static/icons/wallet.svg +3 -0
  380. package/static/icons/watch.svg +5 -0
  381. package/static/icons/wifi.svg +5 -0
  382. package/static/icons/window.svg +5 -0
  383. package/static/icons/wrench.svg +3 -0
  384. package/static/icons/x-circle-fill.svg +3 -0
  385. package/static/icons/x-circle.svg +5 -0
  386. package/static/icons/x-octagon-fill.svg +3 -0
  387. package/static/icons/x-octagon.svg +4 -0
  388. package/static/icons/x-square-fill.svg +3 -0
  389. package/static/icons/x-square.svg +4 -0
  390. package/static/icons/x.svg +4 -0
  391. package/static/index.html +752 -0
  392. package/static/js/emailengine.js +581 -0
  393. package/static/js/jquery-3.4.1.slim.min.js +2 -0
  394. package/static/js/moment-with-locales-2.24.0.min.js +1 -0
  395. package/static/js/popper.min.js +5 -0
  396. package/static/logo.png +0 -0
  397. package/systemd/emailengine.service +89 -0
  398. package/systemd/nginx-proxy.conf +77 -0
  399. package/views/error.hbs +2 -0
  400. package/workers/api.js +2266 -0
  401. package/workers/arena.js +89 -0
  402. package/workers/imap.js +611 -0
  403. package/workers/smtp.js +278 -0
  404. package/workers/submit.js +214 -0
  405. package/workers/webhooks.js +134 -0
package/lib/consts.js ADDED
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ MESSAGE_NEW_NOTIFY: 'messageNew',
5
+ MESSAGE_DELETED_NOTIFY: 'messageDeleted',
6
+ MESSAGE_UPDATED_NOTIFY: 'messageUpdated',
7
+
8
+ EMAIL_SENT_NOTIFY: 'messageSent',
9
+ EMAIL_FAILED_NOTIFY: 'messageFailed',
10
+ EMAIL_BOUNCE_NOTIFY: 'messageBounce',
11
+
12
+ MAILBOX_RESET_NOTIFY: 'mailboxReset',
13
+ MAILBOX_DELETED_NOTIFY: 'mailboxDeleted',
14
+ MAILBOX_NEW_NOTIFY: 'mailboxNew',
15
+
16
+ AUTH_ERROR_NOTIFY: 'authenticationError',
17
+ AUTH_SUCCESS_NOTIFY: 'authenticationSuccess',
18
+
19
+ CONNECT_ERROR_NOTIFY: 'connectError',
20
+
21
+ MAX_DAYS_STATS: 7
22
+ };
package/lib/db.js ADDED
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const config = require('wild-config');
5
+ const pathlib = require('path');
6
+
7
+ config.dbs = config.dbs || {
8
+ redis: 'redis://127.0.0.1:6379/8'
9
+ };
10
+
11
+ const Queue = require('bull');
12
+ const Redis = require('ioredis');
13
+
14
+ const REDIS_CONF = process.env.EENGINE_REDIS || config.dbs.redis;
15
+
16
+ const redis = new Redis(REDIS_CONF);
17
+
18
+ const notifyQueue = new Queue('notify', typeof REDIS_CONF === 'object' ? { redis: REDIS_CONF } : REDIS_CONF);
19
+ const submitQueue = new Queue('submit', typeof REDIS_CONF === 'object' ? { redis: REDIS_CONF } : REDIS_CONF);
20
+
21
+ const zExpungeScript = fs.readFileSync(pathlib.join(__dirname, '/lua/z-expunge.lua'), 'utf-8');
22
+ const zSetScript = fs.readFileSync(pathlib.join(__dirname, 'lua/z-set.lua'), 'utf-8');
23
+ const zGetScript = fs.readFileSync(pathlib.join(__dirname, 'lua/z-get.lua'), 'utf-8');
24
+ const zGetByUidScript = fs.readFileSync(pathlib.join(__dirname, 'lua/z-get-by-uid.lua'), 'utf-8');
25
+ const zGetMailboxIdScript = fs.readFileSync(pathlib.join(__dirname, 'lua/z-get-mailbox-id.lua'), 'utf-8');
26
+ const zGetMailboxPathScript = fs.readFileSync(pathlib.join(__dirname, 'lua/z-get-mailbox-path.lua'), 'utf-8');
27
+ const sListAccountsScript = fs.readFileSync(pathlib.join(__dirname, 'lua/s-list-accounts.lua'), 'utf-8');
28
+ const zPushScript = fs.readFileSync(pathlib.join(__dirname, 'lua/z-push.lua'), 'utf-8');
29
+
30
+ redis.defineCommand('zExpunge', {
31
+ numberOfKeys: 2,
32
+ lua: zExpungeScript
33
+ });
34
+
35
+ redis.defineCommand('zSet', {
36
+ numberOfKeys: 1,
37
+ lua: zSetScript
38
+ });
39
+
40
+ redis.defineCommand('zGet', {
41
+ numberOfKeys: 1,
42
+ lua: zGetScript
43
+ });
44
+
45
+ redis.defineCommand('zPush', {
46
+ numberOfKeys: 1,
47
+ lua: zPushScript
48
+ });
49
+
50
+ redis.defineCommand('zGetByUid', {
51
+ numberOfKeys: 1,
52
+ lua: zGetByUidScript
53
+ });
54
+
55
+ redis.defineCommand('zGetMailboxId', {
56
+ numberOfKeys: 2,
57
+ lua: zGetMailboxIdScript
58
+ });
59
+
60
+ redis.defineCommand('zGetMailboxPath', {
61
+ numberOfKeys: 1,
62
+ lua: zGetMailboxPathScript
63
+ });
64
+
65
+ redis.defineCommand('sListAccounts', {
66
+ numberOfKeys: 1,
67
+ lua: sListAccountsScript
68
+ });
69
+
70
+ module.exports.redis = redis;
71
+ module.exports.notifyQueue = notifyQueue;
72
+ module.exports.submitQueue = submitQueue;
package/lib/encrypt.js ADDED
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const assert = require('assert');
5
+
6
+ function parseEncryptedData(encryptedData) {
7
+ encryptedData = (encryptedData || '').toString();
8
+ if (!encryptedData || encryptedData.charAt(0) !== '$') {
9
+ // cleartext
10
+ return {
11
+ format: 'cleartext',
12
+ data: encryptedData
13
+ };
14
+ }
15
+
16
+ let [, format, cipher, authTag, iv, salt, encryptedText] = encryptedData.split('$');
17
+ if (!format || !cipher || !authTag || !iv || !encryptedText) {
18
+ // assume cleartext if format is not matching
19
+ return {
20
+ format: 'cleartext',
21
+ data: encryptedData
22
+ };
23
+ }
24
+
25
+ authTag = Buffer.from(authTag, 'hex');
26
+ iv = Buffer.from(iv, 'hex');
27
+ salt = Buffer.from(salt, 'hex');
28
+ encryptedText = Buffer.from(encryptedText, 'hex');
29
+
30
+ return {
31
+ format,
32
+ cipher,
33
+ authTag,
34
+ iv,
35
+ salt,
36
+ data: encryptedText
37
+ };
38
+ }
39
+
40
+ function getKeyFromPassword(password, salt, keyLen) {
41
+ return crypto.scryptSync(password, salt, keyLen);
42
+ }
43
+
44
+ function decrypt(encryptedData, secret) {
45
+ const decryptData = parseEncryptedData(encryptedData);
46
+
47
+ switch (decryptData.format) {
48
+ case 'cleartext':
49
+ return encryptedData;
50
+
51
+ case 'wd01':
52
+ try {
53
+ assert.strictEqual(decryptData.authTag.length, 16, 'Invalid auth tag length');
54
+ assert.strictEqual(decryptData.iv.length, 12, 'Invalid iv length');
55
+ assert.strictEqual(decryptData.salt.length, 16, 'Invalid salt length');
56
+
57
+ // convert password to 32B key
58
+ const key = getKeyFromPassword(secret, decryptData.salt, 32);
59
+
60
+ const decipher = crypto.createDecipheriv(decryptData.cipher, key, decryptData.iv, { authTagLength: decryptData.authTag.length });
61
+ decipher.setAuthTag(decryptData.authTag);
62
+
63
+ // try to decipher
64
+ return Buffer.concat([decipher.update(decryptData.data), decipher.final()]).toString('utf-8');
65
+ } catch (E) {
66
+ let err = new Error('Failed to decrypt data. ' + E.message);
67
+ err.responseCode = 500;
68
+ err.code = 'InternalConfigError';
69
+ throw err;
70
+ }
71
+
72
+ default: {
73
+ // assume cleartext
74
+ return encryptedData;
75
+ }
76
+ }
77
+ }
78
+
79
+ function encrypt(cleartext, secret) {
80
+ if (!secret) {
81
+ return cleartext;
82
+ }
83
+
84
+ const iv = crypto.randomBytes(12);
85
+ const salt = crypto.randomBytes(16);
86
+
87
+ const key = getKeyFromPassword(secret, salt, 32);
88
+
89
+ const format = 'wd01';
90
+ const algo = 'aes-256-gcm';
91
+
92
+ const cipher = crypto.createCipheriv(algo, key, iv, { authTagLength: 16 });
93
+ const encryptedText = Buffer.concat([cipher.update(cleartext), cipher.final()]);
94
+
95
+ const authTag = cipher.getAuthTag();
96
+
97
+ return ['', format, algo].concat([authTag, iv, salt, encryptedText].map(buf => buf.toString('hex'))).join('$');
98
+ }
99
+
100
+ module.exports = { encrypt, decrypt, parseEncryptedData };
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ // Ordering matters. Up to 8 flags can be listed here.
4
+ // Once added, do not remove or it messes up bit positions.
5
+
6
+ module.exports = ['\\Seen', '\\Answered', '\\Flagged', '\\Deleted', '\\Draft', '$Forwarded', '$Junk', '$NotJunk'];
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ const MailComposer = require('nodemailer/lib/mail-composer');
4
+ const { Splitter, Joiner } = require('mailsplit');
5
+ const Transform = require('stream').Transform;
6
+ const mimeFuncs = require('nodemailer/lib/mime-funcs');
7
+ const MimeNode = require('nodemailer/lib/mime-node');
8
+ const addressparser = require('nodemailer/lib/addressparser');
9
+ const uuid = require('uuid');
10
+ const os = require('os');
11
+ const libmime = require('libmime');
12
+
13
+ class HeadersRewriter extends Transform {
14
+ constructor(headersCb) {
15
+ let options = {
16
+ readableObjectMode: true,
17
+ writableObjectMode: true
18
+ };
19
+ super(options);
20
+ this.headersCb = headersCb;
21
+ }
22
+
23
+ _transform(obj, encoding, callback) {
24
+ if (obj.type !== 'node' || !obj.root || !typeof this.headersCb) {
25
+ this.push(obj);
26
+ return callback();
27
+ }
28
+ this.headersCb(obj.headers)
29
+ .then(() => {
30
+ this.push(obj);
31
+ return callback();
32
+ })
33
+ .catch(err => callback(err));
34
+ }
35
+
36
+ _flush(callback) {
37
+ return callback();
38
+ }
39
+ }
40
+
41
+ async function processMessage(data) {
42
+ let raw = typeof data.raw === 'string' ? Buffer.from(data.raw, 'base64') : data.raw;
43
+ let hasBcc = false;
44
+
45
+ let messageId, envelope;
46
+ let sendAt = data.sendAt;
47
+
48
+ const splitter = new Splitter();
49
+ const joiner = new Joiner();
50
+ const headersRewriter = new HeadersRewriter(async headers => {
51
+ let mn = new MimeNode();
52
+
53
+ let sendAtVal = headers.getFirst('x-ee-send-at');
54
+ headers.remove('x-ee-send-at');
55
+ if (sendAtVal) {
56
+ if (!isNaN(sendAtVal)) {
57
+ sendAtVal = Number(sendAtVal);
58
+ }
59
+ sendAtVal = new Date(sendAtVal);
60
+ if (sendAtVal.toString() !== 'Invalid Date') {
61
+ sendAt = sendAtVal;
62
+ }
63
+ }
64
+
65
+ for (let addrKey of ['from', 'to', 'cc', 'bcc']) {
66
+ if (!(addrKey in data)) {
67
+ continue;
68
+ }
69
+
70
+ let addrValue = [].concat(data[addrKey] || []);
71
+ if (!addrValue.length) {
72
+ // remove existing
73
+ headers.remove(addrKey);
74
+ continue;
75
+ }
76
+
77
+ // update or add new
78
+ let headerLine = mn._encodeHeaderValue(addrKey, addrValue);
79
+ if (headers.hasHeader(addrKey)) {
80
+ headers.update(
81
+ addrKey.replace(/^./, c => c.toUpperCase()),
82
+ headerLine
83
+ );
84
+ } else {
85
+ // push to bottom
86
+ headers.add(
87
+ addrKey.replace(/^./, c => c.toUpperCase()),
88
+ headerLine,
89
+ Infinity
90
+ );
91
+ }
92
+ }
93
+
94
+ hasBcc = headers.hasHeader('bcc');
95
+
96
+ let addresses = {
97
+ from: headers
98
+ .get('from')
99
+ .flatMap(line => addressparser(line, { flatten: true }))
100
+ .filter(a => a)
101
+ .map(addr => {
102
+ if (addr.name) {
103
+ try {
104
+ addr.name = libmime.decodeWords(addr.name);
105
+ } catch (err) {
106
+ // ignore
107
+ }
108
+ }
109
+ return addr;
110
+ })
111
+ .find(addr => addr && addr.address),
112
+ to: headers
113
+ .get('to')
114
+ .flatMap(line => addressparser(line, { flatten: true }))
115
+ .filter(a => a)
116
+ .map(addr => {
117
+ if (addr.name) {
118
+ try {
119
+ addr.name = libmime.decodeWords(addr.name);
120
+ } catch (err) {
121
+ // ignore
122
+ }
123
+ }
124
+ return addr;
125
+ }),
126
+ cc: headers
127
+ .get('cc')
128
+ .flatMap(line => addressparser(line, { flatten: true }))
129
+ .filter(a => a)
130
+ .map(addr => {
131
+ if (addr.name) {
132
+ try {
133
+ addr.name = libmime.decodeWords(addr.name);
134
+ } catch (err) {
135
+ // ignore
136
+ }
137
+ }
138
+ return addr;
139
+ }),
140
+ bcc: headers
141
+ .get('bcc')
142
+ .flatMap(line => addressparser(line, { flatten: true }))
143
+ .filter(a => a)
144
+ .map(addr => {
145
+ if (addr.name) {
146
+ try {
147
+ addr.name = libmime.decodeWords(addr.name);
148
+ } catch (err) {
149
+ // ignore
150
+ }
151
+ }
152
+ return addr;
153
+ })
154
+ };
155
+
156
+ envelope = data.envelope || {
157
+ from: (addresses.from && addresses.from.address) || '',
158
+ to: Array.from(
159
+ new Set(
160
+ []
161
+ .concat(addresses.to)
162
+ .concat(addresses.cc)
163
+ .concat(addresses.bcc)
164
+ .map(addr => addr.address)
165
+ )
166
+ )
167
+ };
168
+
169
+ envelope.from = [].concat(envelope.from || []).shift() || '';
170
+ envelope.to = [].concat(envelope.to || []);
171
+
172
+ // generate default message-id
173
+ if (!data.messageId && !headers.hasHeader('message-id')) {
174
+ let fromDomain = envelope.from ? envelope.from.split('@').pop().toLowerCase() : os.hostname();
175
+ messageId = `<${uuid.v4()}@${fromDomain}>`;
176
+ headers.add('Message-ID', messageId, Infinity);
177
+ }
178
+
179
+ if (data.messageId) {
180
+ messageId = (data.messageId || '').toString().trim().replace(/^<*/, '<').replace(/>*$/, '>');
181
+ headers.update('Message-ID', messageId);
182
+ }
183
+
184
+ if (!messageId) {
185
+ messageId = headers.getFirst('message-id');
186
+ }
187
+
188
+ // make sure that there is a Date header
189
+ if (!headers.hasHeader('date')) {
190
+ let dateVal = new Date().toUTCString().replace(/GMT/, '+0000');
191
+ headers.add('Date', dateVal, Infinity);
192
+ }
193
+ // ... and MIME-Version
194
+ if (!headers.hasHeader('mime-version')) {
195
+ headers.add('MIME-Version', '1.0', Infinity);
196
+ }
197
+
198
+ if (data.subject) {
199
+ headers.update('Subject', mimeFuncs.encodeWords(data.subject, 'Q', 64, true));
200
+ }
201
+
202
+ if (data.headers) {
203
+ for (let key of Object.keys(headers)) {
204
+ let casedKey = key.replace(/^.|-./g, c => c.toUpperCase());
205
+ switch (key) {
206
+ case 'in-reply-to':
207
+ case 'references':
208
+ headers.update(casedKey, headers[key]);
209
+ break;
210
+ default:
211
+ headers.add(casedKey, headers[key]);
212
+ break;
213
+ }
214
+ }
215
+ }
216
+ });
217
+
218
+ let message = await new Promise((resolve, reject) => {
219
+ let chunks = [];
220
+ let chunklen = 0;
221
+
222
+ joiner.on('readable', () => {
223
+ let chunk;
224
+ while ((chunk = joiner.read()) !== null) {
225
+ chunks.push(chunk);
226
+ chunklen += chunk.length;
227
+ }
228
+ });
229
+
230
+ joiner.once('end', () => {
231
+ resolve(Buffer.concat(chunks, chunklen));
232
+ });
233
+
234
+ joiner.once('error', err => reject(err));
235
+ splitter.pipe(headersRewriter).pipe(joiner);
236
+ splitter.end(raw);
237
+ });
238
+
239
+ return { raw: message, hasBcc, messageId, envelope, sendAt };
240
+ }
241
+
242
+ async function removeBcc(raw) {
243
+ const splitter = new Splitter();
244
+ const joiner = new Joiner();
245
+ const headersRewriter = new HeadersRewriter(async headers => {
246
+ headers.remove('bcc');
247
+ });
248
+
249
+ return new Promise((resolve, reject) => {
250
+ let chunks = [];
251
+ let chunklen = 0;
252
+
253
+ joiner.on('readable', () => {
254
+ let chunk;
255
+ while ((chunk = joiner.read()) !== null) {
256
+ chunks.push(chunk);
257
+ chunklen += chunk.length;
258
+ }
259
+ });
260
+
261
+ joiner.once('end', () => {
262
+ resolve(Buffer.concat(chunks, chunklen));
263
+ });
264
+
265
+ joiner.once('error', err => reject(err));
266
+ splitter.pipe(headersRewriter).pipe(joiner);
267
+ splitter.end(raw);
268
+ });
269
+ }
270
+
271
+ async function getRawEmail(data) {
272
+ if (data.raw) {
273
+ return await processMessage(data);
274
+ }
275
+
276
+ let sendAt = data.sendAt;
277
+ delete data.sendAt;
278
+
279
+ const mail = new MailComposer(data);
280
+ let compiled = mail.compile();
281
+ compiled.keepBcc = true;
282
+
283
+ let messageId = compiled.messageId();
284
+
285
+ let email = await compiled.build();
286
+
287
+ let bcc = [].concat(data.bcc || []);
288
+
289
+ return { raw: email, hasBcc: !!bcc.length, messageId, envelope: compiled.getEnvelope(), sendAt };
290
+ }
291
+
292
+ module.exports = { getRawEmail, removeBcc };
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const config = require('wild-config');
4
+ const vault = require('node-vault');
5
+ const logger = require('./logger');
6
+
7
+ config.service = config.service || {};
8
+
9
+ const ENCRYPT_SECRET = process.env.EENGINE_SECRET || config.service.secret;
10
+
11
+ const VAULT_ADDR = process.env.VAULT_ADDR;
12
+ const VAULT_ROLE_ID = process.env.VAULT_ROLE_ID;
13
+ const VAULT_SECRET_ID = process.env.VAULT_SECRET_ID;
14
+ const VAULT_PATH = process.env.VAULT_PATH;
15
+ const VAULT_KEY = process.env.VAULT_KEY || 'secret';
16
+
17
+ const vaultClient =
18
+ VAULT_ADDR && VAULT_ROLE_ID && VAULT_SECRET_ID && VAULT_PATH
19
+ ? vault({
20
+ apiVersion: 'v1',
21
+ endpoint: VAULT_ADDR
22
+ })
23
+ : false;
24
+
25
+ const cache = new Map();
26
+
27
+ async function getVaultClient(opts) {
28
+ if (!vaultClient) {
29
+ return false;
30
+ }
31
+
32
+ if (!vaultClient.token) {
33
+ const result = await vaultClient.approleLogin({
34
+ role_id: VAULT_ROLE_ID,
35
+ secret_id: VAULT_SECRET_ID
36
+ });
37
+ vault.token = result.auth.client_token;
38
+ if (opts && opts.text) {
39
+ console.error(`Retrieved access token from Vault for role ${VAULT_ROLE_ID}`);
40
+ } else {
41
+ logger.info({ msg: 'Retrieved access token from Vault', role_id: VAULT_ROLE_ID });
42
+ }
43
+ }
44
+
45
+ return vaultClient;
46
+ }
47
+
48
+ async function getSecret(opts) {
49
+ if (cache.has('secret')) {
50
+ return cache.get('secret');
51
+ }
52
+
53
+ if (process.env._VAULT_SECRET) {
54
+ if (opts && opts.text) {
55
+ console.error(`Using cached encryption secret from Vault`);
56
+ } else {
57
+ logger.info({ msg: 'Using cached encryption secret from Vault', role_id: VAULT_ROLE_ID });
58
+ }
59
+ cache.set('secret', process.env._VAULT_SECRET);
60
+ return process.env._VAULT_SECRET;
61
+ }
62
+
63
+ // check vault
64
+ let vaultClient = await getVaultClient(opts);
65
+ if (vaultClient) {
66
+ let vaultRes = await vaultClient.read(VAULT_PATH);
67
+ let secret = vaultRes && vaultRes.data && vaultRes.data.data && vaultRes.data.data[VAULT_KEY];
68
+
69
+ if (opts && opts.text) {
70
+ console.error(`Retrieved encryption secret from Vault for role ${VAULT_ROLE_ID}`);
71
+ } else {
72
+ logger.info({ msg: 'Retrieved encryption secret from Vault', role_id: VAULT_ROLE_ID });
73
+ }
74
+
75
+ process.env._VAULT_SECRET = secret;
76
+ cache.set('secret', process.env._VAULT_SECRET);
77
+ return secret;
78
+ }
79
+
80
+ return ENCRYPT_SECRET;
81
+ }
82
+
83
+ module.exports = getSecret;
package/lib/logger.js ADDED
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const config = require('wild-config');
4
+ const pino = require('pino');
5
+
6
+ config.log = config.log || {
7
+ level: 'trace'
8
+ };
9
+
10
+ let logger = pino();
11
+ logger.level = process.env.EENGINE_LOG_LEVEL || config.log.level;
12
+
13
+ const { threadId } = require('worker_threads');
14
+
15
+ if (threadId) {
16
+ logger = logger.child({ tid: threadId });
17
+ }
18
+
19
+ process.on('uncaughtException', err => {
20
+ logger.fatal({
21
+ msg: 'uncaughtException',
22
+ err
23
+ });
24
+ setTimeout(() => process.exit(1), 10);
25
+ });
26
+
27
+ process.on('unhandledRejection', err => {
28
+ logger.fatal({
29
+ msg: 'unhandledRejection',
30
+ err
31
+ });
32
+ setTimeout(() => process.exit(2), 10);
33
+ });
34
+
35
+ module.exports = logger;
@@ -0,0 +1,51 @@
1
+ local listKey = KEYS[1];
2
+
3
+ local filterState = ARGV[1];
4
+ local skip = tonumber(ARGV[2]) or 0;
5
+ local count = tonumber(ARGV[3]) or 0;
6
+
7
+ local total = redis.call("SCARD", listKey);
8
+
9
+ local list = redis.call("SMEMBERS", listKey);
10
+
11
+ if skip >= total then
12
+ return {total,skip, {}}
13
+ end
14
+
15
+ local shouldSkip = skip;
16
+ local matching = 0;
17
+ local result = {}
18
+
19
+ for index, account in ipairs(list) do
20
+
21
+ local state;
22
+ if filterState ~= '*' then
23
+ -- load only if we actually need to compare account state value
24
+ state = redis.call("HGET", "iad:" .. account, "state");
25
+ end
26
+
27
+ if filterState == '*' or filterState == state then
28
+ -- state matches, can use this entry for listing
29
+
30
+ if shouldSkip == 0 then
31
+ -- enough entries skipped, can use
32
+ if #result < count then
33
+ -- now we can actually use this record
34
+ result[#result + 1] = redis.call("HGETALL", "iad:" .. account);
35
+ else
36
+ -- max number entries in result buffer
37
+ if filterState == '*' then
38
+ -- no point looking further, we already know the total count
39
+ matching = total;
40
+ break;
41
+ end
42
+ end
43
+ else
44
+ shouldSkip = shouldSkip - 1;
45
+ end
46
+
47
+ matching = matching + 1;
48
+ end
49
+ end
50
+
51
+ return {matching, skip, result}
@@ -0,0 +1,20 @@
1
+ local listKey = KEYS[1];
2
+ local mailboxKey = KEYS[2];
3
+
4
+ local seq = tonumber(ARGV[1]) or 0;
5
+
6
+ if redis.call("EXISTS", listKey) == 1 then
7
+ local list = redis.call("ZRANGE", listKey, seq-1, seq-1, "WITHSCORES");
8
+ local entry = list[1];
9
+ local uid = tonumber(list[2]) or 0;
10
+
11
+ if entry ~= nil and entry ~= "" and uid > 0 then
12
+ local y = redis.call("ZREMRANGEBYRANK", listKey, seq-1, seq-1);
13
+ if y == 1 then
14
+ redis.call("HINCRBY", mailboxKey, "messages", -1);
15
+ end
16
+ return {uid, entry};
17
+ end;
18
+ end;
19
+
20
+ return nil;