@oneuptime/common 13.0.7 → 13.0.8
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/OnCallDutyPolicyScheduleCalendarFeed.ts +1 -1
- package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +1 -1
- package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +1 -1
- package/Server/API/AIBillingAPI.ts +1 -0
- package/Server/API/AIInsightAPI.ts +1 -4
- package/Server/API/AIInvestigationAPI.ts +1 -4
- package/Server/API/AIReadinessAPI.ts +4 -5
- package/Server/API/AlertAPI.ts +1 -0
- package/Server/API/AutoRemediationAPI.ts +1 -4
- package/Server/API/BaseAPI.ts +13 -0
- package/Server/API/BaseAnalyticsAPI.ts +80 -9
- package/Server/API/BillingAPI.ts +2 -0
- package/Server/API/BillingInvoiceAPI.ts +1 -0
- package/Server/API/BillingPaymentMethodAPI.ts +2 -0
- package/Server/API/CodeFixRunAPI.ts +1 -3
- package/Server/API/CommonAPI.ts +68 -4
- package/Server/API/DashboardAPI.ts +12 -12
- package/Server/API/GlobalConfigAPI.ts +17 -0
- package/Server/API/IncidentAPI.ts +2 -0
- package/Server/API/IncidentEpisodeAPI.ts +1 -0
- package/Server/API/NotificationAPI.ts +1 -0
- package/Server/API/OnCallCalendarAPI.ts +4 -5
- package/Server/API/OnCallDutyPolicyAPI.ts +7 -0
- package/Server/API/OnCallReadinessAPI.ts +4 -1
- package/Server/API/OnCallScheduleTimelineAPI.ts +731 -0
- package/Server/API/ProjectAPI.ts +1 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +1 -0
- package/Server/API/StatusPageAPI.ts +161 -32
- package/Server/API/TeamMemberAPI.ts +3 -0
- package/Server/API/UserCallAPI.ts +2 -0
- package/Server/API/UserEmailAPI.ts +2 -0
- package/Server/API/UserIncomingCallNumberAPI.ts +2 -0
- package/Server/API/UserMicrosoftTeamsAPI.ts +1 -0
- package/Server/API/UserSlackAPI.ts +1 -0
- package/Server/API/UserSmsAPI.ts +2 -0
- package/Server/API/UserTotpAuthAPI.ts +1 -0
- package/Server/API/UserWebAuthnAPI.ts +2 -0
- package/Server/API/UserWebhookAPI.ts +1 -0
- package/Server/API/UserWhatsAppAPI.ts +2 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +13 -2
- package/Server/Middleware/UserAuthorization.ts +79 -5
- package/Server/Services/ApiKeyPermissionService.ts +101 -4
- package/Server/Services/DatabaseService.ts +245 -0
- package/Server/Services/DetectionRuleService.ts +5 -4
- package/Server/Services/OnCallDutyPolicyChildService.ts +261 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -2
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -2
- package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -2
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -2
- package/Server/Services/PayAsYouGoBillingService.ts +23 -28
- package/Server/Services/ProjectService.ts +18 -0
- package/Server/Services/RumSessionPinService.ts +274 -22
- package/Server/Services/TeamPermissionService.ts +120 -26
- package/Server/Services/ThreatIntelFeedService.ts +6 -3
- package/Server/Types/Database/Permissions/BasePermission.ts +9 -7
- package/Server/Types/Database/Permissions/Index.ts +75 -11
- package/Server/Types/Database/Permissions/TablePermission.ts +24 -28
- package/Server/Utils/AI/AIChatPrivacyFilter.ts +8 -0
- package/Server/Utils/AI/AIRunPrivacyFilter.ts +8 -0
- package/Server/Utils/MasterAdminPageAccess.ts +61 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +3 -1
- package/Server/Utils/Monitor/MonitorIncident.ts +3 -1
- package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1 -1
- package/Server/Utils/Realtime.ts +163 -45
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +11 -28
- package/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.ts +170 -0
- package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +1 -1
- package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +6 -2
- package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +10 -9
- package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +1059 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +376 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +757 -0
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +307 -0
- package/Tests/App/Dashboard/ArgumentsFormGroupByUnlistedAttributes.test.tsx +131 -0
- package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +1 -1
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +9 -1
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +24 -0
- package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +162 -8
- package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +105 -6
- package/Tests/App/Dashboard/EntityFilterDropdownUnlistedSelection.test.tsx +434 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +276 -1
- package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +9 -1
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +58 -42
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +42 -5
- package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +34 -23
- package/Tests/App/Dashboard/ScheduleTimeline.test.tsx +1580 -0
- package/Tests/App/Dashboard/ScheduleTimelineAPI.test.ts +280 -0
- package/Tests/App/Dashboard/ScheduleTimelineModel.test.ts +673 -0
- package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +95 -68
- package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +86 -3
- package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +28 -3
- package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +761 -0
- package/Tests/App/Dashboard/SecurityEventsGuideContent.test.ts +1313 -0
- package/Tests/App/Dashboard/SecurityEventsHowItWorksCard.test.tsx +536 -0
- package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +222 -0
- package/Tests/App/Dashboard/SecurityEventsThreatIntelPage.test.tsx +199 -0
- package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +321 -0
- package/Tests/App/Dashboard/SloOverviewHero.test.tsx +171 -36
- package/Tests/App/Dashboard/SloViewSideMenu.test.tsx +215 -0
- package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +40 -3
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +12 -4
- package/Tests/App/PublicDashboard/PublicDashboardWidgetListAuth.test.tsx +623 -0
- package/Tests/Models/OnCallCalendarFeedModels.test.ts +23 -1
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +138 -5
- package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +187 -0
- package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +51 -3
- package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +55 -2
- package/Tests/Server/API/BaseAnalyticsAPIAggregateCache.test.ts +729 -0
- package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +93 -7
- package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +36 -3
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +1003 -24
- package/Tests/Server/API/DashboardPublicRateLimit.test.ts +6 -3
- package/Tests/Server/API/ExpiredSessionAIRoutes.test.ts +708 -0
- package/Tests/Server/API/ExpiredSessionCustomRoutes.test.ts +928 -0
- package/Tests/Server/API/ExpiredSessionMonitorTemplate.test.ts +319 -0
- package/Tests/Server/API/ExpiredSessionOnCallDutyPolicy.test.ts +336 -0
- package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +820 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +140 -0
- package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +115 -12
- package/Tests/Server/API/OnCallCalendarAPI.test.ts +375 -8
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +175 -17
- package/Tests/Server/API/OnCallScheduleTimelineAPI.test.ts +1513 -0
- package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +119 -7
- package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +111 -4
- package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +615 -0
- package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +89 -2
- package/Tests/Server/API/SessionReplayAPI.test.ts +121 -0
- package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +4 -2
- package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +441 -0
- package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +664 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +7 -0
- package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +6 -0
- package/Tests/Server/API/UserNotificationSettingAPI.test.ts +65 -4
- package/Tests/Server/API/UserProjectsAPI.test.ts +63 -3
- package/Tests/Server/API/UserTotpAuthAPI.test.ts +48 -2
- package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +11 -2
- package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +46 -3
- package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +18 -2
- package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +24 -7
- package/Tests/Server/Middleware/ExpiredSessionUserMiddleware.test.ts +563 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +299 -19
- package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +453 -0
- package/Tests/Server/Services/AIChatPrivacyPin.test.ts +24 -0
- package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +2 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +3 -3
- package/Tests/Server/Services/DatabaseServiceCreateSuppliedId.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServiceLoginBeforeHooks.test.ts +1078 -0
- package/Tests/Server/Services/DatabaseServiceTenantRelationGuard.test.ts +259 -0
- package/Tests/Server/Services/DatabaseServiceWriteOperatorValues.test.ts +147 -0
- package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +115 -1
- package/Tests/Server/Services/OnCallChildCreateAuthorizationPostgres.test.ts +711 -0
- package/Tests/Server/Services/OnCallDutyPolicyChildService.test.ts +733 -0
- package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +190 -140
- package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +4 -9
- package/Tests/Server/Services/PermissionBlockDeletion.test.ts +780 -0
- package/Tests/Server/Services/PermissionBlockDeletionPostgres.test.ts +628 -0
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +77 -1
- package/Tests/Server/Services/RumSessionPinCreatePermission.test.ts +715 -0
- package/Tests/Server/Services/TeamMemberMultiTenantReadPostgres.test.ts +394 -0
- package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +6 -0
- package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +131 -0
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +13 -2
- package/Tests/Server/Types/Database/Permissions/ModelPermissionAnonymousRefusal.test.ts +518 -0
- package/Tests/Server/Types/Database/Permissions/MultiTenantPermissions.test.ts +350 -0
- package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +282 -25
- package/Tests/Server/Types/Database/Permissions/TableLevelBlockPermission.test.ts +459 -0
- package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +101 -9
- package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +88 -4
- package/Tests/Server/Utils/AI/Toolbox/ExceptionTools.test.ts +459 -0
- package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +794 -0
- package/Tests/Server/Utils/AI/Toolbox/RecentChangesTools.test.ts +574 -0
- package/Tests/Server/Utils/AI/Toolbox/SecurityEventTools.test.ts +596 -0
- package/Tests/Server/Utils/CaptchaRequestAndLogging.test.ts +395 -0
- package/Tests/Server/Utils/MasterAdminPageAccess.test.ts +251 -0
- package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +616 -0
- package/Tests/Server/Utils/Realtime.test.ts +502 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorSeverity.test.ts +216 -0
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +142 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.test.ts +140 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepairClickhouseIntegration.test.ts +542 -0
- package/Tests/Server/Utils/Telemetry/ChangeEventRow.test.ts +743 -0
- package/Tests/Server/Utils/Telemetry/RuntimeMetrics.test.ts +465 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Alert.test.ts +202 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.test.ts +194 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/ScheduledMaintenance.test.ts +231 -0
- package/Tests/Server/Utils/Workspace/Slack/Messages/Alert.test.ts +194 -0
- package/Tests/Server/Utils/Workspace/Slack/Messages/AlertEpisode.test.ts +194 -0
- package/Tests/Server/Utils/Workspace/Slack/Messages/Incident.test.ts +194 -0
- package/Tests/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.test.ts +194 -0
- package/Tests/Server/Utils/Workspace/Slack/Messages/ScheduledMaintenance.test.ts +247 -0
- package/Tests/Types/AI/AIInsightEvidence.test.ts +206 -0
- package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtilAnonymous.test.ts +144 -0
- package/Tests/Types/Call/CallProvider.test.ts +528 -0
- package/Tests/Types/Kubernetes/KubernetesCostIngest.test.ts +236 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleTimeline.test.ts +736 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleTimelineLayout.test.ts +996 -0
- package/Tests/Types/SecurityEvent/SigmaRule.test.ts +142 -0
- package/Tests/Types/Workflow/Components/JSON.test.ts +182 -0
- package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +116 -1
- package/Tests/UI/Components/Dropdown/DropdownAsyncOptions.test.tsx +81 -0
- package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +89 -1
- package/Tests/UI/Components/EventAttachmentList.test.tsx +174 -0
- package/Tests/UI/Components/Forms/BasicFormBurnRateTemplateDefaults.test.tsx +395 -0
- package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +286 -0
- package/Tests/UI/Components/Forms/FormSummary.test.tsx +128 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableQueryChange.test.tsx +365 -0
- package/Tests/UI/Components/OpenAuthenticatedUrl.test.ts +358 -0
- package/Tests/UI/Components/SessionAwareImage.test.tsx +496 -0
- package/Tests/UI/Components/TelemetryHistogramZeroBuckets.test.tsx +190 -0
- package/Tests/UI/Components/TooltipInteractive.test.tsx +97 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +1 -1
- package/Tests/UI/Monitor/EvaluationLogListTimestamps.test.tsx +367 -0
- package/Tests/UI/Utils/API/BaseAPIRefreshSession.test.ts +1087 -0
- package/Tests/UI/Utils/API/DashboardHost.ts +12 -0
- package/Tests/UI/Utils/API/FakeAxiosServer.ts +183 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/UI/Utils/Cookie.test.ts +440 -0
- package/Tests/UI/Utils/ModelAPIRequestClient.test.ts +278 -0
- package/Tests/UI/Utils/Navigation.test.ts +33 -0
- package/Tests/UI/Utils/PricingPlan.test.ts +295 -0
- package/Tests/UI/Utils/Realtime.test.ts +759 -0
- package/Tests/UI/Utils/Telemetry/Telemetry.test.ts +500 -0
- package/Tests/Utils/APIInstanceDispatch.test.ts +311 -0
- package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizerSeverity.test.ts +565 -0
- package/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +40 -0
- package/Types/Monitor/MonitorEvaluationSummary.ts +3 -0
- package/Types/OnCallDutyPolicy/ScheduleTimeline.ts +507 -0
- package/Types/OnCallDutyPolicy/ScheduleTimelineLayout.ts +589 -0
- package/Types/Realtime/EventName.ts +11 -0
- package/Types/SecurityEvent/DetectionFindingConstants.ts +14 -0
- package/Types/SecurityEvent/OcsfSeverity.ts +31 -26
- package/Types/SecurityEvent/SigmaRule.ts +51 -0
- package/Types/SecurityEvent/ThreatIntelConstants.ts +18 -0
- package/UI/Components/AttachmentList/EventAttachmentList.tsx +30 -2
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +12 -2
- package/UI/Components/Dropdown/Dropdown.tsx +6 -1
- package/UI/Components/EditionLabel/EditionLabel.tsx +12 -0
- package/UI/Components/EventItem/EventItem.tsx +12 -1
- package/UI/Components/Forms/BasicForm.tsx +151 -126
- package/UI/Components/Forms/CollapsibleFormSection.tsx +63 -0
- package/UI/Components/Forms/Fields/FormField.tsx +10 -4
- package/UI/Components/Forms/FormSummary.tsx +7 -0
- package/UI/Components/Forms/Types/Field.ts +10 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -1
- package/UI/Components/Markdown.tsx/SessionAwareImage.tsx +122 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +34 -5
- package/UI/Components/ModelTable/TableView.tsx +1 -1
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +8 -1
- package/UI/Components/Tooltip/Tooltip.tsx +8 -1
- package/UI/Utils/API/API.ts +173 -12
- package/UI/Utils/API/RequestOptions.ts +11 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +31 -7
- package/UI/Utils/ModelAPI/ModelAPI.ts +30 -9
- package/UI/Utils/Navigation.ts +9 -0
- package/UI/Utils/OpenAuthenticatedUrl.ts +154 -0
- package/UI/Utils/Realtime.ts +176 -30
- package/Utils/API.ts +14 -5
- package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +157 -7
- package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +9 -17
- package/Utils/Slo/SloBurnRateTemplate.ts +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -1
- package/build/dist/Server/API/AIBillingAPI.js +1 -1
- package/build/dist/Server/API/AIBillingAPI.js.map +1 -1
- package/build/dist/Server/API/AIInsightAPI.js +1 -4
- package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +1 -4
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AIReadinessAPI.js +1 -3
- package/build/dist/Server/API/AIReadinessAPI.js.map +1 -1
- package/build/dist/Server/API/AlertAPI.js +1 -1
- package/build/dist/Server/API/AlertAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +1 -4
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +12 -0
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js +55 -10
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/BillingAPI.js +2 -2
- package/build/dist/Server/API/BillingAPI.js.map +1 -1
- package/build/dist/Server/API/BillingInvoiceAPI.js +1 -1
- package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
- package/build/dist/Server/API/BillingPaymentMethodAPI.js +2 -2
- package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
- package/build/dist/Server/API/CodeFixRunAPI.js +1 -3
- package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +50 -5
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +12 -12
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +12 -0
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +2 -2
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
- package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
- package/build/dist/Server/API/NotificationAPI.js +1 -1
- package/build/dist/Server/API/NotificationAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallCalendarAPI.js +1 -4
- package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallDutyPolicyAPI.js +6 -0
- package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallReadinessAPI.js +4 -1
- package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +494 -0
- package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -0
- package/build/dist/Server/API/ProjectAPI.js +1 -1
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +125 -33
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +3 -3
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/API/UserCallAPI.js +2 -2
- package/build/dist/Server/API/UserCallAPI.js.map +1 -1
- package/build/dist/Server/API/UserEmailAPI.js +2 -2
- package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js +2 -2
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +1 -1
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/UserSlackAPI.js +1 -1
- package/build/dist/Server/API/UserSlackAPI.js.map +1 -1
- package/build/dist/Server/API/UserSmsAPI.js +2 -2
- package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
- package/build/dist/Server/API/UserTotpAuthAPI.js +1 -1
- package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
- package/build/dist/Server/API/UserWebAuthnAPI.js +2 -2
- package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
- package/build/dist/Server/API/UserWebhookAPI.js +1 -1
- package/build/dist/Server/API/UserWebhookAPI.js.map +1 -1
- package/build/dist/Server/API/UserWhatsAppAPI.js +2 -2
- package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -2
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +64 -4
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +80 -4
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +171 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DetectionRuleService.js +4 -4
- package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyChildService.js +170 -0
- package/build/dist/Server/Services/OnCallDutyPolicyChildService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -2
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +2 -2
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -2
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -2
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/PayAsYouGoBillingService.js +20 -27
- package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +17 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +180 -16
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/TeamPermissionService.js +89 -27
- package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
- package/build/dist/Server/Services/ThreatIntelFeedService.js +5 -4
- package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +9 -6
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/Index.js +60 -6
- package/build/dist/Server/Types/Database/Permissions/Index.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +14 -13
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +7 -0
- package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -1
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +7 -0
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -1
- package/build/dist/Server/Utils/MasterAdminPageAccess.js +39 -0
- package/build/dist/Server/Utils/MasterAdminPageAccess.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +3 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +3 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Realtime.js +126 -37
- package/build/dist/Server/Utils/Realtime.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +7 -25
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js +127 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +3 -2
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +4 -8
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -1
- package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js +31 -1
- package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js +321 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js +372 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js.map +1 -0
- package/build/dist/Types/Realtime/EventName.js +10 -0
- package/build/dist/Types/Realtime/EventName.js.map +1 -1
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +11 -0
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
- package/build/dist/Types/SecurityEvent/OcsfSeverity.js +30 -25
- package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -1
- package/build/dist/Types/SecurityEvent/SigmaRule.js +47 -0
- package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -1
- package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +14 -0
- package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -1
- package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +9 -4
- package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +10 -2
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +11 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
- package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +70 -64
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +27 -0
- package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -0
- package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -7
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/FormSummary.js +7 -0
- package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js +84 -0
- package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +14 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +2 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Utils/API/API.js +143 -19
- package/build/dist/UI/Utils/API/API.js.map +1 -1
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +21 -7
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +26 -9
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +8 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/UI/Utils/OpenAuthenticatedUrl.js +92 -0
- package/build/dist/UI/Utils/OpenAuthenticatedUrl.js.map +1 -0
- package/build/dist/UI/Utils/Realtime.js +120 -15
- package/build/dist/UI/Utils/Realtime.js.map +1 -1
- package/build/dist/Utils/API.js +13 -5
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +123 -6
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
- package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +4 -18
- package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -1
- package/build/dist/Utils/Slo/SloBurnRateTemplate.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Complements Captcha.test.ts (which covers the enable/disable and fail-closed
|
|
5
|
+
* decisions). This suite pins down the details of the outbound siteverify
|
|
6
|
+
* request - timeout, content type, form encoding - and what gets logged on
|
|
7
|
+
* each failure path, including real axios errors such as timeouts.
|
|
8
|
+
*
|
|
9
|
+
* Captcha reads CaptchaEnabled / CaptchaSecretKey at module load, so each
|
|
10
|
+
* configuration is loaded into an isolated registry. axios and the logger are
|
|
11
|
+
* replaced with recording fakes; nothing here touches the network.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface CaptchaUtilLike {
|
|
15
|
+
isCaptchaEnabled: () => boolean;
|
|
16
|
+
verifyCaptcha: (options: {
|
|
17
|
+
token: string | null | undefined;
|
|
18
|
+
remoteIp?: string | null;
|
|
19
|
+
}) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface RequestConfig {
|
|
23
|
+
headers?: { [key: string]: string };
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PostCall {
|
|
28
|
+
url: string;
|
|
29
|
+
body: string;
|
|
30
|
+
config: RequestConfig;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface FakeAxiosError extends Error {
|
|
34
|
+
isAxiosError: boolean;
|
|
35
|
+
code?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Loaded {
|
|
39
|
+
captcha: CaptchaUtilLike;
|
|
40
|
+
posts: Array<PostCall>;
|
|
41
|
+
errorLogs: Array<string>;
|
|
42
|
+
warnLogs: Array<string>;
|
|
43
|
+
respondWith: (response: unknown) => void;
|
|
44
|
+
rejectWith: (error: unknown) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const GENERIC_ERROR_MESSAGE: string =
|
|
48
|
+
"Captcha verification failed. Please try again.";
|
|
49
|
+
|
|
50
|
+
const makeAxiosError: (message: string, code?: string) => FakeAxiosError = (
|
|
51
|
+
message: string,
|
|
52
|
+
code?: string,
|
|
53
|
+
): FakeAxiosError => {
|
|
54
|
+
const error: FakeAxiosError = new Error(message) as FakeAxiosError;
|
|
55
|
+
error.isAxiosError = true;
|
|
56
|
+
if (code) {
|
|
57
|
+
error.code = code;
|
|
58
|
+
}
|
|
59
|
+
return error;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const loadCaptcha: (config: {
|
|
63
|
+
enabled: boolean;
|
|
64
|
+
secret: string;
|
|
65
|
+
}) => Loaded = (config: { enabled: boolean; secret: string }): Loaded => {
|
|
66
|
+
const posts: Array<PostCall> = [];
|
|
67
|
+
const errorLogs: Array<string> = [];
|
|
68
|
+
const warnLogs: Array<string> = [];
|
|
69
|
+
let response: unknown = { data: { success: true } };
|
|
70
|
+
let rejection: unknown = null;
|
|
71
|
+
let captcha: CaptchaUtilLike | null = null;
|
|
72
|
+
|
|
73
|
+
jest.isolateModules((): void => {
|
|
74
|
+
jest.doMock("../../../Server/EnvironmentConfig", (): unknown => {
|
|
75
|
+
return {
|
|
76
|
+
...(jest.requireActual("../../../Server/EnvironmentConfig") as Record<
|
|
77
|
+
string,
|
|
78
|
+
unknown
|
|
79
|
+
>),
|
|
80
|
+
CaptchaEnabled: config.enabled,
|
|
81
|
+
CaptchaSecretKey: config.secret,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
jest.doMock("../../../Server/Utils/Logger", (): unknown => {
|
|
86
|
+
const logger: unknown = {
|
|
87
|
+
error: (message: unknown): void => {
|
|
88
|
+
errorLogs.push(String(message));
|
|
89
|
+
},
|
|
90
|
+
warn: (message: unknown): void => {
|
|
91
|
+
warnLogs.push(String(message));
|
|
92
|
+
},
|
|
93
|
+
info: (): void => {},
|
|
94
|
+
debug: (): void => {},
|
|
95
|
+
};
|
|
96
|
+
return { __esModule: true, default: logger };
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
jest.doMock("axios", (): unknown => {
|
|
100
|
+
const post: unknown = (
|
|
101
|
+
url: string,
|
|
102
|
+
body: string,
|
|
103
|
+
requestConfig: RequestConfig,
|
|
104
|
+
): Promise<unknown> => {
|
|
105
|
+
posts.push({ url, body, config: requestConfig });
|
|
106
|
+
|
|
107
|
+
if (rejection) {
|
|
108
|
+
return Promise.reject(rejection);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Promise.resolve(response);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const isAxiosError: unknown = (err: unknown): boolean => {
|
|
115
|
+
return Boolean(
|
|
116
|
+
err && (err as { isAxiosError?: boolean }).isAxiosError === true,
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
__esModule: true,
|
|
122
|
+
default: { post, isAxiosError },
|
|
123
|
+
post,
|
|
124
|
+
isAxiosError,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
captcha = (
|
|
129
|
+
jest.requireActual("../../../Server/Utils/Captcha") as {
|
|
130
|
+
default: CaptchaUtilLike;
|
|
131
|
+
}
|
|
132
|
+
).default;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
captcha: captcha as unknown as CaptchaUtilLike,
|
|
137
|
+
posts,
|
|
138
|
+
errorLogs,
|
|
139
|
+
warnLogs,
|
|
140
|
+
respondWith: (next: unknown): void => {
|
|
141
|
+
response = next;
|
|
142
|
+
},
|
|
143
|
+
rejectWith: (next: unknown): void => {
|
|
144
|
+
rejection = next;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const captureRejection: (work: Promise<void>) => Promise<Error> = async (
|
|
150
|
+
work: Promise<void>,
|
|
151
|
+
): Promise<Error> => {
|
|
152
|
+
try {
|
|
153
|
+
await work;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
expect(err).toBeInstanceOf(Error);
|
|
156
|
+
// Class identity differs across isolated registries; compare by name.
|
|
157
|
+
expect((err as Error).constructor.name).toBe("BadDataException");
|
|
158
|
+
return err as Error;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
throw new Error("Expected verifyCaptcha to reject, but it resolved");
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
describe("CaptchaUtil outbound request", () => {
|
|
165
|
+
beforeEach((): void => {
|
|
166
|
+
jest.resetModules();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("posts form-encoded data to hCaptcha with a 5 second timeout", async () => {
|
|
170
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s3cret" });
|
|
171
|
+
|
|
172
|
+
await loaded.captcha.verifyCaptcha({ token: "tok" });
|
|
173
|
+
|
|
174
|
+
expect(loaded.posts).toHaveLength(1);
|
|
175
|
+
const call: PostCall = loaded.posts[0] as PostCall;
|
|
176
|
+
expect(call.url).toBe("https://hcaptcha.com/siteverify");
|
|
177
|
+
expect(call.config.timeout).toBe(5000);
|
|
178
|
+
expect(call.config.headers).toEqual({
|
|
179
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
180
|
+
});
|
|
181
|
+
expect(typeof call.body).toBe("string");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("form-encodes tokens and IPs containing reserved characters", async () => {
|
|
185
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "a&b=c" });
|
|
186
|
+
const token: string = "P1_abc+/=&x y";
|
|
187
|
+
|
|
188
|
+
await loaded.captcha.verifyCaptcha({
|
|
189
|
+
token,
|
|
190
|
+
remoteIp: "2001:db8::1",
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const params: URLSearchParams = new URLSearchParams(
|
|
194
|
+
(loaded.posts[0] as PostCall).body,
|
|
195
|
+
);
|
|
196
|
+
expect(params.get("secret")).toBe("a&b=c");
|
|
197
|
+
expect(params.get("response")).toBe(token);
|
|
198
|
+
expect(params.get("remoteip")).toBe("2001:db8::1");
|
|
199
|
+
expect(Array.from(params.keys())).toEqual([
|
|
200
|
+
"secret",
|
|
201
|
+
"response",
|
|
202
|
+
"remoteip",
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("omits remoteip when it is null", async () => {
|
|
207
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
208
|
+
|
|
209
|
+
await loaded.captcha.verifyCaptcha({ token: "tok", remoteIp: null });
|
|
210
|
+
|
|
211
|
+
const params: URLSearchParams = new URLSearchParams(
|
|
212
|
+
(loaded.posts[0] as PostCall).body,
|
|
213
|
+
);
|
|
214
|
+
expect(params.has("remoteip")).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("makes exactly one request per verification (no retries)", async () => {
|
|
218
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
219
|
+
loaded.rejectWith(
|
|
220
|
+
makeAxiosError("timeout of 5000ms exceeded", "ECONNABORTED"),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
await captureRejection(loaded.captcha.verifyCaptcha({ token: "tok" }));
|
|
224
|
+
|
|
225
|
+
expect(loaded.posts).toHaveLength(1);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("CaptchaUtil token validation", () => {
|
|
230
|
+
beforeEach((): void => {
|
|
231
|
+
jest.resetModules();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it.each([
|
|
235
|
+
["null", null],
|
|
236
|
+
["undefined", undefined],
|
|
237
|
+
["empty", ""],
|
|
238
|
+
["whitespace", " \t\n "],
|
|
239
|
+
])(
|
|
240
|
+
"rejects a %s token with the 'missing' message without calling the provider",
|
|
241
|
+
async (_label: string, token: string | null | undefined) => {
|
|
242
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
243
|
+
|
|
244
|
+
const error: Error = await captureRejection(
|
|
245
|
+
loaded.captcha.verifyCaptcha({ token }),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(error.message).toBe(
|
|
249
|
+
"Captcha token is missing. Please complete the verification challenge.",
|
|
250
|
+
);
|
|
251
|
+
expect(loaded.posts).toHaveLength(0);
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
it("ignores a missing token entirely when captcha is disabled", async () => {
|
|
256
|
+
const loaded: Loaded = loadCaptcha({ enabled: false, secret: "" });
|
|
257
|
+
|
|
258
|
+
await expect(
|
|
259
|
+
loaded.captcha.verifyCaptcha({ token: null }),
|
|
260
|
+
).resolves.toBeUndefined();
|
|
261
|
+
expect(loaded.posts).toHaveLength(0);
|
|
262
|
+
expect(loaded.errorLogs).toHaveLength(0);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("is disabled even with a secret when the flag is off, and skips verification", async () => {
|
|
266
|
+
const loaded: Loaded = loadCaptcha({ enabled: false, secret: "s" });
|
|
267
|
+
|
|
268
|
+
expect(loaded.captcha.isCaptchaEnabled()).toBe(false);
|
|
269
|
+
await loaded.captcha.verifyCaptcha({ token: "anything" });
|
|
270
|
+
expect(loaded.posts).toHaveLength(0);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("CaptchaUtil failure handling and logging", () => {
|
|
275
|
+
beforeEach((): void => {
|
|
276
|
+
jest.resetModules();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("logs a configuration error when enabled without a secret", async () => {
|
|
280
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "" });
|
|
281
|
+
|
|
282
|
+
const error: Error = await captureRejection(
|
|
283
|
+
loaded.captcha.verifyCaptcha({ token: "tok" }),
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(error.message).toBe(GENERIC_ERROR_MESSAGE);
|
|
287
|
+
expect(loaded.errorLogs).toEqual([
|
|
288
|
+
"Captcha is enabled but CAPTCHA_SECRET_KEY is not configured.",
|
|
289
|
+
]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("checks the secret before the token, so a blank token still reports misconfiguration", async () => {
|
|
293
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "" });
|
|
294
|
+
|
|
295
|
+
const error: Error = await captureRejection(
|
|
296
|
+
loaded.captcha.verifyCaptcha({ token: "" }),
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(error.message).toBe(GENERIC_ERROR_MESSAGE);
|
|
300
|
+
expect(loaded.errorLogs).toHaveLength(1);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("fails closed on an axios timeout and logs the axios message", async () => {
|
|
304
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
305
|
+
loaded.rejectWith(
|
|
306
|
+
makeAxiosError("timeout of 5000ms exceeded", "ECONNABORTED"),
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const error: Error = await captureRejection(
|
|
310
|
+
loaded.captcha.verifyCaptcha({ token: "tok" }),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
expect(error.message).toBe(GENERIC_ERROR_MESSAGE);
|
|
314
|
+
expect(loaded.errorLogs).toEqual([
|
|
315
|
+
"Captcha provider verification failure: timeout of 5000ms exceeded",
|
|
316
|
+
]);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("fails closed on an HTTP error status from the provider", async () => {
|
|
320
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
321
|
+
loaded.rejectWith(makeAxiosError("Request failed with status code 500"));
|
|
322
|
+
|
|
323
|
+
const error: Error = await captureRejection(
|
|
324
|
+
loaded.captcha.verifyCaptcha({ token: "tok" }),
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
expect(error.message).toBe(GENERIC_ERROR_MESSAGE);
|
|
328
|
+
expect(loaded.errorLogs[0]).toContain("status code 500");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("fails closed on a non-axios error and logs its message", async () => {
|
|
332
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
333
|
+
loaded.rejectWith(new TypeError("unexpected"));
|
|
334
|
+
|
|
335
|
+
const error: Error = await captureRejection(
|
|
336
|
+
loaded.captcha.verifyCaptcha({ token: "tok" }),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
expect(error.message).toBe(GENERIC_ERROR_MESSAGE);
|
|
340
|
+
expect(loaded.errorLogs).toEqual([
|
|
341
|
+
"Captcha provider verification failure: unexpected",
|
|
342
|
+
]);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("warns with the provider payload when success is false, then logs the failure", async () => {
|
|
346
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
347
|
+
loaded.respondWith({
|
|
348
|
+
data: { success: false, "error-codes": ["invalid-input-response"] },
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const error: Error = await captureRejection(
|
|
352
|
+
loaded.captcha.verifyCaptcha({ token: "tok" }),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
expect(error.message).toBe(GENERIC_ERROR_MESSAGE);
|
|
356
|
+
expect(loaded.warnLogs).toEqual([
|
|
357
|
+
'hCaptcha verification failed: {"success":false,"error-codes":["invalid-input-response"]}',
|
|
358
|
+
]);
|
|
359
|
+
expect(loaded.errorLogs).toEqual([
|
|
360
|
+
`Captcha provider verification failure: ${GENERIC_ERROR_MESSAGE}`,
|
|
361
|
+
]);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("warns with an empty payload when the provider returns no body", async () => {
|
|
365
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
366
|
+
loaded.respondWith({ data: null });
|
|
367
|
+
|
|
368
|
+
await captureRejection(loaded.captcha.verifyCaptcha({ token: "tok" }));
|
|
369
|
+
|
|
370
|
+
expect(loaded.warnLogs).toEqual(["hCaptcha verification failed: {}"]);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("never logs the secret key", async () => {
|
|
374
|
+
const secret: string = "very-private-secret";
|
|
375
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret });
|
|
376
|
+
loaded.respondWith({ data: { success: false } });
|
|
377
|
+
|
|
378
|
+
await captureRejection(loaded.captcha.verifyCaptcha({ token: "tok" }));
|
|
379
|
+
|
|
380
|
+
for (const line of [...loaded.errorLogs, ...loaded.warnLogs]) {
|
|
381
|
+
expect(line).not.toContain(secret);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("does not log anything on success", async () => {
|
|
386
|
+
const loaded: Loaded = loadCaptcha({ enabled: true, secret: "s" });
|
|
387
|
+
loaded.respondWith({ data: { success: true, hostname: "example.com" } });
|
|
388
|
+
|
|
389
|
+
await expect(
|
|
390
|
+
loaded.captcha.verifyCaptcha({ token: "tok" }),
|
|
391
|
+
).resolves.toBeUndefined();
|
|
392
|
+
expect(loaded.errorLogs).toHaveLength(0);
|
|
393
|
+
expect(loaded.warnLogs).toHaveLength(0);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
|
|
2
|
+
import { ExpressRequest, ExpressResponse } from "../../../Server/Utils/Express";
|
|
3
|
+
import JSONWebToken from "../../../Server/Utils/JsonWebToken";
|
|
4
|
+
import logger from "../../../Server/Utils/Logger";
|
|
5
|
+
import {
|
|
6
|
+
ensureMasterAdminPageAccess,
|
|
7
|
+
NOT_A_MASTER_ADMIN_MESSAGE,
|
|
8
|
+
} from "../../../Server/Utils/MasterAdminPageAccess";
|
|
9
|
+
import Response from "../../../Server/Utils/Response";
|
|
10
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
11
|
+
import Exception from "../../../Types/Exception/Exception";
|
|
12
|
+
import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
|
|
13
|
+
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
14
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
15
|
+
import JSONWebTokenData from "../../../Types/JsonWebTokenData";
|
|
16
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
17
|
+
import { getJestSpyOn } from "../../Spy";
|
|
18
|
+
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
19
|
+
|
|
20
|
+
// The decode-failure branch logs at debug; keep the run quiet and observable.
|
|
21
|
+
jest.mock("../../../Server/Utils/Logger");
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* ensureMasterAdminPageAccess gates the Admin Dashboard's HTML shell (not its
|
|
25
|
+
* APIs), and replaced two copies of the same inline gate in
|
|
26
|
+
* App/FeatureSet/Frontend/Index.ts and App/FeatureSet/AdminDashboard/Serve.ts.
|
|
27
|
+
*
|
|
28
|
+
* The case it exists for: the access-token cookie expires together with the
|
|
29
|
+
* JWT inside it. An admin who reloads a tab left open past the token lifetime
|
|
30
|
+
* arrives with NO token at all. The old gates answered that with a 422 JSON
|
|
31
|
+
* error page, so the SPA never loaded and never got the chance to refresh the
|
|
32
|
+
* session - the admin was stuck until they found the login page by hand. Now
|
|
33
|
+
* a missing or undecodable token is served the shell (it holds nothing the
|
|
34
|
+
* public static bundle does not), and the SPA's refresh-aware API client
|
|
35
|
+
* recovers the session or sends a signed-out visitor to the login page.
|
|
36
|
+
*
|
|
37
|
+
* The one refusal that did NOT move: a session that decodes and is positively
|
|
38
|
+
* not a master admin still gets the error. Refreshing would hand back the
|
|
39
|
+
* same non-admin session.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const SERVICE: string = "admin-dashboard-test";
|
|
43
|
+
|
|
44
|
+
describe("ensureMasterAdminPageAccess", () => {
|
|
45
|
+
let request: ExpressRequest;
|
|
46
|
+
let response: ExpressResponse;
|
|
47
|
+
let getAccessTokenSpy: jest.SpyInstance<any, any>;
|
|
48
|
+
let decodeSpy: jest.SpyInstance<any, any>;
|
|
49
|
+
let sendErrorSpy: jest.SpyInstance<any, any>;
|
|
50
|
+
|
|
51
|
+
type RunFunction = () => Promise<JSONObject>;
|
|
52
|
+
|
|
53
|
+
const run: RunFunction = async (): Promise<JSONObject> => {
|
|
54
|
+
return await ensureMasterAdminPageAccess({
|
|
55
|
+
req: request,
|
|
56
|
+
res: response,
|
|
57
|
+
service: SERVICE,
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type DecodedTokenFunction = (isMasterAdmin: unknown) => JSONWebTokenData;
|
|
62
|
+
|
|
63
|
+
const decodedToken: DecodedTokenFunction = (
|
|
64
|
+
isMasterAdmin: unknown,
|
|
65
|
+
): JSONWebTokenData => {
|
|
66
|
+
return {
|
|
67
|
+
userId: ObjectID.generate(),
|
|
68
|
+
isMasterAdmin: isMasterAdmin,
|
|
69
|
+
isGlobalLogin: true,
|
|
70
|
+
} as unknown as JSONWebTokenData;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
jest.restoreAllMocks();
|
|
75
|
+
jest.clearAllMocks();
|
|
76
|
+
|
|
77
|
+
request = { headers: {}, cookies: {} } as unknown as ExpressRequest;
|
|
78
|
+
|
|
79
|
+
response = {
|
|
80
|
+
status: jest.fn().mockReturnThis(),
|
|
81
|
+
json: jest.fn().mockReturnThis(),
|
|
82
|
+
send: jest.fn().mockReturnThis(),
|
|
83
|
+
} as unknown as ExpressResponse;
|
|
84
|
+
|
|
85
|
+
getAccessTokenSpy = getJestSpyOn(
|
|
86
|
+
UserMiddleware,
|
|
87
|
+
"getAccessTokenFromExpressRequest",
|
|
88
|
+
).mockReturnValue(undefined);
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* Each test that needs a decodable token says what it decodes to. The
|
|
92
|
+
* default is the real failure: JSONWebToken.decode wraps every jwt.verify
|
|
93
|
+
* error (expired, bad signature, malformed) in this BadDataException.
|
|
94
|
+
*/
|
|
95
|
+
decodeSpy = getJestSpyOn(JSONWebToken, "decode").mockImplementation(() => {
|
|
96
|
+
throw new BadDataException("AccessToken is invalid or expired");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
sendErrorSpy = getJestSpyOn(
|
|
100
|
+
Response,
|
|
101
|
+
"sendErrorResponse",
|
|
102
|
+
).mockImplementation(() => {
|
|
103
|
+
return undefined as never;
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("with no access token (the expired-cookie reload)", () => {
|
|
108
|
+
test("resolves to an empty object and sends no error, so the SPA loads", async () => {
|
|
109
|
+
await expect(run()).resolves.toEqual({});
|
|
110
|
+
|
|
111
|
+
expect(sendErrorSpy).not.toHaveBeenCalled();
|
|
112
|
+
expect(response.status).not.toHaveBeenCalled();
|
|
113
|
+
expect(response.send).not.toHaveBeenCalled();
|
|
114
|
+
expect(response.json).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("reads the token from the request it was given", async () => {
|
|
118
|
+
await run();
|
|
119
|
+
|
|
120
|
+
expect(getAccessTokenSpy).toHaveBeenCalledTimes(1);
|
|
121
|
+
expect(getAccessTokenSpy).toHaveBeenCalledWith(request);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("never tries to decode a token it does not have", async () => {
|
|
125
|
+
await run();
|
|
126
|
+
|
|
127
|
+
expect(decodeSpy).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("treats an empty-string token as no token", async () => {
|
|
131
|
+
getAccessTokenSpy.mockReturnValue("");
|
|
132
|
+
|
|
133
|
+
await expect(run()).resolves.toEqual({});
|
|
134
|
+
|
|
135
|
+
expect(decodeSpy).not.toHaveBeenCalled();
|
|
136
|
+
expect(sendErrorSpy).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("with a token that does not decode", () => {
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
getAccessTokenSpy.mockReturnValue("expired.jwt.token");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("resolves to an empty object and sends no error", async () => {
|
|
146
|
+
await expect(run()).resolves.toEqual({});
|
|
147
|
+
|
|
148
|
+
expect(decodeSpy).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(decodeSpy).toHaveBeenCalledWith("expired.jwt.token");
|
|
150
|
+
expect(sendErrorSpy).not.toHaveBeenCalled();
|
|
151
|
+
expect(response.status).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Whatever decode throws, the shell is served and nothing escapes.
|
|
155
|
+
test.each([
|
|
156
|
+
[
|
|
157
|
+
"the BadDataException JSONWebToken.decode raises",
|
|
158
|
+
(): Error => {
|
|
159
|
+
return new BadDataException("AccessToken is invalid or expired");
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
"a plain Error",
|
|
164
|
+
(): Error => {
|
|
165
|
+
return new Error("jwt expired");
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
"a NotAuthorizedException",
|
|
170
|
+
(): Error => {
|
|
171
|
+
return new NotAuthorizedException("should not surface");
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
])(
|
|
175
|
+
"does not reject or send an error when decode throws %s",
|
|
176
|
+
async (_label: string, makeError: () => Error) => {
|
|
177
|
+
decodeSpy.mockImplementation(() => {
|
|
178
|
+
throw makeError();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await expect(run()).resolves.toEqual({});
|
|
182
|
+
|
|
183
|
+
expect(sendErrorSpy).not.toHaveBeenCalled();
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
test("logs the decode failure at debug, tagged with the service", async () => {
|
|
188
|
+
const thrown: Error = new Error("jwt expired");
|
|
189
|
+
|
|
190
|
+
decodeSpy.mockImplementation(() => {
|
|
191
|
+
throw thrown;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await run();
|
|
195
|
+
|
|
196
|
+
expect(logger.debug).toHaveBeenCalledWith(thrown, { service: SERVICE });
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("with a token that decodes", () => {
|
|
201
|
+
beforeEach(() => {
|
|
202
|
+
getAccessTokenSpy.mockReturnValue("valid.jwt.token");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("refuses a session that is not a master admin with a 422", async () => {
|
|
206
|
+
decodeSpy.mockReturnValue(decodedToken(false) as never);
|
|
207
|
+
|
|
208
|
+
await expect(run()).resolves.toEqual({});
|
|
209
|
+
|
|
210
|
+
expect(sendErrorSpy).toHaveBeenCalledTimes(1);
|
|
211
|
+
|
|
212
|
+
const [sentRequest, sentResponse, sentError] = sendErrorSpy.mock
|
|
213
|
+
.calls[0] as [ExpressRequest, ExpressResponse, Exception];
|
|
214
|
+
|
|
215
|
+
expect(sentRequest).toBe(request);
|
|
216
|
+
expect(sentResponse).toBe(response);
|
|
217
|
+
expect(sentError).toBeInstanceOf(NotAuthorizedException);
|
|
218
|
+
expect(sentError).not.toBeInstanceOf(NotAuthenticatedException);
|
|
219
|
+
expect(sentError.code).toBe(422);
|
|
220
|
+
expect(sentError.message).toBe(NOT_A_MASTER_ADMIN_MESSAGE);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("refuses a token with no isMasterAdmin claim at all", async () => {
|
|
224
|
+
decodeSpy.mockReturnValue(decodedToken(undefined) as never);
|
|
225
|
+
|
|
226
|
+
await run();
|
|
227
|
+
|
|
228
|
+
expect(sendErrorSpy).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(
|
|
230
|
+
(sendErrorSpy.mock.calls[0] as Array<unknown>)[2] as Exception,
|
|
231
|
+
).toBeInstanceOf(NotAuthorizedException);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("lets a master admin through without sending anything", async () => {
|
|
235
|
+
decodeSpy.mockReturnValue(decodedToken(true) as never);
|
|
236
|
+
|
|
237
|
+
await expect(run()).resolves.toEqual({});
|
|
238
|
+
|
|
239
|
+
expect(decodeSpy).toHaveBeenCalledWith("valid.jwt.token");
|
|
240
|
+
expect(sendErrorSpy).not.toHaveBeenCalled();
|
|
241
|
+
expect(response.status).not.toHaveBeenCalled();
|
|
242
|
+
expect(response.send).not.toHaveBeenCalled();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("the refusal message names the admin dashboard", () => {
|
|
247
|
+
expect(NOT_A_MASTER_ADMIN_MESSAGE).toBe(
|
|
248
|
+
"Unauthorized: Only master admins can access the admin dashboard.",
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
});
|