@oneuptime/common 12.0.13 → 12.0.14

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 (376) hide show
  1. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +14 -0
  2. package/Models/DatabaseModels/UserCall.ts +94 -3
  3. package/Models/DatabaseModels/UserEmail.ts +94 -3
  4. package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
  5. package/Models/DatabaseModels/UserPush.ts +37 -0
  6. package/Models/DatabaseModels/UserSMS.ts +94 -3
  7. package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
  8. package/Server/API/BaseAPI.ts +79 -14
  9. package/Server/API/UserCallAPI.ts +53 -45
  10. package/Server/API/UserEmailAPI.ts +53 -44
  11. package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
  12. package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
  13. package/Server/API/UserPushAPI.ts +126 -2
  14. package/Server/API/UserSmsAPI.ts +53 -44
  15. package/Server/API/UserWhatsAppAPI.ts +53 -50
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
  21. package/Server/Services/AnalyticsDatabaseService.ts +169 -0
  22. package/Server/Services/DatabaseService.ts +101 -0
  23. package/Server/Services/LogAggregationService.ts +5 -0
  24. package/Server/Services/PushNotificationService.ts +127 -12
  25. package/Server/Services/TeamMemberService.ts +53 -13
  26. package/Server/Services/UserCallService.ts +55 -24
  27. package/Server/Services/UserEmailService.ts +83 -17
  28. package/Server/Services/UserIncomingCallNumberService.ts +49 -85
  29. package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
  30. package/Server/Services/UserNotificationRuleService.ts +26 -0
  31. package/Server/Services/UserPushService.ts +89 -0
  32. package/Server/Services/UserSmsService.ts +50 -17
  33. package/Server/Services/UserWhatsAppService.ts +48 -15
  34. package/Server/Types/Database/QueryHelper.ts +30 -0
  35. package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
  36. package/Server/Utils/ChannelVerification.ts +510 -0
  37. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
  38. package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
  39. package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
  40. package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
  41. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
  42. package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
  43. package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
  44. package/Server/Utils/TotpAuth.ts +137 -9
  45. package/Server/Utils/UserRegistrationToken.ts +182 -0
  46. package/Server/Utils/VerificationCode.ts +134 -0
  47. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
  48. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
  49. package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
  50. package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
  51. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
  52. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
  53. package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
  54. package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
  55. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
  56. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
  57. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
  58. package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
  59. package/Tests/Server/API/UserSmsApi.test.ts +107 -14
  60. package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
  61. package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
  62. package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
  63. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
  64. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
  65. package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
  66. package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
  67. package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
  68. package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
  69. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
  70. package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
  71. package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
  72. package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
  73. package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
  74. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
  75. package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
  76. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
  77. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
  78. package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
  79. package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
  80. package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
  81. package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
  82. package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
  83. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
  84. package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
  85. package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
  86. package/Tests/Server/Utils/Browser.test.ts +162 -0
  87. package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
  88. package/Tests/Server/Utils/Express.test.ts +371 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
  90. package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
  91. package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
  92. package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
  93. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
  94. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
  95. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
  96. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
  97. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
  98. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
  99. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
  100. package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
  101. package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
  102. package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
  103. package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
  104. package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
  105. package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
  106. package/Tests/Types/Call/CallRequest.test.ts +189 -0
  107. package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
  108. package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
  109. package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
  110. package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
  111. package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
  112. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
  113. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
  114. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
  115. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
  116. package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
  117. package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
  118. package/Tests/Types/TextRandomGeneration.test.ts +211 -0
  119. package/Tests/UI/Components/Button.test.tsx +175 -1
  120. package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
  121. package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
  122. package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
  123. package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
  124. package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
  125. package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
  126. package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
  127. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
  128. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
  129. package/Tests/UI/Components/List.test.tsx +9 -2
  130. package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
  131. package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
  132. package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
  133. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
  134. package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
  135. package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
  136. package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
  137. package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
  138. package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
  139. package/Tests/UI/Components/Skeleton.test.tsx +71 -0
  140. package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
  141. package/Tests/UI/Components/Tabs.test.tsx +24 -0
  142. package/Tests/UI/Components/Toast.test.tsx +130 -7
  143. package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
  144. package/Tests/UI/Utils/Dropdown.test.ts +423 -0
  145. package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
  146. package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
  147. package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
  148. package/Tests/Utils/API.test.ts +16 -2
  149. package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
  150. package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
  151. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
  152. package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
  153. package/Types/Email/EmailTemplateType.ts +1 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
  155. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
  156. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
  157. package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
  158. package/Types/Permission.ts +35 -0
  159. package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
  160. package/Types/PushNotification/PushNotificationMessage.ts +15 -0
  161. package/Types/PushNotification/PushNotificationRequest.ts +8 -16
  162. package/Types/Service/ServiceLanguage.ts +144 -0
  163. package/Types/Text.ts +111 -19
  164. package/UI/Components/Button/Button.tsx +27 -14
  165. package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
  166. package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
  167. package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
  168. package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
  169. package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
  170. package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
  171. package/UI/Components/CommandPalette/Types.ts +49 -0
  172. package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
  173. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  174. package/UI/Components/Feed/FeedItem.tsx +1 -1
  175. package/UI/Components/List/List.tsx +37 -5
  176. package/UI/Components/List/ListSkeleton.tsx +54 -0
  177. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
  178. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
  179. package/UI/Components/Modal/Modal.tsx +2 -2
  180. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  181. package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
  182. package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
  183. package/UI/Components/Navbar/NavBar.tsx +14 -1
  184. package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
  185. package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
  186. package/UI/Components/SideOver/SideOver.tsx +37 -2
  187. package/UI/Components/Skeleton/Skeleton.tsx +53 -0
  188. package/UI/Components/Table/Table.tsx +74 -44
  189. package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
  190. package/UI/Components/Tabs/Tab.tsx +1 -1
  191. package/UI/Components/Toast/Toast.tsx +118 -53
  192. package/UI/Components/Toast/ToastInit.tsx +82 -32
  193. package/UI/Styles/Theme.css +40 -0
  194. package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
  195. package/UI/Utils/ModelListCache.ts +188 -0
  196. package/UI/Utils/Project.ts +29 -0
  197. package/Utils/API.ts +6 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
  201. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
  203. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
  205. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
  207. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
  209. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
  211. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  212. package/build/dist/Server/API/BaseAPI.js +69 -13
  213. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  214. package/build/dist/Server/API/UserCallAPI.js +42 -38
  215. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  216. package/build/dist/Server/API/UserEmailAPI.js +42 -38
  217. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  218. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
  219. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  220. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
  221. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
  222. package/build/dist/Server/API/UserPushAPI.js +95 -2
  223. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  224. package/build/dist/Server/API/UserSmsAPI.js +42 -38
  225. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  226. package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
  227. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  236. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
  237. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
  239. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  240. package/build/dist/Server/Services/DatabaseService.js +74 -0
  241. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  242. package/build/dist/Server/Services/LogAggregationService.js +5 -0
  243. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  244. package/build/dist/Server/Services/PushNotificationService.js +69 -30
  245. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  246. package/build/dist/Server/Services/TeamMemberService.js +43 -7
  247. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  248. package/build/dist/Server/Services/UserCallService.js +54 -22
  249. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  250. package/build/dist/Server/Services/UserEmailService.js +78 -17
  251. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  252. package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
  253. package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
  254. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
  255. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
  256. package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
  257. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  258. package/build/dist/Server/Services/UserPushService.js +77 -0
  259. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  260. package/build/dist/Server/Services/UserSmsService.js +50 -17
  261. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  262. package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
  263. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  264. package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
  265. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  266. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
  267. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
  268. package/build/dist/Server/Utils/ChannelVerification.js +348 -0
  269. package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
  270. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
  271. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  272. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
  273. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  274. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
  275. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  276. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
  277. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
  278. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
  279. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
  280. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
  281. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
  282. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  283. package/build/dist/Server/Utils/TotpAuth.js +127 -8
  284. package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
  285. package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
  286. package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
  287. package/build/dist/Server/Utils/VerificationCode.js +120 -0
  288. package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
  289. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  290. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  291. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
  292. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  293. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
  294. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  295. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
  296. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  297. package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
  298. package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
  299. package/build/dist/Types/Permission.js +33 -0
  300. package/build/dist/Types/Permission.js.map +1 -1
  301. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
  302. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
  303. package/build/dist/Types/Service/ServiceLanguage.js +96 -0
  304. package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
  305. package/build/dist/Types/Text.js +82 -17
  306. package/build/dist/Types/Text.js.map +1 -1
  307. package/build/dist/UI/Components/Button/Button.js +22 -14
  308. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  309. package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
  310. package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
  311. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
  312. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
  313. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
  314. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
  315. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
  316. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
  317. package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
  318. package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
  319. package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
  320. package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
  321. package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
  322. package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
  323. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
  324. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
  325. package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
  326. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  327. package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
  328. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  329. package/build/dist/UI/Components/List/List.js +19 -5
  330. package/build/dist/UI/Components/List/List.js.map +1 -1
  331. package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
  332. package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
  333. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  334. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
  335. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  336. package/build/dist/UI/Components/Modal/Modal.js +2 -2
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  338. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  339. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
  340. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  343. package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
  344. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  345. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
  346. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
  347. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
  348. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
  349. package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
  350. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  351. package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
  352. package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
  353. package/build/dist/UI/Components/Table/Table.js +35 -24
  354. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  355. package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
  356. package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
  357. package/build/dist/UI/Components/Tabs/Tab.js +1 -1
  358. package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
  359. package/build/dist/UI/Components/Toast/Toast.js +77 -23
  360. package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
  361. package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
  362. package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
  363. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
  364. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  365. package/build/dist/UI/Utils/ModelListCache.js +126 -0
  366. package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
  367. package/build/dist/UI/Utils/Project.js +24 -1
  368. package/build/dist/UI/Utils/Project.js.map +1 -1
  369. package/build/dist/Utils/API.js +6 -1
  370. package/build/dist/Utils/API.js.map +1 -1
  371. package/jest.config.json +1 -1
  372. package/package.json +2 -3
  373. package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
  374. package/Tests/__mocks__/otpauth.js +0 -30
  375. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
  376. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +0 -1
