@oneuptime/common 12.0.6 → 12.0.7

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.
Files changed (424) hide show
  1. package/Models/DatabaseModels/AIInsight.ts +1 -1
  2. package/Models/DatabaseModels/DatabaseBaseModel/FileModel.ts +2 -3
  3. package/Models/DatabaseModels/DeletedProject.ts +595 -0
  4. package/Models/DatabaseModels/Index.ts +2 -0
  5. package/Models/DatabaseModels/Monitor.ts +111 -0
  6. package/Models/DatabaseModels/Project.ts +3 -3
  7. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -1
  8. package/Server/API/FileAPI.ts +15 -2
  9. package/Server/API/MicrosoftTeamsAPI.ts +6 -1
  10. package/Server/API/ProjectAPI.ts +214 -0
  11. package/Server/API/StatusPageAPI.ts +39 -6
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.ts +50 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.ts +67 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.ts +39 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Services/AIAgentService.ts +20 -0
  17. package/Server/Services/DashboardDomainService.ts +73 -28
  18. package/Server/Services/DatabaseService.ts +1 -0
  19. package/Server/Services/DeletedProjectService.ts +138 -0
  20. package/Server/Services/FileService.ts +61 -0
  21. package/Server/Services/IncidentEpisodePublicNoteService.ts +18 -0
  22. package/Server/Services/IncidentPublicNoteService.ts +18 -0
  23. package/Server/Services/Index.ts +2 -0
  24. package/Server/Services/MonitorService.ts +311 -0
  25. package/Server/Services/ProbeService.ts +20 -0
  26. package/Server/Services/ProjectService.ts +297 -1
  27. package/Server/Services/PushNotificationService.ts +71 -0
  28. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +18 -0
  29. package/Server/Services/StatusPageAnnouncementService.ts +53 -0
  30. package/Server/Services/StatusPageDomainService.ts +73 -28
  31. package/Server/Services/TeamMemberService.ts +117 -12
  32. package/Server/Services/TelemetryIngestionKeyService.ts +1 -0
  33. package/Server/Services/UserWebhookService.ts +34 -109
  34. package/Server/Services/WorkspaceNotificationRuleService.ts +69 -0
  35. package/Server/Services/WorkspaceProjectAuthTokenService.ts +37 -1
  36. package/Server/Types/Database/DeleteById.ts +2 -0
  37. package/Server/Types/Database/DeleteOneBy.ts +6 -0
  38. package/Server/Types/Domain.ts +41 -0
  39. package/Server/Types/Workflow/Components/API/Delete.ts +2 -1
  40. package/Server/Types/Workflow/Components/API/Get.ts +2 -1
  41. package/Server/Types/Workflow/Components/API/Patch.ts +2 -1
  42. package/Server/Types/Workflow/Components/API/Post.ts +2 -1
  43. package/Server/Types/Workflow/Components/API/Put.ts +2 -1
  44. package/Server/Types/Workflow/Components/API/Utils.ts +41 -2
  45. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +25 -19
  46. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +6 -5
  47. package/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts +32 -14
  48. package/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts +31 -13
  49. package/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts +29 -20
  50. package/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts +28 -21
  51. package/Server/Types/Workflow/Components/BaseModel/LogComponentError.ts +89 -0
  52. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +208 -0
  53. package/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts +11 -0
  54. package/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts +41 -18
  55. package/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts +45 -19
  56. package/Server/Types/Workflow/Components/Discord/SendMessageToChannel.ts +16 -9
  57. package/Server/Types/Workflow/Components/IncomingWebhookUtils.ts +66 -0
  58. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +15 -9
  59. package/Server/Types/Workflow/Components/Slack/SendMessageToChannel.ts +17 -0
  60. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  61. package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +171 -0
  62. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +3 -3
  63. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +3 -2
  64. package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +1 -1
  65. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +6 -6
  66. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +43 -5
  67. package/Server/Utils/DataSource/EgressGuard.ts +176 -16
  68. package/Server/Utils/DataSource/HttpFetch.ts +6 -45
  69. package/Server/Utils/Execute.ts +8 -0
  70. package/Server/Utils/InlineImageAccessTokenSync.ts +23 -0
  71. package/Server/Utils/LLM/LLMService.ts +57 -12
  72. package/Server/Utils/Monitor/MonitorAlert.ts +40 -0
  73. package/Server/Utils/Monitor/MonitorDependencySuppression.ts +192 -0
  74. package/Server/Utils/Monitor/MonitorIncident.ts +40 -0
  75. package/Server/Utils/Monitor/MonitorResource.ts +36 -0
  76. package/Server/Utils/Response.ts +142 -6
  77. package/Server/Utils/SSRFProtection.ts +273 -60
  78. package/Server/Utils/StartServer.ts +8 -0
  79. package/Server/Utils/VM/VMRunner.ts +88 -1
  80. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +555 -49
  81. package/Server/Utils/Workspace/Slack/Slack.ts +25 -2
  82. package/Server/Utils/Workspace/WorkspaceBase.ts +6 -0
  83. package/Tests/App/Dashboard/DangerZone.test.tsx +334 -0
  84. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +539 -0
  85. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +678 -0
  86. package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +706 -0
  87. package/Tests/App/Dashboard/DashboardVariablesModal.test.tsx +508 -0
  88. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +470 -0
  89. package/Tests/App/StatusPage/StatusPageDetailPagesNotFound.test.tsx +308 -0
  90. package/Tests/Models/DatabaseModels/DeletedProjectModel.test.ts +251 -0
  91. package/Tests/Models/DatabaseModels/SavedViewJSONColumnTuples.test.ts +115 -0
  92. package/Tests/Server/API/FileAPIAccessControl.test.ts +316 -0
  93. package/Tests/Server/API/ProjectAPI.test.ts +299 -1
  94. package/Tests/Server/API/ProjectAPIAdjustBalance.test.ts +459 -0
  95. package/Tests/Server/API/ProjectAPIAdjustBalanceBillingDisabled.test.ts +177 -0
  96. package/Tests/Server/Services/DatabaseServiceDeletionReason.test.ts +123 -0
  97. package/Tests/Server/Services/DeletedProjectService.test.ts +415 -0
  98. package/Tests/Server/Services/DeletedProjectServiceSelfHosted.test.ts +70 -0
  99. package/Tests/Server/Services/DomainSubdomainHooks.test.ts +94 -0
  100. package/Tests/Server/Services/FileIsPublicBooleanMigration.test.ts +244 -0
  101. package/Tests/Server/Services/FileServiceMimeType.test.ts +89 -0
  102. package/Tests/Server/Services/MonitorServiceDependencyValidation.test.ts +702 -0
  103. package/Tests/Server/Services/ProjectServiceAdjustBalance.test.ts +615 -0
  104. package/Tests/Server/Services/ProjectServiceDeleteAudit.test.ts +406 -0
  105. package/Tests/Server/Services/PublishTimeImageVisibility.test.ts +450 -0
  106. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +669 -0
  107. package/Tests/Server/Services/UserWebhookSSRF.test.ts +174 -0
  108. package/Tests/Server/Services/WebPushEndpointAllowlist.test.ts +133 -0
  109. package/Tests/Server/Services/WorkspaceProjectAuthTokenRepoint.test.ts +618 -0
  110. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +26 -1
  111. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +215 -0
  112. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +742 -0
  113. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +256 -0
  114. package/Tests/Server/Types/Workflow/Components/ModelArguments.test.ts +371 -0
  115. package/Tests/Server/Utils/AI/CodeAgentCommandGuard.test.ts +204 -0
  116. package/Tests/Server/Utils/AI/CodeAgentWriteLimits.test.ts +213 -0
  117. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +52 -5
  118. package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +3 -0
  119. package/Tests/Server/Utils/AI/LLMServiceEgressGuard.test.ts +315 -0
  120. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +3 -0
  121. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +17 -5
  122. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +14 -1
  123. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +35 -0
  124. package/Tests/Server/Utils/AI/StubLLMEgressGuard.ts +25 -0
  125. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +62 -7
  126. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +69 -0
  127. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +330 -0
  128. package/Tests/Server/Utils/GitHubCreatePullRequestWithToken.test.ts +393 -0
  129. package/Tests/Server/Utils/InlineImageAccessTokenSync.test.ts +202 -0
  130. package/Tests/Server/Utils/Monitor/MonitorDependencySuppression.test.ts +811 -0
  131. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +604 -0
  132. package/Tests/Server/Utils/ResponseSendFileResponse.test.ts +192 -0
  133. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +406 -0
  134. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +194 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +1169 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +2 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +1605 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTenantResolution.test.ts +1218 -0
  139. package/Tests/Types/AI/AIChatMessageStatus.test.ts +79 -0
  140. package/Tests/Types/AI/AIInsightStatus.test.ts +40 -0
  141. package/Tests/Types/API/HostnameAuthorityParsing.test.ts +157 -0
  142. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +27 -0
  143. package/Tests/Types/DataSource/DataSourceLimits.test.ts +71 -0
  144. package/Tests/Types/DataSource/DataSourceType.test.ts +181 -0
  145. package/Tests/Types/DomainSubdomainValidation.test.ts +90 -0
  146. package/Tests/Types/JSONFunctions.test.ts +252 -1
  147. package/Tests/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.test.ts +61 -0
  148. package/Tests/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.test.ts +62 -0
  149. package/Tests/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.test.ts +68 -0
  150. package/Tests/Types/Monitor/SnmpMonitor/SnmpVersion.test.ts +51 -0
  151. package/Tests/UI/Components/404.test.tsx +1 -1
  152. package/Tests/UI/Components/Alert.test.tsx +1 -1
  153. package/Tests/UI/Components/Badge.test.tsx +1 -1
  154. package/Tests/UI/Components/BasicForm.test.tsx +39 -10
  155. package/Tests/UI/Components/Button.test.tsx +1 -1
  156. package/Tests/UI/Components/Card.test.tsx +1 -1
  157. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +1 -1
  158. package/Tests/UI/Components/ColorViewer.test.tsx +1 -1
  159. package/Tests/UI/Components/ComponentsModal.test.tsx +7 -1
  160. package/Tests/UI/Components/DictionaryOfStrings.test.tsx +1 -1
  161. package/Tests/UI/Components/Dropdown.test.tsx +1 -1
  162. package/Tests/UI/Components/FilePicker.test.tsx +1 -1
  163. package/Tests/UI/Components/HiddenText.test.tsx +1 -1
  164. package/Tests/UI/Components/Input.test.tsx +1 -1
  165. package/Tests/UI/Components/Loader.test.tsx +1 -1
  166. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +1 -1
  167. package/Tests/UI/Components/MasterPage.test.tsx +1 -1
  168. package/Tests/UI/Components/ModalPortalDismissal.test.tsx +489 -0
  169. package/Tests/UI/Components/ModelDelete.test.tsx +214 -0
  170. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1 -1
  171. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +1 -1
  172. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +1 -1
  173. package/Tests/UI/Components/ModelTable/BaseModelTableUrlState.test.tsx +1 -1
  174. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1 -1
  175. package/Tests/UI/Components/NavBar.test.tsx +1 -1
  176. package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +1 -1
  177. package/Tests/UI/Components/Pagination.test.tsx +1 -1
  178. package/Tests/UI/Components/Pill.test.tsx +1 -1
  179. package/Tests/UI/Components/Probe.test.tsx +1 -1
  180. package/Tests/UI/Components/ProgressBar.test.tsx +1 -1
  181. package/Tests/UI/Components/RadioButtons.test.tsx +1 -1
  182. package/Tests/UI/Components/SideMenuItem.test.tsx +1 -1
  183. package/Tests/UI/Components/SideOver.test.tsx +500 -6
  184. package/Tests/UI/Components/Tabs.test.tsx +1 -1
  185. package/Tests/UI/Components/TelemetrySearchBar.test.tsx +1 -1
  186. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +1 -1
  187. package/Tests/UI/Components/TextArea.test.tsx +1 -1
  188. package/Tests/UI/Components/Toast.test.tsx +1 -1
  189. package/Tests/UI/Components/Toggle.test.tsx +1 -1
  190. package/Tests/UI/Components/TopSection.test.tsx +1 -1
  191. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +1 -1
  192. package/Tests/UI/Rum/ReplayScrubber.test.tsx +1 -1
  193. package/Tests/UI/Rum/ReplayStage.test.tsx +1 -1
  194. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +1 -1
  195. package/Tests/UI/Utils/LocalStorageArrayRoundTrip.test.ts +72 -0
  196. package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +19 -2
  197. package/Tests/Utils/Dashboard/ModelQueryVariableInterpolation.test.ts +44 -0
  198. package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +288 -5
  199. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +42 -0
  200. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +27 -0
  201. package/Tests/Utils/LegacySerializedArray.test.ts +139 -0
  202. package/Tests/Utils/Monitor/MonitorDependencyRule.test.ts +190 -0
  203. package/Tests/Utils/Realtime.test.ts +78 -0
  204. package/Tests/Utils/Telemetry/SavedViewFilters.test.ts +246 -0
  205. package/Tests/__mocks__/i18next-browser-languagedetector.js +21 -0
  206. package/Types/AI/FixPullRequestCiStatus.ts +1 -1
  207. package/Types/AI/FixVerificationStatus.ts +2 -2
  208. package/Types/API/Hostname.ts +92 -4
  209. package/Types/API/URL.ts +43 -25
  210. package/Types/Billing/BalanceAdjustmentType.ts +14 -0
  211. package/Types/Billing/ProjectBalanceType.ts +13 -0
  212. package/Types/Domain.ts +26 -0
  213. package/Types/File/MimeType.ts +11 -0
  214. package/Types/JSONFunctions.ts +15 -0
  215. package/Types/Telemetry/TelemetrySavedViewState.ts +9 -1
  216. package/Types/Workflow/Components/BaseModel.ts +92 -28
  217. package/Typings/Index.d.ts +12 -0
  218. package/UI/Components/GanttChart/ChartContainer.tsx +4 -3
  219. package/UI/Components/Modal/ConfirmModal.tsx +10 -5
  220. package/UI/Components/Modal/Modal.tsx +27 -47
  221. package/UI/Components/ModelDelete/ModelDelete.tsx +23 -6
  222. package/UI/Components/SideOver/SideOver.tsx +195 -68
  223. package/UI/Utils/PageScrollLock.ts +66 -0
  224. package/Utils/Dashboard/HtmlWidgetDocument.ts +4 -2
  225. package/Utils/Dashboard/VariableInterpolation.ts +19 -1
  226. package/Utils/LegacySerializedArray.ts +60 -0
  227. package/Utils/Monitor/MonitorDependencyRule.ts +88 -0
  228. package/Utils/Telemetry/SavedViewFilters.ts +88 -0
  229. package/build/dist/Models/DatabaseModels/AIInsight.js +1 -1
  230. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
  231. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js +2 -3
  232. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js.map +1 -1
  233. package/build/dist/Models/DatabaseModels/DeletedProject.js +640 -0
  234. package/build/dist/Models/DatabaseModels/DeletedProject.js.map +1 -0
  235. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  236. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  237. package/build/dist/Models/DatabaseModels/Monitor.js +106 -0
  238. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  239. package/build/dist/Models/DatabaseModels/Project.js +3 -3
  240. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  241. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  242. package/build/dist/Server/API/FileAPI.js +14 -2
  243. package/build/dist/Server/API/FileAPI.js.map +1 -1
  244. package/build/dist/Server/API/MicrosoftTeamsAPI.js +6 -1
  245. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  246. package/build/dist/Server/API/ProjectAPI.js +157 -4
  247. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  248. package/build/dist/Server/API/StatusPageAPI.js +27 -3
  249. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js +34 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js +30 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js +20 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js.map +1 -0
  256. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  257. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  258. package/build/dist/Server/Services/AIAgentService.js +17 -0
  259. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  260. package/build/dist/Server/Services/DashboardDomainService.js +55 -18
  261. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  262. package/build/dist/Server/Services/DatabaseService.js +1 -0
  263. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  264. package/build/dist/Server/Services/DeletedProjectService.js +124 -0
  265. package/build/dist/Server/Services/DeletedProjectService.js.map +1 -0
  266. package/build/dist/Server/Services/FileService.js +52 -0
  267. package/build/dist/Server/Services/FileService.js.map +1 -1
  268. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +10 -1
  269. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  270. package/build/dist/Server/Services/IncidentPublicNoteService.js +10 -1
  271. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  272. package/build/dist/Server/Services/Index.js +2 -0
  273. package/build/dist/Server/Services/Index.js.map +1 -1
  274. package/build/dist/Server/Services/MonitorService.js +232 -0
  275. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  276. package/build/dist/Server/Services/ProbeService.js +17 -0
  277. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  278. package/build/dist/Server/Services/ProjectService.js +228 -6
  279. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  280. package/build/dist/Server/Services/PushNotificationService.js +56 -0
  281. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  282. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +10 -1
  283. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  284. package/build/dist/Server/Services/StatusPageAnnouncementService.js +54 -0
  285. package/build/dist/Server/Services/StatusPageAnnouncementService.js.map +1 -1
  286. package/build/dist/Server/Services/StatusPageDomainService.js +55 -18
  287. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  288. package/build/dist/Server/Services/TeamMemberService.js +97 -6
  289. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  290. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +1 -0
  291. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  292. package/build/dist/Server/Services/UserWebhookService.js +31 -87
  293. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  294. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +65 -0
  295. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  296. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +23 -0
  297. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  298. package/build/dist/Server/Types/Domain.js +37 -0
  299. package/build/dist/Server/Types/Domain.js.map +1 -1
  300. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +2 -1
  301. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  302. package/build/dist/Server/Types/Workflow/Components/API/Get.js +2 -1
  303. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  304. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +2 -1
  305. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  306. package/build/dist/Server/Types/Workflow/Components/API/Post.js +2 -1
  307. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  308. package/build/dist/Server/Types/Workflow/Components/API/Put.js +2 -1
  309. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  310. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +35 -2
  311. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  312. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +15 -11
  313. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  314. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +2 -3
  315. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  316. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js +21 -10
  317. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js.map +1 -1
  318. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js +21 -10
  319. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js.map +1 -1
  320. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js +18 -13
  321. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js.map +1 -1
  322. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js +18 -13
  323. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js.map +1 -1
  324. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js +43 -0
  325. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js.map +1 -0
  326. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +116 -0
  327. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -0
  328. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js +7 -0
  329. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js.map +1 -1
  330. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js +24 -13
  331. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js.map +1 -1
  332. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js +27 -13
  333. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js.map +1 -1
  334. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js +14 -6
  335. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js.map +1 -1
  336. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js +42 -0
  337. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js.map +1 -0
  338. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +15 -6
  339. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  340. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js +10 -2
  341. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js.map +1 -1
  342. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  343. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +141 -0
  344. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js.map +1 -1
  345. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  346. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +1 -1
  347. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +6 -6
  348. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -1
  349. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +39 -5
  350. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  351. package/build/dist/Server/Utils/DataSource/EgressGuard.js +94 -10
  352. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  353. package/build/dist/Server/Utils/DataSource/HttpFetch.js +1 -19
  354. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
  355. package/build/dist/Server/Utils/Execute.js +2 -2
  356. package/build/dist/Server/Utils/Execute.js.map +1 -1
  357. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js +13 -0
  358. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js.map +1 -1
  359. package/build/dist/Server/Utils/LLM/LLMService.js +43 -12
  360. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  361. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +44 -24
  362. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  363. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js +158 -0
  364. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js.map +1 -0
  365. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +50 -30
  366. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  367. package/build/dist/Server/Utils/Monitor/MonitorResource.js +32 -0
  368. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  369. package/build/dist/Server/Utils/Response.js +112 -5
  370. package/build/dist/Server/Utils/Response.js.map +1 -1
  371. package/build/dist/Server/Utils/SSRFProtection.js +219 -53
  372. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  373. package/build/dist/Server/Utils/StartServer.js +7 -0
  374. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  375. package/build/dist/Server/Utils/VM/VMRunner.js +73 -1
  376. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  377. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +445 -43
  378. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  379. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +18 -1
  380. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  381. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  382. package/build/dist/Types/AI/FixPullRequestCiStatus.js +1 -1
  383. package/build/dist/Types/AI/FixVerificationStatus.js +2 -2
  384. package/build/dist/Types/API/Hostname.js +73 -4
  385. package/build/dist/Types/API/Hostname.js.map +1 -1
  386. package/build/dist/Types/API/URL.js +42 -24
  387. package/build/dist/Types/API/URL.js.map +1 -1
  388. package/build/dist/Types/Billing/BalanceAdjustmentType.js +15 -0
  389. package/build/dist/Types/Billing/BalanceAdjustmentType.js.map +1 -0
  390. package/build/dist/Types/Billing/ProjectBalanceType.js +14 -0
  391. package/build/dist/Types/Billing/ProjectBalanceType.js.map +1 -0
  392. package/build/dist/Types/Domain.js +22 -0
  393. package/build/dist/Types/Domain.js.map +1 -1
  394. package/build/dist/Types/File/MimeType.js +11 -0
  395. package/build/dist/Types/File/MimeType.js.map +1 -1
  396. package/build/dist/Types/JSONFunctions.js +14 -0
  397. package/build/dist/Types/JSONFunctions.js.map +1 -1
  398. package/build/dist/Types/Workflow/Components/BaseModel.js +83 -28
  399. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  400. package/build/dist/UI/Components/GanttChart/ChartContainer.js +4 -3
  401. package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +1 -1
  402. package/build/dist/UI/Components/Modal/ConfirmModal.js +3 -1
  403. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  404. package/build/dist/UI/Components/Modal/Modal.js +24 -34
  405. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  406. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +11 -6
  407. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  408. package/build/dist/UI/Components/SideOver/SideOver.js +105 -30
  409. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  410. package/build/dist/UI/Utils/PageScrollLock.js +52 -0
  411. package/build/dist/UI/Utils/PageScrollLock.js.map +1 -0
  412. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +4 -2
  413. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -1
  414. package/build/dist/Utils/Dashboard/VariableInterpolation.js +19 -1
  415. package/build/dist/Utils/Dashboard/VariableInterpolation.js.map +1 -1
  416. package/build/dist/Utils/LegacySerializedArray.js +51 -0
  417. package/build/dist/Utils/LegacySerializedArray.js.map +1 -0
  418. package/build/dist/Utils/Monitor/MonitorDependencyRule.js +47 -0
  419. package/build/dist/Utils/Monitor/MonitorDependencyRule.js.map +1 -0
  420. package/build/dist/Utils/Telemetry/SavedViewFilters.js +52 -0
  421. package/build/dist/Utils/Telemetry/SavedViewFilters.js.map +1 -0
  422. package/jest.config.json +3 -0
  423. package/package.json +1 -1
  424. package/tsconfig.json +3 -1
