@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,510 @@
1
+ import DatabaseService from "../Services/DatabaseService";
2
+ import Select from "../Types/Database/Select";
3
+ import logger from "./Logger";
4
+ import VerificationCode from "./VerificationCode";
5
+ import BadDataException from "../../Types/Exception/BadDataException";
6
+ import ObjectID from "../../Types/ObjectID";
7
+ import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
8
+
9
+ /*
10
+ * The ownership check shared by every notification channel a user can add to
11
+ * their own account: email, SMS, voice call, WhatsApp and the incoming-call
12
+ * number. Each of those had its own copy of "compare the column, set
13
+ * isVerified", and each copy had the same three holes.
14
+ *
15
+ * WHAT WAS WRONG
16
+ *
17
+ * The code was a stable six-digit value with no expiry, no attempt counter
18
+ * and no lockout. Nothing stops a caller creating a channel row pointing at
19
+ * SOMEBODY ELSE'S address or number - that is the normal, intended flow, it
20
+ * is how you add your own phone - so an authenticated low-privilege user
21
+ * could add a colleague's number and then walk the whole 10^6 code space
22
+ * against /verify at request speed. A million requests is minutes of
23
+ * scripting, and the code at the end of it was still the same code it was at
24
+ * the start. Success marks the row verified, which creates the default
25
+ * notification rules, which makes the victim's phone a delivery target for
26
+ * whatever that project pages about.
27
+ *
28
+ * WHAT REPLACES IT
29
+ *
30
+ * Four controls, each of which has to be defeated on its own:
31
+ *
32
+ * EXPIRY a code lives 15 minutes, so the search space is not
33
+ * available indefinitely.
34
+ * ATTEMPTS five wrong guesses per issued code, counted with a single
35
+ * atomic UPDATE ... RETURNING so racing requests cannot all
36
+ * read the same pre-increment value and all decide they are
37
+ * under the limit.
38
+ * ROTATION crossing the attempt limit does not merely pause the
39
+ * attacker with the target still valid behind it - the stored
40
+ * challenge is burned. Continuing costs a resend, which
41
+ * notifies the victim.
42
+ * THROTTLE resends are on a cooldown, so "burn the code and get a new
43
+ * one" is not free either, and the victim is not the one who
44
+ * pays for it in unsolicited messages.
45
+ *
46
+ * On top of that the stored value is a keyed digest rather than the code (see
47
+ * VerificationCode.ts) and the routes are rate limited per user, per row and
48
+ * per address (see Middleware/VerificationCodeRateLimit.ts).
49
+ *
50
+ * Together: five guesses per code, a resend needed for the next five, a
51
+ * cooldown on resends, and every resend visible to the person being attacked.
52
+ */
53
+
54
+ const parsePositiveIntFromEnv: (envKey: string, fallback: number) => number = (
55
+ envKey: string,
56
+ fallback: number,
57
+ ): number => {
58
+ const rawValue: string | undefined = process.env[envKey];
59
+
60
+ if (!rawValue) {
61
+ return fallback;
62
+ }
63
+
64
+ const parsedValue: number = parseInt(rawValue, 10);
65
+
66
+ if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
67
+ return fallback;
68
+ }
69
+
70
+ return parsedValue;
71
+ };
72
+
73
+ /*
74
+ * Long enough to survive a slow SMS carrier, an email that lands in a spam
75
+ * folder the user has to go and look in, or a voice call the user missed and
76
+ * had to wait to be repeated. Short enough that the code space is not
77
+ * standing open. Fifteen minutes is the usual figure for exactly this
78
+ * trade-off and is what the notification copy tells the user.
79
+ */
80
+ export const VERIFICATION_CODE_EXPIRY_MINUTES: number = parsePositiveIntFromEnv(
81
+ "NOTIFICATION_VERIFICATION_CODE_EXPIRY_MINUTES",
82
+ 15,
83
+ );
84
+
85
+ /*
86
+ * Five is chosen for the human, not the attacker: somebody typing a code off
87
+ * a phone screen will fat-finger it once or twice, and almost never five
88
+ * times. The attacker's number is not five - it is five multiplied by how
89
+ * many resends they are willing to send to their victim's phone, which is the
90
+ * point of the cooldown below.
91
+ */
92
+ export const MAX_VERIFICATION_ATTEMPTS: number = parsePositiveIntFromEnv(
93
+ "NOTIFICATION_VERIFICATION_MAX_ATTEMPTS",
94
+ 5,
95
+ );
96
+
97
+ /*
98
+ * A resend both issues a fresh code and, for SMS/call/WhatsApp, spends real
99
+ * money sending it to a real handset. The cooldown is the control on the
100
+ * "burn five guesses, resend, repeat" loop AND on using the resend button as
101
+ * a way to text somebody 200 times.
102
+ *
103
+ * Sixty seconds is the interval a user who did not receive the first message
104
+ * will tolerate, and it caps the loop above at five guesses a minute: roughly
105
+ * 200,000 minutes, or four months of uninterrupted attack with a message
106
+ * landing on the victim's phone every sixty seconds of it, for an even chance
107
+ * of hitting one code.
108
+ */
109
+ export const RESEND_COOLDOWN_SECONDS: number = parsePositiveIntFromEnv(
110
+ "NOTIFICATION_VERIFICATION_RESEND_COOLDOWN_SECONDS",
111
+ 60,
112
+ );
113
+
114
+ /*
115
+ * The verification-related columns every channel model carries. Structural
116
+ * rather than a base class because these are TypeORM entities whose
117
+ * inheritance chain is already spoken for.
118
+ */
119
+ export interface VerifiableChannelFields {
120
+ userId?: ObjectID | undefined;
121
+ projectId?: ObjectID | undefined;
122
+ isVerified?: boolean | undefined;
123
+ verificationCode?: string | undefined;
124
+ verificationCodeExpiresAt?: Date | undefined;
125
+ verificationFailedAttempts?: number | undefined;
126
+ verificationCodeSentAt?: Date | undefined;
127
+ }
128
+
129
+ export type VerifiableChannelModel = BaseModel & VerifiableChannelFields;
130
+
131
+ export enum ChannelVerificationOutcome {
132
+ Verified = "verified",
133
+ NotFound = "not-found",
134
+ NotOwner = "not-owner",
135
+ AlreadyVerified = "already-verified",
136
+ Expired = "expired",
137
+ TooManyAttempts = "too-many-attempts",
138
+ IncorrectCode = "incorrect-code",
139
+ }
140
+
141
+ export interface ChannelVerificationResult {
142
+ outcome: ChannelVerificationOutcome;
143
+
144
+ /*
145
+ * Set only when the outcome is Verified; the rows the caller needs to
146
+ * create the default notification rules.
147
+ */
148
+ itemId?: ObjectID | undefined;
149
+ userId?: ObjectID | undefined;
150
+ projectId?: ObjectID | undefined;
151
+ }
152
+
153
+ /*
154
+ * The slice of a channel service this module actually uses.
155
+ *
156
+ * The five services are DatabaseService<UserEmail>, DatabaseService<UserSMS>
157
+ * and so on, and their write signatures are typed through TypeORM's
158
+ * QueryDeepPartialEntity — a CONDITIONAL type on the entity, which TypeScript
159
+ * cannot resolve while the entity is still a type parameter. Describing the
160
+ * two calls this file makes, and adapting to them once, keeps that problem out
161
+ * of every call site instead of scattering casts through the logic.
162
+ */
163
+ interface VerifiableChannelWriter {
164
+ updateOneById(data: {
165
+ id: ObjectID;
166
+ props: { isRoot: boolean };
167
+ data: VerifiableChannelFields;
168
+ }): Promise<number>;
169
+
170
+ atomicIncrementColumnValueByOneAndGetValue(data: {
171
+ id: ObjectID;
172
+ columnName: string;
173
+ }): Promise<number>;
174
+
175
+ getModel(): { tableName: string | null };
176
+ }
177
+
178
+ const asWriter: (
179
+ service: DatabaseService<VerifiableChannelModel>,
180
+ ) => VerifiableChannelWriter = (
181
+ service: DatabaseService<VerifiableChannelModel>,
182
+ ): VerifiableChannelWriter => {
183
+ return service as unknown as VerifiableChannelWriter;
184
+ };
185
+
186
+ export default class ChannelVerification {
187
+ /*
188
+ * When a code issued now stops being accepted.
189
+ */
190
+ public static getExpiresAt(now: Date = new Date()): Date {
191
+ return new Date(
192
+ now.getTime() + VERIFICATION_CODE_EXPIRY_MINUTES * 60 * 1000,
193
+ );
194
+ }
195
+
196
+ /*
197
+ * A missing expiry is treated as expired, NOT as "never expires".
198
+ *
199
+ * That is the direction that matters for the rows written before this
200
+ * column existed: they carry a plaintext code and no expiry, and the
201
+ * conservative reading turns them into "request a new code" rather than
202
+ * leaving a permanently valid challenge in place. The cost is that a user
203
+ * who had an unverified channel across the upgrade has to press resend
204
+ * once.
205
+ */
206
+ public static isCodeExpired(data: {
207
+ expiresAt?: Date | null | undefined;
208
+ now?: Date | undefined;
209
+ }): boolean {
210
+ if (!data.expiresAt) {
211
+ return true;
212
+ }
213
+
214
+ const now: Date = data.now || new Date();
215
+
216
+ return new Date(data.expiresAt).getTime() <= now.getTime();
217
+ }
218
+
219
+ /*
220
+ * Seconds the caller must wait before another code may be sent, or 0 if a
221
+ * send is allowed right now.
222
+ */
223
+ public static getResendRetryAfterSeconds(data: {
224
+ lastSentAt?: Date | null | undefined;
225
+ now?: Date | undefined;
226
+ }): number {
227
+ if (!data.lastSentAt) {
228
+ return 0;
229
+ }
230
+
231
+ const now: Date = data.now || new Date();
232
+ const elapsedSeconds: number =
233
+ (now.getTime() - new Date(data.lastSentAt).getTime()) / 1000;
234
+
235
+ /*
236
+ * A lastSentAt in the future means a clock went backwards, not that the
237
+ * caller may send freely. Treat it as a full cooldown.
238
+ */
239
+ if (elapsedSeconds < 0) {
240
+ return RESEND_COOLDOWN_SECONDS;
241
+ }
242
+
243
+ if (elapsedSeconds >= RESEND_COOLDOWN_SECONDS) {
244
+ return 0;
245
+ }
246
+
247
+ return Math.max(1, Math.ceil(RESEND_COOLDOWN_SECONDS - elapsedSeconds));
248
+ }
249
+
250
+ /*
251
+ * The column values that put a freshly issued code on a row. Returned
252
+ * rather than written so the caller can fold them into whatever write it
253
+ * was already making, and so the plaintext - which exists in memory for
254
+ * exactly as long as it takes to hand it to the notification service -
255
+ * comes back alongside them.
256
+ */
257
+ public static issueCode(data: {
258
+ channelId: ObjectID;
259
+ now?: Date | undefined;
260
+ }): {
261
+ plainCode: string;
262
+ fields: VerifiableChannelFields;
263
+ } {
264
+ const now: Date = data.now || new Date();
265
+ const plainCode: string = VerificationCode.generate();
266
+
267
+ return {
268
+ plainCode,
269
+ fields: {
270
+ verificationCode: VerificationCode.hashCode({
271
+ code: plainCode,
272
+ channelId: data.channelId,
273
+ }),
274
+ verificationCodeExpiresAt: ChannelVerification.getExpiresAt(now),
275
+ verificationCodeSentAt: now,
276
+ verificationFailedAttempts: 0,
277
+ },
278
+ };
279
+ }
280
+
281
+ /*
282
+ * The column values that leave a row with NO live challenge on it: a digest
283
+ * no code can produce, and no expiry.
284
+ *
285
+ * `verificationCode` is NOT NULL on every one of these tables, so "there is
286
+ * no code" cannot be written as null. A random 256-bit digest says the same
287
+ * thing and cannot be hit by any of the 10^6 codes.
288
+ */
289
+ public static getClearedCodeFields(): VerifiableChannelFields {
290
+ return {
291
+ verificationCode: VerificationCode.generateUnusableHash(),
292
+ verificationCodeExpiresAt: null as unknown as Date,
293
+ };
294
+ }
295
+
296
+ /*
297
+ * Write a fresh code onto an existing row and return the plaintext to send.
298
+ *
299
+ * Callers must have already decided that a send is allowed - this does not
300
+ * check the cooldown, because whether a resend is permitted is a question
301
+ * about the CHANNEL (already verified? project out of SMS credit?) that
302
+ * only the channel's own service can answer.
303
+ */
304
+ public static async issueCodeOnItem<
305
+ TModel extends VerifiableChannelModel,
306
+ >(data: {
307
+ service: DatabaseService<TModel>;
308
+ itemId: ObjectID;
309
+ }): Promise<string> {
310
+ const issued: {
311
+ plainCode: string;
312
+ fields: VerifiableChannelFields;
313
+ } = ChannelVerification.issueCode({ channelId: data.itemId });
314
+
315
+ await asWriter(
316
+ data.service as unknown as DatabaseService<VerifiableChannelModel>,
317
+ ).updateOneById({
318
+ id: data.itemId,
319
+ props: {
320
+ isRoot: true,
321
+ },
322
+ data: issued.fields,
323
+ });
324
+
325
+ return issued.plainCode;
326
+ }
327
+
328
+ /*
329
+ * Check a submitted code and, if it is right, mark the row verified.
330
+ *
331
+ * The ORDER of the steps is the security-relevant part:
332
+ *
333
+ * ownership first, so one user cannot even probe the state of another
334
+ * user's row;
335
+ *
336
+ * already-verified before the counter, so re-submitting a code against a
337
+ * row that is already done is not a way to burn somebody's attempts;
338
+ *
339
+ * the lockout gate before the counter, so a row whose budget is already
340
+ * spent is refused without growing the counter forever and without a
341
+ * database write per request;
342
+ *
343
+ * expiry before the counter, because an expired code is refused
344
+ * unconditionally and there is no reason to spend a user's remaining
345
+ * attempts telling them so;
346
+ *
347
+ * the counter before the comparison, so every attempt - including the
348
+ * ones racing each other - has already consumed its slot by the time
349
+ * anybody learns whether it was right.
350
+ *
351
+ * The lockout gate reads a value that a concurrent request may already have
352
+ * moved, which is exactly why it is a fast path and not the control. The
353
+ * control is the atomic increment below: N requests that all slip past the
354
+ * stale gate together still receive N DISTINCT attempt numbers, so all but
355
+ * the first MAX_VERIFICATION_ATTEMPTS of them are refused.
356
+ */
357
+ public static async verifyCode<TModel extends VerifiableChannelModel>(data: {
358
+ service: DatabaseService<TModel>;
359
+ itemId: ObjectID;
360
+ userId: ObjectID;
361
+ code: string;
362
+ }): Promise<ChannelVerificationResult> {
363
+ const item: TModel | null = await data.service.findOneById({
364
+ id: data.itemId,
365
+ props: {
366
+ isRoot: true,
367
+ },
368
+ select: {
369
+ userId: true,
370
+ projectId: true,
371
+ isVerified: true,
372
+ verificationCode: true,
373
+ verificationCodeExpiresAt: true,
374
+ verificationFailedAttempts: true,
375
+ } as Select<TModel>,
376
+ });
377
+
378
+ const writer: VerifiableChannelWriter = asWriter(
379
+ data.service as unknown as DatabaseService<VerifiableChannelModel>,
380
+ );
381
+
382
+ if (!item) {
383
+ return { outcome: ChannelVerificationOutcome.NotFound };
384
+ }
385
+
386
+ if (item.userId?.toString() !== data.userId.toString()) {
387
+ return { outcome: ChannelVerificationOutcome.NotOwner };
388
+ }
389
+
390
+ if (item.isVerified) {
391
+ return { outcome: ChannelVerificationOutcome.AlreadyVerified };
392
+ }
393
+
394
+ if ((item.verificationFailedAttempts || 0) >= MAX_VERIFICATION_ATTEMPTS) {
395
+ return { outcome: ChannelVerificationOutcome.TooManyAttempts };
396
+ }
397
+
398
+ if (
399
+ ChannelVerification.isCodeExpired({
400
+ expiresAt: item.verificationCodeExpiresAt,
401
+ })
402
+ ) {
403
+ return { outcome: ChannelVerificationOutcome.Expired };
404
+ }
405
+
406
+ const attemptNumber: number =
407
+ await writer.atomicIncrementColumnValueByOneAndGetValue({
408
+ id: data.itemId,
409
+ columnName: "verificationFailedAttempts",
410
+ });
411
+
412
+ /*
413
+ * Only reachable when requests raced past the gate above together. They
414
+ * are refused here rather than being given a comparison, and the request
415
+ * holding attempt number MAX_VERIFICATION_ATTEMPTS still burns the
416
+ * challenge below.
417
+ */
418
+ if (attemptNumber > MAX_VERIFICATION_ATTEMPTS) {
419
+ return { outcome: ChannelVerificationOutcome.TooManyAttempts };
420
+ }
421
+
422
+ const submittedHash: string = VerificationCode.hashCode({
423
+ code: String(data.code),
424
+ channelId: data.itemId,
425
+ });
426
+
427
+ if (
428
+ !VerificationCode.isHashEqual(submittedHash, item.verificationCode || "")
429
+ ) {
430
+ /*
431
+ * That was the last attempt this code gets, so burn it now rather than
432
+ * leave a spent-but-still-valid challenge on the row. The attacker's
433
+ * next move has to be a resend, which costs them the cooldown and tells
434
+ * the victim what is happening.
435
+ *
436
+ * Done exactly once, on the attempt that exhausts the budget; every
437
+ * later request is turned away by the gate above without a write.
438
+ */
439
+ if (attemptNumber >= MAX_VERIFICATION_ATTEMPTS) {
440
+ await writer.updateOneById({
441
+ id: data.itemId,
442
+ props: {
443
+ isRoot: true,
444
+ },
445
+ data: ChannelVerification.getClearedCodeFields(),
446
+ });
447
+
448
+ logger.warn(
449
+ `ChannelVerification: attempt limit reached on ${writer.getModel().tableName} ${data.itemId.toString()} for user ${data.userId.toString()}; verification code invalidated`,
450
+ );
451
+ }
452
+
453
+ return { outcome: ChannelVerificationOutcome.IncorrectCode };
454
+ }
455
+
456
+ /*
457
+ * Correct. Mark verified AND clear the challenge in the same write: a
458
+ * used code must not remain usable, and the attempt counter goes back to
459
+ * zero so a later re-issue (if the row is ever un-verified) starts fresh.
460
+ */
461
+ await writer.updateOneById({
462
+ id: data.itemId,
463
+ props: {
464
+ isRoot: true,
465
+ },
466
+ data: {
467
+ isVerified: true,
468
+ verificationFailedAttempts: 0,
469
+ ...ChannelVerification.getClearedCodeFields(),
470
+ },
471
+ });
472
+
473
+ return {
474
+ outcome: ChannelVerificationOutcome.Verified,
475
+ itemId: data.itemId,
476
+ userId: item.userId,
477
+ projectId: item.projectId,
478
+ };
479
+ }
480
+
481
+ /*
482
+ * The message a failed outcome is reported to the caller with.
483
+ *
484
+ * IncorrectCode and NotFound deliberately do NOT say anything the caller
485
+ * did not already know, and every failure is a plain BadDataException so
486
+ * the shape of the response does not vary with the reason either.
487
+ */
488
+ public static getFailureException(
489
+ outcome: ChannelVerificationOutcome,
490
+ ): BadDataException {
491
+ switch (outcome) {
492
+ case ChannelVerificationOutcome.NotFound:
493
+ return new BadDataException("Item not found");
494
+ case ChannelVerificationOutcome.NotOwner:
495
+ return new BadDataException("Invalid user ID");
496
+ case ChannelVerificationOutcome.AlreadyVerified:
497
+ return new BadDataException("This is already verified");
498
+ case ChannelVerificationOutcome.Expired:
499
+ return new BadDataException(
500
+ "This verification code has expired. Please request a new code.",
501
+ );
502
+ case ChannelVerificationOutcome.TooManyAttempts:
503
+ return new BadDataException(
504
+ "Too many incorrect attempts. This verification code is no longer valid. Please request a new code.",
505
+ );
506
+ default:
507
+ return new BadDataException("Invalid code");
508
+ }
509
+ }
510
+ }
@@ -160,36 +160,33 @@ export default class IncomingRequestCriteria {
160
160
  }
