@oneuptime/common 12.0.2 → 12.0.4

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 (384) hide show
  1. package/Models/DatabaseModels/CephCluster.ts +14 -0
  2. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  3. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  4. package/Models/DatabaseModels/DockerHost.ts +14 -0
  5. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  6. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  7. package/Models/DatabaseModels/Index.ts +2 -0
  8. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  9. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  10. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  11. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  12. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  13. package/Models/DatabaseModels/Project.ts +16 -2
  14. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  15. package/Models/DatabaseModels/Service.ts +30 -0
  16. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  17. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  18. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  19. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  20. package/Models/DatabaseModels/User.ts +30 -0
  21. package/Server/API/AIAgentDataAPI.ts +31 -0
  22. package/Server/API/BaseAPI.ts +13 -2
  23. package/Server/API/DashboardAPI.ts +458 -12
  24. package/Server/API/GitHubAPI.ts +119 -283
  25. package/Server/API/UserAPI.ts +263 -1
  26. package/Server/EnvironmentConfig.ts +38 -4
  27. package/Server/Infrastructure/GlobalCache.ts +152 -0
  28. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  36. package/Server/Services/AlertStateTimelineService.ts +20 -0
  37. package/Server/Services/AnalyticsDatabaseService.ts +72 -1
  38. package/Server/Services/CephClusterService.ts +26 -86
  39. package/Server/Services/CloudResourceService.ts +23 -83
  40. package/Server/Services/CodeRepositoryService.ts +105 -2
  41. package/Server/Services/DatabaseService.ts +296 -2
  42. package/Server/Services/DockerHostService.ts +19 -79
  43. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  44. package/Server/Services/HostService.ts +46 -99
  45. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  46. package/Server/Services/Index.ts +2 -0
  47. package/Server/Services/IoTFleetService.ts +19 -79
  48. package/Server/Services/KubernetesClusterService.ts +17 -78
  49. package/Server/Services/LabelService.ts +57 -10
  50. package/Server/Services/MetricTypeService.ts +84 -0
  51. package/Server/Services/MonitorService.ts +89 -14
  52. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  53. package/Server/Services/MonitorTemplateService.ts +65 -0
  54. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  55. package/Server/Services/PodmanHostService.ts +19 -79
  56. package/Server/Services/ProxmoxClusterService.ts +23 -83
  57. package/Server/Services/RumApplicationService.ts +19 -80
  58. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  59. package/Server/Services/ServerlessFunctionService.ts +24 -85
  60. package/Server/Services/ServiceService.ts +299 -73
  61. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  62. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  63. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  64. package/Server/Services/TelemetryEntityService.ts +1 -0
  65. package/Server/Services/UserService.ts +184 -0
  66. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  67. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  68. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  69. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  72. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  73. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  74. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  75. package/Server/Utils/PasswordHash.ts +306 -0
  76. package/Server/Utils/SingleFlight.ts +83 -0
  77. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  78. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  79. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  80. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  81. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  82. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  83. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  84. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  85. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  86. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  87. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  88. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  89. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  90. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  91. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  92. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  93. package/Tests/Server/API/Helpers.ts +6 -1
  94. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  95. package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
  96. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  97. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  98. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  99. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  100. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
  101. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  102. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  103. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
  104. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  105. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  106. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  107. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  108. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  109. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  110. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  111. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  112. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  113. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  114. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  115. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  116. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  117. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  118. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  119. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  120. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  121. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  122. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  123. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  124. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  125. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  128. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  129. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  130. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  131. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  132. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  133. package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
  134. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  135. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  136. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  137. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  138. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  139. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  140. package/Tests/Types/Decimal.test.ts +37 -0
  141. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  142. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  143. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  144. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  145. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  146. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  147. package/Tests/Types/Port.test.ts +38 -0
  148. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  149. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  150. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  151. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  152. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  153. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  154. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  155. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  156. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  157. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  158. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  159. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  160. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  161. package/Types/Database/ColumnLength.ts +9 -1
  162. package/Types/Database/DatabaseProperty.ts +26 -0
  163. package/Types/Database/NumericColumnValue.ts +128 -0
  164. package/Types/Database/TableColumn.ts +11 -0
  165. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  166. package/Types/Decimal.ts +16 -4
  167. package/Types/HashedString.ts +139 -4
  168. package/Types/Permission.ts +48 -0
  169. package/Types/Port.ts +16 -4
  170. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  171. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  172. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  173. package/UI/Components/Navbar/NavBar.tsx +12 -2
  174. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  175. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  176. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  177. package/Utils/Dashboard/Components/Index.ts +7 -0
  178. package/Utils/TeamMembersByProject.ts +237 -0
  179. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  180. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  182. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  184. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  186. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  188. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  190. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  192. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  193. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  194. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  195. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  196. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  197. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  198. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  199. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  200. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  202. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/Project.js +16 -2
  204. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  206. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  208. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  210. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  211. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  212. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  214. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  216. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/User.js +32 -0
  218. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/BaseAPI.js +11 -1
  222. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +343 -10
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/GitHubAPI.js +94 -167
  226. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  227. package/build/dist/Server/API/UserAPI.js +187 -2
  228. package/build/dist/Server/API/UserAPI.js.map +1 -1
  229. package/build/dist/Server/EnvironmentConfig.js +34 -4
  230. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/GlobalCache.js +120 -0
  232. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  234. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  249. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  250. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/CephClusterService.js +26 -76
  254. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  255. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  256. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  257. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  258. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  259. package/build/dist/Server/Services/DatabaseService.js +246 -2
  260. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  261. package/build/dist/Server/Services/DockerHostService.js +19 -69
  262. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  263. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  264. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  265. package/build/dist/Server/Services/HostService.js +58 -102
  266. package/build/dist/Server/Services/HostService.js.map +1 -1
  267. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  268. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  269. package/build/dist/Server/Services/Index.js +2 -0
  270. package/build/dist/Server/Services/Index.js.map +1 -1
  271. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  272. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  273. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  274. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  275. package/build/dist/Server/Services/LabelService.js +42 -7
  276. package/build/dist/Server/Services/LabelService.js.map +1 -1
  277. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  278. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  279. package/build/dist/Server/Services/MonitorService.js +81 -9
  280. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  281. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  282. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  283. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  284. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  285. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  286. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  287. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  288. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  289. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  290. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  291. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  292. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  293. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  294. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  295. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  296. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  297. package/build/dist/Server/Services/ServiceService.js +236 -61
  298. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  299. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  300. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  301. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  302. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  303. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  305. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  306. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  307. package/build/dist/Server/Services/UserService.js +165 -0
  308. package/build/dist/Server/Services/UserService.js.map +1 -1
  309. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  310. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  311. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  312. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  313. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  314. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  315. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  316. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  317. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  318. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  320. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  322. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  326. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  327. package/build/dist/Server/Utils/PasswordHash.js +237 -0
  328. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  329. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  330. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  331. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  332. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  333. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  334. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  335. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  336. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  337. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  338. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  340. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  342. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  344. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  346. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  348. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  349. package/build/dist/Types/Database/ColumnLength.js +9 -1
  350. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  351. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  352. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  353. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  354. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  355. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  356. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  357. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  358. package/build/dist/Types/Decimal.js +14 -3
  359. package/build/dist/Types/Decimal.js.map +1 -1
  360. package/build/dist/Types/HashedString.js +95 -5
  361. package/build/dist/Types/HashedString.js.map +1 -1
  362. package/build/dist/Types/Permission.js +42 -0
  363. package/build/dist/Types/Permission.js.map +1 -1
  364. package/build/dist/Types/Port.js +15 -5
  365. package/build/dist/Types/Port.js.map +1 -1
  366. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  367. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  368. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  369. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  370. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  371. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  372. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  373. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  374. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  375. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  376. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  377. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  378. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  379. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  380. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  381. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  382. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  383. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  384. package/package.json +1 -1
