@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,481 @@
1
+ import UserMiddleware from "../Middleware/UserAuthorization";
2
+ import UserNotificationMethodAdminService, {
3
+ AdminNotificationMethodDeletionPreview,
4
+ AdminNotificationMethodView,
5
+ } from "../Services/UserNotificationMethodAdminService";
6
+ import TeamMemberService from "../Services/TeamMemberService";
7
+ import Express, {
8
+ ExpressRequest,
9
+ ExpressResponse,
10
+ ExpressRouter,
11
+ NextFunction,
12
+ } from "../Utils/Express";
13
+ import Response from "../Utils/Response";
14
+ import CommonAPI from "./CommonAPI";
15
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
16
+ import DatabaseCommonInteractionPropsUtil, {
17
+ PermissionType,
18
+ } from "../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
19
+ import BadDataException from "../../Types/Exception/BadDataException";
20
+ import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
21
+ import { JSONObject } from "../../Types/JSON";
22
+ import ObjectID from "../../Types/ObjectID";
23
+ import Permission, { UserPermission } from "../../Types/Permission";
24
+ import TeamMember from "../../Models/DatabaseModels/TeamMember";
25
+
26
+ /*
27
+ * Managing another project member's notification methods over HTTP.
28
+ *
29
+ * Everything this router does is done by UserNotificationMethodAdminService,
30
+ * which holds the whole of the reasoning about masking, verification and the
31
+ * owner's mail. This file is transport and AUTHORISATION, and the authorisation
32
+ * is the part worth reading, because the usual reasoning does not apply here.
33
+ *
34
+ * The seven notification method models are scoped to the person who owns the
35
+ * device, and they stay that way — see the essay at the top of UserEmail.ts.
36
+ * The service therefore does its reads and writes with `isRoot: true`, which
37
+ * means the model's own TableAccessControl never runs on this path AND THESE
38
+ * HANDLERS ARE THE ONLY GATE UNDERNEATH. UserMiddleware.getUserMiddleware is
39
+ * not that gate: it admits anonymous callers as UserType.Public and calls
40
+ * next(), and the tenant id it attaches comes straight off the caller-supplied
41
+ * `tenantid` header before any authorisation runs.
42
+ *
43
+ * So every route below asserts, itself, in this order:
44
+ *
45
+ * 1. the caller is a signed-in member of the project they named
46
+ * (CommonAPI.assertAuthenticatedProjectMember, which checks the caller
47
+ * against that same header);
48
+ * 2. the caller holds a permission that lets them act on other people's
49
+ * accounts in it (assertCanManageUserNotificationMethods below), EXCEPT on
50
+ * the read, which additionally lets a person read their own; and
51
+ * 3. the user named in the path is a member of that same project — enforced
52
+ * again inside the service, on purpose, so the service is safe for any
53
+ * caller and not only for this router.
54
+ *
55
+ * WHY THE READ IS SEPARATELY GATED FROM THE WRITES. Reading the masked list is
56
+ * a disclosure ("this person has an unverified SMS"), and the readiness surface
57
+ * already discloses exactly that to every project member. Writing is an ACTION
58
+ * on somebody else's account. Those are different questions and the router does
59
+ * not answer them with one permission.
60
+ */
61
+ const router: ExpressRouter = Express.getRouter();
62
+
63
+ /*
64
+ * Who may LOOK at another member's notification methods.
65
+ *
66
+ * The same triple the admin rules page uses, and for the same reason: existing
67
+ * project teams are seeded with ROLES, never with individual granular
68
+ * permissions, so a permission introduced in this release is held by nobody at
69
+ * all until an administrator explicitly grants it. A gate naming only the
70
+ * granular permission would ship a feature that is dead on arrival for every
71
+ * project that already exists — the owner of a project would open the page and
72
+ * be told they may not look at it.
73
+ */
74
+ const READ_PERMISSIONS: Array<Permission> = [
75
+ Permission.ProjectOwner,
76
+ Permission.ProjectAdmin,
77
+ Permission.ReadProjectUserNotificationRule,
78
+ ];
79
+
80
+ /*
81
+ * Who may ADD or REMOVE one.
82
+ *
83
+ * Deliberately NOT EditProjectUserNotificationRule. Repairing somebody's rules
84
+ * and putting a new address on their account are different powers with
85
+ * different blast radii: the first re-points pages between devices that person
86
+ * has already proved they hold, the second introduces a device nobody has
87
+ * proved anything about. An installation that wants to hand out the first
88
+ * without the second must be able to, so this is its own permission.
89
+ */
90
+ const MANAGE_PERMISSIONS: Array<Permission> = [
91
+ Permission.ProjectOwner,
92
+ Permission.ProjectAdmin,
93
+ Permission.ManageProjectUserNotificationMethod,
94
+ ];
95
+
96
+ /*
97
+ * The permission check, read the way the rest of the API reads permissions.
98
+ *
99
+ * getUserPermissions(Allow) rather than indexing userTenantAccessPermission
100
+ * directly, and that is not a stylistic preference. That dictionary's entries
101
+ * hold GRANTS AND DENIALS in one array, discriminated only by
102
+ * `isBlockPermission`, so mapping it raw would count a team's explicit BLOCK of
103
+ * ProjectAdmin as a grant of it — the admin action that restricts a team would
104
+ * be the thing that handed it this endpoint.
105
+ *
106
+ * Every refusal in this file reuses one sentence, so an under-privileged member
107
+ * cannot tell "you may not do this" apart from "you are not in this project"
108
+ * and use the difference to probe.
109
+ */
110
+ function assertHasAnyPermission(
111
+ databaseProps: DatabaseCommonInteractionProps,
112
+ allowed: Array<Permission>,
113
+ ): void {
114
+ if (databaseProps.isMasterAdmin) {
115
+ return;
116
+ }
117
+
118
+ const permissions: Array<Permission> =
119
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
120
+ databaseProps,
121
+ PermissionType.Allow,
122
+ ).map((userPermission: UserPermission): Permission => {
123
+ return userPermission.permission;
124
+ });
125
+
126
+ const isAllowed: boolean = permissions.some(
127
+ (permission: Permission): boolean => {
128
+ return allowed.includes(permission);
129
+ },
130
+ );
131
+
132
+ if (!isAllowed) {
133
+ throw new NotAuthorizedException(
134
+ "You are not authorized to access this project's data.",
135
+ );
136
+ }
137
+ }
138
+
139
+ /*
140
+ * Project membership for the user NAMED IN THE PATH, not the caller.
141
+ *
142
+ * Without it these routes are a cross-project probe: the service's reads are
143
+ * projectId-scoped and would come back empty for a stranger, but a write path
144
+ * that skipped this would let somebody holding ProjectAdmin on one throwaway
145
+ * project act on a user id belonging to any other.
146
+ *
147
+ * The rejection reuses the router's single refusal sentence so a caller cannot
148
+ * tell "this user exists but is in another project" apart from "this user does
149
+ * not exist", and therefore cannot enumerate users through it. The service
150
+ * repeats the check with its own wording once the caller is past this point.
151
+ */
152
+ async function assertUserBelongsToProject(
153
+ userId: ObjectID,
154
+ projectId: ObjectID,
155
+ ): Promise<void> {
156
+ const memberships: Array<TeamMember> = await TeamMemberService.findBy({
157
+ query: {
158
+ userId: userId,
159
+ projectId: projectId,
160
+ },
161
+ select: {
162
+ _id: true,
163
+ },
164
+ limit: 1,
165
+ skip: 0,
166
+ props: {
167
+ isRoot: true,
168
+ },
169
+ });
170
+
171
+ if (memberships.length === 0) {
172
+ throw new NotAuthorizedException(
173
+ "You are not authorized to access this project's data.",
174
+ );
175
+ }
176
+ }
177
+
178
+ /*
179
+ * ObjectID's constructor accepts any string, so an unvalidated path segment
180
+ * would travel all the way to a query that matches nothing and read as "this
181
+ * user has no notification methods" rather than as "you sent nonsense".
182
+ */
183
+ function readObjectIdParam(req: ExpressRequest, name: string): ObjectID {
184
+ const raw: string = (req.params[name] as string) || "";
185
+ ObjectID.validateUUID(raw);
186
+ return new ObjectID(raw);
187
+ }
188
+
189
+ function readStringFromBody(req: ExpressRequest, name: string): string {
190
+ const body: JSONObject = (req.body || {}) as JSONObject;
191
+ const raw: unknown = body[name];
192
+
193
+ if (typeof raw !== "string" || !raw.trim()) {
194
+ throw new BadDataException(`${name} is required.`);
195
+ }
196
+
197
+ return raw.trim();
198
+ }
199
+
200
+ /*
201
+ * ObjectID.toJSON() serialises to `{ _type: "ObjectID", value: "..." }`, which
202
+ * is right for model payloads the client parses back through JSONFunctions but
203
+ * wrong for a hand-rolled response body. Ids are flattened to plain strings, the
204
+ * same way OnCallReadinessAPI does it, so the browser can read `methodId`
205
+ * without knowing about ObjectID at all.
206
+ *
207
+ * `maskedIdentifier` is copied and nothing else is: there is no raw field on
208
+ * the service's view type to leak, which is deliberate — a future change would
209
+ * have to widen that contract in the open rather than slip a phone number
210
+ * through a boolean flag here.
211
+ */
212
+ function serializeMethod(method: AdminNotificationMethodView): JSONObject {
213
+ return {
214
+ methodId: method.methodId.toString(),
215
+ methodType: method.methodType,
216
+ maskedIdentifier: method.maskedIdentifier,
217
+ isVerified: method.isVerified,
218
+ isAdminAddable: method.isAdminAddable,
219
+ createdAt: method.createdAt ? method.createdAt.toISOString() : undefined,
220
+ };
221
+ }
222
+
223
+ /*
224
+ * Every notification method this user has, masked.
225
+ *
226
+ * A member reading their OWN row is allowed through without the admin
227
+ * permission. The page behind this is reachable from the project's user list,
228
+ * and a person looking at themselves there is reading rows the self-serve
229
+ * settings pages already show them in full.
230
+ */
231
+ router.get(
232
+ "/user-notification-method-admin/user/:userId",
233
+ UserMiddleware.getUserMiddleware,
234
+ async (
235
+ req: ExpressRequest,
236
+ res: ExpressResponse,
237
+ next: NextFunction,
238
+ ): Promise<void> => {
239
+ try {
240
+ const databaseProps: DatabaseCommonInteractionProps =
241
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
242
+
243
+ const projectId: ObjectID =
244
+ CommonAPI.assertAuthenticatedProjectMember(databaseProps);
245
+
246
+ const userId: ObjectID = readObjectIdParam(req, "userId");
247
+
248
+ const isSelf: boolean =
249
+ Boolean(databaseProps.userId) &&
250
+ databaseProps.userId!.toString() === userId.toString();
251
+
252
+ if (!isSelf) {
253
+ assertHasAnyPermission(databaseProps, READ_PERMISSIONS);
254
+ }
255
+
256
+ await assertUserBelongsToProject(userId, projectId);
257
+
258
+ const methods: Array<AdminNotificationMethodView> =
259
+ await UserNotificationMethodAdminService.listMethodsForUser({
260
+ projectId: projectId,
261
+ userId: userId,
262
+ });
263
+
264
+ return Response.sendJsonObjectResponse(req, res, {
265
+ methods: methods.map(serializeMethod),
266
+ });
267
+ } catch (err) {
268
+ next(err);
269
+ }
270
+ },
271
+ );
272
+
273
+ /*
274
+ * Add a notification method to this user's account.
275
+ *
276
+ * The row lands UNVERIFIED and a verification code goes to the address itself,
277
+ * which is what keeps this from being a way to redirect somebody's pages: the
278
+ * verify endpoints refuse anybody but the row's owner, so the administrator who
279
+ * just typed the value in cannot complete it. See the service header.
280
+ */
281
+ router.post(
282
+ "/user-notification-method-admin/user/:userId",
283
+ UserMiddleware.getUserMiddleware,
284
+ async (
285
+ req: ExpressRequest,
286
+ res: ExpressResponse,
287
+ next: NextFunction,
288
+ ): Promise<void> => {
289
+ try {
290
+ const databaseProps: DatabaseCommonInteractionProps =
291
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
292
+
293
+ const projectId: ObjectID =
294
+ CommonAPI.assertAuthenticatedProjectMember(databaseProps);
295
+
296
+ /*
297
+ * Both gates run before the body is read. A route that validates a
298
+ * payload for a caller it is about to refuse has told that caller
299
+ * something about the payload format.
300
+ */
301
+ assertHasAnyPermission(databaseProps, MANAGE_PERMISSIONS);
302
+
303
+ const userId: ObjectID = readObjectIdParam(req, "userId");
304
+
305
+ await assertUserBelongsToProject(userId, projectId);
306
+
307
+ const methodType: string = readStringFromBody(req, "methodType");
308
+ const value: string = readStringFromBody(req, "value");
309
+
310
+ const created: AdminNotificationMethodView =
311
+ await UserNotificationMethodAdminService.addMethodForUser({
312
+ projectId: projectId,
313
+ targetUserId: userId,
314
+ /*
315
+ * The actor the SERVER resolved, never anything from the body. This
316
+ * value names somebody in an audit entry and in a mail to the account
317
+ * holder, so a body-supplied one would let a caller sign their own
318
+ * change with a colleague's name.
319
+ */
320
+ actorUserId: databaseProps.userId,
321
+ methodType: methodType,
322
+ value: value,
323
+ props: databaseProps,
324
+ });
325
+
326
+ return Response.sendJsonObjectResponse(
327
+ req,
328
+ res,
329
+ serializeMethod(created),
330
+ );
331
+ } catch (err) {
332
+ next(err);
333
+ }
334
+ },
335
+ );
336
+
337
+ /*
338
+ * What removing this method would cost, before anybody confirms it.
339
+ *
340
+ * Read-only, and gated as a WRITE rather than as a read: the numbers it returns
341
+ * are about this user's rule coverage, and the only reason to ask for them is
342
+ * that you are about to delete something.
343
+ */
344
+ router.get(
345
+ "/user-notification-method-admin/user/:userId/:methodType/:methodId/deletion-impact",
346
+ UserMiddleware.getUserMiddleware,
347
+ async (
348
+ req: ExpressRequest,
349
+ res: ExpressResponse,
350
+ next: NextFunction,
351
+ ): Promise<void> => {
352
+ try {
353
+ const databaseProps: DatabaseCommonInteractionProps =
354
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
355
+
356
+ const projectId: ObjectID =
357
+ CommonAPI.assertAuthenticatedProjectMember(databaseProps);
358
+
359
+ assertHasAnyPermission(databaseProps, MANAGE_PERMISSIONS);
360
+
361
+ const userId: ObjectID = readObjectIdParam(req, "userId");
362
+
363
+ await assertUserBelongsToProject(userId, projectId);
364
+
365
+ const methodId: ObjectID = readObjectIdParam(req, "methodId");
366
+
367
+ const preview: AdminNotificationMethodDeletionPreview =
368
+ await UserNotificationMethodAdminService.getDeletionPreview({
369
+ projectId: projectId,
370
+ targetUserId: userId,
371
+ methodType: (req.params["methodType"] as string) || "",
372
+ methodId: methodId,
373
+ });
374
+
375
+ return Response.sendJsonObjectResponse(
376
+ req,
377
+ res,
378
+ preview as unknown as JSONObject,
379
+ );
380
+ } catch (err) {
381
+ next(err);
382
+ }
383
+ },
384
+ );
385
+
386
+ /*
387
+ * Send the verification code again, to the device.
388
+ *
389
+ * Discloses nothing — the code goes to the address on the row, which is the one
390
+ * thing on it the administrator cannot read — and it is the lever that makes
391
+ * adding a method on somebody's behalf actually finish: "I added your work
392
+ * mobile, here is another code" rather than re-typing the number.
393
+ */
394
+ router.post(
395
+ "/user-notification-method-admin/user/:userId/:methodType/:methodId/resend-verification-code",
396
+ UserMiddleware.getUserMiddleware,
397
+ async (
398
+ req: ExpressRequest,
399
+ res: ExpressResponse,
400
+ next: NextFunction,
401
+ ): Promise<void> => {
402
+ try {
403
+ const databaseProps: DatabaseCommonInteractionProps =
404
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
405
+
406
+ const projectId: ObjectID =
407
+ CommonAPI.assertAuthenticatedProjectMember(databaseProps);
408
+
409
+ assertHasAnyPermission(databaseProps, MANAGE_PERMISSIONS);
410
+
411
+ const userId: ObjectID = readObjectIdParam(req, "userId");
412
+
413
+ await assertUserBelongsToProject(userId, projectId);
414
+
415
+ const methodId: ObjectID = readObjectIdParam(req, "methodId");
416
+
417
+ await UserNotificationMethodAdminService.resendVerificationCodeForUser({
418
+ projectId: projectId,
419
+ targetUserId: userId,
420
+ methodType: (req.params["methodType"] as string) || "",
421
+ methodId: methodId,
422
+ });
423
+
424
+ return Response.sendEmptySuccessResponse(req, res);
425
+ } catch (err) {
426
+ next(err);
427
+ }
428
+ },
429
+ );
430
+
431
+ /*
432
+ * Remove one of this user's notification methods.
433
+ *
434
+ * DELETE rather than a POST to a /delete path, because it is one, and because
435
+ * the id it names is the only thing that identifies the row — there is no body
436
+ * to get out of step with the URL.
437
+ *
438
+ * Every rule pointing at this method goes with it (the foreign keys are all
439
+ * onDelete: "CASCADE"), which is why the preview route above exists and why the
440
+ * owner is always mailed about this one.
441
+ */
442
+ router.delete(
443
+ "/user-notification-method-admin/user/:userId/:methodType/:methodId",
444
+ UserMiddleware.getUserMiddleware,
445
+ async (
446
+ req: ExpressRequest,
447
+ res: ExpressResponse,
448
+ next: NextFunction,
449
+ ): Promise<void> => {
450
+ try {
451
+ const databaseProps: DatabaseCommonInteractionProps =
452
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
453
+
454
+ const projectId: ObjectID =
455
+ CommonAPI.assertAuthenticatedProjectMember(databaseProps);
456
+
457
+ assertHasAnyPermission(databaseProps, MANAGE_PERMISSIONS);
458
+
459
+ const userId: ObjectID = readObjectIdParam(req, "userId");
460
+
461
+ await assertUserBelongsToProject(userId, projectId);
462
+
463
+ const methodId: ObjectID = readObjectIdParam(req, "methodId");
464
+
465
+ await UserNotificationMethodAdminService.deleteMethodForUser({
466
+ projectId: projectId,
467
+ targetUserId: userId,
468
+ actorUserId: databaseProps.userId,
469
+ methodType: (req.params["methodType"] as string) || "",
470
+ methodId: methodId,
471
+ props: databaseProps,
472
+ });
473
+
474
+ return Response.sendEmptySuccessResponse(req, res);
475
+ } catch (err) {
476
+ next(err);
477
+ }
478
+ },
479
+ );
480
+
481
+ export default router;
@@ -21,6 +21,35 @@ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
21
21
  import UserPush from "../../Models/DatabaseModels/UserPush";