@@ -20,6 +20,7 @@ import SessionReplayGateCacheStore from "../Utils/SessionReplay/SessionReplayGat
20
20
  import AccessTokenService from "./AccessTokenService";
21
21
  import BillingService from "./BillingService";
22
22
  import DatabaseService from "./DatabaseService";
23
+ import DeletedProjectService from "./DeletedProjectService";
23
24
  import IncidentSeverityService from "./IncidentSeverityService";
24
25
  import IncidentStateService from "./IncidentStateService";
25
26
  import IncidentRoleService from "./IncidentRoleService";
@@ -39,6 +40,8 @@ import SubscriptionPlan, {
39
40
  PlanType,
40
41
  } from "../../Types/Billing/SubscriptionPlan";
41
42
  import SubscriptionStatus from "../../Types/Billing/SubscriptionStatus";
43
+ import ProjectBalanceType from "../../Types/Billing/ProjectBalanceType";
44
+ import BalanceAdjustmentType from "../../Types/Billing/BalanceAdjustmentType";
42
45
  import {
43
46
  Black,
44
47
  Blue500,
@@ -97,6 +100,24 @@ export interface CurrentPlan {
97
100
  isSubscriptionUnpaid: boolean;
98
101
  }
99
102
 
103
+ export interface ProjectBalanceAdjustmentResult {
104
+ previousBalanceInUSDCents: number;
105
+ newBalanceInUSDCents: number;
106
+ }
107
+
108
+ // The project columns that hold a prepaid balance, in cents.
109
+ export type ProjectBalanceColumnName =
110
+ | "smsOrCallCurrentBalanceInUSDCents"
111
+ | "aiCurrentBalanceInUSDCents";
112
+
113
+ /*
114
+ * Ceiling on a single manual adjustment. Staff type dollars and the column
115
+ * stores cents, so the classic slip is a factor of 100 - this turns that into
116
+ * a rejected form instead of a five-figure credit nobody notices until the
117
+ * month closes. Larger corrections are still possible, just not in one click.
118
+ */
119
+ export const MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS: number = 10_000 * 100;
120
+
100
121
  export class ProjectService extends DatabaseService<Model> {
101
122
  /*
102
123
  * Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
@@ -2095,6 +2116,12 @@ These are no longer recorded against the project and have to be cancelled by han
2095
2116
  protected override async onBeforeDelete(
2096
2117
  deleteBy: DeleteBy<Model>,
2097
2118
  ): Promise<OnDelete<Model>> {
2119
+ /*
2120
+ * Everything onDeleteSuccess needs has to be read here: projects are hard
2121
+ * deleted, so by the time that hook runs there is nothing left to query.
2122
+ * The extra columns beyond the billing ones are the snapshot that goes
2123
+ * into DeletedProject for customer outreach.
2124
+ */
2098
2125
  const projects: Array<Model> = await this.findBy({
2099
2126
  query: deleteBy.query,
2100
2127
  props: {
@@ -2107,11 +2134,24 @@ These are no longer recorded against the project and have to be cancelled by han
2107
2134
  paymentProviderSubscriptionId: true,
2108
2135
  paymentProviderMeteredSubscriptionId: true,
2109
2136
  name: true,
2137
+ slug: true,
2110
2138
  createdAt: true,
2111
2139
  planName: true,
2140
+ paymentProviderPlanId: true,
2141
+ paymentProviderCustomerId: true,
2142
+ paymentProviderSubscriptionStatus: true,
2143
+ paymentProviderSubscriptionSeats: true,
2144
+ trialEndsAt: true,
2145
+ currentActiveMonitorsCount: true,
2146
+ isBlocked: true,
2147
+ createdByUserId: true,
2112
2148
  createdByUser: {
2113
2149
  name: true,
2114
2150
  email: true,
2151
+ companyName: true,
2152
+ companyPhoneNumber: true,
2153
+ companySize: true,
2154
+ jobRole: true,
2115
2155
  },
2116
2156
  },
2117
2157
  });
@@ -2122,8 +2162,45 @@ These are no longer recorded against the project and have to be cancelled by han
2122
2162
  @CaptureSpan()
2123
2163
  protected override async onDeleteSuccess(
2124
2164
  onDelete: OnDelete<Model>,
2125
- _itemIdsBeforeDelete: ObjectID[],
2165
+ itemIdsBeforeDelete: ObjectID[],
2126
2166
  ): Promise<OnDelete<Model>> {
2167
+ /*
2168
+ * Record the deleted project first. Everything else in this hook can throw
2169
+ * - Stripe in particular - and the customer we would want to follow up
2170
+ * with is exactly the one whose deletion hit an error.
2171
+ *
2172
+ * Only the projects that were really deleted are recorded: onBeforeDelete
2173
+ * snapshots everything the query matched, which is a superset once
2174
+ * permissions narrow the delete.
2175
+ */
2176
+ const deletedProjectIds: Set<string> = new Set<string>(
2177
+ itemIdsBeforeDelete.map((id: ObjectID) => {
2178
+ return id.toString();
2179
+ }),
2180
+ );
2181
+
2182
+ for (const project of onDelete.carryForward) {
2183
+ if (project.id && !deletedProjectIds.has(project.id.toString())) {
2184
+ continue;
2185
+ }
2186
+
2187
+ try {
2188
+ await DeletedProjectService.logProjectDeletion({
2189
+ project: project,
2190
+ deletedByUserId:
2191
+ onDelete.deleteBy.deletedByUser?.id ||
2192
+ onDelete.deleteBy.props.userId,
2193
+ deletionReason: onDelete.deleteBy.deletionReason,
2194
+ });
2195
+ } catch (err) {
2196
+ /*
2197
+ * The project is already gone. Failing the request now would tell the
2198
+ * customer their delete did not work when it did.
2199
+ */
2200
+ logger.error(err);
2201
+ }
2202
+ }
2203
+
2127
2204
  if (NotificationSlackWebhookOnDeleteProject) {
2128
2205
  for (const project of onDelete.carryForward) {
2129
2206
  let subscriptionStatus: SubscriptionStatus | null = null;
@@ -2496,6 +2573,225 @@ These are no longer recorded against the project and have to be cancelled by han
2496
2573
  });
2497
2574
  }
2498
2575
 
2576
+ /*
2577
+ * Manually correct one of a project's prepaid balances. Master-admin only -
2578
+ * this hands out (or claws back) paid service without a payment, so the API
2579
+ * in front of it sits behind the master admin middleware.
2580
+ *
2581
+ * Add and Deduct go through a single atomic `col = col + delta` statement,
2582
+ * because SMS sends and AI runs are deducting from the same column
2583
+ * concurrently: a read-then-write here would silently swallow whichever
2584
+ * charge landed in between. Set is an absolute assignment and has no such
2585
+ * guarantee by construction - it is last-writer-wins, which is what "make it
2586
+ * read exactly this" means.
2587
+ *
2588
+ * The write is hookless. Project's update hooks only react to session
2589
+ * replay, SSO and billing-detail fields, so none of them have anything to do
2590
+ * for a balance change, and skipping them keeps the whole adjustment in one
2591
+ * statement.
2592
+ */
2593
+ @CaptureSpan()
2594
+ public async adjustBalance(data: {
2595
+ projectId: ObjectID;
2596
+ balanceType: ProjectBalanceType;
2597
+ adjustmentType: BalanceAdjustmentType;
2598
+ amountInUSDCents: number;
2599
+ reason: string;
2600
+ adjustedByUserId?: ObjectID | undefined;
2601
+ }): Promise<ProjectBalanceAdjustmentResult> {
2602
+ if (!IsBillingEnabled) {
2603
+ throw new BadDataException("Billing is not enabled for this server");
2604
+ }
2605
+
2606
+ if (
2607
+ typeof data.amountInUSDCents !== "number" ||
2608
+ !Number.isFinite(data.amountInUSDCents)
2609
+ ) {
2610
+ throw new BadDataException("Adjustment amount is not a valid number");
2611
+ }
2612
+
2613
+ /*
2614
+ * A fractional cent cannot be stored, and rounding it here would make the
2615
+ * balance disagree with the amount the audit log records.
2616
+ */
2617
+ if (!Number.isInteger(data.amountInUSDCents)) {
2618
+ throw new BadDataException(
2619
+ "Adjustment amount must be a whole number of cents",
2620
+ );
2621
+ }
2622
+
2623
+ if (data.amountInUSDCents < 0) {
2624
+ throw new BadDataException("Adjustment amount cannot be negative");
2625
+ }
2626
+
2627
+ /*
2628
+ * Adding or deducting nothing is a no-op that would still write an audit
2629
+ * line. Setting to zero is a real instruction, so it is allowed.
2630
+ */
2631
+ if (
2632
+ data.adjustmentType !== BalanceAdjustmentType.Set &&
2633
+ data.amountInUSDCents === 0
2634
+ ) {
2635
+ throw new BadDataException("Adjustment amount must be greater than zero");
2636
+ }
2637
+
2638
+ if (data.amountInUSDCents > MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS) {
2639
+ throw new BadDataException(
2640
+ `Adjustment amount cannot be more than ${
2641
+ MAX_BALANCE_ADJUSTMENT_IN_USD_CENTS / 100
2642
+ } USD in a single adjustment`,
2643
+ );
2644
+ }
2645
+
2646
+ const reason: string = (data.reason || "").trim();
2647
+
2648
+ if (!reason) {
2649
+ throw new BadDataException("A reason is required to adjust the balance");
2650
+ }
2651
+
2652
+ const balanceColumnName: ProjectBalanceColumnName =
2653
+ ProjectService.getBalanceColumnName(data.balanceType);
2654
+
2655
+ const project: Model | null = await this.findOneById({
2656
+ id: data.projectId,
2657
+ select: {
2658
+ _id: true,
2659
+ [balanceColumnName]: true,
2660
+ } as Select<Model>,
2661
+ props: {
2662
+ isRoot: true,
2663
+ },
2664
+ });
2665
+
2666
+ if (!project) {
2667
+ throw new BadDataException("Project not found");
2668
+ }
2669
+
2670
+ const previousBalanceInUSDCents: number = project[balanceColumnName] || 0;
2671
+
2672
+ let newBalanceInUSDCents: number = previousBalanceInUSDCents;
2673
+
2674
+ if (data.adjustmentType === BalanceAdjustmentType.Add) {
2675
+ newBalanceInUSDCents = previousBalanceInUSDCents + data.amountInUSDCents;
2676
+ }
2677
+
2678
+ if (data.adjustmentType === BalanceAdjustmentType.Deduct) {
2679
+ newBalanceInUSDCents = previousBalanceInUSDCents - data.amountInUSDCents;
2680
+
2681
+ /*
2682
+ * Usage is allowed to overdraw a balance (the charge is taken, the next
2683
+ * request is refused), but a manual deduction pushing a customer into
2684
+ * the red is a mistake every time - so refuse rather than reproduce a
2685
+ * typo as a negative balance the customer then has to pay off.
2686
+ */
2687
+ if (newBalanceInUSDCents < 0) {
2688
+ throw new BadDataException(
2689
+ `Cannot deduct ${data.amountInUSDCents / 100} USD - the balance is only ${
2690
+ previousBalanceInUSDCents / 100
2691
+ } USD`,
2692
+ );
2693
+ }
2694
+ }
2695
+
2696
+ if (data.adjustmentType === BalanceAdjustmentType.Set) {
2697
+ newBalanceInUSDCents = data.amountInUSDCents;
2698
+ }
2699
+
2700
+ /*
2701
+ * Topping a project back up has to clear the "we already told the owners"
2702
+ * latches, exactly as a paid recharge does. Leaving them set means the
2703
+ * next genuine low balance passes in silence, because the flags say the
2704
+ * owners were warned - about the shortfall this adjustment just fixed.
2705
+ */
2706
+ const set: QueryDeepPartialEntity<Model> =
2707
+ newBalanceInUSDCents > previousBalanceInUSDCents
2708
+ ? ProjectService.getBalanceNotificationFlagResets(data.balanceType)
2709
+ : {};
2710
+
2711
+ if (data.adjustmentType === BalanceAdjustmentType.Set) {
2712
+ await this.atomicAddToColumnsByIdWithoutHooks({
2713
+ id: data.projectId,
2714
+ add: {},
2715
+ set: {
2716
+ ...set,
2717
+ [balanceColumnName]: newBalanceInUSDCents,
2718
+ } as QueryDeepPartialEntity<Model>,
2719
+ });
2720
+ } else {
2721
+ const delta: number =
2722
+ data.adjustmentType === BalanceAdjustmentType.Add
2723
+ ? data.amountInUSDCents
2724
+ : -data.amountInUSDCents;
2725
+
2726
+ await this.atomicAddToColumnsByIdWithoutHooks({
2727
+ id: data.projectId,
2728
+ add: {
2729
+ [balanceColumnName]: delta,
2730
+ } as Partial<Record<keyof Model, number>>,
2731
+ set: set,
2732
+ });
2733
+ }
2734
+
2735
+ /*
2736
+ * The audit trail for moving money. There is no generic admin-action
2737
+ * table, so the acting master admin and their stated reason go in the log
2738
+ * line - the same shape extendTrial uses.
2739
+ */
2740
+ logger.info(
2741
+ `${data.balanceType} balance for project ${data.projectId.toString()} adjusted (${
2742
+ data.adjustmentType
2743
+ } ${data.amountInUSDCents} cents) from ${previousBalanceInUSDCents} to ${newBalanceInUSDCents} cents by master admin ${
2744
+ data.adjustedByUserId?.toString() || "unknown"
2745
+ }. Reason: ${reason}`,
2746
+ );
2747
+
2748
+ return {
2749
+ previousBalanceInUSDCents: previousBalanceInUSDCents,
2750
+ newBalanceInUSDCents: newBalanceInUSDCents,
2751
+ };
2752
+ }
2753
+
2754
+ private static getBalanceColumnName(
2755
+ balanceType: ProjectBalanceType,
2756
+ ): ProjectBalanceColumnName {
2757
+ if (balanceType === ProjectBalanceType.SmsOrCall) {
2758
+ return "smsOrCallCurrentBalanceInUSDCents";
2759
+ }
2760
+
2761
+ if (balanceType === ProjectBalanceType.AI) {
2762
+ return "aiCurrentBalanceInUSDCents";
2763
+ }
2764
+
2765
+ throw new BadDataException(`Invalid balance type: ${balanceType}`);
2766
+ }
2767
+
2768
+ /*
2769
+ * The "owners have been notified" latches that belong to each balance. They
2770
+ * are reset whenever that balance goes up, which is what the paid recharge
2771
+ * paths in NotificationService and AIBillingService do too.
2772
+ */
2773
+ private static getBalanceNotificationFlagResets(
2774
+ balanceType: ProjectBalanceType,
2775
+ ): QueryDeepPartialEntity<Model> {
2776
+ if (balanceType === ProjectBalanceType.SmsOrCall) {
2777
+ return {
2778
+ lowCallAndSMSBalanceNotificationSentToOwners: false,
2779
+ failedCallAndSMSBalanceChargeNotificationSentToOwners: false,
2780
+ notEnabledSmsOrCallNotificationSentToOwners: false,
2781
+ };
2782
+ }
2783
+
2784
+ if (balanceType === ProjectBalanceType.AI) {
2785
+ return {
2786
+ lowAiBalanceNotificationSentToOwners: false,
2787
+ failedAiBalanceChargeNotificationSentToOwners: false,
2788
+ notEnabledAiNotificationSentToOwners: false,
2789
+ };
2790
+ }
2791
+
2792
+ throw new BadDataException(`Invalid balance type: ${balanceType}`);
2793
+ }
2794
+
2499
2795
  @CaptureSpan()
2500
2796
  public async incrementAndGetIncidentCounter(
2501
2797
  projectId: ObjectID,
@@ -27,6 +27,25 @@ import PushNotificationLog from "../../Models/DatabaseModels/PushNotificationLog
27
27
  import PushNotificationLogService from "./PushNotificationLogService";
28
28
  import PushStatus from "../../Types/PushNotification/PushStatus";
29
29
 
30
+ /*
31
+ * The push services the browsers actually use. A Web Push subscription is a
32
+ * JSON blob supplied by the client and stored verbatim in UserPush.deviceToken;
33
+ * web-push then dials whatever host:port its "endpoint" names. Nothing else is
34
+ * a legitimate destination, so this is an allowlist rather than a blocklist —
35
+ * there is no self-hosted deployment of a browser push service to accommodate.
36
+ *
37
+ * Matching is on the registrable host (exact, or a subdomain) and https only,
38
+ * which covers the real endpoints: updates.push.services.mozilla.com,
39
+ * web.push.apple.com, and the regional *.notify.windows.com hosts.
40
+ */
41
+ const ALLOWED_WEB_PUSH_HOSTS: Array<string> = [
42
+ "push.services.mozilla.com",
43
+ "fcm.googleapis.com",
44
+ "android.googleapis.com",
45
+ "notify.windows.com",
46
+ "push.apple.com",
47
+ ];
48
+
30
49
  export interface PushNotificationOptions {
31
50
  projectId?: ObjectID | undefined;
32
51
  isSensitive?: boolean;
@@ -267,6 +286,54 @@ export default class PushNotificationService {
267
286
  }
268
287
  }
269
288
 
289
+ /*
290
+ * Refuse to hand web-push an endpoint that is not one of the browser push
291
+ * services. The subscription JSON is client-supplied, so without this the
292
+ * endpoint is a free choice of host and port for a POST the server makes
293
+ * with its own credentials — the usual internal-service and metadata
294
+ * targets included.
295
+ */
296
+ public static assertWebPushEndpointIsAllowed(endpoint: unknown): void {
297
+ if (!endpoint || typeof endpoint !== "string") {
298
+ throw new Error(
299
+ "Web push subscription is missing its endpoint and cannot be delivered.",
300
+ );
301
+ }
302
+
303
+ /*
304
+ * Parsed with the WHATWG parser deliberately: that is the parser web-push
305
+ * itself uses, so what is checked here is exactly what gets dialed. Going
306
+ * through OneUptime's URL type instead would introduce a differential —
307
+ * it reads "https://push.apple.com:80@evil.com/" as host push.apple.com,
308
+ * while WHATWG (correctly) reads userinfo push.apple.com:80 and host
309
+ * evil.com.
310
+ */
311
+ let parsed: globalThis.URL;
312
+ try {
313
+ parsed = new globalThis.URL(endpoint);
314
+ } catch {
315
+ throw new Error("Web push subscription endpoint is not a valid URL.");
316
+ }
317
+
318
+ if (parsed.protocol !== "https:") {
319
+ throw new Error("Web push subscription endpoint must use https.");
320
+ }
321
+
322
+ const host: string = parsed.hostname.toLowerCase();
323
+
324
+ const isAllowed: boolean = ALLOWED_WEB_PUSH_HOSTS.some(
325
+ (allowedHost: string) => {
326
+ return host === allowedHost || host.endsWith(`.${allowedHost}`);
327
+ },
328
+ );
329
+
330
+ if (!isAllowed) {
331
+ throw new Error(
332
+ `Web push subscription endpoint ${host} is not a recognised browser push service and will not be contacted.`,
333
+ );
334
+ }
335
+ }
336
+
270
337
  private static async sendWebPushNotification(
271
338
  deviceToken: string,
272
339
  message: PushNotificationMessage,
@@ -307,6 +374,10 @@ export default class PushNotificationService {
307
374
  throw new Error(`Invalid device token format: ${parseError}`);
308
375
  }
309
376
 
377
+ PushNotificationService.assertWebPushEndpointIsAllowed(
378
+ subscriptionObject?.endpoint,
379
+ );
380
+
310
381
  const result: webpush.SendResult = await webpush.sendNotification(
311
382
  subscriptionObject,
312
383
  payload,
@@ -14,6 +14,7 @@ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
14
14
  import StatusPageSubscriberNotificationStatus from "../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
15
15
  import File from "../../Models/DatabaseModels/File";
16
16
  import FileAttachmentMarkdownUtil from "../Utils/FileAttachmentMarkdownUtil";
17
+ import { syncIsPublicForMarkdownImages } from "../Utils/InlineImageAccessTokenSync";
17
18
 
18
19
  export class Service extends DatabaseService<Model> {
19
20
  public constructor() {
@@ -57,6 +58,17 @@ export class Service extends DatabaseService<Model> {
57
58
  const userId: ObjectID | null | undefined =
58
59
  createdItem.createdByUserId || createdItem.createdByUser?.id;
59
60
 
61
+ /*
62
+ * A public note is always rendered on the status page, so any inline
63
+ * image the markdown editor uploaded as private must flip to public
64
+ * for anonymous status page viewers to be able to render it.
65
+ */
66
+ await syncIsPublicForMarkdownImages(
67
+ createdItem.note,
68
+ true,
69
+ `scheduled maintenance public note ${createdItem.id?.toString()}`,
70
+ );
71
+
60
72
  const scheduledMaintenanceId: ObjectID =
61
73
  createdItem.scheduledMaintenanceId!;
62
74
  const projectId: ObjectID = createdItem.projectId!;
@@ -129,6 +141,12 @@ ${(createdItem.note || "") + attachmentsMarkdown}
129
141
  const scheduledMaintenance: ScheduledMaintenance =
130
142
  updatedItem.scheduledMaintenance!;
131
143
 
144
+ await syncIsPublicForMarkdownImages(
145
+ updatedItem.note,
146
+ true,
147
+ `scheduled maintenance public note ${updatedItem.id?.toString()}`,
148
+ );
149
+
132
150
  const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
133
151
  updatedItem.id!,
134
152
  "/scheduled-maintenance-public-note/attachment",
@@ -4,6 +4,10 @@ import CreateBy from "../Types/Database/CreateBy";
4
4
  import UpdateBy from "../Types/Database/UpdateBy";
5
5
  import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
6
6
  import StatusPageSubscriberNotificationStatus from "../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
7
+ import ObjectID from "../../Types/ObjectID";
8
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
9
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
10
+ import { syncIsPublicForMarkdownImages } from "../Utils/InlineImageAccessTokenSync";
7
11
 
8
12
  export class Service extends DatabaseService<Model> {
9
13
  public constructor() {
@@ -51,6 +55,55 @@ export class Service extends DatabaseService<Model> {
51
55
  carryForward: null,
52
56
  };
53
57
  }
58
+
59
+ /*
60
+ * An announcement is always rendered on the status pages it is attached
61
+ * to, so any inline image the markdown editor uploaded as private must
62
+ * flip to public for anonymous status page viewers to render it.
63
+ */
64
+ @CaptureSpan()
65
+ public override async onCreateSuccess(
66
+ _onCreate: OnCreate<Model>,
67
+ createdItem: Model,
68
+ ): Promise<Model> {
69
+ await syncIsPublicForMarkdownImages(
70
+ createdItem.description,
71
+ true,
72
+ `status page announcement ${createdItem.id?.toString()}`,
73
+ );
74
+
75
+ return createdItem;
76
+ }
77
+
78
+ @CaptureSpan()
79
+ public override async onUpdateSuccess(
80
+ onUpdate: OnUpdate<Model>,
81
+ _updatedItemIds: Array<ObjectID>,
82
+ ): Promise<OnUpdate<Model>> {
83
+ if (onUpdate.updateBy.data.description) {
84
+ const updatedItems: Array<Model> = await this.findBy({
85
+ query: onUpdate.updateBy.query,
86
+ limit: LIMIT_PER_PROJECT,
87
+ skip: 0,
88
+ props: {
89
+ isRoot: true,
90
+ },
91
+ select: {
92
+ description: true,
93
+ },
94
+ });
95
+
96
+ for (const updatedItem of updatedItems) {
97
+ await syncIsPublicForMarkdownImages(
98
+ updatedItem.description,
99
+ true,
100
+ `status page announcement ${updatedItem.id?.toString()}`,
101
+ );
102
+ }
103
+ }
104
+
105
+ return onUpdate;
106
+ }
54
107
  }
55
108
 
56
109
  export default new Service();