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