@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
@@ -619,6 +619,7 @@ describe("UserNotificationRuleService.executeNotificationRuleItem", () => {
619
619
  deviceType: true,
620
620
  isVerified: true,
621
621
  userId: true,
622
+ isCriticalAlertEnabled: true,
622
623
  },
623
624
  });
624
625
  });
@@ -0,0 +1,255 @@
1
+ import UserPushService from "../../../Server/Services/UserPushService";
2
+ import UserPush from "../../../Models/DatabaseModels/UserPush";
3
+ import PushDeviceType from "../../../Types/PushNotification/PushDeviceType";
4
+ import BadDataException from "../../../Types/Exception/BadDataException";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+ import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
7
+
8
+ /*
9
+ * setCriticalAlertEnabledForDeviceToken is how a phone tells the server "wake
10
+ * me through Do Not Disturb". It is keyed on the push TOKEN rather than a row
11
+ * id because one handset owns one UserPush row per project it is registered
12
+ * against, and a responder who flips the switch means it for the phone, not
13
+ * for whichever project they happened to be looking at.
14
+ *
15
+ * The failure this shape exists to prevent is a responder who is loud for one
16
+ * project and silent for another - a distinction no screen offers to make, so
17
+ * nobody would think to check it.
18
+ */
19
+
20
+ const USER_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
21
+ const OTHER_USER_ID: ObjectID = new ObjectID(
22
+ "45454545-4545-4545-8545-454545454545",
23
+ );
24
+ const DEVICE_TOKEN: string = "ExponentPushToken[handset]";
25
+
26
+ const DEVICE_ONE_ID: string = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
27
+ const DEVICE_TWO_ID: string = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
28
+
29
+ function device(id: string, deviceType: PushDeviceType): UserPush {
30
+ const userPush: UserPush = new UserPush();
31
+ userPush._id = id;
32
+ userPush.deviceType = deviceType;
33
+
34
+ return userPush;
35
+ }
36
+
37
+ interface UpdateCall {
38
+ id: string;
39
+ isCriticalAlertEnabled: boolean | undefined;
40
+ isRoot: boolean | undefined;
41
+ }
42
+
43
+ describe("UserPushService.setCriticalAlertEnabledForDeviceToken", () => {
44
+ let findSpy: jest.SpyInstance;
45
+ let updateSpy: jest.SpyInstance;
46
+ let updateCalls: Array<UpdateCall>;
47
+
48
+ beforeEach(() => {
49
+ updateCalls = [];
50
+
51
+ findSpy = jest.spyOn(UserPushService, "findBy");
52
+
53
+ updateSpy = jest.spyOn(UserPushService, "updateOneBy");
54
+ updateSpy.mockImplementation((updateBy: unknown): Promise<number> => {
55
+ const call: {
56
+ query: { _id: string };
57
+ data: { isCriticalAlertEnabled?: boolean };
58
+ props: { isRoot?: boolean };
59
+ } = updateBy as {
60
+ query: { _id: string };
61
+ data: { isCriticalAlertEnabled?: boolean };
62
+ props: { isRoot?: boolean };
63
+ };
64
+
65
+ updateCalls.push({
66
+ id: call.query._id,
67
+ isCriticalAlertEnabled: call.data.isCriticalAlertEnabled,
68
+ isRoot: call.props.isRoot,
69
+ });
70
+
71
+ return Promise.resolve(1);
72
+ });
73
+ });
74
+
75
+ afterEach(() => {
76
+ jest.restoreAllMocks();
77
+ });
78
+
79
+ test("turns the override on for every project the handset is registered against", async () => {
80
+ findSpy.mockResolvedValue([
81
+ device(DEVICE_ONE_ID, PushDeviceType.iOS),
82
+ device(DEVICE_TWO_ID, PushDeviceType.iOS),
83
+ ] as never);
84
+
85
+ const updated: number =
86
+ await UserPushService.setCriticalAlertEnabledForDeviceToken({
87
+ userId: USER_ID,
88
+ deviceToken: DEVICE_TOKEN,
89
+ isEnabled: true,
90
+ });
91
+
92
+ expect(updated).toBe(2);
93
+ expect(
94
+ updateCalls.map((call: UpdateCall) => {
95
+ return call.id;
96
+ }),
97
+ ).toEqual([DEVICE_ONE_ID, DEVICE_TWO_ID]);
98
+ expect(
99
+ updateCalls.every((call: UpdateCall) => {
100
+ return call.isCriticalAlertEnabled === true;
101
+ }),
102
+ ).toBe(true);
103
+ });
104
+
105
+ test("turns the override off again", async () => {
106
+ findSpy.mockResolvedValue([
107
+ device(DEVICE_ONE_ID, PushDeviceType.Android),
108
+ ] as never);
109
+
110
+ await UserPushService.setCriticalAlertEnabledForDeviceToken({
111
+ userId: USER_ID,
112
+ deviceToken: DEVICE_TOKEN,
113
+ isEnabled: false,
114
+ });
115
+
116
+ expect(updateCalls[0]?.isCriticalAlertEnabled).toBe(false);
117
+ });
118
+
119
+ test("looks the handset up scoped to its owner, so a token cannot reconfigure somebody else's phone", async () => {
120
+ findSpy.mockResolvedValue([
121
+ device(DEVICE_ONE_ID, PushDeviceType.iOS),
122
+ ] as never);
123
+
124
+ await UserPushService.setCriticalAlertEnabledForDeviceToken({
125
+ userId: USER_ID,
126
+ deviceToken: DEVICE_TOKEN,
127
+ isEnabled: true,
128
+ });
129
+
130
+ const query: { userId: ObjectID; deviceToken: string } = (
131
+ findSpy.mock.calls[0]![0] as {
132
+ query: { userId: ObjectID; deviceToken: string };
133
+ }
134
+ ).query;
135
+
136
+ expect(query.userId.toString()).toBe(USER_ID.toString());
137
+ expect(query.userId.toString()).not.toBe(OTHER_USER_ID.toString());
138
+ expect(query.deviceToken).toBe(DEVICE_TOKEN);
139
+ });
140
+
141
+ test("writes as root, because the column grants update to nobody", async () => {
142
+ findSpy.mockResolvedValue([
143
+ device(DEVICE_ONE_ID, PushDeviceType.iOS),
144
+ ] as never);
145
+
146
+ await UserPushService.setCriticalAlertEnabledForDeviceToken({
147
+ userId: USER_ID,
148
+ deviceToken: DEVICE_TOKEN,
149
+ isEnabled: true,
150
+ });
151
+
152
+ expect(updateCalls[0]?.isRoot).toBe(true);
153
+ });
154
+
155
+ test("refuses a token that matches no registered device", async () => {
156
+ findSpy.mockResolvedValue([] as never);
157
+
158
+ await expect(
159
+ UserPushService.setCriticalAlertEnabledForDeviceToken({
160
+ userId: USER_ID,
161
+ deviceToken: DEVICE_TOKEN,
162
+ isEnabled: true,
163
+ }),
164
+ ).rejects.toThrow(BadDataException);
165
+
166
+ expect(updateSpy).not.toHaveBeenCalled();
167
+ });
168
+
169
+ test("refuses a web device - a browser cannot override a phone's ringer", async () => {
170
+ findSpy.mockResolvedValue([
171
+ device(DEVICE_ONE_ID, PushDeviceType.Web),
172
+ ] as never);
173
+
174
+ await expect(
175
+ UserPushService.setCriticalAlertEnabledForDeviceToken({
176
+ userId: USER_ID,
177
+ deviceToken: DEVICE_TOKEN,
178
+ isEnabled: true,
179
+ }),
180
+ ).rejects.toThrow(
181
+ "Critical alerts are only available on iOS and Android devices.",
182
+ );
183
+
184
+ expect(updateSpy).not.toHaveBeenCalled();
185
+ });
186
+
187
+ test("updates only the mobile rows when a token somehow spans device types", async () => {
188
+ /*
189
+ * Not expected in practice - a web subscription blob and an Expo token do
190
+ * not collide - but storing the preference on a web row would read back
191
+ * later as though the browser would ring, which is worse than refusing it.
192
+ */
193
+ findSpy.mockResolvedValue([
194
+ device(DEVICE_ONE_ID, PushDeviceType.Web),
195
+ device(DEVICE_TWO_ID, PushDeviceType.Android),
196
+ ] as never);
197
+
198
+ const updated: number =
199
+ await UserPushService.setCriticalAlertEnabledForDeviceToken({
200
+ userId: USER_ID,
201
+ deviceToken: DEVICE_TOKEN,
202
+ isEnabled: true,
203
+ });
204
+
205
+ expect(updated).toBe(1);
206
+ expect(updateCalls).toHaveLength(1);
207
+ expect(updateCalls[0]?.id).toBe(DEVICE_TWO_ID);
208
+ });
209
+
210
+ test("reports how many rows were actually written", async () => {
211
+ /*
212
+ * The count is what tells a caller "this matched nothing" apart from "this
213
+ * saved". updateOneBy returns 0 when the row vanished between the lookup
214
+ * and the write.
215
+ */
216
+ findSpy.mockResolvedValue([
217
+ device(DEVICE_ONE_ID, PushDeviceType.iOS),
218
+ device(DEVICE_TWO_ID, PushDeviceType.iOS),
219
+ ] as never);
220
+
221
+ updateSpy.mockResolvedValueOnce(1 as never);
222
+ updateSpy.mockResolvedValueOnce(0 as never);
223
+
224
+ const updated: number =
225
+ await UserPushService.setCriticalAlertEnabledForDeviceToken({
226
+ userId: USER_ID,
227
+ deviceToken: DEVICE_TOKEN,
228
+ isEnabled: true,
229
+ });
230
+
231
+ expect(updated).toBe(1);
232
+ });
233
+
234
+ test("only ever writes the critical alert column", async () => {
235
+ /*
236
+ * A toggle that also carried, say, isVerified would let the settings
237
+ * screen re-verify a device the user had deliberately unverified.
238
+ */
239
+ findSpy.mockResolvedValue([
240
+ device(DEVICE_ONE_ID, PushDeviceType.iOS),
241
+ ] as never);
242
+
243
+ await UserPushService.setCriticalAlertEnabledForDeviceToken({
244
+ userId: USER_ID,
245
+ deviceToken: DEVICE_TOKEN,
246
+ isEnabled: true,
247
+ });
248
+
249
+ const data: Record<string, unknown> = (
250
+ updateSpy.mock.calls[0]![0] as { data: Record<string, unknown> }
251
+ ).data;
252
+
253
+ expect(Object.keys(data)).toEqual(["isCriticalAlertEnabled"]);
254
+ });
255
+ });
@@ -0,0 +1,218 @@
1
+ import UserTotpAuthService from "../../../Server/Services/UserTotpAuthService";
2
+ import UserService from "../../../Server/Services/UserService";
3
+ import TotpAuth from "../../../Server/Utils/TotpAuth";
4
+ import CreateBy from "../../../Server/Types/Database/CreateBy";
5
+ import { OnCreate } from "../../../Server/Types/Database/Hooks";
6
+ import Email from "../../../Types/Email";
7
+ import BadDataException from "../../../Types/Exception/BadDataException";
8
+ import ObjectID from "../../../Types/ObjectID";
9
+ import UserTotpAuth from "../../../Models/DatabaseModels/UserTotpAuth";
10
+ import User from "../../../Models/DatabaseModels/User";
11
+ import * as OTPAuth from "otpauth";
12
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
13
+
14
+ /*
15
+ * Enrolment — the moment a secret is minted and the QR code the user is about
16
+ * to scan is frozen into `twoFactorOtpUrl`.
17
+ *
18
+ * This is where issue #3275 was born. The URL written here is the ONLY thing
19
+ * the authenticator app ever sees; nothing downstream can renegotiate it. If
20
+ * it advertises an algorithm the app will not honour, the enrolment is dead on
21
+ * arrival and no amount of retrying at verification time can rescue it. So the
22
+ * property under test is not "a URL was produced" but "the code an app
23
+ * computes from this URL is the code the server will accept" — asserted by
24
+ * going through the round trip rather than by string-matching.
25
+ */
26
+
27
+ jest.mock("../../../Server/Services/UserService");
28
+
29
+ const USER_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
30
+ const USER_EMAIL: Email = new Email("enrolling.user@example.com");
31
+
32
+ /*
33
+ * `signedInUserId: null` models a request that reached the service with no
34
+ * authenticated user on it.
35
+ */
36
+ type BuildCreateByFunction = (options?: {
37
+ signedInUserId?: ObjectID | null;
38
+ }) => CreateBy<UserTotpAuth>;
39
+
40
+ const buildCreateBy: BuildCreateByFunction = (options?: {
41
+ signedInUserId?: ObjectID | null;
42
+ }): CreateBy<UserTotpAuth> => {
43
+ const model: UserTotpAuth = new UserTotpAuth();
44
+ model.name = "Google Authenticator";
45
+
46
+ const signedInUserId: ObjectID | null =
47
+ options && "signedInUserId" in options
48
+ ? (options.signedInUserId as ObjectID | null)
49
+ : USER_ID;
50
+
51
+ return {
52
+ data: model,
53
+ props: {
54
+ userId: signedInUserId === null ? undefined : signedInUserId,
55
+ },
56
+ } as unknown as CreateBy<UserTotpAuth>;
57
+ };
58
+
59
+ type RunOnBeforeCreateFunction = (
60
+ createBy: CreateBy<UserTotpAuth>,
61
+ ) => Promise<OnCreate<UserTotpAuth>>;
62
+
63
+ const runOnBeforeCreate: RunOnBeforeCreateFunction = async (
64
+ createBy: CreateBy<UserTotpAuth>,
65
+ ): Promise<OnCreate<UserTotpAuth>> => {
66
+ return (await (
67
+ UserTotpAuthService as unknown as {
68
+ onBeforeCreate: RunOnBeforeCreateFunction;
69
+ }
70
+ ).onBeforeCreate(createBy)) as OnCreate<UserTotpAuth>;
71
+ };
72
+
73
+ describe("UserTotpAuthService enrolment", () => {
74
+ beforeEach(() => {
75
+ const user: User = new User();
76
+ user.id = USER_ID;
77
+ user.email = USER_EMAIL;
78
+
79
+ UserService.findOneById = jest.fn().mockResolvedValue(user) as never;
80
+ });
81
+
82
+ afterEach(() => {
83
+ jest.restoreAllMocks();
84
+ });
85
+
86
+ describe("the QR code it freezes into the row", () => {
87
+ test("advertises SHA1, so Google Authenticator computes what the server checks", async () => {
88
+ const result: OnCreate<UserTotpAuth> =
89
+ await runOnBeforeCreate(buildCreateBy());
90
+
91
+ const url: string = result.createBy.data.twoFactorOtpUrl!;
92
+
93
+ expect(url).toContain("algorithm=SHA1");
94
+ expect(url).not.toContain("SHA256");
95
+ });
96
+
97
+ /*
98
+ * The end-to-end property, and the one that actually matters: take the URL
99
+ * exactly as the phone would read it, produce a code from it, and hand
100
+ * that code to the same verifier the validate endpoint uses.
101
+ */
102
+ test("produces a QR code whose codes the verifier accepts", async () => {
103
+ const result: OnCreate<UserTotpAuth> =
104
+ await runOnBeforeCreate(buildCreateBy());
105
+
106
+ const scanned: OTPAuth.TOTP = OTPAuth.URI.parse(
107
+ result.createBy.data.twoFactorOtpUrl!,
108
+ ) as OTPAuth.TOTP;
109
+
110
+ expect(
111
+ TotpAuth.verifyToken({
112
+ secret: result.createBy.data.twoFactorSecret!,
113
+ token: scanned.generate(),
114
+ email: USER_EMAIL,
115
+ }),
116
+ ).toBe(true);
117
+ });
118
+
119
+ test("labels the entry with the enrolling user's email", async () => {
120
+ const result: OnCreate<UserTotpAuth> =
121
+ await runOnBeforeCreate(buildCreateBy());
122
+
123
+ expect(
124
+ decodeURIComponent(result.createBy.data.twoFactorOtpUrl!),
125
+ ).toContain("enrolling.user@example.com");
126
+ });
127
+
128
+ test("embeds the same secret it stores on the row", async () => {
129
+ const result: OnCreate<UserTotpAuth> =
130
+ await runOnBeforeCreate(buildCreateBy());
131
+
132
+ const scanned: OTPAuth.TOTP = OTPAuth.URI.parse(
133
+ result.createBy.data.twoFactorOtpUrl!,
134
+ ) as OTPAuth.TOTP;
135
+
136
+ expect(scanned.secret.base32).toBe(result.createBy.data.twoFactorSecret);
137
+ });
138
+ });
139
+
140
+ describe("the row it writes", () => {
141
+ test("mints a fresh secret rather than trusting anything from the client", async () => {
142
+ const createBy: CreateBy<UserTotpAuth> = buildCreateBy();
143
+
144
+ (createBy.data as UserTotpAuth).twoFactorSecret =
145
+ "ATTACKERCHOSENSECRETAAAAAAAAAAAA";
146
+
147
+ const result: OnCreate<UserTotpAuth> = await runOnBeforeCreate(createBy);
148
+
149
+ expect(result.createBy.data.twoFactorSecret).not.toBe(
150
+ "ATTACKERCHOSENSECRETAAAAAAAAAAAA",
151
+ );
152
+ });
153
+
154
+ test("issues a different secret to every enrolment", async () => {
155
+ const first: OnCreate<UserTotpAuth> =
156
+ await runOnBeforeCreate(buildCreateBy());
157
+ const second: OnCreate<UserTotpAuth> =
158
+ await runOnBeforeCreate(buildCreateBy());
159
+
160
+ expect(first.createBy.data.twoFactorSecret).not.toBe(
161
+ second.createBy.data.twoFactorSecret,
162
+ );
163
+ });
164
+
165
+ /*
166
+ * A newly created row is a claim, not a second factor. It only becomes one
167
+ * once the user proves they scanned it — otherwise anyone could create a
168
+ * row and count it as their 2FA method.
169
+ */
170
+ test("starts unverified no matter what the client asked for", async () => {
171
+ const createBy: CreateBy<UserTotpAuth> = buildCreateBy();
172
+ (createBy.data as UserTotpAuth).isVerified = true;
173
+
174
+ const result: OnCreate<UserTotpAuth> = await runOnBeforeCreate(createBy);
175
+
176
+ expect(result.createBy.data.isVerified).toBe(false);
177
+ });
178
+
179
+ test("binds the row to the signed-in user, not to a client-supplied id", async () => {
180
+ const createBy: CreateBy<UserTotpAuth> = buildCreateBy();
181
+
182
+ (createBy.data as UserTotpAuth).userId = new ObjectID(
183
+ "55555555-5555-4555-8555-555555555555",
184
+ );
185
+
186
+ const result: OnCreate<UserTotpAuth> = await runOnBeforeCreate(createBy);
187
+
188
+ expect(result.createBy.data.userId).toBe(USER_ID);
189
+ });
190
+ });
191
+
192
+ describe("when it cannot enrol", () => {
193
+ test("refuses without a signed-in user", async () => {
194
+ await expect(
195
+ runOnBeforeCreate(buildCreateBy({ signedInUserId: null })),
196
+ ).rejects.toThrow(BadDataException);
197
+ });
198
+
199
+ test("refuses when the user record has gone", async () => {
200
+ UserService.findOneById = jest.fn().mockResolvedValue(null) as never;
201
+
202
+ await expect(runOnBeforeCreate(buildCreateBy())).rejects.toThrow(
203
+ BadDataException,
204
+ );
205
+ });
206
+
207
+ test("refuses when the user has no email to label the entry with", async () => {
208
+ const emailless: User = new User();
209
+ emailless.id = USER_ID;
210
+
211
+ UserService.findOneById = jest.fn().mockResolvedValue(emailless) as never;
212
+
213
+ await expect(runOnBeforeCreate(buildCreateBy())).rejects.toThrow(
214
+ BadDataException,
215
+ );
216
+ });
217
+ });
218
+ });