@@ -0,0 +1,190 @@
1
+ import Models from "../../../Models/DatabaseModels/Index";
2
+ import StatusPageMonitorRule from "../../../Models/DatabaseModels/StatusPageMonitorRule";
3
+ import StatusPageResource from "../../../Models/DatabaseModels/StatusPageResource";
4
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
5
+ import Migrations from "../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
6
+ import { ColumnAccessControl } from "../../../Types/BaseDatabase/AccessControl";
7
+ import Permission, {
8
+ PermissionGroup,
9
+ PermissionHelper,
10
+ PermissionProps,
11
+ } from "../../../Types/Permission";
12
+ import { describe, expect, it } from "@jest/globals";
13
+
14
+ /*
15
+ * Schema-level guarantees behind status page monitor rules. Each of these is
16
+ * something the engine's own tests cannot see: they pass just as happily
17
+ * against a model that is unreachable over the API or writable by the wrong
18
+ * person.
19
+ */
20
+
21
+ describe("StatusPageMonitorRule model", () => {
22
+ const model: StatusPageMonitorRule = new StatusPageMonitorRule();
23
+
24
+ it("is registered in the model index, so its table is created and its API is mounted", () => {
25
+ const isRegistered: boolean = Models.some(
26
+ (registered: { new (): BaseModel }) => {
27
+ return registered === StatusPageMonitorRule;
28
+ },
29
+ );
30
+
31
+ expect(isRegistered).toBe(true);
32
+ });
33
+
34
+ it("is scoped to a project, so one tenant's rules cannot read another's", () => {
35
+ expect(model.getTenantColumn()).toBe("projectId");
36
+ });
37
+
38
+ it("is served from its own CRUD endpoint", () => {
39
+ expect(model.getCrudApiPath()?.toString()).toBe(
40
+ "/status-page-monitor-rule",
41
+ );
42
+ });
43
+
44
+ it("declares every field the rule form writes", () => {
45
+ const columns: Array<string> = model.getTableColumns().columns;
46
+
47
+ for (const column of [
48
+ "statusPageId",
49
+ "statusPageGroupId",
50
+ "isEnabled",
51
+ "monitorLabels",
52
+ "monitorNamePattern",
53
+ "monitorDescriptionPattern",
54
+ "showCurrentStatus",
55
+ "showUptimePercent",
56
+ "uptimePercentPrecision",
57
+ "showStatusHistoryChart",
58
+ ]) {
59
+ expect(columns).toContain(column);
60
+ }
61
+ });
62
+
63
+ /*
64
+ * The rule decides what shows up on a public page, so it is gated behind
65
+ * the status page permissions rather than the generic project ones.
66
+ */
67
+ it("requires a status page permission to create", () => {
68
+ const create: Array<Permission> = model.getCreatePermissions();
69
+
70
+ expect(create).toContain(Permission.CreateStatusPageMonitorRule);
71
+ expect(create).toContain(Permission.StatusPageAdmin);
72
+ });
73
+
74
+ it("requires a status page permission to delete", () => {
75
+ expect(model.getDeletePermissions()).toContain(
76
+ Permission.DeleteStatusPageMonitorRule,
77
+ );
78
+ });
79
+
80
+ it("does not let a plain viewer edit a rule", () => {
81
+ expect(model.getUpdatePermissions()).not.toContain(Permission.Viewer);
82
+ });
83
+
84
+ /*
85
+ * A saved rule must not be re-pointable at a different status page: the
86
+ * project-scope check runs on create, and letting statusPageId move
87
+ * afterwards would route a rule's monitors onto a page nobody re-validated.
88
+ * Create-only in the column ACL is what enforces it.
89
+ */
90
+ it("does not let a saved rule be re-pointed at another status page", () => {
91
+ const accessControl: Record<string, ColumnAccessControl> =
92
+ model.getColumnAccessControlForAllColumns();
93
+
94
+ expect(accessControl["statusPageId"]?.update).toEqual([]);
95
+ expect(accessControl["statusPageId"]?.create?.length).toBeGreaterThan(0);
96
+ });
97
+
98
+ it("lets the destination group be changed on a saved rule", () => {
99
+ const accessControl: Record<string, ColumnAccessControl> =
100
+ model.getColumnAccessControlForAllColumns();
101
+
102
+ expect(accessControl["statusPageGroupId"]?.update).toContain(
103
+ Permission.EditStatusPageMonitorRule,
104
+ );
105
+ });
106
+
107
+ it("is scoped by the status page it belongs to, like every other status page child", () => {
108
+ expect(model.canAccessIfCanReadOn).toBe("statusPage");
109
+ });
110
+ });
111
+
112
+ describe("Status page monitor rule migration", () => {
113
+ /*
114
+ * A migration that is not in this array never runs, so the table simply does
115
+ * not exist in a fresh database and the CI drift check fails. Cheaper to
116
+ * catch here than in the drift job.
117
+ */
118
+ it("is registered, so the table is actually created", () => {
119
+ // The array holds migration classes; `.name` is the class name.
120
+ const registeredNames: Array<string> = (
121
+ Migrations as unknown as Array<{ name: string }>
122
+ ).map((migration: { name: string }) => {
123
+ return migration.name;
124
+ });
125
+
126
+ expect(registeredNames).toContain("AddStatusPageMonitorRule1786005052769");
127
+ });
128
+ });
129
+
130
+ describe("StatusPageResource.statusPageMonitorRuleId - the ownership marker", () => {
131
+ const accessControl: Record<string, ColumnAccessControl> =
132
+ new StatusPageResource().getColumnAccessControlForAllColumns();
133
+
134
+ it("exists on the resource, so a rule can tell its own work apart from a human's", () => {
135
+ expect(new StatusPageResource().getTableColumns().columns).toContain(
136
+ "statusPageMonitorRuleId",
137
+ );
138
+ });
139
+
140
+ /*
141
+ * This is the load-bearing one. If the column were writable over the API,
142
+ * anyone could stamp a rule onto a hand-made resource and that resource
143
+ * would then be deleted the next time the rule stopped matching - silently
144
+ * losing configuration the rule never created. The engine writes it as
145
+ * root, which bypasses column ACLs, so an empty list costs nothing.
146
+ */
147
+ it("cannot be set through the API on create", () => {
148
+ expect(accessControl["statusPageMonitorRuleId"]?.create).toEqual([]);
149
+ });
150
+
151
+ it("cannot be reassigned through the API on update", () => {
152
+ expect(accessControl["statusPageMonitorRuleId"]?.update).toEqual([]);
153
+ });
154
+
155
+ it("is readable, so the dashboard can tell which resources a rule owns", () => {
156
+ expect(accessControl["statusPageMonitorRuleId"]?.read).toContain(
157
+ Permission.ReadStatusPageResource,
158
+ );
159
+ });
160
+ });
161
+
162
+ describe("Status page monitor rule permissions", () => {
163
+ const allProps: Array<PermissionProps> =
164
+ PermissionHelper.getAllPermissionProps();
165
+
166
+ /*
167
+ * A permission that is only in the enum cannot be granted: the roles UI is
168
+ * built from the catalogue, so an unlisted permission is one nobody can ever
169
+ * hold, and the feature is then unusable by anyone but a project admin.
170
+ */
171
+ it.each([
172
+ Permission.CreateStatusPageMonitorRule,
173
+ Permission.ReadStatusPageMonitorRule,
174
+ Permission.EditStatusPageMonitorRule,
175
+ Permission.DeleteStatusPageMonitorRule,
176
+ ])(
177
+ "describes %s in the catalogue so it can be granted",
178
+ (permission: Permission) => {
179
+ const props: PermissionProps | undefined = allProps.find(
180
+ (candidate: PermissionProps) => {
181
+ return candidate.permission === permission;
182
+ },
183
+ );
184
+
185
+ expect(props).toBeDefined();
186
+ expect(props!.isAssignableToTenant).toBe(true);
187
+ expect(props!.group).toBe(PermissionGroup.StatusPage);
188
+ },
189
+ );
190
+ });
@@ -0,0 +1,381 @@
1
+ import AIAgentDataAPI from "../../../Server/API/AIAgentDataAPI";
2
+ import CodeFixAgentAuth, {
3
+ CodeFixAgentSource,
4
+ } from "../../../Server/Utils/AI/CodeFix/CodeFixAgentAuth";
5
+ import CodeRepositoryService from "../../../Server/Services/CodeRepositoryService";
6
+ import GitHubUtil from "../../../Server/Utils/CodeRepository/GitHub/GitHub";
7
+ import GitHubInstallationBinding from "../../../Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding";
8
+ import OpenPullRequestCap from "../../../Server/Utils/AI/CodeFix/OpenPullRequestCap";
9
+ import Response from "../../../Server/Utils/Response";
10
+ import {
11
+ ExpressRequest,
12
+ ExpressResponse,
13
+ NextFunction,
14
+ } from "../../../Server/Utils/Express";
15
+ import { mockRouter } from "./Helpers";
16
+ import CodeRepository from "../../../Models/DatabaseModels/CodeRepository";
17
+ import CodeRepositoryType from "../../../Types/CodeRepository/CodeRepositoryType";
18
+ import ObjectID from "../../../Types/ObjectID";
19
+ import OneUptimeDate from "../../../Types/Date";
20
+ import BadDataException from "../../../Types/Exception/BadDataException";
21
+ import { JSONObject } from "../../../Types/JSON";
22
+ import {
23
+ afterEach,
24
+ beforeAll,
25
+ beforeEach,
26
+ describe,
27
+ expect,
28
+ test,
29
+ } from "@jest/globals";
30
+
31
+ /*
32
+ * GHSA-xx95-gmcf-7q86 — the endpoint at the sharp end.
33
+ *
34
+ * This route hands back a live GitHub installation token with contents:write
35
+ * and pull_requests:write. It used to authorize by comparing the repository
36
+ * row's projectId against the calling agent's project — but an attacker
37
+ * controls BOTH sides of that comparison, because they can create their own
38
+ * project, issue themselves an agent key in it, and (before this fix) put a
39
+ * row in it carrying any installation ID they liked. The row being "in your
40
+ * project" says nothing about whether the INSTALLATION is.
41
+ *
42
+ * So the binding is re-derived from the project itself, immediately before
43
+ * minting.
44
+ */
45
+
46
+ jest.mock("../../../Server/Utils/Express", () => {
47
+ return {
48
+ getRouter: () => {
49
+ return mockRouter;
50
+ },
51
+ };
52
+ });
53
+
54
+ jest.mock("../../../Server/Utils/Response", () => {
55
+ return {
56
+ sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => {
57
+ return args;
58
+ }),
59
+ sendEmptySuccessResponse: jest.fn(),
60
+ sendEntityResponse: jest.fn().mockImplementation((...args: []) => {
61
+ return args;
62
+ }),
63
+ sendErrorResponse: jest.fn().mockImplementation((...args: []) => {
64
+ return args;
65
+ }),
66
+ redirect: jest.fn(),
67
+ };
68
+ });
69
+
70
+ const TOKEN_ROUTE: string = "/ai-agent-data/get-repository-token";
71
+
72
+ function thrownError(): Error | undefined {
73
+ const sendErrorResponse: jest.Mock =
74
+ Response.sendErrorResponse as unknown as jest.Mock;
75
+
76
+ if (sendErrorResponse.mock.calls.length === 0) {
77
+ return undefined;
78
+ }
79
+
80
+ return sendErrorResponse.mock.calls[0]![2] as Error;
81
+ }
82
+
83
+ function jsonResponse(): JSONObject | undefined {
84
+ const sendJson: jest.Mock =
85
+ Response.sendJsonObjectResponse as unknown as jest.Mock;
86
+
87
+ if (sendJson.mock.calls.length === 0) {
88
+ return undefined;
89
+ }
90
+
91
+ return sendJson.mock.calls[0]![2] as JSONObject;
92
+ }
93
+
94
+ describe("POST /ai-agent-data/get-repository-token", () => {
95
+ let attackerProjectId: ObjectID;
96
+ let codeRepositoryId: ObjectID;
97
+ let victimInstallationId: string;
98
+ let ownInstallationId: string;
99
+
100
+ let mintTokenSpy: jest.SpyInstance;
101
+ let bindingSpy: jest.SpyInstance;
102
+ let findRepositorySpy: jest.SpyInstance;
103
+
104
+ beforeAll(() => {
105
+ mockRouter.routes.length = 0;
106
+ new AIAgentDataAPI();
107
+ });
108
+
109
+ beforeEach(() => {
110
+ jest.clearAllMocks();
111
+
112
+ attackerProjectId = ObjectID.generate();
113
+ codeRepositoryId = ObjectID.generate();
114
+ victimInstallationId = "99999999";
115
+ ownInstallationId = "11111111";
116
+
117
+ // The agent key is project-scoped and self-issued in the attacker's project.
118
+ jest.spyOn(CodeFixAgentAuth, "resolveAgentIdentity").mockResolvedValue({
119
+ id: ObjectID.generate(),
120
+ projectId: attackerProjectId,
121
+ source: CodeFixAgentSource.AIAgent,
122
+ });
123
+
124
+ findRepositorySpy = jest.spyOn(CodeRepositoryService, "findOneById");
125
+
126
+ const capSpy: jest.SpyInstance = jest.spyOn(
127
+ OpenPullRequestCap,
128
+ "checkForRepository",
129
+ );
130
+ capSpy.mockResolvedValue({ allowed: true });
131
+
132
+ bindingSpy = jest.spyOn(
133
+ GitHubInstallationBinding,
134
+ "isInstallationBoundToProject",
135
+ );
136
+
137
+ mintTokenSpy = jest.spyOn(GitHubUtil, "getInstallationAccessToken");
138
+ mintTokenSpy.mockResolvedValue({
139
+ token: "ghs_live_token",
140
+ expiresAt: OneUptimeDate.getCurrentDate(),
141
+ });
142
+ });
143
+
144
+ afterEach(() => {
145
+ jest.restoreAllMocks();
146
+ });
147
+
148
+ /*
149
+ * The row IS in the attacker's own project — that check has always passed.
150
+ * The installation ID on it is what decides whose repositories the token
151
+ * opens.
152
+ */
153
+ function mockRepository(installationId: string | null): void {
154
+ const repository: CodeRepository = new CodeRepository();
155
+ repository.id = codeRepositoryId;
156
+ repository.projectId = attackerProjectId;
157
+ repository.repositoryHostedAt = CodeRepositoryType.GitHub;
158
+ repository.organizationName = "victim-org";
159
+ repository.repositoryName = "victim-private-repo";
160
+
161
+ if (installationId) {
162
+ repository.gitHubAppInstallationId = installationId;
163
+ }
164
+
165
+ findRepositorySpy.mockResolvedValue(repository);
166
+ }
167
+
168
+ async function callRoute(): Promise<void> {
169
+ const req: ExpressRequest = {
170
+ params: {},
171
+ query: {},
172
+ body: {
173
+ aiAgentId: ObjectID.generate().toString(),
174
+ aiAgentKey: "attacker-self-issued-key",
175
+ codeRepositoryId: codeRepositoryId.toString(),
176
+ },
177
+ headers: {},
178
+ } as unknown as ExpressRequest;
179
+
180
+ const res: ExpressResponse = {
181
+ send: jest.fn(),
182
+ json: jest.fn(),
183
+ status: jest.fn().mockReturnThis(),
184
+ } as unknown as ExpressResponse;
185
+
186
+ const next: jest.Mock = jest.fn();
187
+
188
+ await mockRouter
189
+ .match("POST", TOKEN_ROUTE)
190
+ .handlerFunction(req, res, next as unknown as NextFunction);
191
+ }
192
+
193
+ describe("cross-tenant token minting", () => {
194
+ /*
195
+ * The advisory's step 3. Everything up to here passes: valid agent key,
196
+ * repository row in the agent's own project. Only the binding check stands
197
+ * between the attacker and a `ghs_` token over the victim's repositories.
198
+ */
199
+ test("refuses to mint when the row names an installation the project does not own", async () => {
200
+ mockRepository(victimInstallationId);
201
+ bindingSpy.mockResolvedValue(false);
202
+
203
+ await callRoute();
204
+
205
+ expect(mintTokenSpy).not.toHaveBeenCalled();
206
+ expect(thrownError()).toBeInstanceOf(BadDataException);
207
+ });
208
+
209
+ test("returns no token in the response when the binding is missing", async () => {
210
+ mockRepository(victimInstallationId);
211
+ bindingSpy.mockResolvedValue(false);
212
+
213
+ await callRoute();
214
+
215
+ expect(jsonResponse()).toBeUndefined();
216
+ });
217
+
218
+ test("checks the binding against the repository's project and installation", async () => {
219
+ mockRepository(ownInstallationId);
220
+ bindingSpy.mockResolvedValue(true);
221
+
222
+ await callRoute();
223
+
224
+ expect(bindingSpy).toHaveBeenCalledTimes(1);
225
+
226
+ const args: { projectId: ObjectID; installationId: string } = bindingSpy
227
+ .mock.calls[0]![0] as {
228
+ projectId: ObjectID;
229
+ installationId: string;
230
+ };
231
+
232
+ expect(args.projectId.toString()).toBe(attackerProjectId.toString());
233
+ expect(args.installationId).toBe(ownInstallationId);
234
+ });
235
+
236
+ /*
237
+ * Being in the right project is necessary but not sufficient — pin that
238
+ * the binding check is what gates the mint, so nobody "simplifies" it away
239
+ * on the grounds that the project check already ran.
240
+ */
241
+ test("the project check alone does not authorize a mint", async () => {
242
+ mockRepository(victimInstallationId);
243
+ bindingSpy.mockResolvedValue(false);
244
+
245
+ await callRoute();
246
+
247
+ // Repository genuinely belongs to the caller's project...
248
+ const repository: CodeRepository = (await findRepositorySpy.mock
249
+ .results[0]!.value) as CodeRepository;
250
+ expect(repository.projectId!.toString()).toBe(
251
+ attackerProjectId.toString(),
252
+ );
253
+
254
+ // ...and it still does not get a token.
255
+ expect(mintTokenSpy).not.toHaveBeenCalled();
256
+ });
257
+
258
+ test("checks the binding before calling GitHub at all", async () => {
259
+ const callOrder: Array<string> = [];
260
+
261
+ mockRepository(ownInstallationId);
262
+ bindingSpy.mockImplementation(async (): Promise<boolean> => {
263
+ callOrder.push("binding");
264
+ return true;
265
+ });
266
+ mintTokenSpy.mockImplementation(async (): Promise<JSONObject> => {
267
+ callOrder.push("mint");
268
+ return {
269
+ token: "ghs_live_token",
270
+ expiresAt: OneUptimeDate.getCurrentDate(),
271
+ } as unknown as JSONObject;
272
+ });
273
+
274
+ await callRoute();
275
+
276
+ expect(callOrder).toEqual(["binding", "mint"]);
277
+ });
278
+ });
279
+
280
+ describe("legitimate use keeps working", () => {
281
+ test("mints a token when the project genuinely owns the installation", async () => {
282
+ mockRepository(ownInstallationId);
283
+ bindingSpy.mockResolvedValue(true);
284
+
285
+ await callRoute();
286
+
287
+ expect(thrownError()).toBeUndefined();
288
+ expect(mintTokenSpy).toHaveBeenCalledTimes(1);
289
+ expect(mintTokenSpy.mock.calls[0]![0]).toBe(ownInstallationId);
290
+ expect(jsonResponse()!["token"]).toBe("ghs_live_token");
291
+ });
292
+
293
+ test("still requests only the scopes the agent needs", async () => {
294
+ mockRepository(ownInstallationId);
295
+ bindingSpy.mockResolvedValue(true);
296
+
297
+ await callRoute();
298
+
299
+ const options: {
300
+ permissions: {
301
+ contents?: string;
302
+ pull_requests?: string;
303
+ metadata?: string;
304
+ };
305
+ } = mintTokenSpy.mock.calls[0]![1] as {
306
+ permissions: {
307
+ contents?: string;
308
+ pull_requests?: string;
309
+ metadata?: string;
310
+ };
311
+ };
312
+
313
+ expect(options.permissions.contents).toBe("write");
314
+ expect(options.permissions.pull_requests).toBe("write");
315
+ expect(options.permissions.metadata).toBe("read");
316
+ });
317
+ });
318
+
319
+ describe("pre-existing guards still hold", () => {
320
+ test("rejects invalid agent credentials before reading anything", async () => {
321
+ jest
322
+ .spyOn(CodeFixAgentAuth, "resolveAgentIdentity")
323
+ .mockResolvedValue(null);
324
+
325
+ await callRoute();
326
+
327
+ expect(thrownError()).toBeInstanceOf(BadDataException);
328
+ expect(findRepositorySpy).not.toHaveBeenCalled();
329
+ expect(mintTokenSpy).not.toHaveBeenCalled();
330
+ });
331
+
332
+ test("rejects a repository in another project without checking a binding", async () => {
333
+ const repository: CodeRepository = new CodeRepository();
334
+ repository.id = codeRepositoryId;
335
+ repository.projectId = ObjectID.generate(); // someone else's project
336
+ repository.repositoryHostedAt = CodeRepositoryType.GitHub;
337
+ repository.gitHubAppInstallationId = victimInstallationId;
338
+ findRepositorySpy.mockResolvedValue(repository);
339
+
340
+ await callRoute();
341
+
342
+ expect(thrownError()).toBeInstanceOf(BadDataException);
343
+ expect(bindingSpy).not.toHaveBeenCalled();
344
+ expect(mintTokenSpy).not.toHaveBeenCalled();
345
+ });
346
+
347
+ test("rejects a repository with no installation id", async () => {
348
+ mockRepository(null);
349
+
350
+ await callRoute();
351
+
352
+ expect(thrownError()).toBeInstanceOf(BadDataException);
353
+ expect(mintTokenSpy).not.toHaveBeenCalled();
354
+ });
355
+
356
+ test("rejects a repository that does not exist", async () => {
357
+ findRepositorySpy.mockResolvedValue(null);
358
+
359
+ await callRoute();
360
+
361
+ expect(thrownError()).toBeInstanceOf(BadDataException);
362
+ expect(mintTokenSpy).not.toHaveBeenCalled();
363
+ });
364
+
365
+ test("still enforces the open pull request cap", async () => {
366
+ mockRepository(ownInstallationId);
367
+ bindingSpy.mockResolvedValue(true);
368
+
369
+ const capSpy: jest.SpyInstance = jest.spyOn(
370
+ OpenPullRequestCap,
371
+ "checkForRepository",
372
+ );
373
+ capSpy.mockResolvedValue({ allowed: false, openCount: 5, limit: 5 });
374
+
375
+ await callRoute();
376
+
377
+ expect(thrownError()).toBeInstanceOf(BadDataException);
378
+ expect(mintTokenSpy).not.toHaveBeenCalled();
379
+ });
380
+ });
381
+ });