@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,453 @@
|
|
|
1
|
+
import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
|
|
2
|
+
import ProjectMiddleware from "../../../Server/Middleware/ProjectAuthorization";
|
|
3
|
+
import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
|
|
4
|
+
import AccessTokenService from "../../../Server/Services/AccessTokenService";
|
|
5
|
+
import ProjectService from "../../../Server/Services/ProjectService";
|
|
6
|
+
import UserService from "../../../Server/Services/UserService";
|
|
7
|
+
import {
|
|
8
|
+
ExpressResponse,
|
|
9
|
+
NextFunction,
|
|
10
|
+
OneUptimeRequest,
|
|
11
|
+
} from "../../../Server/Utils/Express";
|
|
12
|
+
import JSONWebToken from "../../../Server/Utils/JsonWebToken";
|
|
13
|
+
import logger from "../../../Server/Utils/Logger";
|
|
14
|
+
import Response from "../../../Server/Utils/Response";
|
|
15
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
16
|
+
import Email from "../../../Types/Email";
|
|
17
|
+
import Exception from "../../../Types/Exception/Exception";
|
|
18
|
+
import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
|
|
19
|
+
import Name from "../../../Types/Name";
|
|
20
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
21
|
+
import { UserGlobalAccessPermission } from "../../../Types/Permission";
|
|
22
|
+
import UserType from "../../../Types/UserType";
|
|
23
|
+
import { getJestSpyOn } from "../../Spy";
|
|
24
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
25
|
+
import jwt from "jsonwebtoken";
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* getPublicRouteUserMiddleware: getUserMiddleware for the public dashboard and
|
|
29
|
+
* public status page routes.
|
|
30
|
+
*
|
|
31
|
+
* THE BUG
|
|
32
|
+
*
|
|
33
|
+
* The host-wide dashboard access-token cookie reaches the public routes
|
|
34
|
+
* whenever the page is served from the dashboard's host. When that cookie is
|
|
35
|
+
* still in the browser but no longer decodes - the encryption secret was
|
|
36
|
+
* rotated, say - getUserMiddleware answered 401 "AccessToken is invalid or
|
|
37
|
+
* expired" before the route ever asked whether the dashboard was public. The
|
|
38
|
+
* public clients cannot refresh a dashboard session, so the 401 went straight
|
|
39
|
+
* to the login redirect: a reload loop on the preview route, and a password
|
|
40
|
+
* prompt for a master password the viewer had already entered.
|
|
41
|
+
*
|
|
42
|
+
* WHAT THIS FILE PINS
|
|
43
|
+
*
|
|
44
|
+
* 1. On the public variant, a token that does not decode is anonymity, not
|
|
45
|
+
* a refusal: UserType.Public, no identity, next().
|
|
46
|
+
* 2. Everything else is getUserMiddleware's behaviour: a good token is
|
|
47
|
+
* resolved the same way, an API key is still validated, no token is
|
|
48
|
+
* still anonymous.
|
|
49
|
+
* 3. getUserMiddleware itself, which every authenticated route mounts,
|
|
50
|
+
* still answers the same tokens with a 401.
|
|
51
|
+
*
|
|
52
|
+
* Tokens are signed and decoded with the real JSONWebToken, so "does not
|
|
53
|
+
* decode" means exactly what it means in production.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
const INVALID_ACCESS_TOKEN_MESSAGE: string =
|
|
57
|
+
"AccessToken is invalid or expired. Please refresh your token.";
|
|
58
|
+
|
|
59
|
+
const USER_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
|
|
60
|
+
|
|
61
|
+
type TokenPayloadFunction = () => Dictionary<string | boolean>;
|
|
62
|
+
|
|
63
|
+
const tokenPayload: TokenPayloadFunction = (): Dictionary<string | boolean> => {
|
|
64
|
+
return {
|
|
65
|
+
userId: USER_ID.toString(),
|
|
66
|
+
email: "viewer@example.com",
|
|
67
|
+
name: "Viewer",
|
|
68
|
+
isMasterAdmin: false,
|
|
69
|
+
isGlobalLogin: true,
|
|
70
|
+
sessionId: ObjectID.generate().toString(),
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Each one well-formed enough to be sent as a session, none of them one this
|
|
76
|
+
* instance will accept.
|
|
77
|
+
*/
|
|
78
|
+
const UNDECODABLE_TOKENS: Array<[string, () => string]> = [
|
|
79
|
+
[
|
|
80
|
+
"a token signed with a rotated-out encryption secret",
|
|
81
|
+
(): string => {
|
|
82
|
+
return jwt.sign(
|
|
83
|
+
tokenPayload(),
|
|
84
|
+
`${EncryptionSecret.toString()}-before-rotation`,
|
|
85
|
+
{ expiresIn: 15 * 60 },
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"an expired token",
|
|
91
|
+
(): string => {
|
|
92
|
+
return jwt.sign(tokenPayload(), EncryptionSecret.toString(), {
|
|
93
|
+
expiresIn: -60,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
"a value that is not a JWT at all",
|
|
99
|
+
(): string => {
|
|
100
|
+
return "not-a-jwt";
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
type SignValidTokenFunction = (data?: { isMasterAdmin?: boolean }) => string;
|
|
106
|
+
|
|
107
|
+
const signValidToken: SignValidTokenFunction = (data?: {
|
|
108
|
+
isMasterAdmin?: boolean;
|
|
109
|
+
}): string => {
|
|
110
|
+
return JSONWebToken.signUserLoginToken({
|
|
111
|
+
tokenData: {
|
|
112
|
+
userId: USER_ID,
|
|
113
|
+
email: new Email("viewer@example.com"),
|
|
114
|
+
name: new Name("Viewer"),
|
|
115
|
+
timezone: null,
|
|
116
|
+
isMasterAdmin: Boolean(data?.isMasterAdmin),
|
|
117
|
+
isGlobalLogin: true,
|
|
118
|
+
sessionId: ObjectID.generate(),
|
|
119
|
+
},
|
|
120
|
+
expiresInSeconds: 15 * 60,
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type BuildRequestFunction = (data: {
|
|
125
|
+
cookies?: Dictionary<string>;
|
|
126
|
+
headers?: Dictionary<string>;
|
|
127
|
+
}) => OneUptimeRequest;
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* Shaped like a public client's request: the public dashboard and status page
|
|
131
|
+
* clients send an empty tenantid header, so no tenant is resolved.
|
|
132
|
+
*/
|
|
133
|
+
const buildRequest: BuildRequestFunction = (data: {
|
|
134
|
+
cookies?: Dictionary<string>;
|
|
135
|
+
headers?: Dictionary<string>;
|
|
136
|
+
}): OneUptimeRequest => {
|
|
137
|
+
return {
|
|
138
|
+
params: {},
|
|
139
|
+
query: {},
|
|
140
|
+
body: {},
|
|
141
|
+
cookies: data.cookies || {},
|
|
142
|
+
headers: { tenantid: "", ...(data.headers || {}) },
|
|
143
|
+
} as unknown as OneUptimeRequest;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type MiddlewareFunction = (
|
|
147
|
+
req: OneUptimeRequest,
|
|
148
|
+
res: ExpressResponse,
|
|
149
|
+
next: NextFunction,
|
|
150
|
+
) => Promise<void>;
|
|
151
|
+
|
|
152
|
+
type MiddlewareCallResult = {
|
|
153
|
+
nextCalls: Array<Array<unknown>>;
|
|
154
|
+
sentError: Exception | undefined;
|
|
155
|
+
sendErrorCallCount: number;
|
|
156
|
+
headersSet: Array<Array<unknown>>;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
type CallMiddlewareFunction = (
|
|
160
|
+
middleware: MiddlewareFunction,
|
|
161
|
+
req: OneUptimeRequest,
|
|
162
|
+
) => Promise<MiddlewareCallResult>;
|
|
163
|
+
|
|
164
|
+
const callMiddleware: CallMiddlewareFunction = async (
|
|
165
|
+
middleware: MiddlewareFunction,
|
|
166
|
+
req: OneUptimeRequest,
|
|
167
|
+
): Promise<MiddlewareCallResult> => {
|
|
168
|
+
const set: jest.Mock = jest.fn();
|
|
169
|
+
const res: ExpressResponse = { set } as unknown as ExpressResponse;
|
|
170
|
+
const next: jest.Mock = jest.fn();
|
|
171
|
+
|
|
172
|
+
await middleware(req, res, next as unknown as NextFunction);
|
|
173
|
+
|
|
174
|
+
const errorCalls: Array<Array<unknown>> = (
|
|
175
|
+
Response.sendErrorResponse as unknown as jest.Mock
|
|
176
|
+
).mock.calls as Array<Array<unknown>>;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
nextCalls: next.mock.calls as Array<Array<unknown>>,
|
|
180
|
+
sentError: errorCalls[0]?.[2] as Exception | undefined,
|
|
181
|
+
sendErrorCallCount: errorCalls.length,
|
|
182
|
+
headersSet: set.mock.calls as Array<Array<unknown>>,
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
type ExpectServedAnonymouslyFunction = (
|
|
187
|
+
result: MiddlewareCallResult,
|
|
188
|
+
req: OneUptimeRequest,
|
|
189
|
+
) => void;
|
|
190
|
+
|
|
191
|
+
const expectServedAnonymously: ExpectServedAnonymouslyFunction = (
|
|
192
|
+
result: MiddlewareCallResult,
|
|
193
|
+
req: OneUptimeRequest,
|
|
194
|
+
): void => {
|
|
195
|
+
expect(result.sendErrorCallCount).toBe(0);
|
|
196
|
+
expect(result.nextCalls).toHaveLength(1);
|
|
197
|
+
// next() with an argument is Express's error path, not a pass.
|
|
198
|
+
expect(result.nextCalls[0]).toEqual([]);
|
|
199
|
+
expect(req.userType).toBe(UserType.Public);
|
|
200
|
+
expect(req.userAuthorization).toBeUndefined();
|
|
201
|
+
expect(UserMiddleware.isAnonymousRequest(req)).toBe(true);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
type ExpectInvalidTokenRefusalFunction = (result: MiddlewareCallResult) => void;
|
|
205
|
+
|
|
206
|
+
const expectInvalidTokenRefusal: ExpectInvalidTokenRefusalFunction = (
|
|
207
|
+
result: MiddlewareCallResult,
|
|
208
|
+
): void => {
|
|
209
|
+
expect(result.nextCalls).toHaveLength(0);
|
|
210
|
+
expect(result.sendErrorCallCount).toBe(1);
|
|
211
|
+
expect(result.sentError).toBeInstanceOf(NotAuthenticatedException);
|
|
212
|
+
expect(result.sentError?.code).toBe(401);
|
|
213
|
+
expect(result.sentError?.message).toBe(INVALID_ACCESS_TOKEN_MESSAGE);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const GLOBAL_PERMISSION: UserGlobalAccessPermission = {
|
|
217
|
+
_type: "UserGlobalAccessPermission",
|
|
218
|
+
projectIds: [],
|
|
219
|
+
globalPermissions: [],
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
let getUserGlobalAccessPermission: jest.SpyInstance;
|
|
223
|
+
let userUpdateLastActive: jest.SpyInstance;
|
|
224
|
+
|
|
225
|
+
beforeEach(() => {
|
|
226
|
+
jest.clearAllMocks();
|
|
227
|
+
|
|
228
|
+
getJestSpyOn(Response, "sendErrorResponse").mockImplementation(() => {
|
|
229
|
+
return undefined as never;
|
|
230
|
+
});
|
|
231
|
+
// JSONWebToken.decode logs every token it refuses.
|
|
232
|
+
getJestSpyOn(logger, "error").mockImplementation(() => {
|
|
233
|
+
return undefined;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
getUserGlobalAccessPermission = getJestSpyOn(
|
|
237
|
+
AccessTokenService,
|
|
238
|
+
"getUserGlobalAccessPermission",
|
|
239
|
+
).mockResolvedValue(GLOBAL_PERMISSION);
|
|
240
|
+
userUpdateLastActive = getJestSpyOn(
|
|
241
|
+
UserService,
|
|
242
|
+
"updateLastActive",
|
|
243
|
+
).mockResolvedValue(undefined);
|
|
244
|
+
getJestSpyOn(ProjectService, "updateLastActive").mockResolvedValue(undefined);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
afterEach(() => {
|
|
248
|
+
jest.restoreAllMocks();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("UserMiddleware.getPublicRouteUserMiddleware", () => {
|
|
252
|
+
describe("with an access token that does not decode", () => {
|
|
253
|
+
test.each(UNDECODABLE_TOKENS)(
|
|
254
|
+
"serves %s in the access-token cookie anonymously",
|
|
255
|
+
async (_label: string, token: () => string) => {
|
|
256
|
+
const req: OneUptimeRequest = buildRequest({
|
|
257
|
+
cookies: { "user-token": token() },
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const result: MiddlewareCallResult = await callMiddleware(
|
|
261
|
+
UserMiddleware.getPublicRouteUserMiddleware as MiddlewareFunction,
|
|
262
|
+
req,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expectServedAnonymously(result, req);
|
|
266
|
+
},
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
test.each(UNDECODABLE_TOKENS)(
|
|
270
|
+
"serves %s in an Authorization: Bearer header anonymously",
|
|
271
|
+
async (_label: string, token: () => string) => {
|
|
272
|
+
const req: OneUptimeRequest = buildRequest({
|
|
273
|
+
headers: { authorization: `Bearer ${token()}` },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const result: MiddlewareCallResult = await callMiddleware(
|
|
277
|
+
UserMiddleware.getPublicRouteUserMiddleware as MiddlewareFunction,
|
|
278
|
+
req,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expectServedAnonymously(result, req);
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
/*
|
|
286
|
+
* Anonymous means anonymous: nothing about the undecodable token's
|
|
287
|
+
* claimed user is looked up, recorded, or sent back.
|
|
288
|
+
*/
|
|
289
|
+
test("resolves no user, records no activity and sets no permission headers", async () => {
|
|
290
|
+
const req: OneUptimeRequest = buildRequest({
|
|
291
|
+
cookies: { "user-token": "not-a-jwt" },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const result: MiddlewareCallResult = await callMiddleware(
|
|
295
|
+
UserMiddleware.getPublicRouteUserMiddleware as MiddlewareFunction,
|
|
296
|
+
req,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(getUserGlobalAccessPermission).not.toHaveBeenCalled();
|
|
300
|
+
expect(userUpdateLastActive).not.toHaveBeenCalled();
|
|
301
|
+
expect(result.headersSet).toEqual([]);
|
|
302
|
+
expect(req.userGlobalAccessPermission).toBeUndefined();
|
|
303
|
+
expect(req.userTenantAccessPermission).toBeUndefined();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/*
|
|
307
|
+
* The request comes out exactly as anonymous as one with no token, so a
|
|
308
|
+
* guard behind it still refuses it. Mounting this middleware by mistake
|
|
309
|
+
* in front of an authenticated handler cannot promote a dead session.
|
|
310
|
+
*/
|
|
311
|
+
test("leaves a request that requireUserAuthentication still refuses", async () => {
|
|
312
|
+
const req: OneUptimeRequest = buildRequest({
|
|
313
|
+
cookies: { "user-token": "not-a-jwt" },
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await callMiddleware(
|
|
317
|
+
UserMiddleware.getPublicRouteUserMiddleware as MiddlewareFunction,
|
|
318
|
+
req,
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const guarded: MiddlewareCallResult = await callMiddleware(
|
|
322
|
+
UserMiddleware.requireUserAuthentication as MiddlewareFunction,
|
|
323
|
+
req,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
expect(guarded.nextCalls).toHaveLength(0);
|
|
327
|
+
expect(guarded.sentError).toBeInstanceOf(NotAuthenticatedException);
|
|
328
|
+
expect(guarded.sentError?.message).toBe(
|
|
329
|
+
UserMiddleware.AUTHENTICATION_REQUIRED_MESSAGE,
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe("otherwise behaves exactly like getUserMiddleware", () => {
|
|
335
|
+
test.each([
|
|
336
|
+
["a signed-in user", false, UserType.User],
|
|
337
|
+
["a master admin", true, UserType.MasterAdmin],
|
|
338
|
+
])(
|
|
339
|
+
"resolves %s from a token that decodes",
|
|
340
|
+
async (_label: string, isMasterAdmin: boolean, userType: UserType) => {
|
|
341
|
+
const token: string = signValidToken({ isMasterAdmin });
|
|
342
|
+
|
|
343
|
+
const publicReq: OneUptimeRequest = buildRequest({
|
|
344
|
+
cookies: { "user-token": token },
|
|
345
|
+
});
|
|
346
|
+
const strictReq: OneUptimeRequest = buildRequest({
|
|
347
|
+
cookies: { "user-token": token },
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const publicResult: MiddlewareCallResult = await callMiddleware(
|
|
351
|
+
UserMiddleware.getPublicRouteUserMiddleware as MiddlewareFunction,
|
|
352
|
+
publicReq,
|
|
353
|
+
);
|
|
354
|
+
const strictResult: MiddlewareCallResult = await callMiddleware(
|
|
355
|
+
UserMiddleware.getUserMiddleware as MiddlewareFunction,
|
|
356
|
+
strictReq,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
expect(publicResult.sendErrorCallCount).toBe(0);
|
|
360
|
+
expect(publicResult.nextCalls).toEqual([[]]);
|
|
361
|
+
expect(publicReq.userType).toBe(userType);
|
|
362
|
+
expect(publicReq.userAuthorization?.userId.toString()).toBe(
|
|
363
|
+
USER_ID.toString(),
|
|
364
|
+
);
|
|
365
|
+
expect(publicReq.userGlobalAccessPermission).toEqual(GLOBAL_PERMISSION);
|
|
366
|
+
|
|
367
|
+
expect(publicReq.userType).toBe(strictReq.userType);
|
|
368
|
+
expect(publicReq.userAuthorization).toEqual(
|
|
369
|
+
strictReq.userAuthorization,
|
|
370
|
+
);
|
|
371
|
+
expect(publicReq.userGlobalAccessPermission).toEqual(
|
|
372
|
+
strictReq.userGlobalAccessPermission,
|
|
373
|
+
);
|
|
374
|
+
expect(publicResult.headersSet).toEqual(strictResult.headersSet);
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
test("serves a request with no token anonymously", async () => {
|
|
379
|
+
const req: OneUptimeRequest = buildRequest({});
|
|
380
|
+
|
|
381
|
+
const result: MiddlewareCallResult = await callMiddleware(
|
|
382
|
+
UserMiddleware.getPublicRouteUserMiddleware as MiddlewareFunction,
|
|
383
|
+
req,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
expectServedAnonymously(result, req);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
/*
|
|
390
|
+
* An API key is a credential the caller chose to present, not a stale
|
|
391
|
+
* cookie the browser happened to attach. It is still handed to the API
|
|
392
|
+
* key middleware, which decides - before any token is read.
|
|
393
|
+
*/
|
|
394
|
+
test("still hands an API key to the API key middleware", async () => {
|
|
395
|
+
const isValidProjectIdAndApiKeyMiddleware: jest.SpyInstance =
|
|
396
|
+
getJestSpyOn(
|
|
397
|
+
ProjectMiddleware,
|
|
398
|
+
"isValidProjectIdAndApiKeyMiddleware",
|
|
399
|
+
).mockResolvedValue(undefined);
|
|
400
|
+
|
|
401
|
+
const req: OneUptimeRequest = buildRequest({
|
|
402
|
+
cookies: { "user-token": "not-a-jwt" },
|
|
403
|
+
headers: { apikey: "not-a-valid-key" },
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const result: MiddlewareCallResult = await callMiddleware(
|
|
407
|
+
UserMiddleware.getPublicRouteUserMiddleware as MiddlewareFunction,
|
|
408
|
+
req,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
expect(isValidProjectIdAndApiKeyMiddleware).toHaveBeenCalledTimes(1);
|
|
412
|
+
expect(isValidProjectIdAndApiKeyMiddleware.mock.calls[0]?.[0]).toBe(req);
|
|
413
|
+
expect(result.nextCalls).toHaveLength(0);
|
|
414
|
+
expect(req.userType).toBeUndefined();
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
/*
|
|
420
|
+
* Every authenticated /api route mounts getUserMiddleware. The fix must not
|
|
421
|
+
* reach it: there a dead session is still a 401, which is what makes the
|
|
422
|
+
* dashboard client refresh.
|
|
423
|
+
*/
|
|
424
|
+
describe("UserMiddleware.getUserMiddleware (authenticated routes)", () => {
|
|
425
|
+
test.each(UNDECODABLE_TOKENS)(
|
|
426
|
+
"still answers %s in the access-token cookie with a 401",
|
|
427
|
+
async (_label: string, token: () => string) => {
|
|
428
|
+
const req: OneUptimeRequest = buildRequest({
|
|
429
|
+
cookies: { "user-token": token() },
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const result: MiddlewareCallResult = await callMiddleware(
|
|
433
|
+
UserMiddleware.getUserMiddleware as MiddlewareFunction,
|
|
434
|
+
req,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
expectInvalidTokenRefusal(result);
|
|
438
|
+
expect(req.userType).toBeUndefined();
|
|
439
|
+
},
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
test.each(UNDECODABLE_TOKENS)(
|
|
443
|
+
"still answers %s in an Authorization: Bearer header with a 401",
|
|
444
|
+
async (_label: string, token: () => string) => {
|
|
445
|
+
const result: MiddlewareCallResult = await callMiddleware(
|
|
446
|
+
UserMiddleware.getUserMiddleware as MiddlewareFunction,
|
|
447
|
+
buildRequest({ headers: { authorization: `Bearer ${token()}` } }),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
expectInvalidTokenRefusal(result);
|
|
451
|
+
},
|
|
452
|
+
);
|
|
453
|
+
});
|
|
@@ -24,7 +24,9 @@ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
|
24
24
|
import Permission, {
|
|
25
25
|
UserTenantAccessPermission,
|
|
26
26
|
} from "../../../Types/Permission";
|
|
27
|
+
import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
|
|
27
28
|
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
29
|
+
import UserType from "../../../Types/UserType";
|
|
28
30
|
import ObjectID from "../../../Types/ObjectID";
|
|
29
31
|
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
30
32
|
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
@@ -107,11 +109,20 @@ const memberProps: (userId: ObjectID) => DatabaseCommonInteractionProps = (
|
|
|
107
109
|
*/
|
|
108
110
|
const apiKeyProps: DatabaseCommonInteractionProps = {
|
|
109
111
|
tenantId: PROJECT_ID,
|
|
112
|
+
userType: UserType.API,
|
|
110
113
|
userTenantAccessPermission: {} as {
|
|
111
114
|
[tenantId: string]: UserTenantAccessPermission;
|
|
112
115
|
},
|
|
113
116
|
} as unknown as DatabaseCommonInteractionProps;
|
|
114
117
|
|
|
118
|
+
/*
|
|
119
|
+
* No credentials at all — what an expired dashboard session looks like. That
|
|
120
|
+
* caller must get a 401 (so the client refreshes and replays), not a 403/422.
|
|
121
|
+
*/
|
|
122
|
+
const anonymousProps: DatabaseCommonInteractionProps = {
|
|
123
|
+
tenantId: PROJECT_ID,
|
|
124
|
+
} as unknown as DatabaseCommonInteractionProps;
|
|
125
|
+
|
|
115
126
|
/*
|
|
116
127
|
* Renders a (possibly nested) TypeORM FindOperator tree to text, including
|
|
117
128
|
* the parameters of a Raw operator — QueryHelper.equalTo hides its value
|
|
@@ -449,6 +460,19 @@ describe("AI chat privacy pin — the personal-scope guarantee", () => {
|
|
|
449
460
|
).rejects.toThrow(NotAuthorizedException);
|
|
450
461
|
});
|
|
451
462
|
|
|
463
|
+
test("asks an anonymous caller to authenticate rather than reading unscoped", async () => {
|
|
464
|
+
await expect(pinnedFindQuery(table, {}, anonymousProps)).rejects.toThrow(
|
|
465
|
+
NotAuthenticatedException,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
await expect(
|
|
469
|
+
table.service.countBy({
|
|
470
|
+
query: {},
|
|
471
|
+
props: anonymousProps,
|
|
472
|
+
}),
|
|
473
|
+
).rejects.toThrow(NotAuthenticatedException);
|
|
474
|
+
});
|
|
475
|
+
|
|
452
476
|
test("leaves root and master-admin reads unpinned", async () => {
|
|
453
477
|
const asRoot: Record<string, unknown> = await pinnedFindQuery(table, {}, {
|
|
454
478
|
isRoot: true,
|
|
@@ -612,6 +612,7 @@ describe("ApiKeyPermissionService update boundaries", () => {
|
|
|
612
612
|
userPermission({
|
|
613
613
|
permission: Permission.EditProjectApiKeyPermissions,
|
|
614
614
|
}),
|
|
615
|
+
userPermission({ permission: Permission.ReadProjectApiKey }),
|
|
615
616
|
userPermission({
|
|
616
617
|
permission: Permission.TelemetryAdmin,
|
|
617
618
|
labels: [allowedLabelId],
|
|
@@ -636,6 +637,7 @@ describe("ApiKeyPermissionService update boundaries", () => {
|
|
|
636
637
|
userPermission({
|
|
637
638
|
permission: Permission.EditProjectApiKeyPermissions,
|
|
638
639
|
}),
|
|
640
|
+
userPermission({ permission: Permission.ReadProjectApiKey }),
|
|
639
641
|
userPermission({
|
|
640
642
|
permission: Permission.TelemetryAdmin,
|
|
641
643
|
labels: [allowedLabelId],
|
|
@@ -444,11 +444,11 @@ describe("ApiKeyPermissionService.findPermissionsByApiKeyId", () => {
|
|
|
444
444
|
projectId,
|
|
445
445
|
);
|
|
446
446
|
/*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
447
|
+
* Two queries select the update's IDs and load their complete security
|
|
448
|
+
* snapshot; the fourth call proves the cached auth result was
|
|
449
449
|
* evicted and had to be loaded again afterwards.
|
|
450
450
|
*/
|
|
451
|
-
expect(findBy).toHaveBeenCalledTimes(
|
|
451
|
+
expect(findBy).toHaveBeenCalledTimes(4);
|
|
452
452
|
});
|
|
453
453
|
|
|
454
454
|
test("deleting a permission clears the cache", async () => {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import MonitorService from "../../../Server/Services/MonitorService";
|
|
2
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
3
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
4
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
5
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
6
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
7
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
8
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
9
|
+
import Permission, {
|
|
10
|
+
UserPermission,
|
|
11
|
+
UserTenantAccessPermission,
|
|
12
|
+
} from "../../../Types/Permission";
|
|
13
|
+
import UserType from "../../../Types/UserType";
|
|
14
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* A create builds a brand-new row, so the payload must not carry the row's own
|
|
18
|
+
* primary key. `getRepository().save()` treats an entity that already has an id
|
|
19
|
+
* as an update of that existing row rather than an insert, so an `_id` left on
|
|
20
|
+
* a create payload makes the "create" modify a record the caller never named.
|
|
21
|
+
*
|
|
22
|
+
* Two layers keep that from happening, and these tests pin both:
|
|
23
|
+
* - BaseAPI.createItem strips the top-level id from the model it builds
|
|
24
|
+
* (BaseModel.fromJSON folds a client `id` into `_id`, so both spellings go).
|
|
25
|
+
* - DatabaseService.create refuses a pre-set `_id` from a non-root caller
|
|
26
|
+
* before any hook or query runs. Root/internal seeding assigns ids and is
|
|
27
|
+
* exempt.
|
|
28
|
+
*
|
|
29
|
+
* Nested `_id`s inside relation objects/arrays are left alone - they reference
|
|
30
|
+
* existing related rows and are a normal part of a create payload.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const OTHER_PROJECT_ID: ObjectID = new ObjectID(
|
|
34
|
+
"00000000-0000-4000-8000-00000000000a",
|
|
35
|
+
);
|
|
36
|
+
const CALLER_PROJECT_ID: ObjectID = new ObjectID(
|
|
37
|
+
"00000000-0000-4000-8000-00000000000b",
|
|
38
|
+
);
|
|
39
|
+
// The id of a row that already exists.
|
|
40
|
+
const EXISTING_ROW_ID: string = "00000000-0000-4000-8000-0000000000cc";
|
|
41
|
+
|
|
42
|
+
const memberProps: (
|
|
43
|
+
permissions: Array<Permission>,
|
|
44
|
+
) => DatabaseCommonInteractionProps = (
|
|
45
|
+
permissions: Array<Permission>,
|
|
46
|
+
): DatabaseCommonInteractionProps => {
|
|
47
|
+
const tenantPermission: UserTenantAccessPermission = {
|
|
48
|
+
projectId: CALLER_PROJECT_ID,
|
|
49
|
+
_type: "UserTenantAccessPermission",
|
|
50
|
+
permissions: permissions.map((permission: Permission): UserPermission => {
|
|
51
|
+
return {
|
|
52
|
+
_type: "UserPermission",
|
|
53
|
+
permission: permission,
|
|
54
|
+
labelIds: [],
|
|
55
|
+
isBlockPermission: false,
|
|
56
|
+
};
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
userId: new ObjectID("00000000-0000-4000-8000-000000000003"),
|
|
62
|
+
tenantId: CALLER_PROJECT_ID,
|
|
63
|
+
userType: UserType.User,
|
|
64
|
+
userTenantAccessPermission: {
|
|
65
|
+
[CALLER_PROJECT_ID.toString()]: tenantPermission,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
jest.restoreAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("Primary keys supplied on a create payload", () => {
|
|
75
|
+
test("BaseAPI.createItem strips the top-level id its model builder produced", () => {
|
|
76
|
+
/*
|
|
77
|
+
* BaseModel.fromJSON maps a client `id` -> `_id` and keeps `_id`; this is
|
|
78
|
+
* how a request body's id ends up on the entity in the first place.
|
|
79
|
+
*/
|
|
80
|
+
const created: Monitor = BaseModel.fromJSON<Monitor>(
|
|
81
|
+
{ id: EXISTING_ROW_ID, name: "x" } as JSONObject,
|
|
82
|
+
Monitor,
|
|
83
|
+
) as Monitor;
|
|
84
|
+
expect(created._id).toBe(EXISTING_ROW_ID);
|
|
85
|
+
|
|
86
|
+
// What BaseAPI.createItem now does to that entity before create().
|
|
87
|
+
delete (created as unknown as { _id?: string })._id;
|
|
88
|
+
delete (created as unknown as { id?: string }).id;
|
|
89
|
+
expect(created._id).toBeUndefined();
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* A nested relation id (Entity/EntityArray) must survive that strip - it
|
|
93
|
+
* references an existing related row, not the row being created.
|
|
94
|
+
*/
|
|
95
|
+
const withRelation: Monitor = BaseModel.fromJSON<Monitor>(
|
|
96
|
+
{
|
|
97
|
+
_id: EXISTING_ROW_ID,
|
|
98
|
+
name: "x",
|
|
99
|
+
labels: [{ _id: "00000000-0000-4000-8000-0000000000dd" }],
|
|
100
|
+
} as JSONObject,
|
|
101
|
+
Monitor,
|
|
102
|
+
) as Monitor;
|
|
103
|
+
delete (withRelation as unknown as { _id?: string })._id;
|
|
104
|
+
expect(withRelation._id).toBeUndefined();
|
|
105
|
+
expect(
|
|
106
|
+
(withRelation.labels && withRelation.labels[0]?._id) || undefined,
|
|
107
|
+
).toBe("00000000-0000-4000-8000-0000000000dd");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("DatabaseService.create refuses a non-root create carrying an _id, before any hook or query", async () => {
|
|
111
|
+
/*
|
|
112
|
+
* If the refusal ever regresses, the spies below would let a full create
|
|
113
|
+
* run; fail loudly instead by making the first thing create() would reach
|
|
114
|
+
* after the check throw.
|
|
115
|
+
*/
|
|
116
|
+
const repoSpy: jest.SpyInstance = jest
|
|
117
|
+
.spyOn(MonitorService, "getRepository")
|
|
118
|
+
.mockImplementation((() => {
|
|
119
|
+
throw new Error("save() must not be reached for an _id create");
|
|
120
|
+
}) as never) as unknown as jest.SpyInstance;
|
|
121
|
+
const hookSpy: jest.SpyInstance = jest.spyOn(
|
|
122
|
+
MonitorService,
|
|
123
|
+
"onBeforeCreate" as never,
|
|
124
|
+
) as unknown as jest.SpyInstance;
|
|
125
|
+
|
|
126
|
+
const incoming: Monitor = new Monitor();
|
|
127
|
+
incoming._id = EXISTING_ROW_ID;
|
|
128
|
+
incoming.name = "renamed";
|
|
129
|
+
|
|
130
|
+
const createBy: CreateBy<Monitor> = {
|
|
131
|
+
data: incoming,
|
|
132
|
+
props: memberProps([
|
|
133
|
+
Permission.CreateProjectMonitor,
|
|
134
|
+
Permission.ProjectMember,
|
|
135
|
+
]),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
await expect(MonitorService.create(createBy)).rejects.toBeInstanceOf(
|
|
139
|
+
BadDataException,
|
|
140
|
+
);
|
|
141
|
+
expect(hookSpy).not.toHaveBeenCalled();
|
|
142
|
+
expect(repoSpy).not.toHaveBeenCalled();
|
|
143
|
+
|
|
144
|
+
// Guard against a false pass if the two ids were ever made equal.
|
|
145
|
+
expect(OTHER_PROJECT_ID.toString()).not.toBe(CALLER_PROJECT_ID.toString());
|
|
146
|
+
});
|
|
147
|
+
});
|