22
22
  import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
23
23
 
24
+ /*
25
+ * Booleans arrive from the mobile client as JSON booleans, but the same routes
26
+ * get called by hand and from form posts where "true" is a string.
27
+ *
28
+ * On REGISTRATION, absence is the normal case and means off: overriding a
29
+ * silenced phone is never something a device registration turns on by itself.
30
+ */
31
+ export function parseCriticalAlertFlag(raw: unknown): boolean {
32
+ return raw === true || raw === "true";
33
+ }
34
+
35
+ /*
36
+ * On the toggle route the caller is stating an intent, so an unrecognised
37
+ * value is refused rather than read as false. Quietly storing "off" for a
38
+ * client that meant "on" leaves a responder believing their phone will ring
39
+ * while it will not, and nothing surfaces the mistake until a missed page.
40
+ */
41
+ export function parseCriticalAlertFlagStrict(raw: unknown): boolean {
42
+ if (raw === true || raw === "true") {
43
+ return true;
44
+ }
45
+
46
+ if (raw === false || raw === "false") {
47
+ return false;
48
+ }
49
+
50
+ throw new BadDataException("isEnabled must be either true or false.");
51
+ }
52
+
24
53
  function getAuthenticatedUserId(req: ExpressRequest): ObjectID {
25
54
  const userId: ObjectID | undefined = (req as OneUptimeRequest)
26
55
  .userAuthorization?.userId;
@@ -111,6 +140,16 @@ export default class UserPushAPI extends BaseAPI<
111
140
  userPush.deviceType = req.body.deviceType;
112
141
  userPush.deviceName = req.body.deviceName || "Unknown Device";
113
142
  userPush.isVerified = true; // Web, iOS, and Android devices are verified immediately
143
+ /*
144
+ * The mobile app sends this when the responder already had critical
145
+ * alerts on and the device is re-registering (a reinstall, a new push
146
+ * token, a second project). Absent, it stays off: overriding a
147
+ * silenced phone is never something a registration turns on by
148
+ * itself.
149
+ */
150
+ userPush.isCriticalAlertEnabled = parseCriticalAlertFlag(
151
+ req.body.isCriticalAlertEnabled,
152
+ );
114
153
 
115
154
  const savedDevice: UserPush = await this.service.create({
116
155
  data: userPush,
@@ -216,6 +255,7 @@ export default class UserPushAPI extends BaseAPI<
216
255
  deviceType: true,
217
256
  isVerified: true,
218
257
  projectId: true,
258
+ isCriticalAlertEnabled: true,
219
259
  },
220
260
  });
