@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,543 @@
1
+ import UserCallAPI from "../../../Server/API/UserCallAPI";
2
+ import UserEmailAPI from "../../../Server/API/UserEmailAPI";
3
+ import UserIncomingCallNumberAPI from "../../../Server/API/UserIncomingCallNumberAPI";
4
+ import UserSmsAPI from "../../../Server/API/UserSmsAPI";
5
+ import UserWhatsAppAPI from "../../../Server/API/UserWhatsAppAPI";
6
+ import UserCallService from "../../../Server/Services/UserCallService";
7
+ import UserEmailService from "../../../Server/Services/UserEmailService";
8
+ import UserIncomingCallNumberService from "../../../Server/Services/UserIncomingCallNumberService";
9
+ import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
10
+ import UserSmsService from "../../../Server/Services/UserSmsService";
11
+ import UserWhatsAppService from "../../../Server/Services/UserWhatsAppService";
12
+ import ChannelVerification, {
13
+ MAX_VERIFICATION_ATTEMPTS,
14
+ VerifiableChannelFields,
15
+ } from "../../../Server/Utils/ChannelVerification";
16
+ import VerificationCode from "../../../Server/Utils/VerificationCode";
17
+ import {
18
+ NextFunction,
19
+ OneUptimeRequest,
20
+ OneUptimeResponse,
21
+ } from "../../../Server/Utils/Express";
22
+ import Response from "../../../Server/Utils/Response";
23
+ import { mockRouter } from "./Helpers";
24
+ import BadDataException from "../../../Types/Exception/BadDataException";
25
+ import JSONWebTokenData from "../../../Types/JsonWebTokenData";
26
+ import ObjectID from "../../../Types/ObjectID";
27
+ import { beforeEach, describe, expect, it } from "@jest/globals";
28
+
29
+ jest.mock("../../../Server/Utils/Express", () => {
30
+ return {
31
+ getRouter: () => {
32
+ return mockRouter;
33
+ },
34
+ };
35
+ });
36
+
37
+ jest.mock("../../../Server/Utils/Response", () => {
38
+ return {
39
+ sendEntityArrayResponse: jest.fn(),
40
+ sendJsonObjectResponse: jest.fn(),
41
+ sendEmptySuccessResponse: jest.fn(),
42
+ sendEntityResponse: jest.fn(),
43
+ sendErrorResponse: jest.fn(),
44
+ };
45
+ });
46
+
47
+ jest.mock("../../../Server/Services/UserEmailService");
48
+ jest.mock("../../../Server/Services/UserSmsService");
49
+ jest.mock("../../../Server/Services/UserCallService");
50
+ jest.mock("../../../Server/Services/UserWhatsAppService");
51
+ jest.mock("../../../Server/Services/UserIncomingCallNumberService");
52
+ jest.mock("../../../Server/Services/UserNotificationRuleService");
53
+
54
+ /*
55
+ * GHSA-5cr8-vph4-3hrf, at the HTTP layer, for every affected channel at once.
56
+ *
57
+ * The advisory's own words are "the same pattern is used by the user email,
58
+ * SMS, incoming call, and WhatsApp verification APIs" — five copies of the
59
+ * same handler, so a fix applied to one of them proves nothing about the other
60
+ * four. This file therefore runs one table of cases against all five routes,
61
+ * and a channel that regresses on its own fails here rather than quietly
62
+ * shipping.
63
+ *
64
+ * The service is faked at the row level rather than method-by-method, so these
65
+ * exercise the real ChannelVerification state machine reached through the real
66
+ * route handler. A test that stubbed verifyCode would pass against a handler
67
+ * that never called it.
68
+ */
69
+
70
+ const ITEM_ID: string = "9d3f0e2a-1111-4111-8111-111111111111";
71
+ const OWNER_ID: string = "9d3f0e2a-2222-4222-8222-222222222222";
72
+ const OTHER_USER_ID: string = "9d3f0e2a-3333-4333-8333-333333333333";
73
+ const PROJECT_ID: string = "9d3f0e2a-4444-4444-8444-444444444444";
74
+
75
+ const CORRECT_CODE: string = "424242";
76
+
77
+ interface FakeRow extends VerifiableChannelFields {
78
+ _id?: string | undefined;
79
+ }
80
+
81
+ type AnyService = Record<string, unknown>;
82
+
83
+ /*
84
+ * Point a mocked service at an in-memory row: reads return it, writes merge
85
+ * into it, and the attempt counter really counts.
86
+ */
87
+ const installFakeRow: (data: { service: unknown; row: FakeRow | null }) => {
88
+ current: FakeRow | null;
89
+ } = (data: { service: unknown; row: FakeRow | null }) => {
90
+ const store: { current: FakeRow | null } = { current: data.row };
91
+ const service: AnyService = data.service as AnyService;
92
+
93
+ service["findOneById"] = jest.fn().mockImplementation(() => {
94
+ return Promise.resolve(store.current ? { ...store.current } : null);
95
+ });
96
+
97
+ service["updateOneById"] = jest
98
+ .fn()
99
+ .mockImplementation((update: { data: Record<string, unknown> }) => {
100
+ if (store.current) {
101
+ store.current = { ...store.current, ...(update.data as FakeRow) };
102
+ }
103
+ return Promise.resolve(1);
104
+ });
105
+
106
+ service["atomicIncrementColumnValueByOneAndGetValue"] = jest
107
+ .fn()
108
+ .mockImplementation((input: { columnName: string }) => {
109
+ if (!store.current) {
110
+ return Promise.reject(new Error("row not found"));
111
+ }
112
+
113
+ const next: number =
114
+ ((store.current as unknown as Record<string, number>)[
115
+ input.columnName
116
+ ] || 0) + 1;
117
+
118
+ (store.current as unknown as Record<string, number>)[input.columnName] =
119
+ next;
120
+
121
+ return Promise.resolve(next);
122
+ });
123
+
124
+ service["getModel"] = jest.fn().mockReturnValue({ tableName: "FakeTable" });
125
+
126
+ service["resendVerificationCode"] = jest.fn().mockResolvedValue(undefined);
127
+
128
+ return store;
129
+ };
130
+
131
+ const buildRow: (overrides?: Partial<FakeRow>) => FakeRow = (
132
+ overrides: Partial<FakeRow> = {},
133
+ ) => {
134
+ return {
135
+ _id: ITEM_ID,
136
+ userId: new ObjectID(OWNER_ID),
137
+ projectId: new ObjectID(PROJECT_ID),
138
+ isVerified: false,
139
+ verificationCode: VerificationCode.hashCode({
140
+ code: CORRECT_CODE,
141
+ channelId: new ObjectID(ITEM_ID),
142
+ }),
143
+ verificationCodeExpiresAt: ChannelVerification.getExpiresAt(),
144
+ verificationFailedAttempts: 0,
145
+ verificationCodeSentAt: new Date(),
146
+ ...overrides,
147
+ };
148
+ };
149
+
150
+ interface Channel {
151
+ name: string;
152
+ verifyPath: string;
153
+ resendPath: string;
154
+ service: unknown;
155
+ build: () => void;
156
+ createsDefaultRules: boolean;
157
+ }
158
+
159
+ const CHANNELS: Array<Channel> = [
160
+ {
161
+ name: "UserEmailAPI",
162
+ verifyPath: "/user-email/verify",
163
+ resendPath: "/user-email/resend-verification-code",
164
+ service: UserEmailService,
165
+ build: () => {
166
+ new UserEmailAPI();
167
+ },
168
+ createsDefaultRules: true,
169
+ },
170
+ {
171
+ name: "UserSmsAPI",
172
+ verifyPath: "/user-sms/verify",
173
+ resendPath: "/user-sms/resend-verification-code",
174
+ service: UserSmsService,
175
+ build: () => {
176
+ new UserSmsAPI();
177
+ },
178
+ createsDefaultRules: true,
179
+ },
180
+ {
181
+ name: "UserCallAPI",
182
+ verifyPath: "/user-call/verify",
183
+ resendPath: "/user-call/resend-verification-code",
184
+ service: UserCallService,
185
+ build: () => {
186
+ new UserCallAPI();
187
+ },
188
+ createsDefaultRules: true,
189
+ },
190
+ {
191
+ name: "UserWhatsAppAPI",
192
+ verifyPath: "/user-whatsapp/verify",
193
+ resendPath: "/user-whatsapp/resend-verification-code",
194
+ service: UserWhatsAppService,
195
+ build: () => {
196
+ new UserWhatsAppAPI();
197
+ },
198
+ createsDefaultRules: true,
199
+ },
200
+ {
201
+ name: "UserIncomingCallNumberAPI",
202
+ verifyPath: "/user-incoming-call-number/verify",
203
+ resendPath: "/user-incoming-call-number/resend-verification-code",
204
+ service: UserIncomingCallNumberService,
205
+ build: () => {
206
+ new UserIncomingCallNumberAPI();
207
+ },
208
+ createsDefaultRules: false,
209
+ },
210
+ ];
211
+
212
+ describe.each(CHANNELS)("$name verification routes", (channel: Channel) => {
213
+ let mockRequest: OneUptimeRequest;
214
+ let mockResponse: OneUptimeResponse;
215
+ let nextFunction: NextFunction;
216
+ let store: { current: FakeRow | null };
217
+
218
+ const callVerify: (body: Record<string, unknown>) => Promise<void> = async (
219
+ body: Record<string, unknown>,
220
+ ) => {
221
+ mockRequest.body = body;
222
+
223
+ await mockRouter
224
+ .match("post", channel.verifyPath)
225
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
226
+ };
227
+
228
+ const callResend: (body: Record<string, unknown>) => Promise<void> = async (
229
+ body: Record<string, unknown>,
230
+ ) => {
231
+ mockRequest.body = body;
232
+
233
+ await mockRouter
234
+ .match("post", channel.resendPath)
235
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
236
+ };
237
+
238
+ const lastError: () => BadDataException = () => {
239
+ const calls: Array<Array<unknown>> = (
240
+ Response.sendErrorResponse as unknown as jest.Mock
241
+ ).mock.calls as Array<Array<unknown>>;
242
+
243
+ return calls[calls.length - 1]?.[2] as BadDataException;
244
+ };
245
+
246
+ beforeEach(() => {
247
+ jest.clearAllMocks();
248
+ mockRouter.routes.length = 0;
249
+
250
+ channel.build();
251
+
252
+ mockRequest = {} as OneUptimeRequest;
253
+ mockRequest.userAuthorization = {
254
+ userId: new ObjectID(OWNER_ID),
255
+ } as JSONWebTokenData;
256
+
257
+ mockResponse = {
258
+ send: jest.fn(),
259
+ json: jest.fn(),
260
+ status: jest.fn().mockReturnThis(),
261
+ setHeader: jest.fn(),
262
+ } as unknown as OneUptimeResponse;
263
+
264
+ nextFunction = jest.fn();
265
+
266
+ store = installFakeRow({ service: channel.service, row: buildRow() });
267
+
268
+ (
269
+ UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod as unknown as jest.Mock
270
+ ).mockResolvedValue(undefined as never);
271
+ });
272
+
273
+ describe("POST verify", () => {
274
+ it("requires an item ID", async () => {
275
+ await callVerify({});
276
+
277
+ expect(Response.sendErrorResponse).toHaveBeenCalledWith(
278
+ mockRequest,
279
+ mockResponse,
280
+ new BadDataException("Invalid item ID"),
281
+ );
282
+ });
283
+
284
+ it("requires a code", async () => {
285
+ await callVerify({ itemId: ITEM_ID });
286
+
287
+ expect(lastError().message).toBe("Invalid code");
288
+ });
289
+
290
+ it("refuses an unknown item", async () => {
291
+ store.current = null;
292
+
293
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
294
+
295
+ expect(lastError().message).toBe("Item not found");
296
+ });
297
+
298
+ /*
299
+ * Ownership. A row can legitimately hold somebody else's address — that
300
+ * is how "add my phone" works — so this is what keeps one user from
301
+ * verifying another user's channel.
302
+ */
303
+ it("refuses a caller who does not own the item", async () => {
304
+ mockRequest.userAuthorization = {
305
+ userId: new ObjectID(OTHER_USER_ID),
306
+ } as JSONWebTokenData;
307
+
308
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
309
+
310
+ expect(lastError().message).toBe("Invalid user ID");
311
+ expect(store.current?.isVerified).toBeFalsy();
312
+ });
313
+
314
+ it("refuses a wrong code", async () => {
315
+ await callVerify({ itemId: ITEM_ID, code: "111111" });
316
+
317
+ expect(lastError().message).toBe("Invalid code");
318
+ expect(store.current?.isVerified).toBeFalsy();
319
+ });
320
+
321
+ it("accepts the right code and marks the row verified", async () => {
322
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
323
+
324
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
325
+ mockRequest,
326
+ mockResponse,
327
+ );
328
+ expect(store.current?.isVerified).toBe(true);
329
+ });
330
+
331
+ /*
332
+ * The stored column is a digest now. This is the test that fails if
333
+ * anybody reintroduces `item.verificationCode === req.body.code`.
334
+ */
335
+ it("does not accept the stored column value as the code", async () => {
336
+ await callVerify({
337
+ itemId: ITEM_ID,
338
+ code: store.current?.verificationCode as string,
339
+ });
340
+
341
+ expect(lastError().message).toBe("Invalid code");
342
+ expect(store.current?.isVerified).toBeFalsy();
343
+ });
344
+
345
+ it("refuses a code that has expired", async () => {
346
+ store.current = buildRow({
347
+ verificationCodeExpiresAt: new Date(Date.now() - 1000),
348
+ });
349
+
350
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
351
+
352
+ expect(lastError().message).toContain("expired");
353
+ expect(store.current?.isVerified).toBeFalsy();
354
+ });
355
+
356
+ /*
357
+ * Rows written before the expiry column existed carry a plaintext code
358
+ * and no expiry. They must read as "ask for a new code", not as a
359
+ * permanently valid challenge.
360
+ */
361
+ it("refuses a legacy row that carries no expiry", async () => {
362
+ store.current = buildRow({ verificationCodeExpiresAt: undefined });
363
+
364
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
365
+
366
+ expect(lastError().message).toContain("expired");
367
+ });
368
+
369
+ it("refuses a row that is already verified", async () => {
370
+ store.current = buildRow({ isVerified: true });
371
+
372
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
373
+
374
+ expect(lastError().message).toBe("This is already verified");
375
+ });
376
+
377
+ /*
378
+ * The advisory's reproduction, run against the route: submit many
379
+ * incorrect values and check that the attempts are not all processed,
380
+ * that the stored code does not survive, and that the correct code
381
+ * submitted after the failures no longer verifies the row.
382
+ */
383
+ describe("brute force", () => {
384
+ it("stops accepting guesses after the attempt limit", async () => {
385
+ for (let i: number = 0; i < MAX_VERIFICATION_ATTEMPTS; i++) {
386
+ await callVerify({
387
+ itemId: ITEM_ID,
388
+ code: i.toString().padStart(6, "0"),
389
+ });
390
+
391
+ expect(lastError().message).toBe("Invalid code");
392
+ }
393
+
394
+ await callVerify({ itemId: ITEM_ID, code: "999999" });
395
+
396
+ expect(lastError().message).toContain("Too many incorrect attempts");
397
+ });
398
+
399
+ it("refuses the correct code once the limit has been reached", async () => {
400
+ for (let i: number = 0; i < MAX_VERIFICATION_ATTEMPTS; i++) {
401
+ await callVerify({ itemId: ITEM_ID, code: "111111" });
402
+ }
403
+
404
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
405
+
406
+ expect(lastError().message).toContain("Too many incorrect attempts");
407
+ expect(store.current?.isVerified).toBeFalsy();
408
+ });
409
+
410
+ it("burns the stored challenge rather than merely pausing the caller", async () => {
411
+ const originalDigest: string | undefined =
412
+ store.current?.verificationCode;
413
+
414
+ for (let i: number = 0; i < MAX_VERIFICATION_ATTEMPTS; i++) {
415
+ await callVerify({ itemId: ITEM_ID, code: "111111" });
416
+ }
417
+
418
+ expect(store.current?.verificationCode).not.toBe(originalDigest);
419
+ expect(store.current?.verificationCodeExpiresAt).toBeNull();
420
+ });
421
+
422
+ it("cannot be walked to a verification", async () => {
423
+ for (let guess: number = 0; guess < 300; guess++) {
424
+ await callVerify({
425
+ itemId: ITEM_ID,
426
+ code: guess.toString().padStart(6, "0"),
427
+ });
428
+ }
429
+
430
+ expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
431
+ expect(store.current?.isVerified).toBeFalsy();
432
+ });
433
+ });
434
+
435
+ if (channel.createsDefaultRules) {
436
+ it("creates the default notification rules once verified", async () => {
437
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
438
+
439
+ expect(
440
+ UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod,
441
+ ).toHaveBeenCalled();
442
+ });
443
+
444
+ it("does not create notification rules for a failed verification", async () => {
445
+ await callVerify({ itemId: ITEM_ID, code: "111111" });
446
+
447
+ expect(
448
+ UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod,
449
+ ).not.toHaveBeenCalled();
450
+ });
451
+
452
+ it("still reports success if rule creation fails", async () => {
453
+ (
454
+ UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod as unknown as jest.Mock
455
+ ).mockRejectedValue(new Error("boom") as never);
456
+
457
+ await callVerify({ itemId: ITEM_ID, code: CORRECT_CODE });
458
+
459
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalled();
460
+ });
461
+ }
462
+ });
463
+
464
+ describe("POST resend-verification-code", () => {
465
+ it("requires an item ID", async () => {
466
+ await callResend({});
467
+
468
+ expect(lastError().message).toBe("Invalid item ID");
469
+ });
470
+
471
+ it("refuses an unknown item", async () => {
472
+ store.current = null;
473
+
474
+ await callResend({ itemId: ITEM_ID });
475
+
476
+ expect(lastError().message).toBe("Item not found");
477
+ expect(
478
+ (channel.service as AnyService)["resendVerificationCode"],
479
+ ).not.toHaveBeenCalled();
480
+ });
481
+
482
+ /*
483
+ * Without this the resend route is a way to make somebody else's device
484
+ * ring on demand, at the project's expense.
485
+ */
486
+ it("refuses to send a code for a row the caller does not own", async () => {
487
+ mockRequest.userAuthorization = {
488
+ userId: new ObjectID(OTHER_USER_ID),
489
+ } as JSONWebTokenData;
490
+
491
+ await callResend({ itemId: ITEM_ID });
492
+
493
+ expect(lastError().message).toBe("Invalid user ID");
494
+ expect(
495
+ (channel.service as AnyService)["resendVerificationCode"],
496
+ ).not.toHaveBeenCalled();
497
+ });
498
+
499
+ it("sends a code for a row the caller owns", async () => {
500
+ await callResend({ itemId: ITEM_ID });
501
+
502
+ expect(
503
+ (channel.service as AnyService)["resendVerificationCode"],
504
+ ).toHaveBeenCalledWith(ITEM_ID);
505
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
506
+ mockRequest,
507
+ mockResponse,
508
+ );
509
+ });
510
+ });
511
+
512
+ /*
513
+ * The limiter is what bounds the two things a per-row counter cannot see:
514
+ * a caller rotating rows to farm fresh attempt budgets, and the volume of
515
+ * real messages sent to somebody who never asked for them. A route that
516
+ * loses it still passes every functional test above, so its registration
517
+ * is asserted directly.
518
+ */
519
+ describe("rate limiting", () => {
520
+ it("registers a limiter in front of the verify handler", () => {
521
+ expect(
522
+ mockRouter.match("post", channel.verifyPath).middlewares.length,
523
+ ).toBeGreaterThanOrEqual(2);
524
+ });
525
+
526
+ it("registers a limiter in front of the resend handler", () => {
527
+ expect(
528
+ mockRouter.match("post", channel.resendPath).middlewares.length,
529
+ ).toBeGreaterThanOrEqual(2);
530
+ });
531
+
532
+ it("runs the limiter after user authorization, so it can key on the user", () => {
533
+ const middlewares: Array<unknown> = mockRouter.match(
534
+ "post",
535
+ channel.verifyPath,
536
+ ).middlewares;
537
+
538
+ /* UserMiddleware.getUserMiddleware is registered first. */
539
+ expect(middlewares[0]).not.toBe(middlewares[1]);
540
+ expect(typeof middlewares[1]).toBe("function");
541
+ });
542
+ });
543
+ });