@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
|
@@ -9,7 +9,10 @@ import Permission, {
|
|
|
9
9
|
} from "../../../Types/Permission";
|
|
10
10
|
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
11
11
|
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
12
|
+
import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
|
|
12
13
|
import Exception from "../../../Types/Exception/Exception";
|
|
14
|
+
import ExceptionCode from "../../../Types/Exception/ExceptionCode";
|
|
15
|
+
import UserType from "../../../Types/UserType";
|
|
13
16
|
|
|
14
17
|
/*
|
|
15
18
|
* Tests for CommonAPI.assertAuthenticatedProjectMember — the shared auth
|
|
@@ -18,6 +21,18 @@ import Exception from "../../../Types/Exception/Exception";
|
|
|
18
21
|
* through as "public" and takes the tenant id from a caller-supplied
|
|
19
22
|
* header, so this guard must reject anything that is not an authenticated
|
|
20
23
|
* member of the tenant project.
|
|
24
|
+
*
|
|
25
|
+
* HOW it rejects matters as much as THAT it rejects. A caller with no
|
|
26
|
+
* credentials at all gets NotAuthenticatedException (401) before anything
|
|
27
|
+
* else is looked at, including the tenant header; every other refusal keeps
|
|
28
|
+
* its old status (400 for a missing tenant, 422 for "not a member" / "not
|
|
29
|
+
* permitted"). That split is what fixed the customer report: the dashboard's
|
|
30
|
+
* access-token cookie expires with the JWT inside it, so an idle tab's next
|
|
31
|
+
* request arrives anonymous, and the browser client only refreshes the
|
|
32
|
+
* session and replays the request on a 401. Answering that request with a
|
|
33
|
+
* 422 left a signed-in admin staring at "You are not authorized to access
|
|
34
|
+
* this project's data." Assertions below therefore pin the exception class
|
|
35
|
+
* AND its code, because the code is the HTTP status the client acts on.
|
|
21
36
|
*/
|
|
22
37
|
|
|
23
38
|
function buildTenantPermission(
|
|
@@ -41,6 +56,7 @@ function buildTenantPermission(
|
|
|
41
56
|
function buildProps(overrides?: {
|
|
42
57
|
tenantId?: ObjectID | undefined;
|
|
43
58
|
userId?: ObjectID | undefined;
|
|
59
|
+
userType?: UserType | undefined;
|
|
44
60
|
userTenantAccessPermission?:
|
|
45
61
|
| Dictionary<UserTenantAccessPermission>
|
|
46
62
|
| undefined;
|
|
@@ -48,11 +64,29 @@ function buildProps(overrides?: {
|
|
|
48
64
|
const props: DatabaseCommonInteractionProps = {
|
|
49
65
|
tenantId: overrides?.tenantId,
|
|
50
66
|
userId: overrides?.userId,
|
|
67
|
+
userType: overrides?.userType,
|
|
51
68
|
userTenantAccessPermission: overrides?.userTenantAccessPermission,
|
|
52
69
|
};
|
|
53
70
|
return props;
|
|
54
71
|
}
|
|
55
72
|
|
|
73
|
+
/*
|
|
74
|
+
* The props a project API key produces. ProjectMiddleware sets userType API,
|
|
75
|
+
* the tenant the KEY belongs to, and the permissions granted to the key - and
|
|
76
|
+
* never a userId, which is the whole reason API keys need their own cases in
|
|
77
|
+
* this file: "no userId" alone does not mean "anonymous".
|
|
78
|
+
*/
|
|
79
|
+
function buildApiKeyProps(projectId: ObjectID): DatabaseCommonInteractionProps {
|
|
80
|
+
return buildProps({
|
|
81
|
+
tenantId: projectId,
|
|
82
|
+
userId: undefined,
|
|
83
|
+
userType: UserType.API,
|
|
84
|
+
userTenantAccessPermission: {
|
|
85
|
+
[projectId.toString()]: buildTenantPermission(projectId),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
56
90
|
/*
|
|
57
91
|
* The guard is synchronous, so failures are captured with try/catch (via
|
|
58
92
|
* this helper) or expect(() => ...).toThrow — never .rejects.
|
|
@@ -66,15 +100,66 @@ function captureThrown(fn: () => void): unknown {
|
|
|
66
100
|
return undefined;
|
|
67
101
|
}
|
|
68
102
|
|
|
103
|
+
/*
|
|
104
|
+
* The refusal an expired session must get: NotAuthenticatedException, code
|
|
105
|
+
* 401, with the shared message. Checked on the class, the code and the text,
|
|
106
|
+
* because each is load-bearing somewhere different - the class for the
|
|
107
|
+
* server's error middleware, the code for the HTTP status the browser client
|
|
108
|
+
* keys its refresh-and-replay on, the text for what a user sees if the
|
|
109
|
+
* refresh itself fails.
|
|
110
|
+
*/
|
|
111
|
+
function expectAuthenticationRequired(thrown: unknown): void {
|
|
112
|
+
expect(thrown).toBeInstanceOf(NotAuthenticatedException);
|
|
113
|
+
expect(thrown).not.toBeInstanceOf(NotAuthorizedException);
|
|
114
|
+
expect(thrown).not.toBeInstanceOf(BadDataException);
|
|
115
|
+
expect((thrown as Exception).code).toBe(
|
|
116
|
+
ExceptionCode.NotAuthenticatedException,
|
|
117
|
+
);
|
|
118
|
+
expect((thrown as Exception).code).toBe(401);
|
|
119
|
+
expect((thrown as Exception).message).toBe(
|
|
120
|
+
CommonAPI.AUTHENTICATION_REQUIRED_MESSAGE,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// An authenticated caller refused access: the pre-existing 422.
|
|
125
|
+
function expectNotAuthorized(thrown: unknown, message?: string): void {
|
|
126
|
+
expect(thrown).toBeInstanceOf(NotAuthorizedException);
|
|
127
|
+
expect(thrown).not.toBeInstanceOf(NotAuthenticatedException);
|
|
128
|
+
expect((thrown as Exception).code).toBe(ExceptionCode.NotAuthorizedException);
|
|
129
|
+
expect((thrown as Exception).code).toBe(422);
|
|
130
|
+
|
|
131
|
+
if (message !== undefined) {
|
|
132
|
+
expect((thrown as Exception).message).toBe(message);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// An authenticated caller that named no project: the pre-existing 400.
|
|
137
|
+
function expectBadData(thrown: unknown, messageFragment: string): void {
|
|
138
|
+
expect(thrown).toBeInstanceOf(BadDataException);
|
|
139
|
+
expect((thrown as Exception).code).toBe(ExceptionCode.BadDataException);
|
|
140
|
+
expect((thrown as Exception).code).toBe(400);
|
|
141
|
+
expect((thrown as Exception).message).toContain(messageFragment);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const NOT_AUTHORIZED_MESSAGE: string =
|
|
145
|
+
"You are not authorized to access this project's data.";
|
|
146
|
+
|
|
69
147
|
afterEach(() => {
|
|
70
148
|
jest.restoreAllMocks();
|
|
71
149
|
});
|
|
72
150
|
|
|
73
151
|
describe("CommonAPI.assertAuthenticatedProjectMember", () => {
|
|
74
152
|
describe("missing tenant id", () => {
|
|
75
|
-
|
|
153
|
+
/*
|
|
154
|
+
* The caller is logged in (a userId), so this pins the 400 an
|
|
155
|
+
* authenticated caller has always had for forgetting the header. The same
|
|
156
|
+
* request WITHOUT a userId is an expired session and is pinned as a 401
|
|
157
|
+
* below; before the credential check existed both got this 400.
|
|
158
|
+
*/
|
|
159
|
+
test("an authenticated user with no tenantId gets BadDataException 'Project ID is required' (400)", () => {
|
|
76
160
|
const props: DatabaseCommonInteractionProps = buildProps({
|
|
77
161
|
tenantId: undefined,
|
|
162
|
+
userId: ObjectID.generate(),
|
|
78
163
|
});
|
|
79
164
|
|
|
80
165
|
expect(() => {
|
|
@@ -83,9 +168,16 @@ describe("CommonAPI.assertAuthenticatedProjectMember", () => {
|
|
|
83
168
|
expect(() => {
|
|
84
169
|
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
85
170
|
}).toThrow("Project ID is required");
|
|
171
|
+
|
|
172
|
+
expectBadData(
|
|
173
|
+
captureThrown(() => {
|
|
174
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
175
|
+
}),
|
|
176
|
+
"Project ID is required",
|
|
177
|
+
);
|
|
86
178
|
});
|
|
87
179
|
|
|
88
|
-
test("missing tenantId wins over
|
|
180
|
+
test("missing tenantId wins over the membership check — a user holding permissions for some project still gets BadDataException", () => {
|
|
89
181
|
const projectId: ObjectID = ObjectID.generate();
|
|
90
182
|
const props: DatabaseCommonInteractionProps = buildProps({
|
|
91
183
|
tenantId: undefined,
|
|
@@ -106,17 +198,31 @@ describe("CommonAPI.assertAuthenticatedProjectMember", () => {
|
|
|
106
198
|
);
|
|
107
199
|
});
|
|
108
200
|
|
|
109
|
-
|
|
201
|
+
/*
|
|
202
|
+
* Was a BadDataException (400) before the credential check. An empty
|
|
203
|
+
* props object is the most anonymous caller there is, and "who are you?"
|
|
204
|
+
* now comes before "which project?": a 400 would not make the browser
|
|
205
|
+
* refresh the session, a 401 does.
|
|
206
|
+
*/
|
|
207
|
+
test("a completely empty props object is anonymous: NotAuthenticatedException (401), not the missing-tenant 400", () => {
|
|
110
208
|
const props: DatabaseCommonInteractionProps = {};
|
|
111
209
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
210
|
+
expectAuthenticationRequired(
|
|
211
|
+
captureThrown(() => {
|
|
212
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
115
215
|
});
|
|
116
216
|
});
|
|
117
217
|
|
|
118
218
|
describe("unauthenticated / unauthorized callers", () => {
|
|
119
|
-
|
|
219
|
+
/*
|
|
220
|
+
* The customer's request, exactly: the tab sat idle, the access-token
|
|
221
|
+
* cookie expired and stopped being sent, so getUserMiddleware produced a
|
|
222
|
+
* Public request that still carries the tenant header. Previously a 422
|
|
223
|
+
* "not authorized"; now a 401 the client answers by refreshing.
|
|
224
|
+
*/
|
|
225
|
+
test("tenantId present but no userId and no credentialed userType (an expired session) throws NotAuthenticatedException (401)", () => {
|
|
120
226
|
const projectId: ObjectID = ObjectID.generate();
|
|
121
227
|
const props: DatabaseCommonInteractionProps = buildProps({
|
|
122
228
|
tenantId: projectId,
|
|
@@ -126,13 +232,43 @@ describe("CommonAPI.assertAuthenticatedProjectMember", () => {
|
|
|
126
232
|
},
|
|
127
233
|
});
|
|
128
234
|
|
|
129
|
-
|
|
130
|
-
|
|
235
|
+
expectAuthenticationRequired(
|
|
236
|
+
captureThrown(() => {
|
|
237
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("an explicit UserType.Public caller with a tenant header gets the same 401", () => {
|
|
243
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
244
|
+
const props: DatabaseCommonInteractionProps = buildProps({
|
|
245
|
+
tenantId: projectId,
|
|
246
|
+
userType: UserType.Public,
|
|
131
247
|
});
|
|
132
248
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
249
|
+
expectAuthenticationRequired(
|
|
250
|
+
captureThrown(() => {
|
|
251
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* The original intent of the "no userId" case, kept: a caller with tenant
|
|
258
|
+
* permissions but no user is still not a logged-in member. A project API
|
|
259
|
+
* key IS authenticated, though, so it keeps the 422 - a 401 would send an
|
|
260
|
+
* API client off to refresh a session it never had.
|
|
261
|
+
*/
|
|
262
|
+
test("a project API key (tenant permissions, no userId) is still refused with NotAuthorizedException (422)", () => {
|
|
263
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
264
|
+
|
|
265
|
+
expectNotAuthorized(
|
|
266
|
+
captureThrown(() => {
|
|
267
|
+
CommonAPI.assertAuthenticatedProjectMember(
|
|
268
|
+
buildApiKeyProps(projectId),
|
|
269
|
+
);
|
|
270
|
+
}),
|
|
271
|
+
NOT_AUTHORIZED_MESSAGE,
|
|
136
272
|
);
|
|
137
273
|
});
|
|
138
274
|
|
|
@@ -198,7 +334,8 @@ describe("CommonAPI.assertAuthenticatedProjectMember", () => {
|
|
|
198
334
|
expect(thrown).toBeInstanceOf(Error);
|
|
199
335
|
});
|
|
200
336
|
|
|
201
|
-
|
|
337
|
+
// Was NotAuthorizedException (422) before the credential check.
|
|
338
|
+
test("no userId AND no permission map (public caller) throws NotAuthenticatedException (401)", () => {
|
|
202
339
|
const props: DatabaseCommonInteractionProps = buildProps({
|
|
203
340
|
tenantId: ObjectID.generate(),
|
|
204
341
|
userId: undefined,
|
|
@@ -207,10 +344,16 @@ describe("CommonAPI.assertAuthenticatedProjectMember", () => {
|
|
|
207
344
|
|
|
208
345
|
expect(() => {
|
|
209
346
|
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
210
|
-
}).toThrow(
|
|
347
|
+
}).toThrow(NotAuthenticatedException);
|
|
211
348
|
expect(() => {
|
|
212
349
|
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
213
|
-
}).toThrow(
|
|
350
|
+
}).toThrow(CommonAPI.AUTHENTICATION_REQUIRED_MESSAGE);
|
|
351
|
+
|
|
352
|
+
expectAuthenticationRequired(
|
|
353
|
+
captureThrown(() => {
|
|
354
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
214
357
|
});
|
|
215
358
|
});
|
|
216
359
|
|
|
@@ -307,11 +450,118 @@ describe("CommonAPI.assertAuthenticatedProjectMember", () => {
|
|
|
307
450
|
/*
|
|
308
451
|
* Pin current behavior: the guard checks userId + the tenant access
|
|
309
452
|
* permission map ONLY — root / master-admin flags do not grant
|
|
310
|
-
* access to these endpoints.
|
|
453
|
+
* access to these endpoints. They do not count as credentials either:
|
|
454
|
+
* with no userId and no API / MasterAdmin userType this is an anonymous
|
|
455
|
+
* caller, so the refusal is now the 401 (it was a 422 before the
|
|
456
|
+
* credential check existed). Still refused, which is what matters.
|
|
311
457
|
*/
|
|
312
458
|
expect(() => {
|
|
313
459
|
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
314
|
-
}).toThrow(
|
|
460
|
+
}).toThrow(NotAuthenticatedException);
|
|
461
|
+
|
|
462
|
+
expectAuthenticationRequired(
|
|
463
|
+
captureThrown(() => {
|
|
464
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
465
|
+
}),
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("a logged-in member keeps working when the request also carries isRoot / isMasterAdmin flags", () => {
|
|
470
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
471
|
+
const props: DatabaseCommonInteractionProps = {
|
|
472
|
+
...buildProps({
|
|
473
|
+
tenantId: projectId,
|
|
474
|
+
userId: ObjectID.generate(),
|
|
475
|
+
userTenantAccessPermission: {
|
|
476
|
+
[projectId.toString()]: buildTenantPermission(projectId),
|
|
477
|
+
},
|
|
478
|
+
}),
|
|
479
|
+
isRoot: true,
|
|
480
|
+
isMasterAdmin: true,
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
expect(CommonAPI.assertAuthenticatedProjectMember(props)).toBe(projectId);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
/*
|
|
488
|
+
* The credential check is the FIRST thing the guard does, and each refusal
|
|
489
|
+
* a caller can get has a fixed status. These tests pin the precedence
|
|
490
|
+
* directly: every pair differs in exactly one fact about the caller.
|
|
491
|
+
*/
|
|
492
|
+
describe("refusal precedence and status codes", () => {
|
|
493
|
+
test("anonymous with no tenant header: 401 wins over the missing-tenant 400", () => {
|
|
494
|
+
expectAuthenticationRequired(
|
|
495
|
+
captureThrown(() => {
|
|
496
|
+
CommonAPI.assertAuthenticatedProjectMember(
|
|
497
|
+
buildProps({ tenantId: undefined, userId: undefined }),
|
|
498
|
+
);
|
|
499
|
+
}),
|
|
500
|
+
);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("authenticated with no tenant header: still the 400", () => {
|
|
504
|
+
expectBadData(
|
|
505
|
+
captureThrown(() => {
|
|
506
|
+
CommonAPI.assertAuthenticatedProjectMember(
|
|
507
|
+
buildProps({
|
|
508
|
+
tenantId: undefined,
|
|
509
|
+
userId: ObjectID.generate(),
|
|
510
|
+
userType: UserType.User,
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
513
|
+
}),
|
|
514
|
+
"Project ID is required",
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("an API key with no tenant: the 400, because a key is a credential", () => {
|
|
519
|
+
expectBadData(
|
|
520
|
+
captureThrown(() => {
|
|
521
|
+
CommonAPI.assertAuthenticatedProjectMember(
|
|
522
|
+
buildProps({ tenantId: undefined, userType: UserType.API }),
|
|
523
|
+
);
|
|
524
|
+
}),
|
|
525
|
+
"Project ID is required",
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test("a logged-in member of ANOTHER project: still the 422", () => {
|
|
530
|
+
const requestedProjectId: ObjectID = ObjectID.generate();
|
|
531
|
+
const otherProjectId: ObjectID = ObjectID.generate();
|
|
532
|
+
|
|
533
|
+
expectNotAuthorized(
|
|
534
|
+
captureThrown(() => {
|
|
535
|
+
CommonAPI.assertAuthenticatedProjectMember(
|
|
536
|
+
buildProps({
|
|
537
|
+
tenantId: requestedProjectId,
|
|
538
|
+
userId: ObjectID.generate(),
|
|
539
|
+
userType: UserType.User,
|
|
540
|
+
userTenantAccessPermission: {
|
|
541
|
+
[otherProjectId.toString()]:
|
|
542
|
+
buildTenantPermission(otherProjectId),
|
|
543
|
+
},
|
|
544
|
+
}),
|
|
545
|
+
);
|
|
546
|
+
}),
|
|
547
|
+
NOT_AUTHORIZED_MESSAGE,
|
|
548
|
+
);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("a master-admin session with no userId is credentialed, so it gets the member check's 422 rather than a 401", () => {
|
|
552
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
553
|
+
|
|
554
|
+
expectNotAuthorized(
|
|
555
|
+
captureThrown(() => {
|
|
556
|
+
CommonAPI.assertAuthenticatedProjectMember(
|
|
557
|
+
buildProps({
|
|
558
|
+
tenantId: projectId,
|
|
559
|
+
userType: UserType.MasterAdmin,
|
|
560
|
+
}),
|
|
561
|
+
);
|
|
562
|
+
}),
|
|
563
|
+
NOT_AUTHORIZED_MESSAGE,
|
|
564
|
+
);
|
|
315
565
|
});
|
|
316
566
|
});
|
|
317
567
|
});
|
|
@@ -417,8 +667,13 @@ describe("CommonAPI.assertTenantScoped", () => {
|
|
|
417
667
|
});
|
|
418
668
|
|
|
419
669
|
test("the message points at the header rather than at permissions", () => {
|
|
670
|
+
/*
|
|
671
|
+
* An authenticated caller: with no credentials at all the guard now asks
|
|
672
|
+
* for a login instead (pinned below), so `{}` no longer reaches the
|
|
673
|
+
* header message this test is about.
|
|
674
|
+
*/
|
|
420
675
|
const thrown: unknown = captureThrown(() => {
|
|
421
|
-
CommonAPI.assertTenantScoped({});
|
|
676
|
+
CommonAPI.assertTenantScoped(buildProps({ userId: ObjectID.generate() }));
|
|
422
677
|
});
|
|
423
678
|
|
|
424
679
|
/*
|
|
@@ -429,6 +684,15 @@ describe("CommonAPI.assertTenantScoped", () => {
|
|
|
429
684
|
expect((thrown as Exception).message).not.toContain("permission");
|
|
430
685
|
});
|
|
431
686
|
|
|
687
|
+
test("an API key with no tenant gets the header message too", () => {
|
|
688
|
+
expectBadData(
|
|
689
|
+
captureThrown(() => {
|
|
690
|
+
CommonAPI.assertTenantScoped(buildProps({ userType: UserType.API }));
|
|
691
|
+
}),
|
|
692
|
+
"tenantid",
|
|
693
|
+
);
|
|
694
|
+
});
|
|
695
|
+
|
|
432
696
|
test("returns the tenant id when the request is project scoped", () => {
|
|
433
697
|
const projectId: ObjectID = ObjectID.generate();
|
|
434
698
|
const props: DatabaseCommonInteractionProps = buildProps({
|
|
@@ -450,33 +714,234 @@ describe("CommonAPI.assertTenantScoped", () => {
|
|
|
450
714
|
const permissions: Dictionary<UserTenantAccessPermission> = {};
|
|
451
715
|
permissions[projectId.toString()] = buildTenantPermission(projectId);
|
|
452
716
|
|
|
717
|
+
/*
|
|
718
|
+
* userType API is what ProjectMiddleware sets for a key, and it is now
|
|
719
|
+
* what tells the guard this caller is credentialed. Without it the same
|
|
720
|
+
* props are an anonymous request (see the 401 cases below).
|
|
721
|
+
*/
|
|
453
722
|
const props: DatabaseCommonInteractionProps = buildProps({
|
|
454
723
|
tenantId: projectId,
|
|
455
724
|
userId: undefined,
|
|
725
|
+
userType: UserType.API,
|
|
456
726
|
userTenantAccessPermission: permissions,
|
|
457
727
|
});
|
|
458
728
|
|
|
459
729
|
expect(() => {
|
|
460
730
|
CommonAPI.assertTenantScoped(props);
|
|
461
731
|
}).not.toThrow();
|
|
732
|
+
expect(CommonAPI.assertTenantScoped(props)).toBe(projectId);
|
|
462
733
|
});
|
|
463
734
|
|
|
464
735
|
test("does not require membership — that stays with the tenant-scoped read", () => {
|
|
465
736
|
/*
|
|
466
737
|
* assertTenantScoped is a diagnostic for a missing header, not an
|
|
467
|
-
* authorization check. A caller who sends a tenant id for a
|
|
468
|
-
* are not a member of passes here and is rejected by the
|
|
469
|
-
* adding this guard cannot loosen or tighten access
|
|
738
|
+
* authorization check. A logged-in caller who sends a tenant id for a
|
|
739
|
+
* project they are not a member of passes here and is rejected by the
|
|
740
|
+
* read itself, so adding this guard cannot loosen or tighten access for
|
|
741
|
+
* an authenticated caller. (This used to be shown with a caller carrying
|
|
742
|
+
* no userId at all; that caller is anonymous and is now a 401, below.)
|
|
470
743
|
*/
|
|
471
744
|
const props: DatabaseCommonInteractionProps = buildProps({
|
|
472
745
|
tenantId: ObjectID.generate(),
|
|
473
|
-
userId:
|
|
746
|
+
userId: ObjectID.generate(),
|
|
474
747
|
userTenantAccessPermission: undefined,
|
|
475
748
|
});
|
|
476
749
|
|
|
477
750
|
expect(() => {
|
|
478
751
|
CommonAPI.assertTenantScoped(props);
|
|
479
752
|
}).not.toThrow();
|
|
753
|
+
|
|
754
|
+
// Nor for an API key that has no grants in the project it named.
|
|
755
|
+
expect(() => {
|
|
756
|
+
CommonAPI.assertTenantScoped(
|
|
757
|
+
buildProps({
|
|
758
|
+
tenantId: ObjectID.generate(),
|
|
759
|
+
userType: UserType.API,
|
|
760
|
+
userTenantAccessPermission: undefined,
|
|
761
|
+
}),
|
|
762
|
+
);
|
|
763
|
+
}).not.toThrow();
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
/*
|
|
767
|
+
* A tenant header on its own proves nothing. Before the credential check,
|
|
768
|
+
* the guard let this caller through to a tenant-scoped read that then
|
|
769
|
+
* failed as "You do not have permissions to read <model>" - the permissions
|
|
770
|
+
* message this guard exists to avoid - or, for a custom route, to whatever
|
|
771
|
+
* refusal the route produced. Now the caller is told to log in.
|
|
772
|
+
*/
|
|
773
|
+
test("an anonymous caller carrying only a tenant header is refused with NotAuthenticatedException (401)", () => {
|
|
774
|
+
expectAuthenticationRequired(
|
|
775
|
+
captureThrown(() => {
|
|
776
|
+
CommonAPI.assertTenantScoped(
|
|
777
|
+
buildProps({ tenantId: ObjectID.generate() }),
|
|
778
|
+
);
|
|
779
|
+
}),
|
|
780
|
+
);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
test("an explicit UserType.Public caller with a tenant header is the same 401", () => {
|
|
784
|
+
expectAuthenticationRequired(
|
|
785
|
+
captureThrown(() => {
|
|
786
|
+
CommonAPI.assertTenantScoped(
|
|
787
|
+
buildProps({
|
|
788
|
+
tenantId: ObjectID.generate(),
|
|
789
|
+
userType: UserType.Public,
|
|
790
|
+
}),
|
|
791
|
+
);
|
|
792
|
+
}),
|
|
793
|
+
);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("anonymous with no tenant header: 401 wins over the missing-tenant 400", () => {
|
|
797
|
+
expectAuthenticationRequired(
|
|
798
|
+
captureThrown(() => {
|
|
799
|
+
CommonAPI.assertTenantScoped({});
|
|
800
|
+
}),
|
|
801
|
+
);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("a master-admin session is admitted without a userId", () => {
|
|
805
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
806
|
+
|
|
807
|
+
expect(
|
|
808
|
+
CommonAPI.assertTenantScoped(
|
|
809
|
+
buildProps({ tenantId: projectId, userType: UserType.MasterAdmin }),
|
|
810
|
+
),
|
|
811
|
+
).toBe(projectId);
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
/*
|
|
816
|
+
* Tests for CommonAPI.assertAuthenticatedProjectPrincipal — the member guard
|
|
817
|
+
* that ALSO admits a project API key, for endpoints meant to be automatable.
|
|
818
|
+
* Same precedence as the member guard: credentials first (401), then the
|
|
819
|
+
* tenant (400), then access to that tenant (422).
|
|
820
|
+
*/
|
|
821
|
+
describe("CommonAPI.assertAuthenticatedProjectPrincipal", () => {
|
|
822
|
+
test("admits a logged-in member of the tenant project", () => {
|
|
823
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
824
|
+
|
|
825
|
+
expect(
|
|
826
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(
|
|
827
|
+
buildProps({
|
|
828
|
+
tenantId: projectId,
|
|
829
|
+
userId: ObjectID.generate(),
|
|
830
|
+
userType: UserType.User,
|
|
831
|
+
userTenantAccessPermission: {
|
|
832
|
+
[projectId.toString()]: buildTenantPermission(projectId),
|
|
833
|
+
},
|
|
834
|
+
}),
|
|
835
|
+
),
|
|
836
|
+
).toBe(projectId);
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("admits a project API key with permissions in the tenant project", () => {
|
|
840
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
841
|
+
|
|
842
|
+
expect(
|
|
843
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(
|
|
844
|
+
buildApiKeyProps(projectId),
|
|
845
|
+
),
|
|
846
|
+
).toBe(projectId);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test("an API key with no permission entry for the tenant is still the 422", () => {
|
|
850
|
+
expectNotAuthorized(
|
|
851
|
+
captureThrown(() => {
|
|
852
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(
|
|
853
|
+
buildProps({
|
|
854
|
+
tenantId: ObjectID.generate(),
|
|
855
|
+
userType: UserType.API,
|
|
856
|
+
userTenantAccessPermission: {},
|
|
857
|
+
}),
|
|
858
|
+
);
|
|
859
|
+
}),
|
|
860
|
+
NOT_AUTHORIZED_MESSAGE,
|
|
861
|
+
);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("a logged-in member of ANOTHER project is still the 422", () => {
|
|
865
|
+
const requestedProjectId: ObjectID = ObjectID.generate();
|
|
866
|
+
const otherProjectId: ObjectID = ObjectID.generate();
|
|
867
|
+
|
|
868
|
+
expectNotAuthorized(
|
|
869
|
+
captureThrown(() => {
|
|
870
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(
|
|
871
|
+
buildProps({
|
|
872
|
+
tenantId: requestedProjectId,
|
|
873
|
+
userId: ObjectID.generate(),
|
|
874
|
+
userTenantAccessPermission: {
|
|
875
|
+
[otherProjectId.toString()]:
|
|
876
|
+
buildTenantPermission(otherProjectId),
|
|
877
|
+
},
|
|
878
|
+
}),
|
|
879
|
+
);
|
|
880
|
+
}),
|
|
881
|
+
NOT_AUTHORIZED_MESSAGE,
|
|
882
|
+
);
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
test("an authenticated caller with no tenant is still the 400", () => {
|
|
886
|
+
expectBadData(
|
|
887
|
+
captureThrown(() => {
|
|
888
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(
|
|
889
|
+
buildProps({ userId: ObjectID.generate() }),
|
|
890
|
+
);
|
|
891
|
+
}),
|
|
892
|
+
"Project ID is required",
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
expectBadData(
|
|
896
|
+
captureThrown(() => {
|
|
897
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(
|
|
898
|
+
buildProps({ userType: UserType.API }),
|
|
899
|
+
);
|
|
900
|
+
}),
|
|
901
|
+
"Project ID is required",
|
|
902
|
+
);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test("an anonymous caller with a tenant header and a permission map is refused with 401", () => {
|
|
906
|
+
/*
|
|
907
|
+
* The permission map is there to prove it is not what decides this: an
|
|
908
|
+
* anonymous request never legitimately carries one, but if it did, the
|
|
909
|
+
* missing credential still wins.
|
|
910
|
+
*/
|
|
911
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
912
|
+
|
|
913
|
+
expectAuthenticationRequired(
|
|
914
|
+
captureThrown(() => {
|
|
915
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(
|
|
916
|
+
buildProps({
|
|
917
|
+
tenantId: projectId,
|
|
918
|
+
userTenantAccessPermission: {
|
|
919
|
+
[projectId.toString()]: buildTenantPermission(projectId),
|
|
920
|
+
},
|
|
921
|
+
}),
|
|
922
|
+
);
|
|
923
|
+
}),
|
|
924
|
+
);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
test("anonymous with no tenant header: 401 wins over the missing-tenant 400", () => {
|
|
928
|
+
expectAuthenticationRequired(
|
|
929
|
+
captureThrown(() => {
|
|
930
|
+
CommonAPI.assertAuthenticatedProjectPrincipal({});
|
|
931
|
+
}),
|
|
932
|
+
);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
test("isRoot / isMasterAdmin flags are not credentials", () => {
|
|
936
|
+
expectAuthenticationRequired(
|
|
937
|
+
captureThrown(() => {
|
|
938
|
+
CommonAPI.assertAuthenticatedProjectPrincipal({
|
|
939
|
+
tenantId: ObjectID.generate(),
|
|
940
|
+
isRoot: true,
|
|
941
|
+
isMasterAdmin: true,
|
|
942
|
+
});
|
|
943
|
+
}),
|
|
944
|
+
);
|
|
480
945
|
});
|
|
481
946
|
});
|
|
482
947
|
|
|
@@ -630,7 +1095,12 @@ describe("CommonAPI.assertPermittedInProject", () => {
|
|
|
630
1095
|
}).toThrow(NotAuthorizedException);
|
|
631
1096
|
});
|
|
632
1097
|
|
|
633
|
-
|
|
1098
|
+
/*
|
|
1099
|
+
* Still refused - and now refused as what it is. This caller was a 422 from
|
|
1100
|
+
* the permission intersection; the credential check answers it with a 401
|
|
1101
|
+
* before the permission lists are even read.
|
|
1102
|
+
*/
|
|
1103
|
+
test("Permission.Public in the allowed list does not admit an anonymous caller: NotAuthenticatedException (401)", () => {
|
|
634
1104
|
const props: DatabaseCommonInteractionProps = {
|
|
635
1105
|
tenantId: ObjectID.generate(),
|
|
636
1106
|
};
|
|
@@ -640,7 +1110,50 @@ describe("CommonAPI.assertPermittedInProject", () => {
|
|
|
640
1110
|
databaseProps: props,
|
|
641
1111
|
allowedPermissions: [Permission.Public],
|
|
642
1112
|
});
|
|
643
|
-
}).toThrow(
|
|
1113
|
+
}).toThrow(NotAuthenticatedException);
|
|
1114
|
+
|
|
1115
|
+
expectAuthenticationRequired(
|
|
1116
|
+
captureThrown(() => {
|
|
1117
|
+
CommonAPI.assertPermittedInProject({
|
|
1118
|
+
databaseProps: props,
|
|
1119
|
+
allowedPermissions: [Permission.Public],
|
|
1120
|
+
});
|
|
1121
|
+
}),
|
|
1122
|
+
);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
test("an anonymous caller is asked to log in even when the route supplied its own refusal message", () => {
|
|
1126
|
+
/*
|
|
1127
|
+
* errorMessage words the "not permitted" refusal; it must not leak onto
|
|
1128
|
+
* the 401, whose text is the shared log-in prompt.
|
|
1129
|
+
*/
|
|
1130
|
+
expectAuthenticationRequired(
|
|
1131
|
+
captureThrown(() => {
|
|
1132
|
+
CommonAPI.assertPermittedInProject({
|
|
1133
|
+
databaseProps: { tenantId: ObjectID.generate() },
|
|
1134
|
+
allowedPermissions: [Permission.ProjectOwner],
|
|
1135
|
+
errorMessage:
|
|
1136
|
+
"You do not have permission to read this project's AI providers.",
|
|
1137
|
+
});
|
|
1138
|
+
}),
|
|
1139
|
+
);
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
test("the refusal for an authenticated member without the permission keeps its 422", () => {
|
|
1143
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
1144
|
+
|
|
1145
|
+
expectNotAuthorized(
|
|
1146
|
+
captureThrown(() => {
|
|
1147
|
+
CommonAPI.assertPermittedInProject({
|
|
1148
|
+
databaseProps: buildPermittedProps({
|
|
1149
|
+
projectId: projectId,
|
|
1150
|
+
granted: [Permission.ProjectMember],
|
|
1151
|
+
}),
|
|
1152
|
+
allowedPermissions: [Permission.ProjectOwner],
|
|
1153
|
+
});
|
|
1154
|
+
}),
|
|
1155
|
+
"You do not have permission to access this project's data.",
|
|
1156
|
+
);
|
|
644
1157
|
});
|
|
645
1158
|
|
|
646
1159
|
test("Permission.CurrentUser, which every logged-in caller carries, does not admit them either", () => {
|
|
@@ -740,10 +1253,67 @@ describe("CommonAPI.assertPermittedInProject", () => {
|
|
|
740
1253
|
}).not.toThrow();
|
|
741
1254
|
});
|
|
742
1255
|
|
|
1256
|
+
test("a master-admin session (userType MasterAdmin, no userId) bypasses the permission check", () => {
|
|
1257
|
+
expect(() => {
|
|
1258
|
+
CommonAPI.assertPermittedInProject({
|
|
1259
|
+
databaseProps: {
|
|
1260
|
+
tenantId: ObjectID.generate(),
|
|
1261
|
+
userType: UserType.MasterAdmin,
|
|
1262
|
+
isMasterAdmin: true,
|
|
1263
|
+
},
|
|
1264
|
+
allowedPermissions: [Permission.ProjectOwner],
|
|
1265
|
+
});
|
|
1266
|
+
}).not.toThrow();
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
/*
|
|
1270
|
+
* The bypass is for a master admin, and being one is a credential. The
|
|
1271
|
+
* isMasterAdmin FLAG on its own, on a request with no user and no
|
|
1272
|
+
* MasterAdmin userType, is not - so it is asked to log in rather than
|
|
1273
|
+
* waved through.
|
|
1274
|
+
*/
|
|
1275
|
+
test("the isMasterAdmin flag without credentials is a 401, not a bypass", () => {
|
|
1276
|
+
expectAuthenticationRequired(
|
|
1277
|
+
captureThrown(() => {
|
|
1278
|
+
CommonAPI.assertPermittedInProject({
|
|
1279
|
+
databaseProps: {
|
|
1280
|
+
tenantId: ObjectID.generate(),
|
|
1281
|
+
isMasterAdmin: true,
|
|
1282
|
+
},
|
|
1283
|
+
allowedPermissions: [Permission.ProjectOwner],
|
|
1284
|
+
});
|
|
1285
|
+
}),
|
|
1286
|
+
);
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
test("an API key holding an allowed permission is admitted", () => {
|
|
1290
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
1291
|
+
const props: DatabaseCommonInteractionProps = buildPermittedProps({
|
|
1292
|
+
projectId: projectId,
|
|
1293
|
+
granted: [Permission.ProjectOwner],
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
props.userId = undefined;
|
|
1297
|
+
props.userType = UserType.API;
|
|
1298
|
+
|
|
1299
|
+
expect(() => {
|
|
1300
|
+
CommonAPI.assertPermittedInProject({
|
|
1301
|
+
databaseProps: props,
|
|
1302
|
+
allowedPermissions: [Permission.ProjectOwner],
|
|
1303
|
+
});
|
|
1304
|
+
}).not.toThrow();
|
|
1305
|
+
});
|
|
1306
|
+
|
|
743
1307
|
/*
|
|
744
1308
|
* The guard answers one question only. Membership is assertAuthenticated-
|
|
745
1309
|
* ProjectMember's job, and a route must call both — this test documents
|
|
746
1310
|
* that calling only this one is not enough.
|
|
1311
|
+
*
|
|
1312
|
+
* The caller that proves it is one assertAuthenticatedProjectMember would
|
|
1313
|
+
* refuse: no userId. It is an API key here (userType API) because a caller
|
|
1314
|
+
* with no userId AND no credentialed userType is anonymous, and every
|
|
1315
|
+
* guard, this one included, now answers that with a 401 first - pinned at
|
|
1316
|
+
* the end of this test.
|
|
747
1317
|
*/
|
|
748
1318
|
test("does not itself check membership — that stays with assertAuthenticatedProjectMember", () => {
|
|
749
1319
|
const projectId: ObjectID = ObjectID.generate();
|
|
@@ -760,6 +1330,7 @@ describe("CommonAPI.assertPermittedInProject", () => {
|
|
|
760
1330
|
}).not.toThrow();
|
|
761
1331
|
|
|
762
1332
|
props.userId = undefined;
|
|
1333
|
+
props.userType = UserType.API;
|
|
763
1334
|
|
|
764
1335
|
expect(() => {
|
|
765
1336
|
CommonAPI.assertPermittedInProject({
|
|
@@ -767,5 +1338,413 @@ describe("CommonAPI.assertPermittedInProject", () => {
|
|
|
767
1338
|
allowedPermissions: [Permission.ProjectOwner],
|
|
768
1339
|
});
|
|
769
1340
|
}).not.toThrow();
|
|
1341
|
+
|
|
1342
|
+
// The member guard, by contrast, refuses this caller.
|
|
1343
|
+
expectNotAuthorized(
|
|
1344
|
+
captureThrown(() => {
|
|
1345
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
1346
|
+
}),
|
|
1347
|
+
NOT_AUTHORIZED_MESSAGE,
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1350
|
+
// With the credential gone too, the grants no longer matter: 401.
|
|
1351
|
+
props.userType = undefined;
|
|
1352
|
+
|
|
1353
|
+
expectAuthenticationRequired(
|
|
1354
|
+
captureThrown(() => {
|
|
1355
|
+
CommonAPI.assertPermittedInProject({
|
|
1356
|
+
databaseProps: props,
|
|
1357
|
+
allowedPermissions: [Permission.ProjectOwner],
|
|
1358
|
+
});
|
|
1359
|
+
}),
|
|
1360
|
+
);
|
|
1361
|
+
});
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
/*
|
|
1365
|
+
* ---------------------------------------------------------------------------
|
|
1366
|
+
* The credential helpers every guard above is built on.
|
|
1367
|
+
* ---------------------------------------------------------------------------
|
|
1368
|
+
*/
|
|
1369
|
+
|
|
1370
|
+
/*
|
|
1371
|
+
* isAnonymous decides who gets a 401. It must be exactly "no user, no API
|
|
1372
|
+
* key, no master key": too narrow and an expired session keeps getting 422s
|
|
1373
|
+
* the browser will not refresh on; too wide and an API client gets a 401 it
|
|
1374
|
+
* answers by trying to refresh a session it never had.
|
|
1375
|
+
*/
|
|
1376
|
+
describe("CommonAPI.isAnonymous", () => {
|
|
1377
|
+
type IsAnonymousCase = {
|
|
1378
|
+
name: string;
|
|
1379
|
+
props: DatabaseCommonInteractionProps;
|
|
1380
|
+
expected: boolean;
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
const cases: Array<IsAnonymousCase> = [
|
|
1384
|
+
{
|
|
1385
|
+
name: "a userId and nothing else (a logged-in user)",
|
|
1386
|
+
props: { userId: ObjectID.generate() },
|
|
1387
|
+
expected: false,
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
name: "a userId with userType User",
|
|
1391
|
+
props: { userId: ObjectID.generate(), userType: UserType.User },
|
|
1392
|
+
expected: false,
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
name: "userType API with no userId (a project API key)",
|
|
1396
|
+
props: { userType: UserType.API },
|
|
1397
|
+
expected: false,
|
|
1398
|
+
},
|
|
1399
|
+
{
|
|
1400
|
+
name: "userType MasterAdmin with no userId (the master API key)",
|
|
1401
|
+
props: { userType: UserType.MasterAdmin },
|
|
1402
|
+
expected: false,
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
name: "userType Public (getUserMiddleware found no token)",
|
|
1406
|
+
props: { userType: UserType.Public },
|
|
1407
|
+
expected: true,
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: "userType Public with a tenant header",
|
|
1411
|
+
props: { userType: UserType.Public, tenantId: ObjectID.generate() },
|
|
1412
|
+
expected: true,
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: "userType User WITHOUT a userId",
|
|
1416
|
+
props: { userType: UserType.User },
|
|
1417
|
+
expected: true,
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
name: "no userType and no userId at all",
|
|
1421
|
+
props: { userType: undefined, userId: undefined },
|
|
1422
|
+
expected: true,
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
name: "an empty props object",
|
|
1426
|
+
props: {},
|
|
1427
|
+
expected: true,
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
name: "isRoot without a userType or userId",
|
|
1431
|
+
props: { isRoot: true },
|
|
1432
|
+
expected: true,
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
name: "isMasterAdmin without a userType or userId",
|
|
1436
|
+
props: { isMasterAdmin: true },
|
|
1437
|
+
expected: true,
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
name: "isRoot and isMasterAdmin together, with a tenant and permissions",
|
|
1441
|
+
props: {
|
|
1442
|
+
isRoot: true,
|
|
1443
|
+
isMasterAdmin: true,
|
|
1444
|
+
tenantId: ObjectID.generate(),
|
|
1445
|
+
userTenantAccessPermission: {},
|
|
1446
|
+
},
|
|
1447
|
+
expected: true,
|
|
1448
|
+
},
|
|
1449
|
+
];
|
|
1450
|
+
|
|
1451
|
+
test.each(cases)(
|
|
1452
|
+
"$name -> anonymous: $expected",
|
|
1453
|
+
(testCase: IsAnonymousCase) => {
|
|
1454
|
+
expect(CommonAPI.isAnonymous(testCase.props)).toBe(testCase.expected);
|
|
1455
|
+
},
|
|
1456
|
+
);
|
|
1457
|
+
|
|
1458
|
+
test("a userId makes the caller non-anonymous whatever the userType says", () => {
|
|
1459
|
+
expect(
|
|
1460
|
+
CommonAPI.isAnonymous({
|
|
1461
|
+
userId: ObjectID.generate(),
|
|
1462
|
+
userType: UserType.Public,
|
|
1463
|
+
}),
|
|
1464
|
+
).toBe(false);
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
describe("CommonAPI.assertCredentialsPresent", () => {
|
|
1469
|
+
test("the shared message is the exact log-in prompt", () => {
|
|
1470
|
+
expect(CommonAPI.AUTHENTICATION_REQUIRED_MESSAGE).toBe(
|
|
1471
|
+
"Authentication required. Please log in to access this resource.",
|
|
1472
|
+
);
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
test("the 401 is ExceptionCode.NotAuthenticatedException, distinct from the 422 and the 400", () => {
|
|
1476
|
+
/*
|
|
1477
|
+
* Pinned numerically because the numbers are the contract with the
|
|
1478
|
+
* browser client, which refreshes on 401 and on nothing else.
|
|
1479
|
+
*/
|
|
1480
|
+
expect(ExceptionCode.NotAuthenticatedException).toBe(401);
|
|
1481
|
+
expect(ExceptionCode.NotAuthorizedException).toBe(422);
|
|
1482
|
+
expect(ExceptionCode.BadDataException).toBe(400);
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
test("throws NotAuthenticatedException (401) with the exact message for an anonymous caller", () => {
|
|
1486
|
+
expectAuthenticationRequired(
|
|
1487
|
+
captureThrown(() => {
|
|
1488
|
+
CommonAPI.assertCredentialsPresent({});
|
|
1489
|
+
}),
|
|
1490
|
+
);
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
test("throws for a UserType.Public caller carrying a tenant and a permission map", () => {
|
|
1494
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
1495
|
+
|
|
1496
|
+
expectAuthenticationRequired(
|
|
1497
|
+
captureThrown(() => {
|
|
1498
|
+
CommonAPI.assertCredentialsPresent(
|
|
1499
|
+
buildProps({
|
|
1500
|
+
tenantId: projectId,
|
|
1501
|
+
userType: UserType.Public,
|
|
1502
|
+
userTenantAccessPermission: {
|
|
1503
|
+
[projectId.toString()]: buildTenantPermission(projectId),
|
|
1504
|
+
},
|
|
1505
|
+
}),
|
|
1506
|
+
);
|
|
1507
|
+
}),
|
|
1508
|
+
);
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
test("throws for isRoot / isMasterAdmin flags with no credentials", () => {
|
|
1512
|
+
expectAuthenticationRequired(
|
|
1513
|
+
captureThrown(() => {
|
|
1514
|
+
CommonAPI.assertCredentialsPresent({
|
|
1515
|
+
isRoot: true,
|
|
1516
|
+
isMasterAdmin: true,
|
|
1517
|
+
});
|
|
1518
|
+
}),
|
|
1519
|
+
);
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
test("passes for a logged-in user", () => {
|
|
1523
|
+
expect(() => {
|
|
1524
|
+
CommonAPI.assertCredentialsPresent({
|
|
1525
|
+
userId: ObjectID.generate(),
|
|
1526
|
+
userType: UserType.User,
|
|
1527
|
+
});
|
|
1528
|
+
}).not.toThrow();
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
test("passes for a project API key", () => {
|
|
1532
|
+
expect(() => {
|
|
1533
|
+
CommonAPI.assertCredentialsPresent({ userType: UserType.API });
|
|
1534
|
+
}).not.toThrow();
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
test("passes for a master admin", () => {
|
|
1538
|
+
expect(() => {
|
|
1539
|
+
CommonAPI.assertCredentialsPresent({ userType: UserType.MasterAdmin });
|
|
1540
|
+
}).not.toThrow();
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
test("does not look at the tenant: a credentialed caller with no tenant passes", () => {
|
|
1544
|
+
/*
|
|
1545
|
+
* The tenant is the NEXT question, asked by the guard that needs it. This
|
|
1546
|
+
* helper answers "who are you?" only.
|
|
1547
|
+
*/
|
|
1548
|
+
expect(() => {
|
|
1549
|
+
CommonAPI.assertCredentialsPresent({
|
|
1550
|
+
userId: ObjectID.generate(),
|
|
1551
|
+
tenantId: undefined,
|
|
1552
|
+
});
|
|
1553
|
+
}).not.toThrow();
|
|
1554
|
+
});
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
/*
|
|
1558
|
+
* For routes that act AS a person. Three outcomes: no credentials -> 401,
|
|
1559
|
+
* credentials that are not a person (an API key) -> 422, a person -> their
|
|
1560
|
+
* userId.
|
|
1561
|
+
*/
|
|
1562
|
+
describe("CommonAPI.assertAuthenticatedUser", () => {
|
|
1563
|
+
test("returns the caller's userId", () => {
|
|
1564
|
+
const userId: ObjectID = ObjectID.generate();
|
|
1565
|
+
|
|
1566
|
+
const returned: ObjectID = CommonAPI.assertAuthenticatedUser({
|
|
1567
|
+
userId: userId,
|
|
1568
|
+
userType: UserType.User,
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
expect(returned).toBe(userId);
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
test("returns the userId of a master admin who has one", () => {
|
|
1575
|
+
const userId: ObjectID = ObjectID.generate();
|
|
1576
|
+
|
|
1577
|
+
expect(
|
|
1578
|
+
CommonAPI.assertAuthenticatedUser({
|
|
1579
|
+
userId: userId,
|
|
1580
|
+
userType: UserType.MasterAdmin,
|
|
1581
|
+
}),
|
|
1582
|
+
).toBe(userId);
|
|
770
1583
|
});
|
|
1584
|
+
|
|
1585
|
+
test("an anonymous caller is NotAuthenticatedException (401)", () => {
|
|
1586
|
+
expectAuthenticationRequired(
|
|
1587
|
+
captureThrown(() => {
|
|
1588
|
+
CommonAPI.assertAuthenticatedUser({ tenantId: ObjectID.generate() });
|
|
1589
|
+
}),
|
|
1590
|
+
);
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
test("an anonymous caller gets the log-in prompt, never the route's not-a-user message", () => {
|
|
1594
|
+
expectAuthenticationRequired(
|
|
1595
|
+
captureThrown(() => {
|
|
1596
|
+
CommonAPI.assertAuthenticatedUser(
|
|
1597
|
+
{ userType: UserType.Public },
|
|
1598
|
+
"A logged-in user is required.",
|
|
1599
|
+
);
|
|
1600
|
+
}),
|
|
1601
|
+
);
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
test("a project API key is NotAuthorizedException (422) with the default message", () => {
|
|
1605
|
+
expectNotAuthorized(
|
|
1606
|
+
captureThrown(() => {
|
|
1607
|
+
CommonAPI.assertAuthenticatedUser(
|
|
1608
|
+
buildApiKeyProps(ObjectID.generate()),
|
|
1609
|
+
);
|
|
1610
|
+
}),
|
|
1611
|
+
"A logged-in user session is required.",
|
|
1612
|
+
);
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
test("a project API key gets the route's own message when one is given", () => {
|
|
1616
|
+
expectNotAuthorized(
|
|
1617
|
+
captureThrown(() => {
|
|
1618
|
+
CommonAPI.assertAuthenticatedUser(
|
|
1619
|
+
{ userType: UserType.API },
|
|
1620
|
+
"AI readiness requires a logged-in user session.",
|
|
1621
|
+
);
|
|
1622
|
+
}),
|
|
1623
|
+
"AI readiness requires a logged-in user session.",
|
|
1624
|
+
);
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
test("a master-key caller (MasterAdmin, no userId) is credentialed but not a person: 422", () => {
|
|
1628
|
+
expectNotAuthorized(
|
|
1629
|
+
captureThrown(() => {
|
|
1630
|
+
CommonAPI.assertAuthenticatedUser({ userType: UserType.MasterAdmin });
|
|
1631
|
+
}),
|
|
1632
|
+
"A logged-in user session is required.",
|
|
1633
|
+
);
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
test("an empty custom message falls back to the default", () => {
|
|
1637
|
+
expectNotAuthorized(
|
|
1638
|
+
captureThrown(() => {
|
|
1639
|
+
CommonAPI.assertAuthenticatedUser({ userType: UserType.API }, "");
|
|
1640
|
+
}),
|
|
1641
|
+
"A logged-in user session is required.",
|
|
1642
|
+
);
|
|
1643
|
+
});
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
/*
|
|
1647
|
+
* One table across all four project guards: the anonymous caller is a 401 in
|
|
1648
|
+
* every shape it can arrive in, and nothing about the tenant or the grants it
|
|
1649
|
+
* claims changes that.
|
|
1650
|
+
*/
|
|
1651
|
+
describe("every project guard answers an anonymous caller with 401", () => {
|
|
1652
|
+
type GuardCase = {
|
|
1653
|
+
name: string;
|
|
1654
|
+
run: (props: DatabaseCommonInteractionProps) => void;
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
const guards: Array<GuardCase> = [
|
|
1658
|
+
{
|
|
1659
|
+
name: "assertAuthenticatedProjectMember",
|
|
1660
|
+
run: (props: DatabaseCommonInteractionProps): void => {
|
|
1661
|
+
CommonAPI.assertAuthenticatedProjectMember(props);
|
|
1662
|
+
},
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
name: "assertAuthenticatedProjectPrincipal",
|
|
1666
|
+
run: (props: DatabaseCommonInteractionProps): void => {
|
|
1667
|
+
CommonAPI.assertAuthenticatedProjectPrincipal(props);
|
|
1668
|
+
},
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
name: "assertTenantScoped",
|
|
1672
|
+
run: (props: DatabaseCommonInteractionProps): void => {
|
|
1673
|
+
CommonAPI.assertTenantScoped(props);
|
|
1674
|
+
},
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
name: "assertPermittedInProject",
|
|
1678
|
+
run: (props: DatabaseCommonInteractionProps): void => {
|
|
1679
|
+
CommonAPI.assertPermittedInProject({
|
|
1680
|
+
databaseProps: props,
|
|
1681
|
+
allowedPermissions: [Permission.ProjectOwner, Permission.Public],
|
|
1682
|
+
});
|
|
1683
|
+
},
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
name: "assertAuthenticatedUser",
|
|
1687
|
+
run: (props: DatabaseCommonInteractionProps): void => {
|
|
1688
|
+
CommonAPI.assertAuthenticatedUser(props);
|
|
1689
|
+
},
|
|
1690
|
+
},
|
|
1691
|
+
];
|
|
1692
|
+
|
|
1693
|
+
test.each(guards)("$name: no tenant header", (guard: GuardCase) => {
|
|
1694
|
+
expectAuthenticationRequired(
|
|
1695
|
+
captureThrown(() => {
|
|
1696
|
+
guard.run({});
|
|
1697
|
+
}),
|
|
1698
|
+
);
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
test.each(guards)(
|
|
1702
|
+
"$name: tenant header only (the expired-session request)",
|
|
1703
|
+
(guard: GuardCase) => {
|
|
1704
|
+
expectAuthenticationRequired(
|
|
1705
|
+
captureThrown(() => {
|
|
1706
|
+
guard.run({
|
|
1707
|
+
tenantId: ObjectID.generate(),
|
|
1708
|
+
userType: UserType.Public,
|
|
1709
|
+
});
|
|
1710
|
+
}),
|
|
1711
|
+
);
|
|
1712
|
+
},
|
|
1713
|
+
);
|
|
1714
|
+
|
|
1715
|
+
test.each(guards)(
|
|
1716
|
+
"$name: tenant header plus a permission map claiming ProjectOwner",
|
|
1717
|
+
(guard: GuardCase) => {
|
|
1718
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
1719
|
+
const props: DatabaseCommonInteractionProps = buildPermittedProps({
|
|
1720
|
+
projectId: projectId,
|
|
1721
|
+
granted: [Permission.ProjectOwner],
|
|
1722
|
+
});
|
|
1723
|
+
|
|
1724
|
+
props.userId = undefined;
|
|
1725
|
+
|
|
1726
|
+
expectAuthenticationRequired(
|
|
1727
|
+
captureThrown(() => {
|
|
1728
|
+
guard.run(props);
|
|
1729
|
+
}),
|
|
1730
|
+
);
|
|
1731
|
+
},
|
|
1732
|
+
);
|
|
1733
|
+
|
|
1734
|
+
test.each(guards)(
|
|
1735
|
+
"$name: a logged-in owner of the project is not refused as anonymous",
|
|
1736
|
+
(guard: GuardCase) => {
|
|
1737
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
1738
|
+
const props: DatabaseCommonInteractionProps = buildPermittedProps({
|
|
1739
|
+
projectId: projectId,
|
|
1740
|
+
granted: [Permission.ProjectOwner],
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
props.userType = UserType.User;
|
|
1744
|
+
|
|
1745
|
+
expect(() => {
|
|
1746
|
+
guard.run(props);
|
|
1747
|
+
}).not.toThrow();
|
|
1748
|
+
},
|
|
1749
|
+
);
|
|
771
1750
|
});
|