@@ -0,0 +1,401 @@
1
+ import UserEmailService from "../../../Server/Services/UserEmailService";
2
+ import MailService from "../../../Server/Services/MailService";
3
+ import ChannelVerification, {
4
+ RESEND_COOLDOWN_SECONDS,
5
+ } from "../../../Server/Utils/ChannelVerification";
6
+ import VerificationCode from "../../../Server/Utils/VerificationCode";
7
+ import ObjectID from "../../../Types/ObjectID";
8
+ import Email from "../../../Types/Email";
9
+ import ExceptionCode from "../../../Types/Exception/ExceptionCode";
10
+ import Exception from "../../../Types/Exception/Exception";
11
+ import UserEmail from "../../../Models/DatabaseModels/UserEmail";
12
+ import { beforeEach, describe, expect, it } from "@jest/globals";
13
+
14
+ jest.mock("../../../Server/Services/MailService");
15
+ jest.mock("../../../Server/Utils/Logger", () => {
16
+ return {
17
+ __esModule: true,
18
+ default: {
19
+ debug: jest.fn(),
20
+ info: jest.fn(),
21
+ warn: jest.fn(),
22
+ error: jest.fn(),
23
+ },
24
+ getLogAttributesFromRequest: jest.fn().mockReturnValue({}),
25
+ };
26
+ });
27
+
28
+ /*
29
+ * GHSA-5cr8-vph4-3hrf, issuance side.
30
+ *
31
+ * The verify route is only half the story. The other half is what a code's
32
+ * life looks like: it must be minted fresh, stored as a digest, sent in
33
+ * plaintext exactly once, and NOT re-issuable on demand — because "spend the
34
+ * five attempts, ask for another code, repeat" is otherwise a free bypass of
35
+ * the attempt limit, paid for by whoever owns the address in unsolicited
36
+ * messages.
37
+ *
38
+ * UserEmailService stands in for all five channels here. The three that go
39
+ * over Twilio/WhatsApp wrap the identical calls in project balance checks; the
40
+ * shared behaviour lives in ChannelVerification and is what these exercise.
41
+ */
42
+
43
+ const ITEM_ID: ObjectID = new ObjectID("2c4e6a80-1111-4111-8111-111111111111");
44
+ const USER_ID: ObjectID = new ObjectID("2c4e6a80-2222-4222-8222-222222222222");
45
+ const PROJECT_ID: ObjectID = new ObjectID(
46
+ "2c4e6a80-3333-4333-8333-333333333333",
47
+ );
48
+
49
+ type AnyRecord = Record<string, unknown>;
50
+
51
+ const buildItem: (overrides?: Partial<UserEmail>) => UserEmail = (
52
+ overrides: Partial<UserEmail> = {},
53
+ ) => {
54
+ return {
55
+ _id: ITEM_ID.toString(),
56
+ id: ITEM_ID,
57
+ userId: USER_ID,
58
+ projectId: PROJECT_ID,
59
+ email: Email.fromString("responder@example.com"),
60
+ isVerified: false,
61
+ ...overrides,
62
+ } as UserEmail;
63
+ };
64
+
65
+ describe("notification channel verification code issuance", () => {
66
+ let updateOneById: jest.Mock;
67
+
68
+ beforeEach(() => {
69
+ jest.clearAllMocks();
70
+
71
+ updateOneById = jest.fn().mockResolvedValue(1);
72
+ UserEmailService.updateOneById = updateOneById as never;
73
+
74
+ (MailService.sendMail as unknown as jest.Mock).mockResolvedValue(
75
+ undefined as never,
76
+ );
77
+ });
78
+
79
+ describe("issueAndSendVerificationCode", () => {
80
+ it("stores a digest and never the code itself", async () => {
81
+ await UserEmailService.issueAndSendVerificationCode(buildItem());
82
+
83
+ const written: AnyRecord = (
84
+ updateOneById.mock.calls[0]?.[0] as { data: AnyRecord }
85
+ ).data;
86
+
87
+ const sentCode: string = (
88
+ (MailService.sendMail as unknown as jest.Mock).mock.calls[0]?.[0] as {
89
+ vars: Record<string, string>;
90
+ }
91
+ ).vars["code"] as string;
92
+
93
+ expect(sentCode).toMatch(/^[0-9]{6}$/);
94
+ expect(written["verificationCode"]).not.toBe(sentCode);
95
+ expect(written["verificationCode"]).toMatch(/^[0-9a-f]{64}$/);
96
+ });
97
+
98
+ it("stores a digest the sent code actually verifies against", async () => {
99
+ await UserEmailService.issueAndSendVerificationCode(buildItem());
100
+
101
+ const written: AnyRecord = (
102
+ updateOneById.mock.calls[0]?.[0] as { data: AnyRecord }
103
+ ).data;
104
+
105
+ const sentCode: string = (
106
+ (MailService.sendMail as unknown as jest.Mock).mock.calls[0]?.[0] as {
107
+ vars: Record<string, string>;
108
+ }
109
+ ).vars["code"] as string;
110
+
111
+ expect(
112
+ VerificationCode.isHashEqual(
113
+ VerificationCode.hashCode({
114
+ code: sentCode,
115
+ channelId: ITEM_ID,
116
+ }),
117
+ written["verificationCode"] as string,
118
+ ),
119
+ ).toBe(true);
120
+ });
121
+
122
+ it("sets an expiry and zeroes the attempt counter", async () => {
123
+ await UserEmailService.issueAndSendVerificationCode(buildItem());
124
+
125
+ const written: AnyRecord = (
126
+ updateOneById.mock.calls[0]?.[0] as { data: AnyRecord }
127
+ ).data;
128
+
129
+ expect(written["verificationFailedAttempts"]).toBe(0);
130
+ expect(written["verificationCodeExpiresAt"]).toBeInstanceOf(Date);
131
+ expect(
132
+ (written["verificationCodeExpiresAt"] as Date).getTime(),
133
+ ).toBeGreaterThan(Date.now());
134
+ expect(written["verificationCodeSentAt"]).toBeInstanceOf(Date);
135
+ });
136
+
137
+ it("mints a different code every time", async () => {
138
+ const codes: Set<string> = new Set<string>();
139
+
140
+ for (let i: number = 0; i < 25; i++) {
141
+ await UserEmailService.issueAndSendVerificationCode(buildItem());
142
+ }
143
+
144
+ for (const call of (MailService.sendMail as unknown as jest.Mock).mock
145
+ .calls) {
146
+ codes.add(
147
+ ((call as Array<unknown>)[0] as { vars: Record<string, string> })
148
+ .vars["code"] as string,
149
+ );
150
+ }
151
+
152
+ expect(codes.size).toBeGreaterThan(20);
153
+ });
154
+
155
+ it("sends to the address on the row", async () => {
156
+ await UserEmailService.issueAndSendVerificationCode(buildItem());
157
+
158
+ const mail: AnyRecord = (MailService.sendMail as unknown as jest.Mock)
159
+ .mock.calls[0]?.[0] as AnyRecord;
160
+
161
+ expect(mail["toEmail"]?.toString()).toBe("responder@example.com");
162
+ });
163
+ });
164
+
165
+ describe("resendVerificationCode", () => {
166
+ const stubFind: (item: UserEmail | null) => void = (
167
+ item: UserEmail | null,
168
+ ) => {
169
+ UserEmailService.findOneById = jest
170
+ .fn()
171
+ .mockResolvedValue(item as never) as never;
172
+ };
173
+
174
+ it("refuses an unknown item", async () => {
175
+ stubFind(null);
176
+
177
+ await expect(
178
+ UserEmailService.resendVerificationCode(ITEM_ID),
179
+ ).rejects.toThrow("not found");
180
+ });
181
+
182
+ it("refuses a row that is already verified", async () => {
183
+ stubFind(buildItem({ isVerified: true }));
184
+
185
+ await expect(
186
+ UserEmailService.resendVerificationCode(ITEM_ID),
187
+ ).rejects.toThrow("already verified");
188
+
189
+ expect(MailService.sendMail).not.toHaveBeenCalled();
190
+ });
191
+
192
+ it("sends when no code has been sent before", async () => {
193
+ stubFind(buildItem());
194
+
195
+ await UserEmailService.resendVerificationCode(ITEM_ID);
196
+
197
+ expect(MailService.sendMail).toHaveBeenCalled();
198
+ });
199
+
200
+ /*
201
+ * The control on "burn the attempts, request a new code, repeat" — and on
202
+ * using the resend button to message somebody repeatedly.
203
+ */
204
+ it("refuses a resend inside the cooldown", async () => {
205
+ stubFind(
206
+ buildItem({
207
+ verificationCodeSentAt: new Date(Date.now() - 5000),
208
+ }),
209
+ );
210
+
211
+ let thrown: Exception | undefined = undefined;
212
+
213
+ try {
214
+ await UserEmailService.resendVerificationCode(ITEM_ID);
215
+ } catch (err) {
216
+ thrown = err as Exception;
217
+ }
218
+
219
+ expect(thrown?.code).toBe(ExceptionCode.TooManyRequestsException);
220
+ expect(thrown?.message).toContain("Please wait");
221
+ expect(MailService.sendMail).not.toHaveBeenCalled();
222
+ });
223
+
224
+ it("does not issue a new code when it refuses the resend", async () => {
225
+ stubFind(
226
+ buildItem({
227
+ verificationCodeSentAt: new Date(Date.now() - 1000),
228
+ }),
229
+ );
230
+
231
+ await expect(
232
+ UserEmailService.resendVerificationCode(ITEM_ID),
233
+ ).rejects.toThrow();
234
+
235
+ expect(updateOneById).not.toHaveBeenCalled();
236
+ });
237
+
238
+ it("allows a resend once the cooldown has elapsed", async () => {
239
+ stubFind(
240
+ buildItem({
241
+ verificationCodeSentAt: new Date(
242
+ Date.now() - (RESEND_COOLDOWN_SECONDS + 5) * 1000,
243
+ ),
244
+ }),
245
+ );
246
+
247
+ await UserEmailService.resendVerificationCode(ITEM_ID);
248
+
249
+ expect(MailService.sendMail).toHaveBeenCalled();
250
+ });
251
+
252
+ /*
253
+ * A resend must hand out a NEW code. Re-sending the same one would make
254
+ * the attempt counter worthless — the attacker resumes against the value
255
+ * they were already working on.
256
+ */
257
+ it("issues a fresh code rather than resending the old one", async () => {
258
+ const previousDigest: string = VerificationCode.hashCode({
259
+ code: "123456",
260
+ channelId: ITEM_ID,
261
+ });
262
+
263
+ stubFind(
264
+ buildItem({
265
+ verificationCode: previousDigest,
266
+ verificationCodeExpiresAt: ChannelVerification.getExpiresAt(),
267
+ }),
268
+ );
269
+
270
+ await UserEmailService.resendVerificationCode(ITEM_ID);
271
+
272
+ const written: AnyRecord = (
273
+ updateOneById.mock.calls[0]?.[0] as { data: AnyRecord }
274
+ ).data;
275
+
276
+ expect(written["verificationCode"]).not.toBe(previousDigest);
277
+ expect(written["verificationFailedAttempts"]).toBe(0);
278
+ });
279
+
280
+ /*
281
+ * The row is looked up without asking for the code column at all — the
282
+ * send no longer needs it, because the plaintext is produced at issue
283
+ * time and never read back.
284
+ */
285
+ it("does not read the stored code back in order to send it", async () => {
286
+ stubFind(buildItem());
287
+
288
+ await UserEmailService.resendVerificationCode(ITEM_ID);
289
+
290
+ const select: AnyRecord = (
291
+ (UserEmailService.findOneById as unknown as jest.Mock).mock
292
+ .calls[0]?.[0] as { select: AnyRecord }
293
+ ).select;
294
+
295
+ expect(select["verificationCode"]).toBeUndefined();
296
+ });
297
+ });
298
+
299
+ /*
300
+ * The create response must NOT carry the plaintext code unless the e2e-only
301
+ * env flag is exactly "true". This is the whole security boundary of the
302
+ * seam UserEmailService.onCreateSuccess adds for the end-to-end suite: an
303
+ * accidental default-on, or a loose truthy check, would hand every caller a
304
+ * just-issued code on the create response and reinstate the takeover the
305
+ * hashing/expiry/attempt controls exist to stop. Pinned in both directions.
306
+ */
307
+ describe("the e2e verification-code exposure seam (onCreateSuccess)", () => {
308
+ const FLAG: string = "EXPOSE_VERIFICATION_CODE_IN_API_RESPONSE_FOR_E2E";
309
+
310
+ type OnCreateSuccess = (
311
+ onCreate: unknown,
312
+ createdItem: UserEmail,
313
+ ) => Promise<UserEmail>;
314
+
315
+ const runOnCreateSuccess: (item: UserEmail) => Promise<UserEmail> = (
316
+ item: UserEmail,
317
+ ) => {
318
+ return (
319
+ UserEmailService as unknown as { onCreateSuccess: OnCreateSuccess }
320
+ ).onCreateSuccess({}, item);
321
+ };
322
+
323
+ const sentCode: () => string = () => {
324
+ return (
325
+ (MailService.sendMail as unknown as jest.Mock).mock.calls[0]?.[0] as {
326
+ vars: Record<string, string>;
327
+ }
328
+ ).vars["code"] as string;
329
+ };
330
+
331
+ let previousFlag: string | undefined;
332
+
333
+ beforeEach(() => {
334
+ previousFlag = process.env[FLAG];
335
+ delete process.env[FLAG];
336
+ });
337
+
338
+ afterEach(() => {
339
+ if (previousFlag === undefined) {
340
+ delete process.env[FLAG];
341
+ } else {
342
+ process.env[FLAG] = previousFlag;
343
+ }
344
+ });
345
+
346
+ it("with the flag unset, a code is issued and mailed but never returned", async () => {
347
+ const result: UserEmail = await runOnCreateSuccess(buildItem());
348
+
349
+ expect(sentCode()).toMatch(/^[0-9]{6}$/);
350
+ expect(result.verificationCode).toBeUndefined();
351
+ });
352
+
353
+ it('only an exact "true" opens the hatch - any other truthy value stays closed', async () => {
354
+ for (const value of ["1", "TRUE", "yes", "on", " true "]) {
355
+ jest.clearAllMocks();
356
+ updateOneById = jest.fn().mockResolvedValue(1);
357
+ UserEmailService.updateOneById = updateOneById as never;
358
+ (MailService.sendMail as unknown as jest.Mock).mockResolvedValue(
359
+ undefined as never,
360
+ );
361
+
362
+ process.env[FLAG] = value;
363
+ const result: UserEmail = await runOnCreateSuccess(buildItem());
364
+
365
+ expect(result.verificationCode).toBeUndefined();
366
+ }
367
+ });
368
+
369
+ it('with the flag exactly "true", the response carries the mailed plaintext', async () => {
370
+ process.env[FLAG] = "true";
371
+ const result: UserEmail = await runOnCreateSuccess(buildItem());
372
+
373
+ expect(result.verificationCode).toBe(sentCode());
374
+ expect(result.verificationCode).toMatch(/^[0-9]{6}$/);
375
+ });
376
+
377
+ it("even with the hatch open, storage is still the digest and never the code", async () => {
378
+ process.env[FLAG] = "true";
379
+ await runOnCreateSuccess(buildItem());
380
+
381
+ const written: AnyRecord = (
382
+ updateOneById.mock.calls[0]?.[0] as { data: AnyRecord }
383
+ ).data;
384
+
385
+ expect(written["verificationCode"]).toMatch(/^[0-9a-f]{64}$/);
386
+ expect(written["verificationCode"]).not.toBe(sentCode());
387
+ });
388
+
389
+ it("an already-verified row issues no code and exposes nothing, flag on or off", async () => {
390
+ process.env[FLAG] = "true";
391
+ const result: UserEmail = await runOnCreateSuccess(
392
+ buildItem({ isVerified: true }),
393
+ );
394
+
395
+ expect(
396
+ MailService.sendMail as unknown as jest.Mock,
397
+ ).not.toHaveBeenCalled();
398
+ expect(result.verificationCode).toBeUndefined();
399
+ });
400
+ });
401
+ });
@@ -0,0 +1,204 @@
1
+ import PushNotificationService, {
2
+ ExpoDeliveryOptions,
3
+ } from "../../../Server/Services/PushNotificationService";
4
+ import AndroidNotificationChannel from "../../../Types/PushNotification/AndroidNotificationChannel";
5
+ import PushDeviceType from "../../../Types/PushNotification/PushDeviceType";
6
+ import PushNotificationMessage from "../../../Types/PushNotification/PushNotificationMessage";
7
+ import { describe, expect, test } from "@jest/globals";
8
+
9
+ /*
10
+ * getExpoDeliveryOptions is the single point that turns "this device opted into
11
+ * critical alerts" into the payload fields that actually make a handset ring
12
+ * through silent mode. It is the server's half of the three-way handshake
13
+ * documented on PushNotificationMessage.isCriticalAlert, so every branch of it
14
+ * is pinned here: a field quietly dropped from the critical branch is a page
15
+ * that arrives silently to a sleeping responder, and a field wrongly present on
16
+ * the non-critical branch is every ordinary notification screaming through Do
17
+ * Not Disturb.
18
+ */
19
+
20
+ function messageWith(
21
+ overrides: Partial<PushNotificationMessage> = {},
22
+ ): PushNotificationMessage {
23
+ return {
24
+ title: "Alert",
25
+ body: "Something is on fire",
26
+ ...overrides,
27
+ };
28
+ }
29
+
30
+ describe("PushNotificationService.getExpoDeliveryOptions", () => {
31
+ describe("a normal (non-critical) push", () => {
32
+ test.each<[string, PushDeviceType]>([
33
+ ["iOS", PushDeviceType.iOS],
34
+ ["Android", PushDeviceType.Android],
35
+ ])(
36
+ "on %s uses a plain 'default' sound, never the critical sound object",
37
+ (_label: string, deviceType: PushDeviceType) => {
38
+ const options: ExpoDeliveryOptions =
39
+ PushNotificationService.getExpoDeliveryOptions(
40
+ messageWith({ isCriticalAlert: false }),
41
+ deviceType,
42
+ );
43
+
44
+ expect(options.sound).toBe("default");
45
+ expect(options.priority).toBe("high");
46
+ },
47
+ );
48
+
49
+ test("carries NO interruptionLevel - that field is the iOS critical lever", () => {
50
+ const options: ExpoDeliveryOptions =
51
+ PushNotificationService.getExpoDeliveryOptions(
52
+ messageWith({ isCriticalAlert: false }),
53
+ PushDeviceType.iOS,
54
+ );
55
+
56
+ expect(options.interruptionLevel).toBeUndefined();
57
+ });
58
+
59
+ test("an unset isCriticalAlert is treated exactly like false", () => {
60
+ const options: ExpoDeliveryOptions =
61
+ PushNotificationService.getExpoDeliveryOptions(
62
+ messageWith(),
63
+ PushDeviceType.iOS,
64
+ );
65
+
66
+ expect(options.sound).toBe("default");
67
+ expect(options.interruptionLevel).toBeUndefined();
68
+ });
69
+
70
+ test("Android without the critical flag stays on the High channel", () => {
71
+ const options: ExpoDeliveryOptions =
72
+ PushNotificationService.getExpoDeliveryOptions(
73
+ messageWith({ isCriticalAlert: false }),
74
+ PushDeviceType.Android,
75
+ );
76
+
77
+ expect(options.channelId).toBe(AndroidNotificationChannel.High);
78
+ });
79
+
80
+ test("iOS names the payload-sound 'default' channel, not an Android channel", () => {
81
+ const options: ExpoDeliveryOptions =
82
+ PushNotificationService.getExpoDeliveryOptions(
83
+ messageWith({ isCriticalAlert: false }),
84
+ PushDeviceType.iOS,
85
+ );
86
+
87
+ expect(options.channelId).toBe("default");
88
+ });
89
+ });
90
+
91
+ describe("a critical push", () => {
92
+ test("iOS gets the critical sound object AND interruptionLevel 'critical'", () => {
93
+ const options: ExpoDeliveryOptions =
94
+ PushNotificationService.getExpoDeliveryOptions(
95
+ messageWith({ isCriticalAlert: true }),
96
+ PushDeviceType.iOS,
97
+ );
98
+
99
+ /*
100
+ * Both are required together: APNs ignores the ringer only when the
101
+ * sound object says critical AND the interruption level is critical.
102
+ */
103
+ expect(options.sound).toEqual({
104
+ critical: true,
105
+ name: "default",
106
+ volume: PushNotificationService.CRITICAL_ALERT_VOLUME,
107
+ });
108
+ expect(options.interruptionLevel).toBe("critical");
109
+ expect(options.priority).toBe("high");
110
+ });
111
+
112
+ test("the critical volume is pinned to the maximum (1)", () => {
113
+ /*
114
+ * A critical alert exists to wake somebody; a quieter volume would
115
+ * defeat the whole opt-in.
116
+ */
117
+ expect(PushNotificationService.CRITICAL_ALERT_VOLUME).toBe(1);
118
+
119
+ const options: ExpoDeliveryOptions =
120
+ PushNotificationService.getExpoDeliveryOptions(
121
+ messageWith({ isCriticalAlert: true }),
122
+ PushDeviceType.iOS,
123
+ );
124
+
125
+ expect((options.sound as { volume: number }).volume).toBe(1);
126
+ });
127
+
128
+ test("Android is moved to the Critical channel (its only lever for DND bypass)", () => {
129
+ const options: ExpoDeliveryOptions =
130
+ PushNotificationService.getExpoDeliveryOptions(
131
+ messageWith({ isCriticalAlert: true }),
132
+ PushDeviceType.Android,
133
+ );
134
+
135
+ expect(options.channelId).toBe(AndroidNotificationChannel.Critical);
136
+ });
137
+
138
+ test("the critical sound object is sent on Android too, one shape for both platforms", () => {
139
+ /*
140
+ * The comment in source is explicit that keeping one shape avoids a
141
+ * per-platform branch that could drop the iOS half; Android ignores it.
142
+ */
143
+ const options: ExpoDeliveryOptions =
144
+ PushNotificationService.getExpoDeliveryOptions(
145
+ messageWith({ isCriticalAlert: true }),
146
+ PushDeviceType.Android,
147
+ );
148
+
149
+ expect(options.sound).toEqual({
150
+ critical: true,
151
+ name: "default",
152
+ volume: PushNotificationService.CRITICAL_ALERT_VOLUME,
153
+ });
154
+ expect(options.interruptionLevel).toBe("critical");
155
+ });
156
+ });
157
+
158
+ describe("the critical flag is the ONLY thing that changes the sound", () => {
159
+ test.each<[PushDeviceType]>([
160
+ [PushDeviceType.iOS],
161
+ [PushDeviceType.Android],
162
+ [PushDeviceType.Web],
163
+ ])(
164
+ "flipping isCriticalAlert flips the sound between plain and critical (%s)",
165
+ (deviceType: PushDeviceType) => {
166
+ const normal: ExpoDeliveryOptions =
167
+ PushNotificationService.getExpoDeliveryOptions(
168
+ messageWith({ isCriticalAlert: false }),
169
+ deviceType,
170
+ );
171
+ const critical: ExpoDeliveryOptions =
172
+ PushNotificationService.getExpoDeliveryOptions(
173
+ messageWith({ isCriticalAlert: true }),
174
+ deviceType,
175
+ );
176
+
177
+ expect(normal.sound).toBe("default");
178
+ expect(critical.sound).not.toBe("default");
179
+ expect(normal.interruptionLevel).toBeUndefined();
180
+ expect(critical.interruptionLevel).toBe("critical");
181
+ },
182
+ );
183
+ });
184
+
185
+ describe("priority is always high", () => {
186
+ test.each<[string, PushDeviceType, boolean]>([
187
+ ["iOS normal", PushDeviceType.iOS, false],
188
+ ["iOS critical", PushDeviceType.iOS, true],
189
+ ["Android normal", PushDeviceType.Android, false],
190
+ ["Android critical", PushDeviceType.Android, true],
191
+ ])(
192
+ "%s is delivered with priority 'high'",
193
+ (_label: string, deviceType: PushDeviceType, isCritical: boolean) => {
194
+ const options: ExpoDeliveryOptions =
195
+ PushNotificationService.getExpoDeliveryOptions(
196
+ messageWith({ isCriticalAlert: isCritical }),
197
+ deviceType,
198
+ );
199
+
200
+ expect(options.priority).toBe("high");
201
+ },
202
+ );
203
+ });
204
+ });
@@ -21,6 +21,7 @@ import BadDataException from "../../../Types/Exception/BadDataException";
21
21
  import ObjectID from "../../../Types/ObjectID";