221
261
 
@@ -246,15 +286,33 @@ export default class UserPushAPI extends BaseAPI<
246
286
 
247
287
  try {
248
288
  // Send test notification
289
+ const isCriticalAlert: boolean = Boolean(
290
+ device.isCriticalAlertEnabled,
291
+ );
292
+
293
+ /*
294
+ * A test that behaves unlike the real page is not a test of
295
+ * anything. Critical alerts are the one setting whose effect a
296
+ * responder cannot check by reasoning about it - they have to
297
+ * silence the phone and hear it ring - so a device with the option
298
+ * on gets a test that overrides silent mode exactly as a 3am page
299
+ * would.
300
+ */
249
301
  const testMessage: PushNotificationMessage =
250
302
  PushNotificationUtil.createGenericNotification({
251
- title: "Test Notification from OneUptime",
252
- body: "This is a test notification to verify your device is working correctly.",
303
+ title: isCriticalAlert
304
+ ? "Test Critical Alert from OneUptime"
305
+ : "Test Notification from OneUptime",
306
+ body: isCriticalAlert
307
+ ? "This is a test critical alert. If your device is silenced or in Do Not Disturb and you heard this, on-call pages will reach you."
308
+ : "This is a test notification to verify your device is working correctly.",
253
309
  clickAction: "/dashboard",
254
310
  tag: "test-notification",
255
311
  requireInteraction: false,
256
312
  });
257
313
 
314
+ testMessage.isCriticalAlert = isCriticalAlert;
315
+
258
316
  await PushNotificationService.sendPushNotification(
259
317
  {
260
318
  devices: [
@@ -290,6 +348,72 @@ export default class UserPushAPI extends BaseAPI<
290
348
  },
291
349
  );
292
350
 
351
+ /*
352
+ * Turn "ring me through silent mode" on or off for this handset.
353
+ *
354
+ * A dedicated route rather than the generic CRUD update, for the same
355
+ * reason verify/unverify are: UserPush grants no update permission to
356
+ * anybody, so every write to it passes an explicit ownership check first
357
+ * and then runs as root. That keeps the set of things that can change a
358
+ * responder's paging configuration short and readable.
359
+ *
360
+ * Keyed on the device token, like unregister and unlike verify: the mobile
361
+ * app knows its own push token and holds no row ids, and one phone has a
362
+ * row per project it is registered against.
363
+ */
364
+ this.router.post(
365
+ `/user-push/critical-alerts`,
366
+ UserMiddleware.getUserMiddleware,
367
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
368
+ try {
369
+ req = req as OneUptimeRequest;
370
+
371
+ const userId: ObjectID = getAuthenticatedUserId(req);
372
+
373
+ if (!req.body.deviceToken) {
374
+ return Response.sendErrorResponse(
375
+ req,
376
+ res,
377
+ new BadDataException("Device token is required"),
378
+ );
379
+ }
380
+
381
+ /*
382
+ * Required rather than defaulted. A request that forgot the field
383
+ * would otherwise silently turn the setting OFF, and a responder
384
+ * whose pages stopped overriding Do Not Disturb has no way to notice
385
+ * until the page they missed.
386
+ */
387
+ if (req.body.isEnabled === undefined || req.body.isEnabled === null) {
388
+ return Response.sendErrorResponse(
389
+ req,
390
+ res,
391
+ new BadDataException("isEnabled is required"),
392
+ );
393
+ }
394
+
395
+ const isEnabled: boolean = parseCriticalAlertFlagStrict(
396
+ req.body.isEnabled,
397
+ );
398
+
399
+ const updatedCount: number =
400
+ await this.service.setCriticalAlertEnabledForDeviceToken({
401
+ userId: userId,
402
+ deviceToken: req.body.deviceToken,
403
+ isEnabled: isEnabled,
404
+ });
405
+
406
+ return Response.sendJsonObjectResponse(req, res, {
407
+ success: true,
408
+ isCriticalAlertEnabled: isEnabled,
409
+ devicesUpdated: updatedCount,
410
+ });
411
+ } catch (error) {
412
+ return next(error);
413
+ }
414
+ },
415
+ );
416
+
293
417
  this.router.post(
294
418
  `/user-push/:deviceId/verify`,
295
419
  UserMiddleware.getUserMiddleware,