@oneuptime/common 12.0.8 → 12.0.9
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/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +1 -1
- package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +1 -1
- package/Models/DatabaseModels/AIConversation.ts +32 -0
- package/Models/DatabaseModels/AIConversationMessage.ts +41 -0
- package/Models/DatabaseModels/AlertEpisodeMember.ts +27 -0
- package/Models/DatabaseModels/IncidentEpisodeMember.ts +28 -0
- package/Models/DatabaseModels/Index.ts +12 -4
- package/Models/DatabaseModels/{TelemetryEntity.ts → InventoryItem.ts} +139 -9
- package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
- package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +8 -8
- package/Models/DatabaseModels/NetworkDevice.ts +141 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +699 -0
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +467 -0
- package/Models/DatabaseModels/NetworkTopologySuppression.ts +429 -0
- package/Models/DatabaseModels/OnCallDutyPolicyFeed.ts +9 -0
- package/Models/DatabaseModels/Project.ts +78 -0
- package/Models/DatabaseModels/UserCall.ts +42 -0
- package/Models/DatabaseModels/UserEmail.ts +44 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +425 -55
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +24 -2
- package/Models/DatabaseModels/UserPush.ts +49 -0
- package/Models/DatabaseModels/UserSMS.ts +43 -0
- package/Models/DatabaseModels/UserTelegram.ts +59 -0
- package/Models/DatabaseModels/UserWebhook.ts +52 -0
- package/Models/DatabaseModels/UserWhatsApp.ts +41 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
- package/Server/API/AIChatAPI.ts +315 -1
- package/Server/API/DashboardAPI.ts +217 -1
- package/Server/API/OnCallReadinessAPI.ts +841 -0
- package/Server/API/TeamComplianceAPI.ts +69 -17
- package/Server/API/TelemetryAPI.ts +220 -12
- package/Server/EnvironmentConfig.ts +52 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
- package/Server/Infrastructure/Queue.ts +78 -13
- package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
- package/Server/Services/AIService.ts +7 -0
- package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/AlertSeverityService.ts +63 -0
- package/Server/Services/DashboardService.ts +9 -10
- package/Server/Services/DatabaseService.ts +32 -2
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/IncidentSeverityService.ts +76 -0
- package/Server/Services/Index.ts +12 -4
- package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
- package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +4 -4
- package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +89 -20
- package/Server/Services/LogAggregationService.ts +45 -8
- package/Server/Services/MetricAggregationService.ts +121 -0
- package/Server/Services/MetricService.ts +7 -7
- package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
- package/Server/Services/NetworkDeviceLinkService.ts +84 -0
- package/Server/Services/NetworkDeviceService.ts +140 -0
- package/Server/Services/NetworkSiteService.ts +77 -25
- package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
- package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
- package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
- package/Server/Services/OnCallReadinessService.ts +2803 -0
- package/Server/Services/OnCallSetupReminderService.ts +955 -0
- package/Server/Services/ProfileAggregationService.ts +123 -0
- package/Server/Services/StatusPageService.ts +9 -10
- package/Server/Services/TeamComplianceService.ts +429 -252
- package/Server/Services/UserCallService.ts +26 -1
- package/Server/Services/UserEmailService.ts +26 -1
- package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
- package/Server/Services/UserNotificationRuleService.ts +3812 -333
- package/Server/Services/UserOnCallLogService.ts +561 -48
- package/Server/Services/UserPushService.ts +29 -0
- package/Server/Services/UserService.ts +11 -0
- package/Server/Services/UserSmsService.ts +26 -1
- package/Server/Services/UserTelegramService.ts +24 -1
- package/Server/Services/UserWebhookService.ts +28 -1
- package/Server/Services/UserWhatsAppService.ts +24 -1
- package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
- package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
- package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
- package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
- package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
- package/Server/Types/Workflow/Components/API/Get.ts +1 -1
- package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
- package/Server/Types/Workflow/Components/API/Post.ts +1 -1
- package/Server/Types/Workflow/Components/API/Put.ts +1 -1
- package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
- package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
- package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
- package/Server/Types/Workflow/Components/Email.ts +25 -7
- package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
- package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
- package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
- package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
- package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
- package/Server/Utils/AI/Toolbox/Index.ts +48 -0
- package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
- package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
- package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
- package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
- package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
- package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
- package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
- package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
- package/Server/Utils/ClientIp.ts +221 -0
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
- package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
- package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
- package/Server/Utils/Express.ts +12 -17
- package/Server/Utils/LLM/LLMService.ts +85 -8
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
- package/Server/Utils/SSRFProtection.ts +98 -23
- package/Server/Utils/StartServer.ts +12 -3
- package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +25 -25
- package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
- package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
- package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
- package/Tests/Models/InventoryItemModel.test.ts +174 -0
- package/Tests/Models/InventoryItemNaming.test.ts +302 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
- package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
- package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
- package/Tests/Server/API/Helpers.ts +24 -15
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
- package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
- package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
- package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
- package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
- package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
- package/Tests/Server/Services/{TelemetryEntityManualCreate.test.ts → InventoryItemManualCreate.test.ts} +27 -27
- package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
- package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
- package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
- package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
- package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
- package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
- package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
- package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
- package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
- package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
- package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
- package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
- package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
- package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
- package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
- package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
- package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
- package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
- package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
- package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
- package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
- package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
- package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
- package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
- package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
- package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
- package/Tests/Server/Utils/ClientIp.test.ts +438 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
- package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
- package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
- package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +15 -15
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
- package/Tests/Types/IP/IP.test.ts +263 -0
- package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
- package/Tests/Types/IP/IPv6.test.ts +12 -1
- package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +6 -6
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
- package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
- package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
- package/Tests/Types/Workflow/TemplateSyntax.test.ts +112 -0
- package/Tests/Types/Workflow/Templates.test.ts +916 -101
- package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
- package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
- package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
- package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
- package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
- package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
- package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
- package/Tests/UI/Components/Workflow/GraphLint.test.ts +72 -0
- package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +242 -7
- package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +15 -7
- package/Tests/UI/Components/Workflow/ModelSchema.test.ts +242 -28
- package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +59 -1
- package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
- package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
- package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
- package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
- package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
- package/Tests/UI/EsbuildConfig.test.ts +607 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
- package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
- package/Tests/UI/Utils/Platform.test.ts +147 -0
- package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +487 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
- package/Tests/__mocks__/bullmq.js +55 -0
- package/Types/AI/AIChatMessageStatus.ts +8 -1
- package/Types/AI/AIChatTypes.ts +12 -0
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
- package/Types/Exception/ExceptionCode.ts +2 -0
- package/Types/Exception/ServiceUnavailableException.ts +8 -0
- package/Types/Exception/TooManyRequestsException.ts +8 -0
- package/Types/IP/IP.ts +93 -47
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +49 -3
- package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
- package/Types/OnCallDutyPolicy/Layer.ts +203 -149
- package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
- package/Types/Permission.ts +193 -0
- package/Types/Telemetry/EntityRelationshipType.ts +1 -1
- package/Types/Telemetry/EntitySource.ts +1 -1
- package/Types/Telemetry/EntityType.ts +1 -1
- package/Types/Telemetry/EntityTypeGroups.ts +1 -1
- package/Types/Workflow/Components/BaseModel.ts +75 -29
- package/Types/Workflow/Components/Discord.ts +1 -0
- package/Types/Workflow/Components/Email.ts +12 -3
- package/Types/Workflow/Components/JavaScript.ts +7 -0
- package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
- package/Types/Workflow/Components/Slack.ts +1 -0
- package/Types/Workflow/Components/Telegram.ts +1 -0
- package/Types/Workflow/TemplateSyntax.ts +44 -0
- package/Types/Workflow/Templates.ts +2097 -45
- package/UI/Components/Calendar/Calendar.css +43 -0
- package/UI/Components/Calendar/Calendar.tsx +8 -0
- package/UI/Components/Card/Card.tsx +2 -2
- package/UI/Components/Checkbox/Checkbox.tsx +16 -0
- package/UI/Components/Dictionary/Dictionary.tsx +48 -9
- package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
- package/UI/Components/Input/Input.tsx +1 -0
- package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
- package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
- package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
- package/UI/Components/LogsViewer/types.ts +23 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
- package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
- package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
- package/UI/Components/Table/Table.tsx +49 -16
- package/UI/Components/Table/TableBody.tsx +53 -28
- package/UI/Components/Table/TableHeader.tsx +13 -0
- package/UI/Components/Table/TableRow.tsx +58 -26
- package/UI/Components/Workflow/ArgumentsForm.tsx +155 -9
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
- package/UI/Components/Workflow/ComponentSettingsModal.tsx +10 -1
- package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
- package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
- package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
- package/UI/Components/Workflow/GraphLint.ts +42 -5
- package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +154 -22
- package/UI/Components/Workflow/ModelSchema.ts +115 -33
- package/UI/Components/Workflow/RunStatusWatcher.ts +1 -1
- package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
- package/UI/Components/Workflow/UseRunWatch.ts +212 -0
- package/UI/Components/Workflow/VariableModal.tsx +6 -2
- package/UI/Components/Workflow/Workflow.tsx +49 -3
- package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
- package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
- package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
- package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
- package/UI/Utils/Platform.ts +149 -0
- package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
- package/UI/esbuild-config.js +22 -1
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +888 -164
- package/Utils/Telemetry/CrossSignalScope.ts +502 -0
- package/Utils/Telemetry/EntityKey.ts +5 -5
- package/Utils/Telemetry/EntityRelationship.ts +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
- package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +12 -4
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +167 -31
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +27 -27
- package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +80 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
- package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
- package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +237 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +165 -13
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
- package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
- package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
- package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +129 -18
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +45 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +72 -13
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +1 -0
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +54 -0
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/DashboardService.js +10 -9
- package/build/dist/Server/Services/DashboardService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +24 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +12 -4
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +6 -6
- package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +68 -23
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
- package/build/dist/Server/Services/LogAggregationService.js +27 -8
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +80 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +6 -6
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +53 -13
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
- package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
- package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
- package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +11 -10
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceService.js +312 -160
- package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +24 -1
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +24 -1
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +26 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +24 -1
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserTelegramService.js +22 -1
- package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
- package/build/dist/Server/Services/UserWebhookService.js +25 -1
- package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
- package/build/dist/Server/Utils/ClientIp.js +137 -0
- package/build/dist/Server/Utils/ClientIp.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
- package/build/dist/Server/Utils/Express.js +12 -12
- package/build/dist/Server/Utils/Express.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +82 -21
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +11 -11
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
- package/build/dist/Types/AI/AIChatTypes.js +12 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +2 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
- package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
- package/build/dist/Types/IP/IP.js +87 -43
- package/build/dist/Types/IP/IP.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/Permission.js +174 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
- package/build/dist/Types/Telemetry/EntitySource.js +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +66 -28
- package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Discord.js +1 -0
- package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Email.js +12 -3
- package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
- package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Slack.js +1 -0
- package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
- package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
- package/build/dist/Types/Workflow/TemplateSyntax.js +12 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -1
- package/build/dist/Types/Workflow/Templates.js +1931 -38
- package/build/dist/Types/Workflow/Templates.js.map +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +25 -11
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -0
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +27 -15
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +24 -18
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +9 -1
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +29 -21
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +130 -12
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +4 -4
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLint.js +33 -4
- package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +116 -21
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ModelSchema.js +72 -34
- package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
- package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
- package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
- package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +41 -1
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Platform.js +118 -0
- package/build/dist/UI/Utils/Platform.js.map +1 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +642 -136
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +5 -5
- package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
- package/jest.config.json +1 -0
- package/package.json +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
|
@@ -0,0 +1,1546 @@
|
|
|
1
|
+
import UserCall from "../../../../../Models/DatabaseModels/UserCall";
|
|
2
|
+
import UserEmail from "../../../../../Models/DatabaseModels/UserEmail";
|
|
3
|
+
import UserNotificationRule from "../../../../../Models/DatabaseModels/UserNotificationRule";
|
|
4
|
+
import UserPush from "../../../../../Models/DatabaseModels/UserPush";
|
|
5
|
+
import UserSMS from "../../../../../Models/DatabaseModels/UserSMS";
|
|
6
|
+
import UserTelegram from "../../../../../Models/DatabaseModels/UserTelegram";
|
|
7
|
+
import UserWebhook from "../../../../../Models/DatabaseModels/UserWebhook";
|
|
8
|
+
import UserWhatsApp from "../../../../../Models/DatabaseModels/UserWhatsApp";
|
|
9
|
+
import BaseModel from "../../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
10
|
+
import DatabaseRequestType from "../../../../../Server/Types/BaseDatabase/DatabaseRequestType";
|
|
11
|
+
import BasePermission from "../../../../../Server/Types/Database/Permissions/BasePermission";
|
|
12
|
+
import ColumnPermissions from "../../../../../Server/Types/Database/Permissions/ColumnPermission";
|
|
13
|
+
import CreatePermission from "../../../../../Server/Types/Database/Permissions/CreatePermission";
|
|
14
|
+
import TenantPermission from "../../../../../Server/Types/Database/Permissions/TenantPermission";
|
|
15
|
+
import Query from "../../../../../Server/Types/Database/Query";
|
|
16
|
+
import Select from "../../../../../Server/Types/Database/Select";
|
|
17
|
+
import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
18
|
+
import DatabaseCommonInteractionPropsUtil, {
|
|
19
|
+
PermissionType,
|
|
20
|
+
} from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
|
|
21
|
+
import { ColumnAccessControl } from "../../../../../Types/BaseDatabase/AccessControl";
|
|
22
|
+
import BadDataException from "../../../../../Types/Exception/BadDataException";
|
|
23
|
+
import NotAuthorizedException from "../../../../../Types/Exception/NotAuthorizedException";
|
|
24
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
25
|
+
import UserType from "../../../../../Types/UserType";
|
|
26
|
+
import Permission, {
|
|
27
|
+
PermissionHelper,
|
|
28
|
+
PermissionProps,
|
|
29
|
+
UserPermission,
|
|
30
|
+
} from "../../../../../Types/Permission";
|
|
31
|
+
import { describe, expect, test } from "@jest/globals";
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* WHAT THIS FILE GUARDS
|
|
35
|
+
*
|
|
36
|
+
* An administrator has to be able to answer "why did this member's page go
|
|
37
|
+
* unanswered", and until Phase 3 nobody but the member could see the rows that
|
|
38
|
+
* decide it: a member with no matching rule was silently unpageable, and the
|
|
39
|
+
* only person able to notice was the person who was not being notified.
|
|
40
|
+
*
|
|
41
|
+
* PHASE 3 SHIPPED THAT CAPABILITY ON THE RULE TABLE AND ONLY THERE. The seven
|
|
42
|
+
* notification-method models - UserEmail, UserSMS, UserCall, UserPush,
|
|
43
|
+
* UserWhatsApp, UserTelegram, UserWebhook - stay CurrentUser-only on every
|
|
44
|
+
* verb, so an administrator reading one of THEM is refused exactly like any
|
|
45
|
+
* other member. That split is not a leftover; it is the design, and it is the
|
|
46
|
+
* design because the alternative was tried and did not hold.
|
|
47
|
+
*
|
|
48
|
+
* WHY THE METHOD TABLES COULD NOT BE WIDENED. Widening a read on a model that
|
|
49
|
+
* scopes rows by owner is a privilege change rather than a convenience change,
|
|
50
|
+
* and the mechanism is indirect enough to be worth stating:
|
|
51
|
+
* TenantPermission.isAccessGrantedOnlyByCurrentUser intersects what the caller
|
|
52
|
+
* holds with what the model's list accepts, and while the only survivor is the
|
|
53
|
+
* auto-granted Permission.CurrentUser, addCurrentUserScopeToQuery rewrites the
|
|
54
|
+
* query to `userId = me` and rejects outright any attempt to name somebody
|
|
55
|
+
* else. That single stamp is the ONLY thing making the columns underneath
|
|
56
|
+
* owner-only - `read: [Permission.CurrentUser]` on a COLUMN never meant "on my
|
|
57
|
+
* own row", because CurrentUser is auto-granted to every authenticated caller
|
|
58
|
+
* and ColumnPermissions intersects by NAME without ever seeing the query. Add
|
|
59
|
+
* one administrator permission to a method model's table list and the stamp
|
|
60
|
+
* stops being applied for whoever holds it, and the webhook URL and its signing
|
|
61
|
+
* secret, the push device token, and the Telegram chat id and verification code
|
|
62
|
+
* become readable on every member's row in the project. The column-level guard
|
|
63
|
+
* written to contain that was then walked past by nested relation selects, by
|
|
64
|
+
* `query` filters it never inspected, and by sort columns appended to the
|
|
65
|
+
* select after it had run. The widening was reverted rather than patched a
|
|
66
|
+
* fourth time.
|
|
67
|
+
*
|
|
68
|
+
* WHAT AN ADMINISTRATOR USES INSTEAD. OnCallReadinessService answers the
|
|
69
|
+
* diagnosis question as root and returns ReadinessMethod
|
|
70
|
+
* { methodId, methodType, maskedIdentifier, isVerified } - masked server-side
|
|
71
|
+
* by the one code path that holds the raw value. methodId is a foreign key
|
|
72
|
+
* rather than a secret, and it is what lets an administrator POINT A RULE AT a
|
|
73
|
+
* method without the method's row ever being read.
|
|
74
|
+
*
|
|
75
|
+
* So every assertion here is about the exact shape of what did and did not
|
|
76
|
+
* lift:
|
|
77
|
+
*
|
|
78
|
+
* - the rule table lifts for an administrator and not for a plain member, on
|
|
79
|
+
* all four verbs;
|
|
80
|
+
* - the seven method models lift for NOBODY on ANY verb, and the read case is
|
|
81
|
+
* asserted in both directions because it is the one that shipped widened
|
|
82
|
+
* once already;
|
|
83
|
+
* - the rule's method FK id columns are admin-readable while the method
|
|
84
|
+
* RELATION columns are not, which is what keeps "re-point this rule"
|
|
85
|
+
* possible without making "read what it points at" possible;
|
|
86
|
+
* - the ownership and tenant COLUMNS stay frozen, so the capability cannot be
|
|
87
|
+
* turned into "re-point this rule at a different owner", which is the same
|
|
88
|
+
* hijack wearing a different verb;
|
|
89
|
+
* - the two granular permissions are opt-in - held by nobody until an
|
|
90
|
+
* administrator grants them, and never auto-granted the way CurrentUser is;
|
|
91
|
+
* - the roles are listed ALONGSIDE the granular permissions on the rule
|
|
92
|
+
* table, so the feature is not dead on arrival for a project whose teams
|
|
93
|
+
* predate them;
|
|
94
|
+
* - the create-side ownership gate added in Phase 2.5 still refuses a plain
|
|
95
|
+
* member who names another user.
|
|
96
|
+
*
|
|
97
|
+
* The tests are written against the permission layer directly rather than
|
|
98
|
+
* through the API, because the API is not the boundary: POST/PATCH
|
|
99
|
+
* /api/user-notification-rule stays reachable with any member session, and the
|
|
100
|
+
* admin UI is a convenience over these checks rather than a gate in front of
|
|
101
|
+
* them.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
105
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
106
|
+
);
|
|
107
|
+
const CALLER_ID: ObjectID = new ObjectID(
|
|
108
|
+
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
109
|
+
);
|
|
110
|
+
const OTHER_USER_ID: ObjectID = new ObjectID(
|
|
111
|
+
"cccccccc-cccc-4ccc-8ccc-cccccccccccc",
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
type ModelConstructor = { new (): BaseModel };
|
|
115
|
+
|
|
116
|
+
/*
|
|
117
|
+
* The permissions that lift the read scope, and the permissions that lift the
|
|
118
|
+
* write scope, ON THE RULE TABLE. Spelled out here rather than imported from
|
|
119
|
+
* the model, so that a change to the model's private constant has to be
|
|
120
|
+
* reflected deliberately in a test rather than being absorbed silently by a
|
|
121
|
+
* shared reference.
|
|
122
|
+
*
|
|
123
|
+
* They appear in the method-model groups too, but only ever inside a
|
|
124
|
+
* `not.toContain` or a refusal - there is no verb on a notification method that
|
|
125
|
+
* any of them may perform on somebody else's row.
|
|
126
|
+
*/
|
|
127
|
+
const ADMIN_READ_GRANTS: Array<Permission> = [
|
|
128
|
+
Permission.ProjectOwner,
|
|
129
|
+
Permission.ProjectAdmin,
|
|
130
|
+
Permission.ReadProjectUserNotificationRule,
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const ADMIN_WRITE_GRANTS: Array<Permission> = [
|
|
134
|
+
Permission.ProjectOwner,
|
|
135
|
+
Permission.ProjectAdmin,
|
|
136
|
+
Permission.EditProjectUserNotificationRule,
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
* The two permissions this phase introduces. Kept separate from the lists above
|
|
141
|
+
* because several assertions are about the permissions themselves - their
|
|
142
|
+
* catalogue entry, their opt-in-ness - rather than about any model.
|
|
143
|
+
*/
|
|
144
|
+
const NEW_GRANULAR_PERMISSIONS: Array<Permission> = [
|
|
145
|
+
Permission.ReadProjectUserNotificationRule,
|
|
146
|
+
Permission.EditProjectUserNotificationRule,
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
* The seven notification methods a rule can point at. Every method assertion
|
|
151
|
+
* runs against all seven rather than against a representative one: they are
|
|
152
|
+
* seven copies of one decorator shape, and a widening applied to one of them is
|
|
153
|
+
* exactly the kind of thing that survives review.
|
|
154
|
+
*/
|
|
155
|
+
const METHOD_MODELS: Array<[string, ModelConstructor]> = [
|
|
156
|
+
["UserEmail", UserEmail],
|
|
157
|
+
["UserSMS", UserSMS],
|
|
158
|
+
["UserCall", UserCall],
|
|
159
|
+
["UserPush", UserPush],
|
|
160
|
+
["UserWhatsApp", UserWhatsApp],
|
|
161
|
+
["UserTelegram", UserTelegram],
|
|
162
|
+
["UserWebhook", UserWebhook],
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const ROW_SCOPED_REQUEST_TYPES: Array<DatabaseRequestType> = [
|
|
166
|
+
DatabaseRequestType.Read,
|
|
167
|
+
DatabaseRequestType.Update,
|
|
168
|
+
DatabaseRequestType.Delete,
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
/*
|
|
172
|
+
* A plain project member: authenticated, holding nothing but the permissions
|
|
173
|
+
* every logged-in caller is auto-granted. This is the shape an escalation
|
|
174
|
+
* attempt arrives in, and also the ordinary shape of a first-party dashboard
|
|
175
|
+
* request.
|
|
176
|
+
*
|
|
177
|
+
* Rebuilt on every call rather than shared, because
|
|
178
|
+
* DatabaseCommonInteractionPropsUtil.getUserPermissions MUTATES the props it is
|
|
179
|
+
* handed - it pushes Public and CurrentUser into globalPermissions in place - so
|
|
180
|
+
* a shared object would carry state between tests.
|
|
181
|
+
*/
|
|
182
|
+
function memberProps(): DatabaseCommonInteractionProps {
|
|
183
|
+
return {
|
|
184
|
+
userId: CALLER_ID,
|
|
185
|
+
tenantId: PROJECT_ID,
|
|
186
|
+
userType: UserType.User,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/*
|
|
191
|
+
* A caller holding specific permissions FOR THIS TENANT. Note that they also
|
|
192
|
+
* receive the auto-granted CurrentUser on top, exactly as a real session would -
|
|
193
|
+
* an administrator is still a user with notification rules and notification
|
|
194
|
+
* methods of their own, and several of the behaviours below only make sense
|
|
195
|
+
* because both are true at once.
|
|
196
|
+
*/
|
|
197
|
+
function tenantProps(
|
|
198
|
+
...permissions: Array<Permission>
|
|
199
|
+
): DatabaseCommonInteractionProps {
|
|
200
|
+
return {
|
|
201
|
+
userId: CALLER_ID,
|
|
202
|
+
tenantId: PROJECT_ID,
|
|
203
|
+
userType: UserType.User,
|
|
204
|
+
userTenantAccessPermission: {
|
|
205
|
+
[PROJECT_ID.toString()]: {
|
|
206
|
+
projectId: PROJECT_ID,
|
|
207
|
+
permissions: permissions.map((permission: Permission) => {
|
|
208
|
+
return {
|
|
209
|
+
permission: permission,
|
|
210
|
+
labelIds: [],
|
|
211
|
+
isBlockPermission: false,
|
|
212
|
+
_type: "UserPermission" as const,
|
|
213
|
+
};
|
|
214
|
+
}),
|
|
215
|
+
_type: "UserTenantAccessPermission",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function makeRow(
|
|
222
|
+
modelType: ModelConstructor,
|
|
223
|
+
ownerId?: ObjectID | undefined,
|
|
224
|
+
): BaseModel {
|
|
225
|
+
const row: BaseModel = new modelType();
|
|
226
|
+
row.setColumnValue("projectId", PROJECT_ID);
|
|
227
|
+
|
|
228
|
+
if (ownerId) {
|
|
229
|
+
row.setColumnValue("userId", ownerId);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return row;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function scopedUserIdOf(query: Query<BaseModel>): string | undefined {
|
|
236
|
+
return (query as Record<string, unknown>)["userId"]?.toString();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/*
|
|
240
|
+
* The same ownership predicate, read off a query that has been through
|
|
241
|
+
* BasePermission.checkPermissions rather than the tenant scope alone.
|
|
242
|
+
*
|
|
243
|
+
* The two are not interchangeable and the difference is not cosmetic.
|
|
244
|
+
* checkPermissions finishes by running QueryUtil.serializeQuery, which rewrites
|
|
245
|
+
* an ObjectID-valued predicate into QueryHelper.equalTo() - a TypeORM Raw()
|
|
246
|
+
* FindOperator whose uuid does NOT live on `.value` (that slot holds the
|
|
247
|
+
* SQL-generator function) but in `.objectLiteralParameters` under a per-call
|
|
248
|
+
* random key. So the id is the sole VALUE in that bag rather than a value at a
|
|
249
|
+
* known key, and `.toString()` on the operator yields "[object Object]" - which
|
|
250
|
+
* would make a naive assertion pass or fail for reasons unrelated to ownership.
|
|
251
|
+
*
|
|
252
|
+
* Accepts the unserialized form too, so a caller does not have to know which
|
|
253
|
+
* stage produced the query it is holding.
|
|
254
|
+
*/
|
|
255
|
+
function ownerIdOf(query: Query<BaseModel>): string | undefined {
|
|
256
|
+
const predicate: unknown = (query as Record<string, unknown>)["userId"];
|
|
257
|
+
|
|
258
|
+
if (predicate === undefined || predicate === null) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (predicate instanceof ObjectID || typeof predicate === "string") {
|
|
263
|
+
return predicate.toString();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const rawOperator: {
|
|
267
|
+
type?: string | undefined;
|
|
268
|
+
objectLiteralParameters?: Record<string, unknown> | undefined;
|
|
269
|
+
} = predicate as {
|
|
270
|
+
type?: string | undefined;
|
|
271
|
+
objectLiteralParameters?: Record<string, unknown> | undefined;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
if (rawOperator.type !== "raw") {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const boundValues: Array<unknown> = Object.values(
|
|
279
|
+
rawOperator.objectLiteralParameters || {},
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
if (boundValues.length !== 1) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return String(boundValues[0]);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
describe("the rule table - an administrator's row scope lifts, a member's does not", () => {
|
|
290
|
+
test.each(ADMIN_READ_GRANTS)(
|
|
291
|
+
"%s reads another member's rules without being forced onto their own",
|
|
292
|
+
async (grant: Permission): Promise<void> => {
|
|
293
|
+
/*
|
|
294
|
+
* Two halves of the same claim, and both are needed. An unscoped query
|
|
295
|
+
* proves the ownership predicate is no longer stamped on; a query that
|
|
296
|
+
* NAMES the other user proves addCurrentUserScopeToQuery is no longer
|
|
297
|
+
* rejecting the attempt outright. Before this phase the second half threw
|
|
298
|
+
* NotAuthorizedException, which is why an administrator could not even
|
|
299
|
+
* ask the question.
|
|
300
|
+
*/
|
|
301
|
+
const unscoped: Query<BaseModel> =
|
|
302
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
303
|
+
UserNotificationRule,
|
|
304
|
+
{},
|
|
305
|
+
null,
|
|
306
|
+
tenantProps(grant),
|
|
307
|
+
DatabaseRequestType.Read,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
expect(scopedUserIdOf(unscoped)).toBeUndefined();
|
|
311
|
+
expect(
|
|
312
|
+
(unscoped as Record<string, unknown>)["projectId"]?.toString(),
|
|
313
|
+
).toBe(PROJECT_ID.toString());
|
|
314
|
+
|
|
315
|
+
const targeted: Query<BaseModel> =
|
|
316
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
317
|
+
UserNotificationRule,
|
|
318
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
319
|
+
null,
|
|
320
|
+
tenantProps(grant),
|
|
321
|
+
DatabaseRequestType.Read,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(scopedUserIdOf(targeted)).toBe(OTHER_USER_ID.toString());
|
|
325
|
+
},
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
test.each(ADMIN_WRITE_GRANTS)(
|
|
329
|
+
"%s may update and delete another member's rules",
|
|
330
|
+
async (grant: Permission): Promise<void> => {
|
|
331
|
+
/*
|
|
332
|
+
* Repair, not just diagnosis. Update is what re-points a rule at a method
|
|
333
|
+
* the member actually verified; delete is the supported way to move a
|
|
334
|
+
* rule between owners, since the ownership column itself is frozen.
|
|
335
|
+
*/
|
|
336
|
+
for (const requestType of [
|
|
337
|
+
DatabaseRequestType.Update,
|
|
338
|
+
DatabaseRequestType.Delete,
|
|
339
|
+
]) {
|
|
340
|
+
const query: Query<BaseModel> =
|
|
341
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
342
|
+
UserNotificationRule,
|
|
343
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
344
|
+
null,
|
|
345
|
+
tenantProps(grant),
|
|
346
|
+
requestType,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect(scopedUserIdOf(query)).toBe(OTHER_USER_ID.toString());
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
test("a plain member is still forced onto their own rules on every verb", async () => {
|
|
355
|
+
for (const requestType of [
|
|
356
|
+
...ROW_SCOPED_REQUEST_TYPES,
|
|
357
|
+
DatabaseRequestType.Create,
|
|
358
|
+
]) {
|
|
359
|
+
const query: Query<BaseModel> =
|
|
360
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
361
|
+
UserNotificationRule,
|
|
362
|
+
{},
|
|
363
|
+
null,
|
|
364
|
+
memberProps(),
|
|
365
|
+
requestType,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("a plain member naming another user is rejected, not quietly redirected", async () => {
|
|
373
|
+
/*
|
|
374
|
+
* The distinction matters more than it looks. Service hooks run before the
|
|
375
|
+
* final permission check, so silently rewriting the target would let a hook
|
|
376
|
+
* inspect one row while the database operation mutated a different one.
|
|
377
|
+
* Widening the table list must not have turned this refusal into a rewrite.
|
|
378
|
+
*/
|
|
379
|
+
for (const requestType of ROW_SCOPED_REQUEST_TYPES) {
|
|
380
|
+
await expect(
|
|
381
|
+
TenantPermission.addTenantScopeToQuery(
|
|
382
|
+
UserNotificationRule,
|
|
383
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
384
|
+
null,
|
|
385
|
+
memberProps(),
|
|
386
|
+
requestType,
|
|
387
|
+
),
|
|
388
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("the lift is granted per operation, not per model", async () => {
|
|
393
|
+
/*
|
|
394
|
+
* ReadProjectUserNotificationRule appears only in the read lists and
|
|
395
|
+
* EditProjectUserNotificationRule only in the write lists, so each holder is
|
|
396
|
+
* still force-scoped on the verbs the other one covers. This is worth
|
|
397
|
+
* pinning because the natural refactor - one ADMIN_PERMISSIONS constant
|
|
398
|
+
* spread everywhere - would silently turn a read grant into a write grant.
|
|
399
|
+
*/
|
|
400
|
+
const readOnly: Query<BaseModel> =
|
|
401
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
402
|
+
UserNotificationRule,
|
|
403
|
+
{},
|
|
404
|
+
null,
|
|
405
|
+
tenantProps(Permission.ReadProjectUserNotificationRule),
|
|
406
|
+
DatabaseRequestType.Update,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
expect(scopedUserIdOf(readOnly)).toBe(CALLER_ID.toString());
|
|
410
|
+
|
|
411
|
+
const writeOnly: Query<BaseModel> =
|
|
412
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
413
|
+
UserNotificationRule,
|
|
414
|
+
{},
|
|
415
|
+
null,
|
|
416
|
+
tenantProps(Permission.EditProjectUserNotificationRule),
|
|
417
|
+
DatabaseRequestType.Read,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
expect(scopedUserIdOf(writeOnly)).toBe(CALLER_ID.toString());
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("an administrator of a DIFFERENT project gets no lift here", async () => {
|
|
424
|
+
/*
|
|
425
|
+
* userTenantAccessPermission is keyed by project, and getUserPermissions
|
|
426
|
+
* only reads the bucket matching props.tenantId. A ProjectAdmin of some
|
|
427
|
+
* other project therefore arrives holding nothing but CurrentUser and is
|
|
428
|
+
* scoped like any member. This is the tenant half of the isolation the
|
|
429
|
+
* service-layer membership check completes on the create path.
|
|
430
|
+
*/
|
|
431
|
+
const foreignProjectId: ObjectID = ObjectID.generate();
|
|
432
|
+
|
|
433
|
+
const query: Query<BaseModel> =
|
|
434
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
435
|
+
UserNotificationRule,
|
|
436
|
+
{},
|
|
437
|
+
null,
|
|
438
|
+
{
|
|
439
|
+
userId: CALLER_ID,
|
|
440
|
+
tenantId: PROJECT_ID,
|
|
441
|
+
userType: UserType.User,
|
|
442
|
+
userTenantAccessPermission: {
|
|
443
|
+
[foreignProjectId.toString()]: {
|
|
444
|
+
projectId: foreignProjectId,
|
|
445
|
+
permissions: [
|
|
446
|
+
{
|
|
447
|
+
permission: Permission.ProjectAdmin,
|
|
448
|
+
labelIds: [],
|
|
449
|
+
isBlockPermission: false,
|
|
450
|
+
_type: "UserPermission",
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
_type: "UserTenantAccessPermission",
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
DatabaseRequestType.Read,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("the rule table's lists carry the roles and the granular permission and nothing else", () => {
|
|
464
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
465
|
+
|
|
466
|
+
expect(rule.getReadPermissions()).toEqual([
|
|
467
|
+
Permission.CurrentUser,
|
|
468
|
+
...ADMIN_READ_GRANTS,
|
|
469
|
+
]);
|
|
470
|
+
|
|
471
|
+
for (const list of [
|
|
472
|
+
rule.getCreatePermissions(),
|
|
473
|
+
rule.getUpdatePermissions(),
|
|
474
|
+
rule.getDeletePermissions(),
|
|
475
|
+
]) {
|
|
476
|
+
expect(list).toEqual([Permission.CurrentUser, ...ADMIN_WRITE_GRANTS]);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe("the seven methods - an administrator may not look either", () => {
|
|
482
|
+
/*
|
|
483
|
+
* THE CORE DECISION OF THIS PHASE AS IT FINALLY SHIPPED, AND THE ONE MOST
|
|
484
|
+
* LIKELY TO BE "TIDIED UP" LATER, because seven models that differ from their
|
|
485
|
+
* sibling in all four lists look like an oversight rather than a decision.
|
|
486
|
+
*
|
|
487
|
+
* The read half of these lists was widened once and then reverted. The reason
|
|
488
|
+
* it could not stay is that the table-level scope is not one gate among
|
|
489
|
+
* several here - it is the ONLY gate. Every column on these models declares
|
|
490
|
+
* `read: [Permission.CurrentUser]`, and that list is intersected by NAME
|
|
491
|
+
* against a permission set that always contains the auto-granted CurrentUser,
|
|
492
|
+
* so it admits every authenticated caller on its own. What made it mean
|
|
493
|
+
* "owner only" was TenantPermission stamping `userId = me` onto the query,
|
|
494
|
+
* which happens exactly while this table list holds nothing but CurrentUser.
|
|
495
|
+
* Widening the table read removed the stamp and with it the only row scope in
|
|
496
|
+
* the path.
|
|
497
|
+
*
|
|
498
|
+
* Write was never widened and never could be, for a second and independent
|
|
499
|
+
* reason. A rule is a pair - the ownership column decides whose pages select
|
|
500
|
+
* the row, the method relation decides where those pages are delivered - so an
|
|
501
|
+
* administrator who could ADD a method to somebody else's account would hold a
|
|
502
|
+
* one-step paging hijack: register a phone or webhook you control on the
|
|
503
|
+
* victim, point their rules at it, and their pages arrive on your device.
|
|
504
|
+
*
|
|
505
|
+
* Neither restriction costs the administrator the diagnosis. A member with no
|
|
506
|
+
* methods needs to verify a device THEY own, which nobody else can do for
|
|
507
|
+
* them, and "does this colleague have a verified method" is answered by
|
|
508
|
+
* OnCallReadinessService's masked payload without this table being read at
|
|
509
|
+
* all.
|
|
510
|
+
*/
|
|
511
|
+
|
|
512
|
+
test.each(METHOD_MODELS)(
|
|
513
|
+
"an administrator naming another member's %s row for read is refused",
|
|
514
|
+
async (_name: string, modelType: ModelConstructor): Promise<void> => {
|
|
515
|
+
/*
|
|
516
|
+
* The inversion of the assertion this group used to carry, and the reason
|
|
517
|
+
* the group exists at all. The table list is CurrentUser-only, so an
|
|
518
|
+
* administrator's intersection contains nothing but the auto-granted
|
|
519
|
+
* permission; addCurrentUserScopeToQuery therefore refuses the named
|
|
520
|
+
* target outright rather than quietly redirecting it, exactly as it does
|
|
521
|
+
* for a plain member.
|
|
522
|
+
*
|
|
523
|
+
* Both the read grants AND the write grants are exercised, because the
|
|
524
|
+
* claim is that NO permission a project can hand out reaches these rows -
|
|
525
|
+
* not that one particular list was left alone.
|
|
526
|
+
*/
|
|
527
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
528
|
+
await expect(
|
|
529
|
+
TenantPermission.addTenantScopeToQuery(
|
|
530
|
+
modelType,
|
|
531
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
532
|
+
null,
|
|
533
|
+
tenantProps(grant),
|
|
534
|
+
DatabaseRequestType.Read,
|
|
535
|
+
),
|
|
536
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
test.each(METHOD_MODELS)(
|
|
542
|
+
"an administrator's unscoped %s read comes back pinned to their own rows",
|
|
543
|
+
async (_name: string, modelType: ModelConstructor): Promise<void> => {
|
|
544
|
+
/*
|
|
545
|
+
* The shape a leak would actually arrive through, and the reason refusing
|
|
546
|
+
* a NAMED target is not sufficient on its own. An admin listing page asks
|
|
547
|
+
* for a project's methods rather than one member's, so it issues no userId
|
|
548
|
+
* at all - and a query with no userId cannot be refused for naming the
|
|
549
|
+
* wrong one. What protects it is the stamp: the ownership predicate is
|
|
550
|
+
* written onto the query, and the listing comes back containing the
|
|
551
|
+
* administrator's own rows and nobody else's.
|
|
552
|
+
*/
|
|
553
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
554
|
+
const query: Query<BaseModel> =
|
|
555
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
556
|
+
modelType,
|
|
557
|
+
{},
|
|
558
|
+
null,
|
|
559
|
+
tenantProps(grant),
|
|
560
|
+
DatabaseRequestType.Read,
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
test.each(METHOD_MODELS)(
|
|
569
|
+
"an administrator reading their OWN %s row still succeeds end to end",
|
|
570
|
+
async (_name: string, modelType: ModelConstructor): Promise<void> => {
|
|
571
|
+
/*
|
|
572
|
+
* The constraint every version of this design has had to respect, and the
|
|
573
|
+
* one a heavy-handed fix breaks. An administrator is a member too, and
|
|
574
|
+
* /api/user-webhook and its siblings are where they manage their own
|
|
575
|
+
* methods; a change that withheld these rows whenever the caller happens
|
|
576
|
+
* to hold an administrator permission would satisfy every refusal above
|
|
577
|
+
* and break the administrator's own notification settings page.
|
|
578
|
+
*
|
|
579
|
+
* Driven through BasePermission.checkPermissions rather than the tenant
|
|
580
|
+
* scope alone so that the table-level check, the query check and the
|
|
581
|
+
* select check all run - the owner's read has to survive the whole path,
|
|
582
|
+
* not just the first stage of it.
|
|
583
|
+
*/
|
|
584
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
585
|
+
await expect(
|
|
586
|
+
BasePermission.checkPermissions(
|
|
587
|
+
modelType,
|
|
588
|
+
{ userId: CALLER_ID } as Query<BaseModel>,
|
|
589
|
+
{ userId: true } as Select<BaseModel>,
|
|
590
|
+
tenantProps(grant),
|
|
591
|
+
DatabaseRequestType.Read,
|
|
592
|
+
),
|
|
593
|
+
).resolves.toBeDefined();
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
test.each(METHOD_MODELS)(
|
|
599
|
+
"an administrator is still force-scoped when updating or deleting %s",
|
|
600
|
+
async (_name: string, modelType: ModelConstructor): Promise<void> => {
|
|
601
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
602
|
+
for (const requestType of [
|
|
603
|
+
DatabaseRequestType.Update,
|
|
604
|
+
DatabaseRequestType.Delete,
|
|
605
|
+
]) {
|
|
606
|
+
const query: Query<BaseModel> =
|
|
607
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
608
|
+
modelType,
|
|
609
|
+
{},
|
|
610
|
+
null,
|
|
611
|
+
tenantProps(grant),
|
|
612
|
+
requestType,
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
test.each(METHOD_MODELS)(
|
|
622
|
+
"an administrator naming another member's %s row for update or delete is refused",
|
|
623
|
+
async (_name: string, modelType: ModelConstructor): Promise<void> => {
|
|
624
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
625
|
+
for (const requestType of [
|
|
626
|
+
DatabaseRequestType.Update,
|
|
627
|
+
DatabaseRequestType.Delete,
|
|
628
|
+
]) {
|
|
629
|
+
await expect(
|
|
630
|
+
TenantPermission.addTenantScopeToQuery(
|
|
631
|
+
modelType,
|
|
632
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
633
|
+
null,
|
|
634
|
+
tenantProps(grant),
|
|
635
|
+
requestType,
|
|
636
|
+
),
|
|
637
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
test.each(METHOD_MODELS)(
|
|
644
|
+
"an administrator cannot create a %s row on another member's behalf",
|
|
645
|
+
(_name: string, modelType: ModelConstructor): void => {
|
|
646
|
+
/*
|
|
647
|
+
* The create list is [CurrentUser], so an administrator arrives at
|
|
648
|
+
* CreatePermission's ownership gate looking exactly like a member: the
|
|
649
|
+
* intersection contains nothing but the auto-granted permission, the gate
|
|
650
|
+
* does not defer, and a row naming somebody else is refused.
|
|
651
|
+
*/
|
|
652
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
653
|
+
expect(() => {
|
|
654
|
+
return CreatePermission.checkCreatePermissions(
|
|
655
|
+
modelType,
|
|
656
|
+
makeRow(modelType, OTHER_USER_ID) as never,
|
|
657
|
+
tenantProps(grant),
|
|
658
|
+
);
|
|
659
|
+
}).toThrow(NotAuthorizedException);
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
test.each(METHOD_MODELS)(
|
|
665
|
+
"%s's own notification methods are still fully theirs to manage",
|
|
666
|
+
(_name: string, modelType: ModelConstructor): void => {
|
|
667
|
+
/*
|
|
668
|
+
* The other side of every refusal above: withholding these rows from
|
|
669
|
+
* administrators must not have withheld them from the owner, who is the
|
|
670
|
+
* only person able to verify a device in the first place.
|
|
671
|
+
*/
|
|
672
|
+
expect(() => {
|
|
673
|
+
return CreatePermission.checkCreatePermissions(
|
|
674
|
+
modelType,
|
|
675
|
+
makeRow(modelType, CALLER_ID) as never,
|
|
676
|
+
memberProps(),
|
|
677
|
+
);
|
|
678
|
+
}).not.toThrow();
|
|
679
|
+
},
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
test.each(METHOD_MODELS)(
|
|
683
|
+
"%s's table lists name CurrentUser and nothing else, on all four verbs",
|
|
684
|
+
(_name: string, modelType: ModelConstructor): void => {
|
|
685
|
+
/*
|
|
686
|
+
* Asserted literally rather than with toContain. The failure mode this
|
|
687
|
+
* guards is an entry ARRIVING, not one going missing, so a containment
|
|
688
|
+
* check would pass straight through the very change it exists to catch.
|
|
689
|
+
*
|
|
690
|
+
* READ IS IN THIS LIST DELIBERATELY. It is the one that shipped widened
|
|
691
|
+
* and was reverted, so it is the one a later commit is most likely to
|
|
692
|
+
* re-add while "restoring the admin feature" - and re-adding it silently
|
|
693
|
+
* removes the row scope that every column on the model depends on.
|
|
694
|
+
*/
|
|
695
|
+
const model: BaseModel = new modelType();
|
|
696
|
+
|
|
697
|
+
for (const list of [
|
|
698
|
+
model.getReadPermissions(),
|
|
699
|
+
model.getCreatePermissions(),
|
|
700
|
+
model.getUpdatePermissions(),
|
|
701
|
+
model.getDeletePermissions(),
|
|
702
|
+
]) {
|
|
703
|
+
expect(list).toEqual([Permission.CurrentUser]);
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
describe("R2 - the ownership and tenant columns stay frozen after creation", () => {
|
|
710
|
+
/*
|
|
711
|
+
* The original plan said to mirror the widened lists onto the per-column
|
|
712
|
+
* blocks. Applied literally to userId that would let an administrator
|
|
713
|
+
* RE-POINT an existing rule at a different owner: take a rule that already
|
|
714
|
+
* points at your own verified phone, move its userId to a colleague, and the
|
|
715
|
+
* colleague's pages ring your handset. Nothing distinguishes that from a
|
|
716
|
+
* legitimate edit at the row level. It is the hole the create-side ownership
|
|
717
|
+
* gate closes, reopened through a different verb.
|
|
718
|
+
*
|
|
719
|
+
* Both spellings have to be pinned. `user` and `userId` are two decorated
|
|
720
|
+
* members over one physical join column - an update expressed as
|
|
721
|
+
* `user: { _id }` resolves to the same write - so closing one and leaving the
|
|
722
|
+
* other open closes nothing at all.
|
|
723
|
+
*/
|
|
724
|
+
|
|
725
|
+
const FROZEN_COLUMNS: Array<string> = [
|
|
726
|
+
"userId",
|
|
727
|
+
"user",
|
|
728
|
+
"projectId",
|
|
729
|
+
"project",
|
|
730
|
+
];
|
|
731
|
+
|
|
732
|
+
test("the rule model's ownership and tenant columns are writable by nobody", () => {
|
|
733
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
734
|
+
|
|
735
|
+
for (const columnName of FROZEN_COLUMNS) {
|
|
736
|
+
const accessControl: ColumnAccessControl | null =
|
|
737
|
+
rule.getColumnAccessControlFor(columnName);
|
|
738
|
+
|
|
739
|
+
expect(accessControl).not.toBeNull();
|
|
740
|
+
expect(accessControl?.update).toEqual([]);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test.each(METHOD_MODELS)(
|
|
745
|
+
"%s's ownership and tenant columns are writable by nobody",
|
|
746
|
+
(_name: string, modelType: ModelConstructor): void => {
|
|
747
|
+
const model: BaseModel = new modelType();
|
|
748
|
+
|
|
749
|
+
for (const columnName of FROZEN_COLUMNS) {
|
|
750
|
+
const accessControl: ColumnAccessControl | null =
|
|
751
|
+
model.getColumnAccessControlFor(columnName);
|
|
752
|
+
|
|
753
|
+
expect(accessControl).not.toBeNull();
|
|
754
|
+
expect(accessControl?.update).toEqual([]);
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
test("no administrator permission has leaked into a frozen column's update list", () => {
|
|
760
|
+
/*
|
|
761
|
+
* The empty-array assertions above already cover this, but they read as
|
|
762
|
+
* "this happens to be empty" rather than "these permissions specifically
|
|
763
|
+
* must never appear". Stating the second form separately means a future
|
|
764
|
+
* mirroring of the widened lists fails against a test that names the reason.
|
|
765
|
+
*/
|
|
766
|
+
const models: Array<BaseModel> = [
|
|
767
|
+
new UserNotificationRule(),
|
|
768
|
+
...METHOD_MODELS.map(([, modelType]: [string, ModelConstructor]) => {
|
|
769
|
+
return new modelType();
|
|
770
|
+
}),
|
|
771
|
+
];
|
|
772
|
+
|
|
773
|
+
for (const model of models) {
|
|
774
|
+
for (const columnName of FROZEN_COLUMNS) {
|
|
775
|
+
const updateList: Array<Permission> =
|
|
776
|
+
model.getColumnAccessControlFor(columnName)?.update || [];
|
|
777
|
+
|
|
778
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
779
|
+
expect(updateList).not.toContain(grant);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
test("an administrator sending a new owner in an update body is refused by the column gate", () => {
|
|
786
|
+
/*
|
|
787
|
+
* The behavioural teeth of the assertions above. A frozen column is not a
|
|
788
|
+
* documentation exercise: ColumnPermissions rejects the write because the
|
|
789
|
+
* update list is empty and therefore intersects nothing, no matter what the
|
|
790
|
+
* caller holds.
|
|
791
|
+
*/
|
|
792
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
793
|
+
rule.userId = OTHER_USER_ID;
|
|
794
|
+
|
|
795
|
+
expect(() => {
|
|
796
|
+
return ColumnPermissions.checkDataColumnPermissions(
|
|
797
|
+
UserNotificationRule,
|
|
798
|
+
rule,
|
|
799
|
+
tenantProps(Permission.EditProjectUserNotificationRule),
|
|
800
|
+
DatabaseRequestType.Update,
|
|
801
|
+
);
|
|
802
|
+
}).toThrow(BadDataException);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
test("an administrator may still rewrite the columns a repair actually needs", () => {
|
|
806
|
+
/*
|
|
807
|
+
* The capability has to survive the freeze or the phase delivers nothing.
|
|
808
|
+
* Re-pointing the method FK is the repair; the delay and the opt-out flag
|
|
809
|
+
* are the two preference columns worth correcting. All three carry no
|
|
810
|
+
* redirect risk on their own, because the service layer separately asserts
|
|
811
|
+
* that a method FK being written belongs to the same user as the rule's
|
|
812
|
+
* persisted owner.
|
|
813
|
+
*/
|
|
814
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
815
|
+
rule.userEmailId = ObjectID.generate();
|
|
816
|
+
rule.notifyAfterMinutes = 5;
|
|
817
|
+
rule.isOptOut = false;
|
|
818
|
+
|
|
819
|
+
expect(() => {
|
|
820
|
+
return ColumnPermissions.checkDataColumnPermissions(
|
|
821
|
+
UserNotificationRule,
|
|
822
|
+
rule,
|
|
823
|
+
tenantProps(Permission.EditProjectUserNotificationRule),
|
|
824
|
+
DatabaseRequestType.Update,
|
|
825
|
+
);
|
|
826
|
+
}).not.toThrow();
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("the rule's address columns stay create-only even for an administrator", () => {
|
|
830
|
+
/*
|
|
831
|
+
* ruleType and the two severity relations are the cell in the grid a rule
|
|
832
|
+
* occupies. Moving one reads as an edit and behaves as a delete plus a
|
|
833
|
+
* create, so it stays closed to everybody - an administrator repairing a
|
|
834
|
+
* member's setup adds and removes rules rather than sliding one sideways.
|
|
835
|
+
*/
|
|
836
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
837
|
+
|
|
838
|
+
for (const columnName of [
|
|
839
|
+
"ruleType",
|
|
840
|
+
"incidentSeverityId",
|
|
841
|
+
"alertSeverityId",
|
|
842
|
+
]) {
|
|
843
|
+
expect(rule.getColumnAccessControlFor(columnName)?.update).toEqual([]);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
describe("R4 - the new permissions are opt-in, catalogued, and never auto-granted", () => {
|
|
849
|
+
test.each(NEW_GRANULAR_PERMISSIONS)(
|
|
850
|
+
"%s has a catalogue entry, so getTitle does not throw at runtime",
|
|
851
|
+
(permission: Permission): void => {
|
|
852
|
+
/*
|
|
853
|
+
* A granular permission needs BOTH the enum member and a PermissionProps
|
|
854
|
+
* entry. Without the entry, PermissionHelper.getTitle throws
|
|
855
|
+
* BadDataException - and it is called from TablePermission's failure path,
|
|
856
|
+
* so the missing entry would surface as an exception raised while
|
|
857
|
+
* composing the message for a DIFFERENT exception.
|
|
858
|
+
*/
|
|
859
|
+
expect(() => {
|
|
860
|
+
return PermissionHelper.getTitle(permission);
|
|
861
|
+
}).not.toThrow();
|
|
862
|
+
|
|
863
|
+
expect(PermissionHelper.getTitle(permission).length).toBeGreaterThan(0);
|
|
864
|
+
expect(
|
|
865
|
+
PermissionHelper.getDescription(permission).length,
|
|
866
|
+
).toBeGreaterThan(0);
|
|
867
|
+
},
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
test.each(NEW_GRANULAR_PERMISSIONS)(
|
|
871
|
+
"%s is assignable to a tenant but is not a role permission",
|
|
872
|
+
(permission: Permission): void => {
|
|
873
|
+
/*
|
|
874
|
+
* isRolePermission true would file it under getRolePermissionProps(), the
|
|
875
|
+
* list every default project role is built from - so the ability to read
|
|
876
|
+
* and rewrite a colleague's paging configuration would arrive silently
|
|
877
|
+
* with an existing role instead of being handed out on purpose. It also
|
|
878
|
+
* breaks the roles/granular partition asserted in Permission.test.ts.
|
|
879
|
+
*/
|
|
880
|
+
const props: PermissionProps | undefined =
|
|
881
|
+
PermissionHelper.getAllPermissionProps().find(
|
|
882
|
+
(item: PermissionProps) => {
|
|
883
|
+
return item.permission === permission;
|
|
884
|
+
},
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
expect(props).toBeDefined();
|
|
888
|
+
expect(props?.isAssignableToTenant).toBe(true);
|
|
889
|
+
expect(props?.isRolePermission).toBe(false);
|
|
890
|
+
|
|
891
|
+
expect(
|
|
892
|
+
PermissionHelper.getRolePermissionProps().map(
|
|
893
|
+
(item: PermissionProps) => {
|
|
894
|
+
return item.permission;
|
|
895
|
+
},
|
|
896
|
+
),
|
|
897
|
+
).not.toContain(permission);
|
|
898
|
+
|
|
899
|
+
expect(
|
|
900
|
+
PermissionHelper.getTenantPermissionProps().map(
|
|
901
|
+
(item: PermissionProps) => {
|
|
902
|
+
return item.permission;
|
|
903
|
+
},
|
|
904
|
+
),
|
|
905
|
+
).toContain(permission);
|
|
906
|
+
},
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
test.each(NEW_GRANULAR_PERMISSIONS)(
|
|
910
|
+
"%s is not handed to a member who was never granted it",
|
|
911
|
+
(permission: Permission): void => {
|
|
912
|
+
/*
|
|
913
|
+
* Public and CurrentUser are pushed onto every authenticated caller by
|
|
914
|
+
* getUserPermissions itself. Anything that joined them would be held by
|
|
915
|
+
* the entire project the moment it shipped.
|
|
916
|
+
*/
|
|
917
|
+
const resolved: Array<Permission> =
|
|
918
|
+
DatabaseCommonInteractionPropsUtil.getUserPermissions(
|
|
919
|
+
memberProps(),
|
|
920
|
+
PermissionType.Allow,
|
|
921
|
+
).map((item: UserPermission) => {
|
|
922
|
+
return item.permission;
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
expect(resolved).toContain(Permission.CurrentUser);
|
|
926
|
+
expect(resolved).not.toContain(permission);
|
|
927
|
+
},
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
test.each(NEW_GRANULAR_PERMISSIONS)(
|
|
931
|
+
"%s counts as a real grant rather than an auto-granted one",
|
|
932
|
+
(permission: Permission): void => {
|
|
933
|
+
/*
|
|
934
|
+
* This is the behavioural statement of "not in
|
|
935
|
+
* AUTO_GRANTED_TENANT_PERMISSIONS", which is module-private to
|
|
936
|
+
* TenantPermission. That list is what the intersection subtracts before
|
|
937
|
+
* deciding whether the caller is here purely as some logged-in user; a new
|
|
938
|
+
* permission added to it would appear to lift the scope while actually
|
|
939
|
+
* lifting it for everybody. Asserting the consequence rather than the
|
|
940
|
+
* constant also survives the constant being renamed.
|
|
941
|
+
*/
|
|
942
|
+
expect(
|
|
943
|
+
TenantPermission.isAccessGrantedOnlyByCurrentUser(
|
|
944
|
+
UserNotificationRule,
|
|
945
|
+
tenantProps(permission),
|
|
946
|
+
permission === Permission.ReadProjectUserNotificationRule
|
|
947
|
+
? DatabaseRequestType.Read
|
|
948
|
+
: DatabaseRequestType.Update,
|
|
949
|
+
),
|
|
950
|
+
).toBe(false);
|
|
951
|
+
},
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
test("a caller holding neither is still treated as here-by-CurrentUser-alone", () => {
|
|
955
|
+
// The control for the assertion above - without it, `false` proves nothing.
|
|
956
|
+
expect(
|
|
957
|
+
TenantPermission.isAccessGrantedOnlyByCurrentUser(
|
|
958
|
+
UserNotificationRule,
|
|
959
|
+
memberProps(),
|
|
960
|
+
DatabaseRequestType.Read,
|
|
961
|
+
),
|
|
962
|
+
).toBe(true);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
test.each(NEW_GRANULAR_PERMISSIONS)(
|
|
966
|
+
"%s does not lift the scope on a notification method, however real a grant it is",
|
|
967
|
+
(permission: Permission): void => {
|
|
968
|
+
/*
|
|
969
|
+
* The same helper, pointed at the models the permission deliberately does
|
|
970
|
+
* NOT reach. isAccessGrantedOnlyByCurrentUser answers per model, and here
|
|
971
|
+
* it answers true for every verb because the permission appears in none of
|
|
972
|
+
* these lists - which is what makes the "real grant" result above a
|
|
973
|
+
* statement about the rule table rather than about the permission being
|
|
974
|
+
* powerful everywhere it is held.
|
|
975
|
+
*/
|
|
976
|
+
for (const [, modelType] of METHOD_MODELS) {
|
|
977
|
+
for (const requestType of ROW_SCOPED_REQUEST_TYPES) {
|
|
978
|
+
expect(
|
|
979
|
+
TenantPermission.isAccessGrantedOnlyByCurrentUser(
|
|
980
|
+
modelType,
|
|
981
|
+
tenantProps(permission),
|
|
982
|
+
requestType,
|
|
983
|
+
),
|
|
984
|
+
).toBe(true);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
);
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
describe("R5 - a project whose teams predate the granular permissions still gets the feature", () => {
|
|
992
|
+
/*
|
|
993
|
+
* Existing project teams are seeded with ROLES only. A brand-new granular
|
|
994
|
+
* permission is therefore held by literally nobody on the day it ships, so a
|
|
995
|
+
* list containing only ReadProjectUserNotificationRule would leave this
|
|
996
|
+
* capability dead on arrival for every project that already exists. The roles
|
|
997
|
+
* are listed ALONGSIDE the granular permission on the rule table for that
|
|
998
|
+
* reason, and this group asserts the consequence rather than trusting the
|
|
999
|
+
* list.
|
|
1000
|
+
*/
|
|
1001
|
+
|
|
1002
|
+
test.each([Permission.ProjectOwner, Permission.ProjectAdmin])(
|
|
1003
|
+
"%s alone - with no granular grant at all - can read another member's rules",
|
|
1004
|
+
async (role: Permission): Promise<void> => {
|
|
1005
|
+
const query: Query<BaseModel> =
|
|
1006
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
1007
|
+
UserNotificationRule,
|
|
1008
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
1009
|
+
null,
|
|
1010
|
+
tenantProps(role),
|
|
1011
|
+
DatabaseRequestType.Read,
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
expect(scopedUserIdOf(query)).toBe(OTHER_USER_ID.toString());
|
|
1015
|
+
},
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
test.each([Permission.ProjectOwner, Permission.ProjectAdmin])(
|
|
1019
|
+
"%s alone can repair another member's rules",
|
|
1020
|
+
async (role: Permission): Promise<void> => {
|
|
1021
|
+
for (const requestType of [
|
|
1022
|
+
DatabaseRequestType.Update,
|
|
1023
|
+
DatabaseRequestType.Delete,
|
|
1024
|
+
]) {
|
|
1025
|
+
const query: Query<BaseModel> =
|
|
1026
|
+
await TenantPermission.addTenantScopeToQuery(
|
|
1027
|
+
UserNotificationRule,
|
|
1028
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
1029
|
+
null,
|
|
1030
|
+
tenantProps(role),
|
|
1031
|
+
requestType,
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
expect(scopedUserIdOf(query)).toBe(OTHER_USER_ID.toString());
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
);
|
|
1038
|
+
|
|
1039
|
+
test.each(METHOD_MODELS)(
|
|
1040
|
+
"%s is unreadable by a role holder too, so the reach stops at the rule table",
|
|
1041
|
+
async (_name: string, modelType: ModelConstructor): Promise<void> => {
|
|
1042
|
+
/*
|
|
1043
|
+
* The boundary of the previous two assertions. ProjectOwner is the most
|
|
1044
|
+
* powerful role a project has, and it still cannot reach a colleague's
|
|
1045
|
+
* notification method - so "an administrator can see a member's paging
|
|
1046
|
+
* configuration" means the RULES and their method FK ids, and stops
|
|
1047
|
+
* exactly there. Asserted per role rather than only for the granular
|
|
1048
|
+
* permission because a role is what an existing project actually holds.
|
|
1049
|
+
*/
|
|
1050
|
+
for (const role of [Permission.ProjectOwner, Permission.ProjectAdmin]) {
|
|
1051
|
+
await expect(
|
|
1052
|
+
TenantPermission.addTenantScopeToQuery(
|
|
1053
|
+
modelType,
|
|
1054
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
1055
|
+
null,
|
|
1056
|
+
tenantProps(role),
|
|
1057
|
+
DatabaseRequestType.Read,
|
|
1058
|
+
),
|
|
1059
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
);
|
|
1063
|
+
|
|
1064
|
+
test("both roles sit beside the granular permission in every rule-table list", () => {
|
|
1065
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
1066
|
+
|
|
1067
|
+
expect(rule.getReadPermissions()).toContain(Permission.ProjectOwner);
|
|
1068
|
+
expect(rule.getReadPermissions()).toContain(Permission.ProjectAdmin);
|
|
1069
|
+
expect(rule.getReadPermissions()).toContain(
|
|
1070
|
+
Permission.ReadProjectUserNotificationRule,
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
for (const list of [
|
|
1074
|
+
rule.getCreatePermissions(),
|
|
1075
|
+
rule.getUpdatePermissions(),
|
|
1076
|
+
rule.getDeletePermissions(),
|
|
1077
|
+
]) {
|
|
1078
|
+
expect(list).toContain(Permission.ProjectOwner);
|
|
1079
|
+
expect(list).toContain(Permission.ProjectAdmin);
|
|
1080
|
+
expect(list).toContain(Permission.EditProjectUserNotificationRule);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
test.each(METHOD_MODELS)(
|
|
1085
|
+
"neither role nor granular permission appears anywhere in %s's lists",
|
|
1086
|
+
(_name: string, modelType: ModelConstructor): void => {
|
|
1087
|
+
/*
|
|
1088
|
+
* The complement of the assertion above, stated as a prohibition so the
|
|
1089
|
+
* reason is legible at the point of failure. The literal-equality test in
|
|
1090
|
+
* the methods group already fails if any of these arrive; this one fails
|
|
1091
|
+
* with a message that names WHICH permission arrived, which is the
|
|
1092
|
+
* difference between a five-second diagnosis and a five-minute one.
|
|
1093
|
+
*/
|
|
1094
|
+
const model: BaseModel = new modelType();
|
|
1095
|
+
|
|
1096
|
+
for (const list of [
|
|
1097
|
+
model.getReadPermissions(),
|
|
1098
|
+
model.getCreatePermissions(),
|
|
1099
|
+
model.getUpdatePermissions(),
|
|
1100
|
+
model.getDeletePermissions(),
|
|
1101
|
+
]) {
|
|
1102
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
1103
|
+
expect(list).not.toContain(grant);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
);
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
describe("the create-side ownership gate survived the widening", () => {
|
|
1111
|
+
/*
|
|
1112
|
+
* Phase 2.5 closed a hole in which create checked column NAMES but never the
|
|
1113
|
+
* ownership VALUE, so a caller could write a row owned by somebody else. That
|
|
1114
|
+
* gate is permission-AWARE: CurrentUser alone means "you may create rows for
|
|
1115
|
+
* yourself and nobody else", while a real role permission in the create list
|
|
1116
|
+
* makes it defer.
|
|
1117
|
+
*
|
|
1118
|
+
* Widening the rule table's create list is exactly the change that could have
|
|
1119
|
+
* weakened it, because a member's intersection now has more candidates to
|
|
1120
|
+
* survive. It does not - the member holds none of the added permissions - but
|
|
1121
|
+
* that is a fact about the caller, not about the list, so it needs asserting.
|
|
1122
|
+
*/
|
|
1123
|
+
|
|
1124
|
+
test("a plain member still cannot create a rule owned by another user", () => {
|
|
1125
|
+
expect(() => {
|
|
1126
|
+
return CreatePermission.checkCreatePermissions(
|
|
1127
|
+
UserNotificationRule,
|
|
1128
|
+
makeRow(UserNotificationRule, OTHER_USER_ID) as never,
|
|
1129
|
+
memberProps(),
|
|
1130
|
+
);
|
|
1131
|
+
}).toThrow(NotAuthorizedException);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
test("the refusal still names the problem", () => {
|
|
1135
|
+
expect(() => {
|
|
1136
|
+
return CreatePermission.checkCreatePermissions(
|
|
1137
|
+
UserNotificationRule,
|
|
1138
|
+
makeRow(UserNotificationRule, OTHER_USER_ID) as never,
|
|
1139
|
+
memberProps(),
|
|
1140
|
+
);
|
|
1141
|
+
}).toThrow(/another user's/i);
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
test("a member creating a rule for themselves is untouched", () => {
|
|
1145
|
+
expect(() => {
|
|
1146
|
+
return CreatePermission.checkCreatePermissions(
|
|
1147
|
+
UserNotificationRule,
|
|
1148
|
+
makeRow(UserNotificationRule, CALLER_ID) as never,
|
|
1149
|
+
memberProps(),
|
|
1150
|
+
);
|
|
1151
|
+
}).not.toThrow();
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
test("a member omitting the owner is stamped rather than refused", () => {
|
|
1155
|
+
const row: BaseModel = makeRow(UserNotificationRule, undefined);
|
|
1156
|
+
|
|
1157
|
+
CreatePermission.checkCreatePermissions(
|
|
1158
|
+
UserNotificationRule,
|
|
1159
|
+
row as never,
|
|
1160
|
+
memberProps(),
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1163
|
+
expect(row.getColumnValue("userId")?.toString()).toBe(CALLER_ID.toString());
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
test("a member cannot plant an opt-out row to silence somebody else", () => {
|
|
1167
|
+
/*
|
|
1168
|
+
* The denial-of-paging direction of the same hole. An opt-out row is how a
|
|
1169
|
+
* user says "do not page me here"; planted on somebody else it suppresses
|
|
1170
|
+
* their pages AND suppresses the fallback that would otherwise rescue them,
|
|
1171
|
+
* because it is a genuine matching row.
|
|
1172
|
+
*/
|
|
1173
|
+
const optOut: UserNotificationRule = new UserNotificationRule();
|
|
1174
|
+
optOut.projectId = PROJECT_ID;
|
|
1175
|
+
optOut.userId = OTHER_USER_ID;
|
|
1176
|
+
optOut.isOptOut = true;
|
|
1177
|
+
|
|
1178
|
+
expect(() => {
|
|
1179
|
+
return CreatePermission.checkCreatePermissions(
|
|
1180
|
+
UserNotificationRule,
|
|
1181
|
+
optOut,
|
|
1182
|
+
memberProps(),
|
|
1183
|
+
);
|
|
1184
|
+
}).toThrow(NotAuthorizedException);
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
test.each(ADMIN_WRITE_GRANTS)(
|
|
1188
|
+
"%s reaches the service layer rather than being refused by the framework gate",
|
|
1189
|
+
(grant: Permission): void => {
|
|
1190
|
+
/*
|
|
1191
|
+
* The second branch of the create gate, and the one this phase walks
|
|
1192
|
+
* through: a caller holding a real role permission in the create list is
|
|
1193
|
+
* not stamped, so provisioning a rule for a member who has none is
|
|
1194
|
+
* possible at all.
|
|
1195
|
+
*
|
|
1196
|
+
* THE FRAMEWORK GATE IS NOT THE WHOLE CHECK. It deliberately says nothing
|
|
1197
|
+
* about WHICH user may be named, which is why UserNotificationRuleService
|
|
1198
|
+
* separately verifies that the supplied userId is a member of
|
|
1199
|
+
* props.tenantId before the row is written - without that, this branch
|
|
1200
|
+
* hands any administrator the ability to write rules for a user id
|
|
1201
|
+
* belonging to another project entirely. Do not read a passing test here
|
|
1202
|
+
* as "on-behalf-of creation is safe"; read it as "the framework defers,
|
|
1203
|
+
* and the service is where the answer lives".
|
|
1204
|
+
*/
|
|
1205
|
+
expect(() => {
|
|
1206
|
+
return CreatePermission.checkCreatePermissions(
|
|
1207
|
+
UserNotificationRule,
|
|
1208
|
+
makeRow(UserNotificationRule, OTHER_USER_ID) as never,
|
|
1209
|
+
tenantProps(grant),
|
|
1210
|
+
);
|
|
1211
|
+
}).not.toThrow();
|
|
1212
|
+
},
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1215
|
+
test("a read-only grant does not become a create grant by sitting on the same model", () => {
|
|
1216
|
+
/*
|
|
1217
|
+
* ReadProjectUserNotificationRule appears in no create list anywhere, so its
|
|
1218
|
+
* holder's intersection is the auto-granted CurrentUser alone and the gate
|
|
1219
|
+
* enforces ownership on them exactly as on a plain member. Worth its own
|
|
1220
|
+
* test because the two granular permissions are granted and revoked
|
|
1221
|
+
* together in practice, which makes it easy to assume they behave alike.
|
|
1222
|
+
*/
|
|
1223
|
+
expect(() => {
|
|
1224
|
+
return CreatePermission.checkCreatePermissions(
|
|
1225
|
+
UserNotificationRule,
|
|
1226
|
+
makeRow(UserNotificationRule, OTHER_USER_ID) as never,
|
|
1227
|
+
tenantProps(Permission.ReadProjectUserNotificationRule),
|
|
1228
|
+
);
|
|
1229
|
+
}).toThrow(NotAuthorizedException);
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
describe("the credentials behind a notification method, guarded by the table scope alone", () => {
|
|
1234
|
+
/*
|
|
1235
|
+
* REPLACES the group that used to guard these five columns with a per-column
|
|
1236
|
+
* decorator. That decorator - @OwnerOnlyColumn - is gone, and so is the
|
|
1237
|
+
* widening that made it necessary. The invariant it protected did not go
|
|
1238
|
+
* anywhere: an administrator learns THAT a member has a webhook, never where
|
|
1239
|
+
* it points.
|
|
1240
|
+
*
|
|
1241
|
+
* What enforces it now is the table scope itself, which is why this group is
|
|
1242
|
+
* a quarter of the size of the one it replaces. There is no longer a
|
|
1243
|
+
* column-level decision to verify per column and per query shape, because
|
|
1244
|
+
* there is no query on these models that reaches another member's ROW - so
|
|
1245
|
+
* every column on that row, credential or label, is out of reach for the same
|
|
1246
|
+
* single reason. The five columns are still enumerated rather than reduced to
|
|
1247
|
+
* one representative, because they are what a reader comes here looking for
|
|
1248
|
+
* and because "the row is unreachable" is a claim worth checking against the
|
|
1249
|
+
* things that would actually matter if it were false.
|
|
1250
|
+
*
|
|
1251
|
+
* The columns: the webhook URL and its signing secret (both bearer
|
|
1252
|
+
* credentials - anyone holding them can impersonate OneUptime to the member's
|
|
1253
|
+
* endpoint), the push device token, the Telegram chat id an unverified bot
|
|
1254
|
+
* message can be addressed to, and the verification code that turns
|
|
1255
|
+
* possession of that chat id into a verified method.
|
|
1256
|
+
*/
|
|
1257
|
+
|
|
1258
|
+
/*
|
|
1259
|
+
* Ordered name-then-column so the two %s placeholders in a test title consume
|
|
1260
|
+
* the two strings; a model constructor in a substituted position prints its
|
|
1261
|
+
* entire transpiled class body as the test name.
|
|
1262
|
+
*/
|
|
1263
|
+
const CREDENTIAL_COLUMNS: Array<[string, string, ModelConstructor]> = [
|
|
1264
|
+
["UserWebhook", "webhookUrl", UserWebhook],
|
|
1265
|
+
["UserWebhook", "secret", UserWebhook],
|
|
1266
|
+
["UserPush", "deviceToken", UserPush],
|
|
1267
|
+
["UserTelegram", "telegramChatId", UserTelegram],
|
|
1268
|
+
["UserTelegram", "verificationCode", UserTelegram],
|
|
1269
|
+
];
|
|
1270
|
+
|
|
1271
|
+
test.each(CREDENTIAL_COLUMNS)(
|
|
1272
|
+
"an administrator selecting %s.%s on another member's row never reaches the row",
|
|
1273
|
+
async (
|
|
1274
|
+
_name: string,
|
|
1275
|
+
column: string,
|
|
1276
|
+
modelType: ModelConstructor,
|
|
1277
|
+
): Promise<void> => {
|
|
1278
|
+
/*
|
|
1279
|
+
* Driven through BasePermission.checkPermissions - the entry point
|
|
1280
|
+
* ReadPermission.checkReadPermission and therefore every findBy goes
|
|
1281
|
+
* through - rather than through the tenant scope alone, so the refusal
|
|
1282
|
+
* asserted is the one a real request receives.
|
|
1283
|
+
*/
|
|
1284
|
+
for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
|
|
1285
|
+
await expect(
|
|
1286
|
+
BasePermission.checkPermissions(
|
|
1287
|
+
modelType,
|
|
1288
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
1289
|
+
{ [column]: true } as Select<BaseModel>,
|
|
1290
|
+
tenantProps(grant),
|
|
1291
|
+
DatabaseRequestType.Read,
|
|
1292
|
+
),
|
|
1293
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
);
|
|
1297
|
+
|
|
1298
|
+
test.each(CREDENTIAL_COLUMNS)(
|
|
1299
|
+
"an unscoped listing that selects %s.%s comes back holding only the caller's own rows",
|
|
1300
|
+
async (
|
|
1301
|
+
_name: string,
|
|
1302
|
+
column: string,
|
|
1303
|
+
modelType: ModelConstructor,
|
|
1304
|
+
): Promise<void> => {
|
|
1305
|
+
/*
|
|
1306
|
+
* The shape a leak actually arrives through, and the one the refusal
|
|
1307
|
+
* above cannot cover: an admin listing page fetches a project's methods
|
|
1308
|
+
* rather than one member's, so it names no user and cannot be refused for
|
|
1309
|
+
* naming the wrong one. It is allowed - and comes back stamped with the
|
|
1310
|
+
* caller's own id, which is what makes "allowed" harmless.
|
|
1311
|
+
*/
|
|
1312
|
+
const { query } = await BasePermission.checkPermissions(
|
|
1313
|
+
modelType,
|
|
1314
|
+
{} as Query<BaseModel>,
|
|
1315
|
+
{ [column]: true } as Select<BaseModel>,
|
|
1316
|
+
tenantProps(Permission.ProjectAdmin),
|
|
1317
|
+
DatabaseRequestType.Read,
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
expect(ownerIdOf(query)).toBe(CALLER_ID.toString());
|
|
1321
|
+
},
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
test.each(CREDENTIAL_COLUMNS)(
|
|
1325
|
+
"%s.%s is still delivered to its owner, so nothing was fixed by emptying a list",
|
|
1326
|
+
async (
|
|
1327
|
+
_name: string,
|
|
1328
|
+
column: string,
|
|
1329
|
+
modelType: ModelConstructor,
|
|
1330
|
+
): Promise<void> => {
|
|
1331
|
+
/*
|
|
1332
|
+
* The owner reads their own credential constantly - the settings page
|
|
1333
|
+
* renders it, verification compares it, delivery uses it. A fix that
|
|
1334
|
+
* wrote `read: []` on these columns would satisfy every refusal above and
|
|
1335
|
+
* break notification setup for every user in every project, so the
|
|
1336
|
+
* companion assertion is not optional.
|
|
1337
|
+
*
|
|
1338
|
+
* Issued as the UNSCOPED query the settings page actually sends, and by a
|
|
1339
|
+
* plain member, because that is the overwhelming majority of traffic to
|
|
1340
|
+
* these endpoints.
|
|
1341
|
+
*/
|
|
1342
|
+
await expect(
|
|
1343
|
+
BasePermission.checkPermissions(
|
|
1344
|
+
modelType,
|
|
1345
|
+
{} as Query<BaseModel>,
|
|
1346
|
+
{ [column]: true } as Select<BaseModel>,
|
|
1347
|
+
memberProps(),
|
|
1348
|
+
DatabaseRequestType.Read,
|
|
1349
|
+
),
|
|
1350
|
+
).resolves.toBeDefined();
|
|
1351
|
+
},
|
|
1352
|
+
);
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
describe("the rule relation - an administrator reads the FK, never the method behind it", () => {
|
|
1356
|
+
/*
|
|
1357
|
+
* THE ONE PATH THAT DOES NOT PASS THROUGH THE METHOD TABLE'S SCOPE, and
|
|
1358
|
+
* therefore the only place the guarantee above has to be re-established
|
|
1359
|
+
* rather than inherited.
|
|
1360
|
+
*
|
|
1361
|
+
* A read of UserNotificationRule is deliberately NOT row-scoped for an
|
|
1362
|
+
* administrator - that is the whole feature. A nested relation select issued
|
|
1363
|
+
* against that read (`select: { userEmail: { email: true } }`) resolves the
|
|
1364
|
+
* method row through the rule's join, so the only scope anywhere in the path
|
|
1365
|
+
* is the one the RULE table decided. Two checks stand between the request and
|
|
1366
|
+
* the row, and it is worth knowing exactly what each of them looks at:
|
|
1367
|
+
*
|
|
1368
|
+
* - SelectPermission.checkSelectPermission validates the OUTER key
|
|
1369
|
+
* (`userEmail`) against UserNotificationRule's column list, by name.
|
|
1370
|
+
* - QueryPermission.checkRelationQueryPermission validates the INNER key
|
|
1371
|
+
* (`email`) against UserEmail's column list - but only after consulting
|
|
1372
|
+
* that column's `canReadOnRelationQuery` flag, and a column carrying
|
|
1373
|
+
* `true` is waved through with no permission check at all. The related
|
|
1374
|
+
* model's TABLE list is never consulted by either check.
|
|
1375
|
+
*
|
|
1376
|
+
* Neither check can see the query, so neither can tell whose row this is.
|
|
1377
|
+
* That leaves the outer column list as the only place the decision can be
|
|
1378
|
+
* made, and it is why the seven method RELATION columns on
|
|
1379
|
+
* UserNotificationRule are `read: [Permission.CurrentUser]` while the seven
|
|
1380
|
+
* FK ID columns beside them are admin-readable. The pairing is the design in
|
|
1381
|
+
* one line: an administrator may learn WHICH method a rule points at, and may
|
|
1382
|
+
* re-point it at a different one, without ever learning what the method IS.
|
|
1383
|
+
* The id is a foreign key rather than a secret; the identifier behind it is
|
|
1384
|
+
* the secret, and OnCallReadinessService is where an administrator gets a
|
|
1385
|
+
* masked version of it.
|
|
1386
|
+
*
|
|
1387
|
+
* Asserted through the real permission entry point rather than by reading the
|
|
1388
|
+
* decorators, because the decorator is the mechanism and the refusal is the
|
|
1389
|
+
* guarantee - and this is precisely the path on which a plausible-looking
|
|
1390
|
+
* decorator has already failed to produce the refusal once.
|
|
1391
|
+
*/
|
|
1392
|
+
|
|
1393
|
+
const METHOD_FK_COLUMNS: Array<string> = [
|
|
1394
|
+
"userEmailId",
|
|
1395
|
+
"userSmsId",
|
|
1396
|
+
"userCallId",
|
|
1397
|
+
"userPushId",
|
|
1398
|
+
"userWhatsAppId",
|
|
1399
|
+
"userTelegramId",
|
|
1400
|
+
"userWebhookId",
|
|
1401
|
+
];
|
|
1402
|
+
|
|
1403
|
+
/*
|
|
1404
|
+
* Relation column, the identifier column selected through it, and the model
|
|
1405
|
+
* it resolves to. One entry per method, each naming the field that IS the
|
|
1406
|
+
* credential or the contact point for that channel.
|
|
1407
|
+
*/
|
|
1408
|
+
const RELATION_SELECT_SHAPES: Array<[string, string]> = [
|
|
1409
|
+
["userEmail", "email"],
|
|
1410
|
+
["userSms", "phone"],
|
|
1411
|
+
["userCall", "phone"],
|
|
1412
|
+
["userPush", "deviceToken"],
|
|
1413
|
+
["userWhatsApp", "phone"],
|
|
1414
|
+
["userTelegram", "telegramChatId"],
|
|
1415
|
+
["userWebhook", "webhookUrl"],
|
|
1416
|
+
];
|
|
1417
|
+
|
|
1418
|
+
test.each(ADMIN_READ_GRANTS)(
|
|
1419
|
+
"%s reads another member's rule together with every method FK id",
|
|
1420
|
+
async (grant: Permission): Promise<void> => {
|
|
1421
|
+
/*
|
|
1422
|
+
* The capability half, and it has to pass or the refusal below has taken
|
|
1423
|
+
* the feature with it. Selecting all seven ids at once rather than one
|
|
1424
|
+
* representative, because the admin surface renders the whole row and a
|
|
1425
|
+
* single narrowed column would break the page while six tests still
|
|
1426
|
+
* passed.
|
|
1427
|
+
*/
|
|
1428
|
+
const { query } = await BasePermission.checkPermissions(
|
|
1429
|
+
UserNotificationRule,
|
|
1430
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
1431
|
+
{
|
|
1432
|
+
...Object.fromEntries(
|
|
1433
|
+
METHOD_FK_COLUMNS.map((column: string) => {
|
|
1434
|
+
return [column, true];
|
|
1435
|
+
}),
|
|
1436
|
+
),
|
|
1437
|
+
userId: true,
|
|
1438
|
+
ruleType: true,
|
|
1439
|
+
} as Select<BaseModel>,
|
|
1440
|
+
tenantProps(grant),
|
|
1441
|
+
DatabaseRequestType.Read,
|
|
1442
|
+
);
|
|
1443
|
+
|
|
1444
|
+
expect(ownerIdOf(query)).toBe(OTHER_USER_ID.toString());
|
|
1445
|
+
},
|
|
1446
|
+
);
|
|
1447
|
+
|
|
1448
|
+
test("an administrator selecting a method's fields through the relation is refused", async () => {
|
|
1449
|
+
/*
|
|
1450
|
+
* The guarantee, run across every method and every permission that opens
|
|
1451
|
+
* the rule table, and collected rather than asserted one at a time so a
|
|
1452
|
+
* single failure names every combination that leaks instead of stopping at
|
|
1453
|
+
* the first.
|
|
1454
|
+
*
|
|
1455
|
+
* Deliberately agnostic about HOW the framework refuses: throwing is what
|
|
1456
|
+
* the select gate does today, but stripping the relation from the select
|
|
1457
|
+
* would be equally sound. What must never happen is the nested selection
|
|
1458
|
+
* surviving into the read the database is asked to perform, because at that
|
|
1459
|
+
* point the join has already been written against a query scoped to
|
|
1460
|
+
* somebody else.
|
|
1461
|
+
*/
|
|
1462
|
+
const leaked: Array<string> = [];
|
|
1463
|
+
|
|
1464
|
+
for (const [relation, column] of RELATION_SELECT_SHAPES) {
|
|
1465
|
+
for (const grant of ADMIN_READ_GRANTS) {
|
|
1466
|
+
const select: Select<BaseModel> = {
|
|
1467
|
+
[relation]: { [column]: true },
|
|
1468
|
+
} as Select<BaseModel>;
|
|
1469
|
+
|
|
1470
|
+
let refused: boolean = false;
|
|
1471
|
+
|
|
1472
|
+
try {
|
|
1473
|
+
await BasePermission.checkPermissions(
|
|
1474
|
+
UserNotificationRule,
|
|
1475
|
+
{ userId: OTHER_USER_ID } as Query<BaseModel>,
|
|
1476
|
+
select,
|
|
1477
|
+
tenantProps(grant),
|
|
1478
|
+
DatabaseRequestType.Read,
|
|
1479
|
+
);
|
|
1480
|
+
} catch {
|
|
1481
|
+
refused = true;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if (
|
|
1485
|
+
!refused &&
|
|
1486
|
+
(select as Record<string, unknown>)[relation] !== undefined
|
|
1487
|
+
) {
|
|
1488
|
+
leaked.push(`${relation}.${column} to ${grant}`);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
expect(leaked).toEqual([]);
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
test("a member selecting a method's fields through the relation on their OWN rules still works", async () => {
|
|
1497
|
+
/*
|
|
1498
|
+
* The constraint the refusal has to respect, and the reason it cannot be
|
|
1499
|
+
* implemented by narrowing the related column or by clearing
|
|
1500
|
+
* canReadOnRelationQuery on it. Every notification-rule table in the
|
|
1501
|
+
* dashboard renders "which address does this rule page" for its owner, and
|
|
1502
|
+
* that render is exactly this select issued by a plain member whose query
|
|
1503
|
+
* carries no userId until TenantPermission stamps one on.
|
|
1504
|
+
*
|
|
1505
|
+
* A fix that inspects the caller's raw query rather than the scoped one
|
|
1506
|
+
* sees no userId here, concludes the read is not owner-scoped, and breaks
|
|
1507
|
+
* notification setup for every user in every project.
|
|
1508
|
+
*/
|
|
1509
|
+
for (const [relation, column] of RELATION_SELECT_SHAPES) {
|
|
1510
|
+
await expect(
|
|
1511
|
+
BasePermission.checkPermissions(
|
|
1512
|
+
UserNotificationRule,
|
|
1513
|
+
{} as Query<BaseModel>,
|
|
1514
|
+
{ [relation]: { [column]: true } } as Select<BaseModel>,
|
|
1515
|
+
memberProps(),
|
|
1516
|
+
DatabaseRequestType.Read,
|
|
1517
|
+
),
|
|
1518
|
+
).resolves.toBeDefined();
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
test("the FK id columns are admin-readable and the relation columns beside them are not", () => {
|
|
1523
|
+
/*
|
|
1524
|
+
* The decorator-level statement of the pairing the two behavioural tests
|
|
1525
|
+
* above assert from either side. Kept because the behaviour and the
|
|
1526
|
+
* declaration can drift in one direction silently: a relation column
|
|
1527
|
+
* widened to match its FK sibling "for consistency" is a one-line change
|
|
1528
|
+
* that reads as tidying, and this fails on the commit that makes it rather
|
|
1529
|
+
* than on the request that exploits it.
|
|
1530
|
+
*/
|
|
1531
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
1532
|
+
|
|
1533
|
+
for (const column of METHOD_FK_COLUMNS) {
|
|
1534
|
+
expect(rule.getColumnAccessControlFor(column)?.read).toEqual([
|
|
1535
|
+
Permission.CurrentUser,
|
|
1536
|
+
...ADMIN_READ_GRANTS,
|
|
1537
|
+
]);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
for (const [relation] of RELATION_SELECT_SHAPES) {
|
|
1541
|
+
expect(rule.getColumnAccessControlFor(relation)?.read).toEqual([
|
|
1542
|
+
Permission.CurrentUser,
|
|
1543
|
+
]);
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
});
|