161
161
 
162
162
  if (input.criteriaFilter.checkOn === CheckOn.RequestBody) {
163
- let responseBody: string | JSONObject | undefined = (
163
+ let requestBody: string | JSONObject | undefined = (
164
164
  input.dataToProcess as IncomingMonitorRequest
165
165
  ).requestBody;
166
166
 
167
- if (responseBody && typeof responseBody === Typeof.Object) {
168
- responseBody = JSON.stringify(responseBody);
167
+ if (requestBody && typeof requestBody === Typeof.Object) {
168
+ requestBody = JSON.stringify(requestBody);
169
169
  }
170
170
 
171
- if (!responseBody) {
172
- return null;
173
- }
171
+ /*
172
+ * A request that carried no body is an empty body, not an unknown one.
173
+ * Normalising to "" (the same thing IncomingEmailCriteria does for the
174
+ * email fields) is what lets a "Not Contains" filter match a bodiless
175
+ * heartbeat ping - the default online criteria for this monitor type.
176
+ * "Contains" is unaffected: "" contains nothing.
177
+ */
178
+ const body: string = (requestBody as string | undefined) || "";
174
179
 
175
180
  // contains
176
181
  if (input.criteriaFilter.filterType === FilterType.Contains) {
177
- if (
178
- value &&
179
- responseBody &&
180
- (responseBody as string).includes(value as string)
181
- ) {
182
+ if (value && body.includes(value as string)) {
182
183
  return `Request body contains ${value}.`;
183
184
  }
184
185
  return null;
185
186
  }
186
187
 
187
188
  if (input.criteriaFilter.filterType === FilterType.NotContains) {
188
- if (
189
- value &&
190
- responseBody &&
191
- !(responseBody as string).includes(value as string)
192
- ) {
189
+ if (value && !body.includes(value as string)) {
193
190
  return `Request body does not contain ${value}.`;
194
191
  }
195
192
  return null;
@@ -24,11 +24,11 @@ import NetworkDeviceOwnerUserService, {
24
24
  import logger, { LogAttributes } from "../Logger";
25
25
  import CaptureSpan from "../Telemetry/CaptureSpan";
26
26
  import DataToProcess from "./DataToProcess";
27
- import MonitorClusterContextUtil, {
28
- MonitorClusterContext,
29
- } from "./MonitorClusterContext";
27
+ import MonitorResourceContextUtil from "./MonitorResourceContext";
30
28
  import MonitorTemplateUtil from "./MonitorTemplateUtil";
31
- import SeriesResourceLinker from "./SeriesResourceLinker";
29
+ import SeriesResourceLinker, {
30
+ SeriesResolvedResourceIds,
31
+ } from "./SeriesResourceLinker";
32
32
  import MonitorDependencySuppression, {
33
33
  DependencySuppressionResult,
34
34
  } from "./MonitorDependencySuppression";
@@ -322,15 +322,17 @@ export default class MonitorAlert {
322
322
  }
323
323
 
324
324
  /*
325
- * Proxmox/Ceph monitors: resolve the monitored cluster once per
326
- * evaluation (lookup-only, from the step config's clusterIdentifier)
327
- * so every alert created below is attached to it. Series labels
328
- * cannot supply this they carry datapoint labels (`id`,
329
- * `ceph_daemon`, `pool_id`), not cluster identity, and ungrouped
330
- * templates have no series at all. No-op for other monitor types.
325
+ * Resolve the resources this monitor's own config names — its host,
326
+ * cluster, fleet, telemetry services, or the resource its metric
327
+ * filters scope it to once per evaluation, so every alert created
328
+ * below is attached to them. Series labels cannot supply this: they
329
+ * only exist for grouped criteria, and most monitors (every shipped
330
+ * Host/Docker/Podman template, all Logs/Traces/Exceptions monitors)
331
+ * are ungrouped. No-op, and no round-trip, for monitor types whose
332
+ * config names no resource.
331
333
  */
332
- const clusterContext: MonitorClusterContext =
333
- await MonitorClusterContextUtil.resolveClusterContextForMonitor({
334
+ const resourceContext: SeriesResolvedResourceIds =
335
+ await MonitorResourceContextUtil.resolveResourceContextForMonitor({
334
336
  monitor: input.monitor,
335
337
  });
336
338
 
@@ -556,21 +558,22 @@ export default class MonitorAlert {
556
558
  model: alert,
557
559
  seriesLabels,
558
560
  projectId: input.monitor.projectId!,
561
+ monitorType: input.monitor.monitorType,
559
562
  });
560
563
  }
561
564
 
562
565
  /*
563
- * Deterministic Proxmox/Ceph/Swarm/IoT cluster link from the
564
- * monitor's step config (resolved once above). The shipped
565
- * templates for those monitor types group by datapoint labels
566
- * that carry no cluster identity, so this — not the label path
567
- * above is what makes the per-cluster Activity tabs and badge
568
- * counts see monitor-created alerts. Runs for both grouped and
569
- * ungrouped alerts, and merges with whatever the labels resolved.
566
+ * Deterministic resource link from the monitor's step config
567
+ * (resolved once above). For most monitor types this — not the
568
+ * label path above is what makes the per-resource Activity
569
+ * tabs and badge counts see monitor-created alerts, because
570
+ * their criteria are ungrouped and emit no series labels at
571
+ * all. Runs for both grouped and ungrouped alerts, and merges
572
+ * with whatever the labels resolved.
570
573
  */
571
- SeriesResourceLinker.attachClusterContext({
574
+ SeriesResourceLinker.attachResolvedResources({
572
575
  model: alert,
573
- clusterContext,
576
+ resolved: resourceContext,
574
577
  });
575
578
 
576
579
  alert.onCallDutyPolicies =