@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,719 @@
1
+ import TotpAuth, {
2
+ DefaultTotpAlgorithm,
3
+ SupportedTotpAlgorithms,
4
+ TotpDigits,
5
+ TotpPeriodInSeconds,
6
+ TotpValidationWindow,
7
+ } from "../../../Server/Utils/TotpAuth";
8
+ import {
9
+ authenticatorCode,
10
+ base32Decode,
11
+ base32Encode,
12
+ buildOtpauthUri,
13
+ googleAuthenticatorCode,
14
+ } from "../TestingUtils/AuthenticatorApp";
15
+ import Email from "../../../Types/Email";
16
+ import * as OTPAuth from "otpauth";
17
+ import { afterEach, describe, expect, test } from "@jest/globals";
18
+
19
+ /*
20
+ * TOTP enrolment and verification.
21
+ *
22
+ * The bug these tests exist for (issue #3275): OneUptime built its otpauth://
23
+ * URIs with `algorithm=SHA256`, and Google Authenticator — like Microsoft
24
+ * Authenticator, and like most of the phone-based apps people actually use —
25
+ * parses that URI, ignores the `algorithm` parameter, and computes SHA1
26
+ * regardless. Nothing errors on either side. The app shows six confident
27
+ * digits, the server computes six entirely different digits, and enrolment
28
+ * fails with "Invalid code" forever. Waiting for a fresh code cannot help,
29
+ * because every code in the sequence is wrong.
30
+ *
31
+ * That failure is invisible to any test that generates the expected code with
32
+ * the same library the server verifies with — both sides agree on SHA256 and
33
+ * the test passes while every real user is locked out. So the codes here come
34
+ * from TestingUtils/AuthenticatorApp, which derives them from RFC 4226 / RFC
35
+ * 6238 with Node's crypto primitives and no help from `otpauth`, exactly the
36
+ * way a phone does. If the server's algorithm and the app's algorithm ever
37
+ * diverge again, these tests are what notices.
38
+ *
39
+ * The other half is not breaking anyone already enrolled. SHA256 secrets are
40
+ * in the database and they DO work in the apps that honour the parameter
41
+ * (1Password, Bitwarden, Aegis, FreeOTP). Verification therefore has to keep
42
+ * accepting them, which is why SupportedTotpAlgorithms is a list rather than a
43
+ * constant, and why it is pinned below.
44
+ */
45
+
46
+ const EMAIL: Email = new Email("jane@example.com");
47
+
48
+ type QueryParamsOfFunction = (uri: string) => URLSearchParams;
49
+
50
+ const queryParamsOf: QueryParamsOfFunction = (uri: string): URLSearchParams => {
51
+ return new URLSearchParams(uri.slice(uri.indexOf("?") + 1));
52
+ };
53
+
54
+ /*
55
+ * A time far enough from a step boundary that no test straddles one by
56
+ * accident, and exactly divisible by the period so that "T minus 90 seconds"
57
+ * lands on a whole step rather than somewhere inside one.
58
+ */
59
+ const FIXED_NOW_SECONDS: number = 1893456000; // 2030-01-01T00:00:00Z
60
+
61
+ type FreezeClockAtFunction = (unixSeconds: number) => void;
62
+
63
+ const freezeClockAt: FreezeClockAtFunction = (unixSeconds: number): void => {
64
+ jest.useFakeTimers();
65
+ jest.setSystemTime(unixSeconds * 1000);
66
+ };
67
+
68
+ afterEach(() => {
69
+ jest.useRealTimers();
70
+ });
71
+
72
+ describe("TotpAuth.generateSecret", () => {
73
+ test("issues a 160-bit secret, the RFC 4226 recommended length", () => {
74
+ const secret: string = TotpAuth.generateSecret();
75
+
76
+ expect(base32Decode(secret)).toHaveLength(20);
77
+ });
78
+
79
+ test("issues valid base32 so every authenticator app can read the QR", () => {
80
+ const secret: string = TotpAuth.generateSecret();
81
+
82
+ expect(secret).toMatch(/^[A-Z2-7]+$/);
83
+ });
84
+
85
+ test("never issues the same secret twice", () => {
86
+ const secrets: Set<string> = new Set<string>();
87
+
88
+ for (let i: number = 0; i < 200; i++) {
89
+ secrets.add(TotpAuth.generateSecret());
90
+ }
91
+
92
+ expect(secrets.size).toBe(200);
93
+ });
94
+ });
95
+
96
+ describe("the otpauth:// URI handed to the authenticator app", () => {
97
+ test("advertises SHA1 — the algorithm Google Authenticator actually uses", () => {
98
+ const uri: string = TotpAuth.generateUri({
99
+ secret: TotpAuth.generateSecret(),
100
+ userEmail: EMAIL,
101
+ });
102
+
103
+ expect(queryParamsOf(uri).get("algorithm")).toBe("SHA1");
104
+ });
105
+
106
+ /*
107
+ * The literal regression. An `algorithm=SHA256` URI is not rejected by
108
+ * Google Authenticator — it is silently misread — so this is the assertion
109
+ * that has to fail loudly if anyone reinstates it.
110
+ */
111
+ test("never advertises SHA256, which phone apps silently misread (issue #3275)", () => {
112
+ const uri: string = TotpAuth.generateUri({
113
+ secret: TotpAuth.generateSecret(),
114
+ userEmail: EMAIL,
115
+ });
116
+
117
+ expect(uri).not.toContain("SHA256");
118
+ expect(uri).not.toContain("SHA512");
119
+ });
120
+
121
+ test("uses the 6 digit / 30 second shape every authenticator assumes", () => {
122
+ const params: URLSearchParams = queryParamsOf(
123
+ TotpAuth.generateUri({
124
+ secret: TotpAuth.generateSecret(),
125
+ userEmail: EMAIL,
126
+ }),
127
+ );
128
+
129
+ expect(params.get("digits")).toBe("6");
130
+ expect(params.get("period")).toBe("30");
131
+ });
132
+
133
+ test("carries the exact secret it was given", () => {
134
+ const secret: string = TotpAuth.generateSecret();
135
+
136
+ const uri: string = TotpAuth.generateUri({
137
+ secret: secret,
138
+ userEmail: EMAIL,
139
+ });
140
+
141
+ expect(queryParamsOf(uri).get("secret")).toBe(secret);
142
+ });
143
+
144
+ test("names OneUptime and the user so the entry is identifiable in the app", () => {
145
+ const uri: string = TotpAuth.generateUri({
146
+ secret: TotpAuth.generateSecret(),
147
+ userEmail: EMAIL,
148
+ });
149
+
150
+ expect(uri.startsWith("otpauth://totp/")).toBe(true);
151
+ expect(queryParamsOf(uri).get("issuer")).toBe("OneUptime");
152
+ expect(decodeURIComponent(uri.split("?")[0]!)).toContain(
153
+ "jane@example.com",
154
+ );
155
+ });
156
+
157
+ /*
158
+ * The round trip that the bug broke: parse the URI back exactly as an
159
+ * authenticator app would, generate a code from what it says, and hand that
160
+ * code to the server. Before the fix the parsed TOTP was SHA256 while the
161
+ * server's phone-app users were producing SHA1, and this failed.
162
+ */
163
+ test("round trips — a code built from the URI's own parameters verifies", () => {
164
+ const secret: string = TotpAuth.generateSecret();
165
+
166
+ const parsed: OTPAuth.TOTP = OTPAuth.URI.parse(
167
+ TotpAuth.generateUri({ secret: secret, userEmail: EMAIL }),
168
+ ) as OTPAuth.TOTP;
169
+
170
+ expect(
171
+ TotpAuth.verifyToken({
172
+ secret: secret,
173
+ token: parsed.generate(),
174
+ email: EMAIL,
175
+ }),
176
+ ).toBe(true);
177
+ });
178
+ });
179
+
180
+ describe("Google Authenticator compatibility (issue #3275)", () => {
181
+ /*
182
+ * The headline test. The code is computed the way the phone computes it —
183
+ * HMAC-SHA1 over the decoded secret — with no reference to `otpauth` at all.
184
+ */
185
+ test("accepts the code a phone app computes from the QR it was shown", () => {
186
+ const secret: string = TotpAuth.generateSecret();
187
+
188
+ const uri: string = TotpAuth.generateUri({
189
+ secret: secret,
190
+ userEmail: EMAIL,
191
+ });
192
+
193
+ const scannedSecret: string = queryParamsOf(uri).get("secret")!;
194
+
195
+ const codeOnThePhone: string = authenticatorCode({
196
+ secretBase32: scannedSecret,
197
+ algorithm: "SHA1",
198
+ });
199
+
200
+ expect(
201
+ TotpAuth.verifyToken({
202
+ secret: secret,
203
+ token: codeOnThePhone,
204
+ email: EMAIL,
205
+ }),
206
+ ).toBe(true);
207
+ });
208
+
209
+ /*
210
+ * Proof that the mismatch was real and not a rounding artefact: for the same
211
+ * secret at the same instant the two algorithms produce different codes, so
212
+ * a server on SHA256 and a phone on SHA1 can never agree.
213
+ */
214
+ test("SHA1 and SHA256 codes genuinely differ, so the mismatch was fatal", () => {
215
+ const secret: string = TotpAuth.generateSecret();
216
+ const at: number = FIXED_NOW_SECONDS;
217
+
218
+ const sha1Code: string = authenticatorCode({
219
+ secretBase32: secret,
220
+ algorithm: "SHA1",
221
+ atUnixSeconds: at,
222
+ });
223
+
224
+ const sha256Code: string = authenticatorCode({
225
+ secretBase32: secret,
226
+ algorithm: "SHA256",
227
+ atUnixSeconds: at,
228
+ });
229
+
230
+ expect(sha1Code).not.toBe(sha256Code);
231
+ });
232
+
233
+ /*
234
+ * The other half of the repair, and the reason nobody has to delete and
235
+ * re-create a half-finished enrolment: a QR code issued by the OLD code
236
+ * still says SHA256, but Google Authenticator was never reading that
237
+ * parameter anyway. It emits a SHA1 code, and SHA1 is now the first thing
238
+ * the verifier tries — so the enrolments that were stuck start working the
239
+ * moment this ships, before the migration tidies the stored URL.
240
+ */
241
+ test("unsticks an enrolment whose stored QR code still says SHA256", () => {
242
+ const secret: string = TotpAuth.generateSecret();
243
+
244
+ const legacyUri: string = buildOtpauthUri({
245
+ secret: secret,
246
+ label: EMAIL.toString(),
247
+ algorithm: "SHA256",
248
+ });
249
+
250
+ expect(
251
+ TotpAuth.verifyToken({
252
+ secret: secret,
253
+ token: googleAuthenticatorCode(legacyUri),
254
+ email: EMAIL,
255
+ }),
256
+ ).toBe(true);
257
+ });
258
+
259
+ test("the verifier's default algorithm is the one it advertises", () => {
260
+ const secret: string = TotpAuth.generateSecret();
261
+
262
+ const advertised: string = queryParamsOf(
263
+ TotpAuth.generateUri({ secret: secret, userEmail: EMAIL }),
264
+ ).get("algorithm")!;
265
+
266
+ expect(advertised).toBe(DefaultTotpAlgorithm);
267
+ expect(TotpAuth.getTotp({ secret: secret, email: EMAIL }).algorithm).toBe(
268
+ DefaultTotpAlgorithm,
269
+ );
270
+ });
271
+ });
272
+
273
+ /*
274
+ * RFC 6238 Appendix B. The published vectors are 8 digits; a 6 digit code is
275
+ * the same integer reduced modulo 10^6, which is the last 6 characters,
276
+ * because 10^6 divides 10^8.
277
+ */
278
+ describe("RFC 6238 published test vectors", () => {
279
+ const SHA1_SEED: Buffer = Buffer.from("12345678901234567890", "ascii");
280
+
281
+ const SHA256_SEED: Buffer = Buffer.from(
282
+ "12345678901234567890123456789012",
283
+ "ascii",
284
+ );
285
+
286
+ const VECTORS: Array<{
287
+ unixSeconds: number;
288
+ sha1: string;
289
+ sha256: string;
290
+ }> = [
291
+ { unixSeconds: 59, sha1: "94287082", sha256: "46119246" },
292
+ { unixSeconds: 1111111109, sha1: "07081804", sha256: "68084774" },
293
+ { unixSeconds: 1111111111, sha1: "14050471", sha256: "67062674" },
294
+ { unixSeconds: 1234567890, sha1: "89005924", sha256: "91819424" },
295
+ { unixSeconds: 2000000000, sha1: "69279037", sha256: "90698825" },
296
+ ];
297
+
298
+ test("the base32 helpers in this file round trip", () => {
299
+ expect(base32Decode(base32Encode(SHA1_SEED))).toEqual(SHA1_SEED);
300
+ expect(base32Encode(SHA1_SEED)).toBe("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ");
301
+ });
302
+
303
+ test.each(VECTORS)(
304
+ "verifies the RFC's SHA1 code at t=$unixSeconds",
305
+ (vector: { unixSeconds: number; sha1: string; sha256: string }) => {
306
+ freezeClockAt(vector.unixSeconds);
307
+
308
+ expect(
309
+ TotpAuth.verifyToken({
310
+ secret: base32Encode(SHA1_SEED),
311
+ token: vector.sha1.slice(-TotpDigits),
312
+ email: EMAIL,
313
+ }),
314
+ ).toBe(true);
315
+ },
316
+ );
317
+
318
+ test.each(VECTORS)(
319
+ "still verifies the RFC's legacy SHA256 code at t=$unixSeconds",
320
+ (vector: { unixSeconds: number; sha1: string; sha256: string }) => {
321
+ freezeClockAt(vector.unixSeconds);
322
+
323
+ expect(
324
+ TotpAuth.verifyToken({
325
+ secret: base32Encode(SHA256_SEED),
326
+ token: vector.sha256.slice(-TotpDigits),
327
+ email: EMAIL,
328
+ }),
329
+ ).toBe(true);
330
+ },
331
+ );
332
+ });
333
+
334
+ describe("legacy SHA256 enrolments keep working", () => {
335
+ test("SHA1 is tried first and SHA256 is still accepted", () => {
336
+ expect(SupportedTotpAlgorithms[0]).toBe("SHA1");
337
+ expect(SupportedTotpAlgorithms).toContain("SHA256");
338
+ });
339
+
340
+ /*
341
+ * Someone who enrolled before the fix using 1Password or Bitwarden — apps
342
+ * that DO honour `algorithm` — holds a SHA256 entry. Dropping SHA256 from
343
+ * verification would lock them out of their own account at the next login,
344
+ * which is a worse bug than the one being fixed.
345
+ */
346
+ test("a code from an app that honoured algorithm=SHA256 still logs the user in", () => {
347
+ const secret: string = TotpAuth.generateSecret();
348
+
349
+ const legacyCode: string = authenticatorCode({
350
+ secretBase32: secret,
351
+ algorithm: "SHA256",
352
+ });
353
+
354
+ expect(
355
+ TotpAuth.verifyToken({
356
+ secret: secret,
357
+ token: legacyCode,
358
+ email: EMAIL,
359
+ }),
360
+ ).toBe(true);
361
+ });
362
+
363
+ test("both supported algorithms verify against the same secret", () => {
364
+ const secret: string = TotpAuth.generateSecret();
365
+
366
+ for (const algorithm of SupportedTotpAlgorithms) {
367
+ expect(
368
+ TotpAuth.verifyToken({
369
+ secret: secret,
370
+ token: authenticatorCode({
371
+ secretBase32: secret,
372
+ algorithm: algorithm,
373
+ }),
374
+ email: EMAIL,
375
+ }),
376
+ ).toBe(true);
377
+ }
378
+ });
379
+
380
+ test("an unsupported algorithm's code is rejected", () => {
381
+ const secret: string = TotpAuth.generateSecret();
382
+
383
+ expect(
384
+ TotpAuth.verifyToken({
385
+ secret: secret,
386
+ token: authenticatorCode({
387
+ secretBase32: secret,
388
+ algorithm: "SHA512",
389
+ }),
390
+ email: EMAIL,
391
+ }),
392
+ ).toBe(false);
393
+ });
394
+ });
395
+
396
+ describe("TotpAuth.normalizeToken", () => {
397
+ /*
398
+ * Google Authenticator renders a code as "123 456" and both mobile
399
+ * clipboards keep the space. Rejecting the paste is a self-inflicted
400
+ * "Invalid code" for a user holding exactly the right secret.
401
+ */
402
+ test.each([
403
+ ["123 456", "123456"],
404
+ ["123-456", "123456"],
405
+ [" 123456 ", "123456"],
406
+ ["123456\n", "123456"],
407
+ ["1 2 3 4 5 6", "123456"],
408
+ ["", ""],
409
+ ["abcdef", ""],
410
+ ])("normalises %p to %p", (input: string, expected: string) => {
411
+ expect(TotpAuth.normalizeToken(input)).toBe(expected);
412
+ });
413
+
414
+ /*
415
+ * The token comes straight off a JSON body, so it is only a string because
416
+ * the client chose to send one. Anything else has to verify as false, not
417
+ * throw out of `.replace` and surface as a 500.
418
+ */
419
+ test.each([
420
+ ["undefined", undefined],
421
+ ["null", null],
422
+ ["a number", 123456],
423
+ ["an object", { code: "123456" }],
424
+ ["an array", ["123456"]],
425
+ ["a boolean", true],
426
+ ])(
427
+ "returns nothing for %s rather than throwing",
428
+ (_name: string, token: unknown) => {
429
+ expect(TotpAuth.normalizeToken(token as string)).toBe("");
430
+ },
431
+ );
432
+ });
433
+
434
+ describe("TotpAuth.verifyToken accepts the code the user actually holds", () => {
435
+ test("accepts a correctly typed code", () => {
436
+ const secret: string = TotpAuth.generateSecret();
437
+
438
+ expect(
439
+ TotpAuth.verifyToken({
440
+ secret: secret,
441
+ token: authenticatorCode({ secretBase32: secret }),
442
+ email: EMAIL,
443
+ }),
444
+ ).toBe(true);
445
+ });
446
+
447
+ /*
448
+ * The separators the phone and the clipboard insert, applied to a code that
449
+ * is genuinely correct at this instant rather than to a hardcoded literal.
450
+ */
451
+ test.each([
452
+ ["a space, as Google Authenticator displays it", "### ###"],
453
+ ["surrounding whitespace from a trimmed paste", " ###### "],
454
+ ["a hyphen", "###-###"],
455
+ ["a space between every digit", "# # # # # #"],
456
+ ])("accepts a correct code pasted with %s", (_name: string, mask: string) => {
457
+ const secret: string = TotpAuth.generateSecret();
458
+ const code: string = authenticatorCode({ secretBase32: secret });
459
+
460
+ let digitIndex: number = 0;
461
+
462
+ const pasted: string = mask.replace(/#/g, (): string => {
463
+ return code[digitIndex++]!;
464
+ });
465
+
466
+ expect(digitIndex).toBe(TotpDigits);
467
+
468
+ expect(
469
+ TotpAuth.verifyToken({
470
+ secret: secret,
471
+ token: pasted,
472
+ email: EMAIL,
473
+ }),
474
+ ).toBe(true);
475
+ });
476
+
477
+ /*
478
+ * The email is the label on the QR code, not key material. A user who
479
+ * changes their address after enrolling must not be locked out — and
480
+ * verifyToken reads the address back from the User row, which is exactly
481
+ * where that change lands.
482
+ */
483
+ test("still verifies after the user changes their email address", () => {
484
+ const secret: string = TotpAuth.generateSecret();
485
+
486
+ const codeFromTheirApp: string = authenticatorCode({
487
+ secretBase32: secret,
488
+ });
489
+
490
+ expect(
491
+ TotpAuth.verifyToken({
492
+ secret: secret,
493
+ token: codeFromTheirApp,
494
+ email: new Email("jane.renamed@example.com"),
495
+ }),
496
+ ).toBe(true);
497
+ });
498
+ });
499
+
500
+ describe("TotpAuth.verifyToken tolerates clock drift", () => {
501
+ test(`accepts a code ${TotpValidationWindow} steps behind the server clock`, () => {
502
+ const secret: string = TotpAuth.generateSecret();
503
+
504
+ const staleCode: string = authenticatorCode({
505
+ secretBase32: secret,
506
+ atUnixSeconds:
507
+ FIXED_NOW_SECONDS - TotpValidationWindow * TotpPeriodInSeconds,
508
+ });
509
+
510
+ freezeClockAt(FIXED_NOW_SECONDS);
511
+
512
+ expect(
513
+ TotpAuth.verifyToken({ secret: secret, token: staleCode, email: EMAIL }),
514
+ ).toBe(true);
515
+ });
516
+
517
+ test(`accepts a code ${TotpValidationWindow} steps ahead of the server clock`, () => {
518
+ const secret: string = TotpAuth.generateSecret();
519
+
520
+ const futureCode: string = authenticatorCode({
521
+ secretBase32: secret,
522
+ atUnixSeconds:
523
+ FIXED_NOW_SECONDS + TotpValidationWindow * TotpPeriodInSeconds,
524
+ });
525
+
526
+ freezeClockAt(FIXED_NOW_SECONDS);
527
+
528
+ expect(
529
+ TotpAuth.verifyToken({ secret: secret, token: futureCode, email: EMAIL }),
530
+ ).toBe(true);
531
+ });
532
+
533
+ test("rejects a code one step outside the window in either direction", () => {
534
+ const secret: string = TotpAuth.generateSecret();
535
+
536
+ const tooOld: string = authenticatorCode({
537
+ secretBase32: secret,
538
+ atUnixSeconds:
539
+ FIXED_NOW_SECONDS - (TotpValidationWindow + 1) * TotpPeriodInSeconds,
540
+ });
541
+
542
+ const tooNew: string = authenticatorCode({
543
+ secretBase32: secret,
544
+ atUnixSeconds:
545
+ FIXED_NOW_SECONDS + (TotpValidationWindow + 1) * TotpPeriodInSeconds,
546
+ });
547
+
548
+ freezeClockAt(FIXED_NOW_SECONDS);
549
+
550
+ expect(
551
+ TotpAuth.verifyToken({ secret: secret, token: tooOld, email: EMAIL }),
552
+ ).toBe(false);
553
+
554
+ expect(
555
+ TotpAuth.verifyToken({ secret: secret, token: tooNew, email: EMAIL }),
556
+ ).toBe(false);
557
+ });
558
+
559
+ test("the window is not so wide that a whole day of codes is valid", () => {
560
+ const secret: string = TotpAuth.generateSecret();
561
+
562
+ const yesterdaysCode: string = authenticatorCode({
563
+ secretBase32: secret,
564
+ atUnixSeconds: FIXED_NOW_SECONDS - 86400,
565
+ });
566
+
567
+ freezeClockAt(FIXED_NOW_SECONDS);
568
+
569
+ expect(
570
+ TotpAuth.verifyToken({
571
+ secret: secret,
572
+ token: yesterdaysCode,
573
+ email: EMAIL,
574
+ }),
575
+ ).toBe(false);
576
+ });
577
+ });
578
+
579
+ describe("TotpAuth.verifyToken rejects what it must", () => {
580
+ test("rejects a code generated from a different secret", () => {
581
+ const secret: string = TotpAuth.generateSecret();
582
+ const someoneElsesSecret: string = TotpAuth.generateSecret();
583
+
584
+ expect(
585
+ TotpAuth.verifyToken({
586
+ secret: secret,
587
+ token: authenticatorCode({ secretBase32: someoneElsesSecret }),
588
+ email: EMAIL,
589
+ }),
590
+ ).toBe(false);
591
+ });
592
+
593
+ test.each([
594
+ ["nothing at all", ""],
595
+ ["one digit short", "12345"],
596
+ ["one digit too many", "1234567"],
597
+ ["letters", "abcdef"],
598
+ ["a truncated paste", "12 34"],
599
+ ["the six digits of a longer number", "9876543210"],
600
+ ])("rejects %s", (_name: string, token: string) => {
601
+ const secret: string = TotpAuth.generateSecret();
602
+
603
+ expect(
604
+ TotpAuth.verifyToken({ secret: secret, token: token, email: EMAIL }),
605
+ ).toBe(false);
606
+ });
607
+
608
+ /*
609
+ * A six-digit code of the right shape but the wrong value. Derived from the
610
+ * correct code so it cannot accidentally BE the correct code.
611
+ */
612
+ test("rejects a well-formed code that is off by one digit", () => {
613
+ const secret: string = TotpAuth.generateSecret();
614
+ const correct: string = authenticatorCode({ secretBase32: secret });
615
+
616
+ const wrong: string =
617
+ ((parseInt(correct[0]!, 10) + 1) % 10).toString() + correct.slice(1);
618
+
619
+ expect(wrong).not.toBe(correct);
620
+
621
+ expect(
622
+ TotpAuth.verifyToken({ secret: secret, token: wrong, email: EMAIL }),
623
+ ).toBe(false);
624
+ });
625
+
626
+ test("rejects rather than throws when the stored secret is missing", () => {
627
+ expect(
628
+ TotpAuth.verifyToken({ secret: "", token: "123456", email: EMAIL }),
629
+ ).toBe(false);
630
+ });
631
+
632
+ test("rejects rather than throws when the stored secret is not base32", () => {
633
+ expect(() => {
634
+ return TotpAuth.verifyToken({
635
+ secret: "this is not base32!!!",
636
+ token: "123456",
637
+ email: EMAIL,
638
+ });
639
+ }).not.toThrow();
640
+
641
+ expect(
642
+ TotpAuth.verifyToken({
643
+ secret: "this is not base32!!!",
644
+ token: "123456",
645
+ email: EMAIL,
646
+ }),
647
+ ).toBe(false);
648
+ });
649
+
650
+ test.each([
651
+ ["undefined", undefined],
652
+ ["null", null],
653
+ ["a number", 123456],
654
+ ["an object", { code: "123456" }],
655
+ ])("rejects %s rather than throwing", (_name: string, token: unknown) => {
656
+ const secret: string = TotpAuth.generateSecret();
657
+
658
+ expect(() => {
659
+ return TotpAuth.verifyToken({
660
+ secret: secret,
661
+ token: token as string,
662
+ email: EMAIL,
663
+ });
664
+ }).not.toThrow();
665
+
666
+ expect(
667
+ TotpAuth.verifyToken({
668
+ secret: secret,
669
+ token: token as string,
670
+ email: EMAIL,
671
+ }),
672
+ ).toBe(false);
673
+ });
674
+
675
+ /*
676
+ * A brute-force bound. Two algorithms across 2*window+1 steps is the total
677
+ * number of six-digit strings this function will accept at any instant; it
678
+ * has to stay a vanishing fraction of the 10^6 code space.
679
+ */
680
+ test("accepts only a tiny fraction of the six-digit code space", () => {
681
+ const acceptedCodes: number =
682
+ SupportedTotpAlgorithms.length * (2 * TotpValidationWindow + 1);
683
+
684
+ expect(acceptedCodes).toBeLessThanOrEqual(20);
685
+ expect(acceptedCodes / 10 ** TotpDigits).toBeLessThan(0.00002);
686
+ });
687
+ });
688
+
689
+ describe("TotpAuth.getTotp", () => {
690
+ test("defaults to the Google Authenticator compatible parameters", () => {
691
+ const totp: OTPAuth.TOTP = TotpAuth.getTotp({
692
+ secret: TotpAuth.generateSecret(),
693
+ email: EMAIL,
694
+ });
695
+
696
+ expect(totp.algorithm).toBe("SHA1");
697
+ expect(totp.digits).toBe(TotpDigits);
698
+ expect(totp.period).toBe(TotpPeriodInSeconds);
699
+ expect(totp.issuer).toBe("OneUptime");
700
+ });
701
+
702
+ test("honours an explicit algorithm so legacy secrets can be checked", () => {
703
+ const totp: OTPAuth.TOTP = TotpAuth.getTotp({
704
+ secret: TotpAuth.generateSecret(),
705
+ email: EMAIL,
706
+ algorithm: "SHA256",
707
+ });
708
+
709
+ expect(totp.algorithm).toBe("SHA256");
710
+ });
711
+
712
+ test("labels the entry with the user's email", () => {
713
+ expect(TotpAuth.getLabel({ email: EMAIL })).toBe("jane@example.com");
714
+ expect(
715
+ TotpAuth.getTotp({ secret: TotpAuth.generateSecret(), email: EMAIL })
716
+ .label,
717
+ ).toBe("jane@example.com");
718
+ });
719
+ });