@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
|
@@ -10,6 +10,7 @@ import Service from "../../../Models/DatabaseModels/Service";
|
|
|
10
10
|
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
11
11
|
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
12
12
|
import { JSONObject } from "../../../Types/JSON";
|
|
13
|
+
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
13
14
|
import ObjectID from "../../../Types/ObjectID";
|
|
14
15
|
import CephClusterService from "../../Services/CephClusterService";
|
|
15
16
|
import DockerHostService from "../../Services/DockerHostService";
|
|
@@ -20,11 +21,18 @@ import KubernetesClusterService from "../../Services/KubernetesClusterService";
|
|
|
20
21
|
import PodmanHostService from "../../Services/PodmanHostService";
|
|
21
22
|
import ProxmoxClusterService from "../../Services/ProxmoxClusterService";
|
|
22
23
|
import ServiceService from "../../Services/ServiceService";
|
|
23
|
-
import
|
|
24
|
+
import QueryHelper from "../../Types/Database/QueryHelper";
|
|
24
25
|
import SeriesResourceLabels, {
|
|
25
26
|
SeriesResourceRefs,
|
|
26
27
|
} from "./SeriesResourceLabels";
|
|
27
28
|
|
|
29
|
+
/*
|
|
30
|
+
* How a resource's NAME is matched against its identifier column.
|
|
31
|
+
* Ingest-written series labels match byte for byte; user-typed monitor
|
|
32
|
+
* step identifiers may differ from the agent-stamped name by case.
|
|
33
|
+
*/
|
|
34
|
+
export type NameMatchMode = "exact" | "caseInsensitive";
|
|
35
|
+
|
|
28
36
|
/*
|
|
29
37
|
* A grouped monitor opens one alert/incident per breaching series, and
|
|
30
38
|
* each series carries the identity of the resource it came from in its
|
|
@@ -100,11 +108,61 @@ export default class SeriesResourceLinker {
|
|
|
100
108
|
public static async resolveResourcesFromSeriesLabels(input: {
|
|
101
109
|
seriesLabels: JSONObject;
|
|
102
110
|
projectId: ObjectID;
|
|
111
|
+
/*
|
|
112
|
+
* Optional, and only consulted to disambiguate `host.name`. Left
|
|
113
|
+
* undefined by callers that have no monitor in hand.
|
|
114
|
+
*/
|
|
115
|
+
monitorType?: MonitorType | undefined;
|
|
103
116
|
}): Promise<SeriesResolvedResourceIds> {
|
|
104
117
|
const refs: SeriesResourceRefs = SeriesResourceLabels.extractResourceRefs(
|
|
105
118
|
input.seriesLabels,
|
|
106
119
|
);
|
|
107
120
|
|
|
121
|
+
/*
|
|
122
|
+
* On a Docker or Podman monitor, `resource.host.name` names the
|
|
123
|
+
* DockerHost / PodmanHost the containers run on — the agent stamps
|
|
124
|
+
* the container runtime's host under the generic key. The label key
|
|
125
|
+
* map hands that key to Host, which would attach an unrelated Host
|
|
126
|
+
* row and surface the event on that host's Activity tab. The
|
|
127
|
+
* step-config path links the right model instead
|
|
128
|
+
* (MonitorStepResourceIdentity), so drop the host refs here rather
|
|
129
|
+
* than linking a resource the event is not about.
|
|
130
|
+
*/
|
|
131
|
+
if (
|
|
132
|
+
input.monitorType === MonitorType.Docker ||
|
|
133
|
+
input.monitorType === MonitorType.Podman
|
|
134
|
+
) {
|
|
135
|
+
refs.hostIds = [];
|
|
136
|
+
refs.hostNames = [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
* Series labels are ingest-written, so they match the resource
|
|
141
|
+
* rows' identifier columns byte for byte. See resolveResourceIds
|
|
142
|
+
* for why that rules out case-insensitive matching here.
|
|
143
|
+
*/
|
|
144
|
+
return this.resolveResourceRefs({
|
|
145
|
+
refs: refs,
|
|
146
|
+
projectId: input.projectId,
|
|
147
|
+
nameMatch: "exact",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* The one place that turns identifiers into database ids, shared by
|
|
153
|
+
* the series-label path and the monitor-step-config path
|
|
154
|
+
* (MonitorStepResourceIdentity). Keeping the nine-way lookup table,
|
|
155
|
+
* the project scoping and the `Includes` batching in a single
|
|
156
|
+
* function is what stops the two paths from resolving the same
|
|
157
|
+
* identifier to different rows.
|
|
158
|
+
*/
|
|
159
|
+
public static async resolveResourceRefs(input: {
|
|
160
|
+
refs: SeriesResourceRefs;
|
|
161
|
+
projectId: ObjectID;
|
|
162
|
+
nameMatch: NameMatchMode;
|
|
163
|
+
}): Promise<SeriesResolvedResourceIds> {
|
|
164
|
+
const refs: SeriesResourceRefs = input.refs;
|
|
165
|
+
|
|
108
166
|
/*
|
|
109
167
|
* Proxmox / Ceph / Docker Swarm / IoT carry no `oneuptime.*.id`
|
|
110
168
|
* stamp at ingest — they are addressable by name only — so their
|
|
@@ -186,6 +244,7 @@ export default class SeriesResourceLinker {
|
|
|
186
244
|
names: spec.names,
|
|
187
245
|
nameColumn: spec.nameColumn,
|
|
188
246
|
projectId: input.projectId,
|
|
247
|
+
nameMatch: input.nameMatch,
|
|
189
248
|
findBy: spec.findBy,
|
|
190
249
|
});
|
|
191
250
|
}),
|
|
@@ -206,9 +265,10 @@ export default class SeriesResourceLinker {
|
|
|
206
265
|
|
|
207
266
|
/*
|
|
208
267
|
* Resolve the series' resources and MERGE them onto the alert/incident.
|
|
209
|
-
* Merging (rather than assigning) matters because
|
|
210
|
-
* writes the same
|
|
211
|
-
* config; whichever runs second must not erase the
|
|
268
|
+
* Merging (rather than assigning) matters because
|
|
269
|
+
* attachResolvedResources writes the same relations from the
|
|
270
|
+
* monitor's step config; whichever runs second must not erase the
|
|
271
|
+
* other's work.
|
|
212
272
|
*
|
|
213
273
|
* Relations the series says nothing about are left untouched —
|
|
214
274
|
* `undefined`, not `[]` — so an ungrouped event and an event whose
|
|
@@ -223,11 +283,17 @@ export default class SeriesResourceLinker {
|
|
|
223
283
|
model: SeriesLinkableModel;
|
|
224
284
|
seriesLabels: JSONObject;
|
|
225
285
|
projectId: ObjectID;
|
|
286
|
+
/*
|
|
287
|
+
* Optional, and only consulted to disambiguate `host.name`. Left
|
|
288
|
+
* undefined by callers that have no monitor in hand.
|
|
289
|
+
*/
|
|
290
|
+
monitorType?: MonitorType | undefined;
|
|
226
291
|
}): Promise<void> {
|
|
227
292
|
const resolved: SeriesResolvedResourceIds =
|
|
228
293
|
await this.resolveResourcesFromSeriesLabels({
|
|
229
294
|
seriesLabels: input.seriesLabels,
|
|
230
295
|
projectId: input.projectId,
|
|
296
|
+
monitorType: input.monitorType,
|
|
231
297
|
});
|
|
232
298
|
|
|
233
299
|
this.mergeResolvedIdsIntoModel({
|
|
@@ -237,30 +303,22 @@ export default class SeriesResourceLinker {
|
|
|
237
303
|
}
|
|
238
304
|
|
|
239
305
|
/*
|
|
240
|
-
* Attach the
|
|
241
|
-
* (
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
306
|
+
* Attach the resources resolved from the monitor's own step config
|
|
307
|
+
* (MonitorResourceContext), merging with — never overwriting —
|
|
308
|
+
* anything the series-label path already linked. Both paths can
|
|
309
|
+
* resolve the same resource, so this dedupes by id.
|
|
310
|
+
*
|
|
311
|
+
* Unlike the label path this runs for ungrouped events too, and that
|
|
312
|
+
* is the whole point: an ungrouped monitor has no series labels, so
|
|
313
|
+
* before this existed it linked nothing at all.
|
|
246
314
|
*/
|
|
247
|
-
public static
|
|
315
|
+
public static attachResolvedResources(input: {
|
|
248
316
|
model: SeriesLinkableModel;
|
|
249
|
-
|
|
317
|
+
resolved: SeriesResolvedResourceIds;
|
|
250
318
|
}): void {
|
|
251
319
|
this.mergeResolvedIdsIntoModel({
|
|
252
320
|
model: input.model,
|
|
253
|
-
resolved:
|
|
254
|
-
hostIds: [],
|
|
255
|
-
dockerHostIds: [],
|
|
256
|
-
podmanHostIds: [],
|
|
257
|
-
kubernetesClusterIds: [],
|
|
258
|
-
serviceIds: [],
|
|
259
|
-
proxmoxClusterIds: input.clusterContext.proxmoxClusterIds,
|
|
260
|
-
cephClusterIds: input.clusterContext.cephClusterIds,
|
|
261
|
-
dockerSwarmClusterIds: input.clusterContext.dockerSwarmClusterIds,
|
|
262
|
-
iotFleetIds: input.clusterContext.iotFleetIds,
|
|
263
|
-
},
|
|
321
|
+
resolved: input.resolved,
|
|
264
322
|
});
|
|
265
323
|
}
|
|
266
324
|
|
|
@@ -396,16 +454,23 @@ export default class SeriesResourceLinker {
|
|
|
396
454
|
* Costs zero round-trips when the series carries neither, which is the
|
|
397
455
|
* common case for most of the nine types on any given series.
|
|
398
456
|
*
|
|
399
|
-
*
|
|
400
|
-
* by ingest from the same attribute the series
|
|
401
|
-
* identifiers are canonicalized on both sides —
|
|
402
|
-
* what keeps a lookup from straying onto a
|
|
457
|
+
* `nameMatch: "exact"` is for series labels. Every identifier column
|
|
458
|
+
* read here is written by ingest from the same attribute the series
|
|
459
|
+
* label carries — host identifiers are canonicalized on both sides —
|
|
460
|
+
* so exact matching is what keeps a lookup from straying onto a
|
|
461
|
+
* neighbouring row.
|
|
462
|
+
*
|
|
463
|
+
* `nameMatch: "caseInsensitive"` is for monitor step configs, where
|
|
464
|
+
* the identifier was typed by a user and the row's name was stamped
|
|
465
|
+
* by an agent; the two legitimately differ by case. This mirrors what
|
|
466
|
+
* ingest discovery itself does when it keys a row by name.
|
|
403
467
|
*/
|
|
404
468
|
private static async resolveResourceIds(input: {
|
|
405
469
|
ids: Array<string>;
|
|
406
470
|
names: Array<string>;
|
|
407
471
|
nameColumn: string;
|
|
408
472
|
projectId: ObjectID;
|
|
473
|
+
nameMatch: NameMatchMode;
|
|
409
474
|
/*
|
|
410
475
|
* Loosely typed because each resource service has its own
|
|
411
476
|
* `Query<TBaseModel>` shape and we deliberately abstract over
|
|
@@ -443,7 +508,10 @@ export default class SeriesResourceLinker {
|
|
|
443
508
|
input.findBy({
|
|
444
509
|
query: {
|
|
445
510
|
projectId: input.projectId,
|
|
446
|
-
[input.nameColumn]:
|
|
511
|
+
[input.nameColumn]:
|
|
512
|
+
input.nameMatch === "caseInsensitive"
|
|
513
|
+
? QueryHelper.findWithSameTextAnyOf(input.names)
|
|
514
|
+
: new Includes(input.names),
|
|
447
515
|
},
|
|
448
516
|
select: { _id: true },
|
|
449
517
|
skip: 0,
|
package/Server/Utils/TotpAuth.ts
CHANGED
|
@@ -2,6 +2,63 @@ import Email from "../../Types/Email";
|
|
|
2
2
|
import * as OTPAuth from "otpauth";
|
|
3
3
|
import CaptureSpan from "./Telemetry/CaptureSpan";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* The HMAC algorithm every new enrolment is issued with.
|
|
7
|
+
*
|
|
8
|
+
* RFC 6238 fixes SHA1 as the default and every authenticator implements it.
|
|
9
|
+
* The `algorithm` parameter in an otpauth:// URI is, in practice, advisory:
|
|
10
|
+
* Google Authenticator and Microsoft Authenticator parse the URI, ignore the
|
|
11
|
+
* parameter, and compute SHA1 anyway. A server that issues `algorithm=SHA256`
|
|
12
|
+
* therefore hands those apps a QR code they will silently mis-implement — the
|
|
13
|
+
* app shows a confident six digits, the server computes a completely different
|
|
14
|
+
* six digits, and enrolment can never succeed no matter how many codes the
|
|
15
|
+
* user tries. That is the failure this constant exists to prevent.
|
|
16
|
+
*
|
|
17
|
+
* SHA1 is not a weakness here. HMAC-SHA1 is unaffected by the collision
|
|
18
|
+
* attacks that retired SHA1 for signatures (RFC 6234 / RFC 2104), and the
|
|
19
|
+
* output is truncated to six digits over a 30 second window regardless — the
|
|
20
|
+
* brute-force surface is the 10^6 code space, not the hash.
|
|
21
|
+
*/
|
|
22
|
+
export const DefaultTotpAlgorithm: string = "SHA1";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Algorithms a submitted token is checked against, in order.
|
|
26
|
+
*
|
|
27
|
+
* OneUptime issued `SHA256` enrolments before this was understood. Those
|
|
28
|
+
* secrets are still in the database and still work in the minority of apps
|
|
29
|
+
* that do honour the parameter (1Password, Bitwarden, Aegis, FreeOTP), so
|
|
30
|
+
* dropping SHA256 from verification would lock those accounts out of their
|
|
31
|
+
* own logins.
|
|
32
|
+
*
|
|
33
|
+
* The cost is exactly one bit: two algorithms across the validation window
|
|
34
|
+
* means twice as many six-digit strings are accepted at any instant. Against
|
|
35
|
+
* a 10^6 code space that is still a vanishing fraction, and the tests pin the
|
|
36
|
+
* bound so it cannot quietly grow.
|
|
37
|
+
*
|
|
38
|
+
* Retiring SHA256 needs more than time passing. Nothing on the row records
|
|
39
|
+
* which algorithm the user's app actually computes, so "no legacy rows remain"
|
|
40
|
+
* is not a question the database can answer today — removing it means either
|
|
41
|
+
* storing the algorithm that verified, or accepting that some long-dormant
|
|
42
|
+
* account re-enrols instead of logging in.
|
|
43
|
+
*/
|
|
44
|
+
export const SupportedTotpAlgorithms: Array<string> = ["SHA1", "SHA256"];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Number of 30 second steps either side of now that a token is accepted for.
|
|
48
|
+
*
|
|
49
|
+
* Three steps is +/- 90 seconds, which absorbs ordinary phone/server clock
|
|
50
|
+
* drift and the user typing the last digit as the code rolls over.
|
|
51
|
+
*/
|
|
52
|
+
export const TotpValidationWindow: number = 3;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Number of digits in a token, and the length of the time step in seconds.
|
|
56
|
+
* Both are the values Google Authenticator assumes, and — like `algorithm` —
|
|
57
|
+
* both are parameters it ignores when told anything else.
|
|
58
|
+
*/
|
|
59
|
+
export const TotpDigits: number = 6;
|
|
60
|
+
export const TotpPeriodInSeconds: number = 30;
|
|
61
|
+
|
|
5
62
|
/**
|
|
6
63
|
* Utility class for handling TOTP authentication.
|
|
7
64
|
*/
|
|
@@ -21,7 +78,11 @@ export default class TotpAuth {
|
|
|
21
78
|
}
|
|
22
79
|
|
|
23
80
|
@CaptureSpan()
|
|
24
|
-
public static getTotp(data: {
|
|
81
|
+
public static getTotp(data: {
|
|
82
|
+
secret: string;
|
|
83
|
+
email: Email;
|
|
84
|
+
algorithm?: string | undefined;
|
|
85
|
+
}): OTPAuth.TOTP {
|
|
25
86
|
const totp: OTPAuth.TOTP = new OTPAuth.TOTP({
|
|
26
87
|
// Provider or service the account is associated with.
|
|
27
88
|
issuer: "OneUptime",
|
|
@@ -30,11 +91,11 @@ export default class TotpAuth {
|
|
|
30
91
|
email: data.email,
|
|
31
92
|
}),
|
|
32
93
|
// Algorithm used for the HMAC function.
|
|
33
|
-
algorithm:
|
|
94
|
+
algorithm: data.algorithm || DefaultTotpAlgorithm,
|
|
34
95
|
// Length of the generated tokens.
|
|
35
|
-
digits:
|
|
96
|
+
digits: TotpDigits,
|
|
36
97
|
// Interval of time for which a token is valid, in seconds.
|
|
37
|
-
period:
|
|
98
|
+
period: TotpPeriodInSeconds,
|
|
38
99
|
// Arbitrary key encoded in Base32 or OTPAuth.Secret instance.
|
|
39
100
|
secret: data.secret,
|
|
40
101
|
});
|
|
@@ -42,6 +103,33 @@ export default class TotpAuth {
|
|
|
42
103
|
return totp;
|
|
43
104
|
}
|
|
44
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Strips the formatting authenticator apps add for readability.
|
|
108
|
+
*
|
|
109
|
+
* Google Authenticator renders a code as "123 456" and both the iOS and
|
|
110
|
+
* Android clipboards keep that space when the user copies it. Rejecting the
|
|
111
|
+
* paste is a self-inflicted "Invalid code" for a user who supplied exactly
|
|
112
|
+
* the right secret material, so the separators are removed before the token
|
|
113
|
+
* is compared. Anything that is not a digit is dropped, which also covers
|
|
114
|
+
* the hyphen some apps use and stray whitespace from a trimmed paste.
|
|
115
|
+
*
|
|
116
|
+
* @param token - The raw token as submitted.
|
|
117
|
+
* @returns The token reduced to its digits.
|
|
118
|
+
*/
|
|
119
|
+
@CaptureSpan()
|
|
120
|
+
public static normalizeToken(token: string): string {
|
|
121
|
+
/*
|
|
122
|
+
* The token arrives straight off a JSON body, so it is only a string
|
|
123
|
+
* because the client chose to send one. A number or an object here must
|
|
124
|
+
* fail verification, not throw out of `.replace` and surface as a 500.
|
|
125
|
+
*/
|
|
126
|
+
if (typeof token !== "string" || !token) {
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return token.replace(/\D/g, "");
|
|
131
|
+
}
|
|
132
|
+
|
|
45
133
|
/**
|
|
46
134
|
* Verifies if a given token matches the provided secret key.
|
|
47
135
|
* @param data - The data object containing the secret key and token.
|
|
@@ -53,13 +141,49 @@ export default class TotpAuth {
|
|
|
53
141
|
token: string;
|
|
54
142
|
email: Email;
|
|
55
143
|
}): boolean {
|
|
56
|
-
const { secret,
|
|
144
|
+
const { secret, email } = data;
|
|
57
145
|
|
|
58
|
-
|
|
146
|
+
if (!secret) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
59
149
|
|
|
60
|
-
const
|
|
150
|
+
const token: string = this.normalizeToken(data.token);
|
|
61
151
|
|
|
62
|
-
|
|
152
|
+
/*
|
|
153
|
+
* otpauth compares the submitted string against the codes it generates,
|
|
154
|
+
* so a token of the wrong length can never match — but it would still be
|
|
155
|
+
* run through one HMAC per algorithm per window step first. Reject it up
|
|
156
|
+
* front instead.
|
|
157
|
+
*/
|
|
158
|
+
if (token.length !== TotpDigits) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const algorithm of SupportedTotpAlgorithms) {
|
|
163
|
+
let totp: OTPAuth.TOTP;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
totp = this.getTotp({ secret, email, algorithm });
|
|
167
|
+
} catch {
|
|
168
|
+
/*
|
|
169
|
+
* A secret that is not valid base32 throws in the OTPAuth.Secret
|
|
170
|
+
* constructor. That is a corrupt row, not a wrong code, and it fails
|
|
171
|
+
* identically for every algorithm — so stop rather than repeat it.
|
|
172
|
+
*/
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const delta: number | null = totp.validate({
|
|
177
|
+
token,
|
|
178
|
+
window: TotpValidationWindow,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (delta !== null) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return false;
|
|
63
187
|
}
|
|
64
188
|
|
|
65
189
|
/**
|
|
@@ -74,7 +198,11 @@ export default class TotpAuth {
|
|
|
74
198
|
}): string {
|
|
75
199
|
const { secret, userEmail } = data;
|
|
76
200
|
|
|
77
|
-
const totp: OTPAuth.TOTP = this.getTotp({
|
|
201
|
+
const totp: OTPAuth.TOTP = this.getTotp({
|
|
202
|
+
secret,
|
|
203
|
+
email: userEmail,
|
|
204
|
+
algorithm: DefaultTotpAlgorithm,
|
|
205
|
+
});
|
|
78
206
|
|
|
79
207
|
return totp.toString();
|
|
80
208
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { AccountsRoute } from "../../ServiceRoute";
|
|
2
|
+
import Hostname from "../../Types/API/Hostname";
|
|
3
|
+
import Protocol from "../../Types/API/Protocol";
|
|
4
|
+
import Route from "../../Types/API/Route";
|
|
5
|
+
import URL from "../../Types/API/URL";
|
|
6
|
+
import OneUptimeDate from "../../Types/Date";
|
|
7
|
+
import Email from "../../Types/Email";
|
|
8
|
+
import ObjectID from "../../Types/ObjectID";
|
|
9
|
+
import EmailVerificationToken from "../../Models/DatabaseModels/EmailVerificationToken";
|
|
10
|
+
import DatabaseConfig from "../DatabaseConfig";
|
|
11
|
+
import EmailVerificationTokenService from "../Services/EmailVerificationTokenService";
|
|
12
|
+
import CaptureSpan from "./Telemetry/CaptureSpan";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* An invited user exists in the database before they have ever authenticated.
|
|
16
|
+
* TeamMemberService creates the row so the membership has something to point
|
|
17
|
+
* at, and that row carries no password until the person registers.
|
|
18
|
+
*
|
|
19
|
+
* /signup is what hands that unclaimed row over, so the hand-over has to be
|
|
20
|
+
* gated on something only the mailbox owner can hold. Knowing the address is
|
|
21
|
+
* not enough: addresses at a company are guessable by construction, which is
|
|
22
|
+
* exactly how GHSA-qg84-6hrg-mr5g turned a pending invitation into an account
|
|
23
|
+
* takeover -- the guard there was `if (alreadySavedUser.password)`, and an
|
|
24
|
+
* unclaimed row's password is null, which is falsy.
|
|
25
|
+
*
|
|
26
|
+
* This token is that gate. It is minted when the invitation is sent, travels
|
|
27
|
+
* only inside the invitation email, and is spent the first time it is used.
|
|
28
|
+
*
|
|
29
|
+
* It reuses EmailVerificationToken rather than adding a table of its own: the
|
|
30
|
+
* shape is identical (user, email, opaque token, expiry) and the semantics are
|
|
31
|
+
* the same claim -- "the holder of this controls this mailbox". Spending one is
|
|
32
|
+
* therefore also proof of address ownership, which is why the signup path marks
|
|
33
|
+
* the account verified on the way through.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Invitations are read when the person gets round to them, not when they are
|
|
38
|
+
* sent, so these live longer than the 24 hours an email-verification link gets.
|
|
39
|
+
* Expiring in a week still bounds how long a leaked invite mail stays useful.
|
|
40
|
+
*/
|
|
41
|
+
export const REGISTRATION_TOKEN_EXPIRY_IN_DAYS: number = 7;
|
|
42
|
+
|
|
43
|
+
export default class UserRegistrationToken {
|
|
44
|
+
/*
|
|
45
|
+
* Mint a token for `email` and return it. The caller is responsible for
|
|
46
|
+
* putting it somewhere only the mailbox owner will see -- which in practice
|
|
47
|
+
* means a link in an email addressed to that same mailbox, and nowhere else.
|
|
48
|
+
* It must never be written into an API response.
|
|
49
|
+
*/
|
|
50
|
+
@CaptureSpan()
|
|
51
|
+
public static async generateRegistrationToken(data: {
|
|
52
|
+
userId: ObjectID;
|
|
53
|
+
email: Email;
|
|
54
|
+
}): Promise<ObjectID> {
|
|
55
|
+
const token: ObjectID = ObjectID.generate();
|
|
56
|
+
|
|
57
|
+
const emailVerificationToken: EmailVerificationToken =
|
|
58
|
+
new EmailVerificationToken();
|
|
59
|
+
emailVerificationToken.userId = data.userId;
|
|
60
|
+
emailVerificationToken.email = data.email;
|
|
61
|
+
emailVerificationToken.token = token;
|
|
62
|
+
emailVerificationToken.expires = OneUptimeDate.getSomeDaysAfter(
|
|
63
|
+
REGISTRATION_TOKEN_EXPIRY_IN_DAYS,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await EmailVerificationTokenService.create({
|
|
67
|
+
data: emailVerificationToken,
|
|
68
|
+
props: {
|
|
69
|
+
isRoot: true,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return token;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* The /accounts/register link an invited person follows. The email is carried
|
|
78
|
+
* alongside the token only to prefill (and lock) the form field; the token is
|
|
79
|
+
* the part that authorizes anything.
|
|
80
|
+
*/
|
|
81
|
+
@CaptureSpan()
|
|
82
|
+
public static async getRegistrationLink(data: {
|
|
83
|
+
email: Email;
|
|
84
|
+
token: ObjectID;
|
|
85
|
+
}): Promise<URL> {
|
|
86
|
+
const host: Hostname = await DatabaseConfig.getHost();
|
|
87
|
+
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
88
|
+
|
|
89
|
+
return URL.fromString(
|
|
90
|
+
new URL(
|
|
91
|
+
httpProtocol,
|
|
92
|
+
host,
|
|
93
|
+
new Route(AccountsRoute.toString()),
|
|
94
|
+
).toString(),
|
|
95
|
+
)
|
|
96
|
+
.addRoute("/register")
|
|
97
|
+
.addQueryParam("email", data.email.toString(), true)
|
|
98
|
+
.addQueryParam("token", data.token.toString(), true);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Mint a token and build the link that carries it, in one step.
|
|
102
|
+
@CaptureSpan()
|
|
103
|
+
public static async generateRegistrationLink(data: {
|
|
104
|
+
userId: ObjectID;
|
|
105
|
+
email: Email;
|
|
106
|
+
}): Promise<URL> {
|
|
107
|
+
const token: ObjectID = await this.generateRegistrationToken({
|
|
108
|
+
userId: data.userId,
|
|
109
|
+
email: data.email,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return await this.getRegistrationLink({
|
|
113
|
+
email: data.email,
|
|
114
|
+
token: token,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Spend a token. Returns true only if it exists, was minted for this exact
|
|
120
|
+
* address, has not expired, and had not already been spent.
|
|
121
|
+
*
|
|
122
|
+
* The delete is what makes it single-use, and it is deliberately the last
|
|
123
|
+
* word: `deleteOneBy` reports how many rows it removed, so two requests
|
|
124
|
+
* racing on the same token produce exactly one winner (the loser deletes
|
|
125
|
+
* nothing and gets false back) rather than two callers who both read a live
|
|
126
|
+
* token and both proceed.
|
|
127
|
+
*
|
|
128
|
+
* Every failure returns the same `false`. The caller must not report which
|
|
129
|
+
* check failed -- "expired" and "no such token" would tell someone probing
|
|
130
|
+
* with guessed tokens which of them had ever been real.
|
|
131
|
+
*/
|
|
132
|
+
@CaptureSpan()
|
|
133
|
+
public static async consumeRegistrationToken(data: {
|
|
134
|
+
token: ObjectID;
|
|
135
|
+
email: Email;
|
|
136
|
+
}): Promise<boolean> {
|
|
137
|
+
const savedToken: EmailVerificationToken | null =
|
|
138
|
+
await EmailVerificationTokenService.findOneBy({
|
|
139
|
+
query: {
|
|
140
|
+
token: data.token,
|
|
141
|
+
},
|
|
142
|
+
select: {
|
|
143
|
+
_id: true,
|
|
144
|
+
email: true,
|
|
145
|
+
expires: true,
|
|
146
|
+
},
|
|
147
|
+
props: {
|
|
148
|
+
isRoot: true,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!savedToken || !savedToken.email || !savedToken.expires) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* A token is bound to the address it was mailed to. Without this, a token
|
|
158
|
+
* from any invitation the attacker had legitimately received would claim
|
|
159
|
+
* any other unclaimed account. Email lowercases on construction, so the
|
|
160
|
+
* string comparison is already case-insensitive.
|
|
161
|
+
*/
|
|
162
|
+
if (savedToken.email.toString() !== data.email.toString()) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (OneUptimeDate.hasExpired(savedToken.expires)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const deletedCount: number =
|
|
171
|
+
await EmailVerificationTokenService.deleteOneBy({
|
|
172
|
+
query: {
|
|
173
|
+
_id: savedToken._id!,
|
|
174
|
+
},
|
|
175
|
+
props: {
|
|
176
|
+
isRoot: true,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return deletedCount === 1;
|
|
181
|
+
}
|
|
182
|
+
}
|