@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.
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +14 -0
- package/Models/DatabaseModels/UserCall.ts +94 -3
- package/Models/DatabaseModels/UserEmail.ts +94 -3
- package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
- package/Models/DatabaseModels/UserPush.ts +37 -0
- package/Models/DatabaseModels/UserSMS.ts +94 -3
- package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
- package/Server/API/BaseAPI.ts +79 -14
- package/Server/API/UserCallAPI.ts +53 -45
- package/Server/API/UserEmailAPI.ts +53 -44
- package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
- package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
- package/Server/API/UserPushAPI.ts +126 -2
- package/Server/API/UserSmsAPI.ts +53 -44
- package/Server/API/UserWhatsAppAPI.ts +53 -50
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
- package/Server/Services/AnalyticsDatabaseService.ts +169 -0
- package/Server/Services/DatabaseService.ts +101 -0
- package/Server/Services/LogAggregationService.ts +5 -0
- package/Server/Services/PushNotificationService.ts +127 -12
- package/Server/Services/TeamMemberService.ts +53 -13
- package/Server/Services/UserCallService.ts +55 -24
- package/Server/Services/UserEmailService.ts +83 -17
- package/Server/Services/UserIncomingCallNumberService.ts +49 -85
- package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
- package/Server/Services/UserNotificationRuleService.ts +26 -0
- package/Server/Services/UserPushService.ts +89 -0
- package/Server/Services/UserSmsService.ts +50 -17
- package/Server/Services/UserWhatsAppService.ts +48 -15
- package/Server/Types/Database/QueryHelper.ts +30 -0
- package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
- package/Server/Utils/ChannelVerification.ts +510 -0
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
- package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
- package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
- package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
- package/Server/Utils/TotpAuth.ts +137 -9
- package/Server/Utils/UserRegistrationToken.ts +182 -0
- package/Server/Utils/VerificationCode.ts +134 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
- package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
- package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
- package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
- package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
- package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
- package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
- package/Tests/Server/API/UserSmsApi.test.ts +107 -14
- package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
- package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
- package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
- package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
- package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
- package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
- package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
- package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
- package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
- package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
- package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
- package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
- package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
- package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
- package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
- package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
- package/Tests/Server/Utils/Browser.test.ts +162 -0
- package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
- package/Tests/Server/Utils/Express.test.ts +371 -0
- package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
- package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
- package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
- package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
- package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
- package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
- package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
- package/Tests/Types/Call/CallRequest.test.ts +189 -0
- package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
- package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
- package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
- package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
- package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
- package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
- package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
- package/Tests/Types/TextRandomGeneration.test.ts +211 -0
- package/Tests/UI/Components/Button.test.tsx +175 -1
- package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
- package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
- package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
- package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
- package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
- package/Tests/UI/Components/List.test.tsx +9 -2
- package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
- package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
- package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
- package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
- package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
- package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
- package/Tests/UI/Components/Skeleton.test.tsx +71 -0
- package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
- package/Tests/UI/Components/Tabs.test.tsx +24 -0
- package/Tests/UI/Components/Toast.test.tsx +130 -7
- package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
- package/Tests/UI/Utils/Dropdown.test.ts +423 -0
- package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
- package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
- package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
- package/Tests/Utils/API.test.ts +16 -2
- package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
- package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
- package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
- package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
- package/Types/Permission.ts +35 -0
- package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
- package/Types/PushNotification/PushNotificationMessage.ts +15 -0
- package/Types/PushNotification/PushNotificationRequest.ts +8 -16
- package/Types/Service/ServiceLanguage.ts +144 -0
- package/Types/Text.ts +111 -19
- package/UI/Components/Button/Button.tsx +27 -14
- package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
- package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
- package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
- package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
- package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
- package/UI/Components/CommandPalette/Types.ts +49 -0
- package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
- package/UI/Components/Dropdown/Dropdown.tsx +6 -1
- package/UI/Components/Feed/FeedItem.tsx +1 -1
- package/UI/Components/List/List.tsx +37 -5
- package/UI/Components/List/ListSkeleton.tsx +54 -0
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
- package/UI/Components/Modal/Modal.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
- package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
- package/UI/Components/Navbar/NavBar.tsx +14 -1
- package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
- package/UI/Components/SideOver/SideOver.tsx +37 -2
- package/UI/Components/Skeleton/Skeleton.tsx +53 -0
- package/UI/Components/Table/Table.tsx +74 -44
- package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
- package/UI/Components/Tabs/Tab.tsx +1 -1
- package/UI/Components/Toast/Toast.tsx +118 -53
- package/UI/Components/Toast/ToastInit.tsx +82 -32
- package/UI/Styles/Theme.css +40 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
- package/UI/Utils/ModelListCache.ts +188 -0
- package/UI/Utils/Project.ts +29 -0
- package/Utils/API.ts +6 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +69 -13
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/UserCallAPI.js +42 -38
- package/build/dist/Server/API/UserCallAPI.js.map +1 -1
- package/build/dist/Server/API/UserEmailAPI.js +42 -38
- package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
- package/build/dist/Server/API/UserPushAPI.js +95 -2
- package/build/dist/Server/API/UserPushAPI.js.map +1 -1
- package/build/dist/Server/API/UserSmsAPI.js +42 -38
- package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
- package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
- package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +74 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +5 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/PushNotificationService.js +69 -30
- package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +43 -7
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +54 -22
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +78 -17
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
- package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +77 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +50 -17
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
- package/build/dist/Server/Utils/ChannelVerification.js +348 -0
- package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/TotpAuth.js +127 -8
- package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
- package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
- package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
- package/build/dist/Server/Utils/VerificationCode.js +120 -0
- package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +33 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
- package/build/dist/Types/Service/ServiceLanguage.js +96 -0
- package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
- package/build/dist/Types/Text.js +82 -17
- package/build/dist/Types/Text.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +22 -14
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
- package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
- package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +19 -5
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
- package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
- package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
- package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
- package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +35 -24
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
- package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
- package/build/dist/UI/Components/Tabs/Tab.js +1 -1
- package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
- package/build/dist/UI/Components/Toast/Toast.js +77 -23
- package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
- package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/ModelListCache.js +126 -0
- package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
- package/build/dist/UI/Utils/Project.js +24 -1
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/Utils/API.js +6 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/jest.config.json +1 -1
- package/package.json +2 -3
- package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
- package/Tests/__mocks__/otpauth.js +0 -30
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
- 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
|
+
});
|