22
22
  import PositiveNumber from "../../../Types/PositiveNumber";
23
23
  import EmailMessage from "../../../Types/Email/EmailMessage";
24
+ import HashedString from "../../../Types/HashedString";
24
25
  import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
25
26
 
26
27
  /*
@@ -156,6 +157,20 @@ function verifiedUser(): User {
156
157
  return user;
157
158
  }
158
159
 
160
+ /*
161
+ * The invitee in this file has already finished registering. The password is
162
+ * what says so: onBeforeCreate reads it to decide whether the invitation needs
163
+ * to carry a registration token, and an invitee without one sends it down the
164
+ * token-minting path these tests do not stub. Which link the mail carries is
165
+ * covered in TeamMemberInviteRegistrationToken.test.ts instead.
166
+ */
167
+ function registeredUser(): User {
168
+ const user: User = verifiedUser();
169
+ user.password = new HashedString("already-registered", true);
170
+
171
+ return user;
172
+ }
173
+
159
174
  let sendMailSpy: jest.SpyInstance;
160
175
  let addSettingsSpy: jest.SpyInstance;
161
176
  let addRuleSpy: jest.SpyInstance;
@@ -191,7 +206,12 @@ beforeEach(() => {
191
206
  findUserByIdSpy = jest
192
207
  .spyOn(UserService, "findOneById")
193
208
  .mockResolvedValue(verifiedUser());
194
- jest.spyOn(UserService, "findByEmail").mockResolvedValue(verifiedUser());
209
+
210
+ /*
211
+ * onBeforeCreate looks the invitee up with findOneBy rather than findByEmail,
212
+ * because it needs the password column and findByEmail selects only the id.
213
+ */
214
+ jest.spyOn(UserService, "findOneBy").mockResolvedValue(registeredUser());
195
215
 
196
216
  addSettingsSpy = jest
197
217
  .spyOn(