@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,340 @@
1
+ import GitHubUtil from "../../../Server/Utils/CodeRepository/GitHub/GitHub";
2
+ import API from "../../../Utils/API";
3
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
4
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
5
+ import BadDataException from "../../../Types/Exception/BadDataException";
6
+ import { JSONObject } from "../../../Types/JSON";
7
+ import URL from "../../../Types/API/URL";
8
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
9
+
10
+ /*
11
+ * GHSA-xx95-gmcf-7q86 — proving the installer owns the installation.
12
+ *
13
+ * `installation_id` arrives on the install redirect as a bare integer in a
14
+ * query string. Anyone can type any value there, so on its own it proves
15
+ * nothing about who owns the installation — and OneUptime writes that value
16
+ * into Project.gitHubAppInstallationId, which every token-minting path then
17
+ * treats as authoritative.
18
+ *
19
+ * The OAuth `code` is what closes the gap: GitHub mints it for one specific
20
+ * GitHub account and it is single-use, so trading it in reveals which
21
+ * installations that account can actually administer.
22
+ */
23
+
24
+ jest.mock("../../../Server/EnvironmentConfig", () => {
25
+ return {
26
+ ...(jest.requireActual("../../../Server/EnvironmentConfig") as Record<
27
+ string,
28
+ unknown
29
+ >),
30
+ GitHubAppClientId: "test-client-id",
31
+ GitHubAppClientSecret: "test-client-secret",
32
+ };
33
+ });
34
+
35
+ function okResponse(data: JSONObject): HTTPResponse<JSONObject> {
36
+ return new HTTPResponse<JSONObject>(200, data, {});
37
+ }
38
+
39
+ function errorResponse(statusCode: number): HTTPErrorResponse {
40
+ return new HTTPErrorResponse(statusCode, {}, {});
41
+ }
42
+
43
+ function installationsPage(ids: Array<number>): JSONObject {
44
+ return {
45
+ installations: ids.map((id: number) => {
46
+ return { id: id };
47
+ }),
48
+ };
49
+ }
50
+
51
+ describe("GitHub installation ownership verification", () => {
52
+ let postSpy: jest.SpyInstance;
53
+ let getSpy: jest.SpyInstance;
54
+
55
+ beforeEach(() => {
56
+ postSpy = jest.spyOn(API, "post");
57
+ getSpy = jest.spyOn(API, "get");
58
+ });
59
+
60
+ afterEach(() => {
61
+ jest.restoreAllMocks();
62
+ });
63
+
64
+ describe("exchangeOAuthCodeForUserAccessToken", () => {
65
+ test("returns the user access token GitHub issues for the code", async () => {
66
+ postSpy.mockResolvedValue(okResponse({ access_token: "gho_user_token" }));
67
+
68
+ await expect(
69
+ GitHubUtil.exchangeOAuthCodeForUserAccessToken("the-code"),
70
+ ).resolves.toBe("gho_user_token");
71
+ });
72
+
73
+ test("sends the app credentials and the code to GitHub's token endpoint", async () => {
74
+ postSpy.mockResolvedValue(okResponse({ access_token: "gho_user_token" }));
75
+
76
+ await GitHubUtil.exchangeOAuthCodeForUserAccessToken("the-code");
77
+
78
+ const callArgs: {
79
+ url: URL;
80
+ data: JSONObject;
81
+ } = postSpy.mock.calls[0]![0] as { url: URL; data: JSONObject };
82
+
83
+ expect(callArgs.url.toString()).toContain(
84
+ "github.com/login/oauth/access_token",
85
+ );
86
+ expect(callArgs.data["client_id"]).toBe("test-client-id");
87
+ expect(callArgs.data["client_secret"]).toBe("test-client-secret");
88
+ expect(callArgs.data["code"]).toBe("the-code");
89
+ });
90
+
91
+ /*
92
+ * GitHub answers a bad, expired or already-redeemed code with HTTP 200 and
93
+ * an `error` body rather than a 4xx. Treating "not an HTTP error" as
94
+ * success would accept a replayed or invented code and let the caller skip
95
+ * verification entirely.
96
+ */
97
+ test("rejects a 200 response that carries no access token", async () => {
98
+ postSpy.mockResolvedValue(
99
+ okResponse({
100
+ error: "bad_verification_code",
101
+ error_description: "The code passed is incorrect or expired.",
102
+ }),
103
+ );
104
+
105
+ await expect(
106
+ GitHubUtil.exchangeOAuthCodeForUserAccessToken("replayed-code"),
107
+ ).rejects.toBeInstanceOf(BadDataException);
108
+ });
109
+
110
+ test("rejects when GitHub returns an HTTP error", async () => {
111
+ postSpy.mockResolvedValue(errorResponse(401));
112
+
113
+ await expect(
114
+ GitHubUtil.exchangeOAuthCodeForUserAccessToken("the-code"),
115
+ ).rejects.toBeInstanceOf(BadDataException);
116
+ });
117
+ });
118
+
119
+ describe("listInstallationIdsForUserAccessToken", () => {
120
+ test("returns the installation ids the user can administer", async () => {
121
+ getSpy.mockResolvedValue(okResponse(installationsPage([111, 222])));
122
+
123
+ await expect(
124
+ GitHubUtil.listInstallationIdsForUserAccessToken("gho_user_token"),
125
+ ).resolves.toEqual(["111", "222"]);
126
+ });
127
+
128
+ test("asks GitHub as the user, not as the app", async () => {
129
+ getSpy.mockResolvedValue(okResponse(installationsPage([111])));
130
+
131
+ await GitHubUtil.listInstallationIdsForUserAccessToken("gho_user_token");
132
+
133
+ const callArgs: {
134
+ url: URL;
135
+ headers: { Authorization?: string };
136
+ } = getSpy.mock.calls[0]![0] as {
137
+ url: URL;
138
+ headers: { Authorization?: string };
139
+ };
140
+
141
+ expect(callArgs.url.toString()).toContain(
142
+ "api.github.com/user/installations",
143
+ );
144
+ expect(callArgs.headers.Authorization).toBe("Bearer gho_user_token");
145
+ });
146
+
147
+ /*
148
+ * A user with more than a page of installations must not silently lose the
149
+ * one they are installing — that would fail a legitimate install.
150
+ */
151
+ test("pages through every installation", async () => {
152
+ const firstPage: Array<number> = Array.from(
153
+ { length: 100 },
154
+ (_unused: unknown, index: number) => {
155
+ return index + 1;
156
+ },
157
+ );
158
+
159
+ getSpy
160
+ .mockResolvedValueOnce(okResponse(installationsPage(firstPage)))
161
+ .mockResolvedValueOnce(okResponse(installationsPage([999])));
162
+
163
+ const ids: Array<string> =
164
+ await GitHubUtil.listInstallationIdsForUserAccessToken("gho_token");
165
+
166
+ expect(getSpy).toHaveBeenCalledTimes(2);
167
+ expect(ids).toHaveLength(101);
168
+ expect(ids).toContain("999");
169
+ });
170
+
171
+ test("returns an empty list when the user administers nothing", async () => {
172
+ getSpy.mockResolvedValue(okResponse({ installations: [] }));
173
+
174
+ await expect(
175
+ GitHubUtil.listInstallationIdsForUserAccessToken("gho_token"),
176
+ ).resolves.toEqual([]);
177
+ });
178
+
179
+ test("rejects when GitHub returns an HTTP error", async () => {
180
+ getSpy.mockResolvedValue(errorResponse(401));
181
+
182
+ await expect(
183
+ GitHubUtil.listInstallationIdsForUserAccessToken("gho_token"),
184
+ ).rejects.toBeInstanceOf(BadDataException);
185
+ });
186
+ });
187
+
188
+ describe("assertUserControlsInstallation", () => {
189
+ beforeEach(() => {
190
+ postSpy.mockResolvedValue(okResponse({ access_token: "gho_user_token" }));
191
+ });
192
+
193
+ test("resolves when the installation is one the user administers", async () => {
194
+ getSpy.mockResolvedValue(okResponse(installationsPage([111, 222])));
195
+
196
+ await expect(
197
+ GitHubUtil.assertUserControlsInstallation({
198
+ oauthCode: "the-code",
199
+ installationId: "222",
200
+ }),
201
+ ).resolves.toBeUndefined();
202
+ });
203
+
204
+ /*
205
+ * The exact attack: a valid code for the attacker's own GitHub account,
206
+ * paired with a victim's installation ID typed into the redirect URL.
207
+ */
208
+ test("rejects an installation the authorizing account does not administer", async () => {
209
+ getSpy.mockResolvedValue(okResponse(installationsPage([111])));
210
+
211
+ await expect(
212
+ GitHubUtil.assertUserControlsInstallation({
213
+ oauthCode: "attackers-own-valid-code",
214
+ installationId: "99999999",
215
+ }),
216
+ ).rejects.toBeInstanceOf(BadDataException);
217
+ });
218
+
219
+ test("rejects when the user administers no installations at all", async () => {
220
+ getSpy.mockResolvedValue(okResponse({ installations: [] }));
221
+
222
+ await expect(
223
+ GitHubUtil.assertUserControlsInstallation({
224
+ oauthCode: "the-code",
225
+ installationId: "111",
226
+ }),
227
+ ).rejects.toBeInstanceOf(BadDataException);
228
+ });
229
+
230
+ /*
231
+ * Fail closed: a verification step that can be skipped by breaking the
232
+ * call it depends on is not a verification step.
233
+ */
234
+ test("rejects rather than passing when the code exchange fails", async () => {
235
+ postSpy.mockResolvedValue(okResponse({ error: "bad_verification_code" }));
236
+
237
+ await expect(
238
+ GitHubUtil.assertUserControlsInstallation({
239
+ oauthCode: "bad-code",
240
+ installationId: "111",
241
+ }),
242
+ ).rejects.toBeInstanceOf(BadDataException);
243
+
244
+ expect(getSpy).not.toHaveBeenCalled();
245
+ });
246
+
247
+ test("rejects rather than passing when the installation list cannot be read", async () => {
248
+ getSpy.mockResolvedValue(errorResponse(500));
249
+
250
+ await expect(
251
+ GitHubUtil.assertUserControlsInstallation({
252
+ oauthCode: "the-code",
253
+ installationId: "111",
254
+ }),
255
+ ).rejects.toBeInstanceOf(BadDataException);
256
+ });
257
+
258
+ // Ids cross the boundary as strings on one side and numbers on the other.
259
+ test("matches ids regardless of string/number representation", async () => {
260
+ getSpy.mockResolvedValue(okResponse(installationsPage([12345678])));
261
+
262
+ await expect(
263
+ GitHubUtil.assertUserControlsInstallation({
264
+ oauthCode: "the-code",
265
+ installationId: "12345678",
266
+ }),
267
+ ).resolves.toBeUndefined();
268
+ });
269
+
270
+ /*
271
+ * A prefix or substring match would let "1111" pass for installation
272
+ * "11110". Membership must be exact.
273
+ */
274
+ test("does not accept an installation id that merely looks similar", async () => {
275
+ getSpy.mockResolvedValue(okResponse(installationsPage([11110])));
276
+
277
+ await expect(
278
+ GitHubUtil.assertUserControlsInstallation({
279
+ oauthCode: "the-code",
280
+ installationId: "1111",
281
+ }),
282
+ ).rejects.toBeInstanceOf(BadDataException);
283
+ });
284
+ });
285
+ });
286
+
287
+ describe("GitHub OAuth verification when the app is not configured", () => {
288
+ afterEach(() => {
289
+ jest.restoreAllMocks();
290
+ jest.resetModules();
291
+ });
292
+
293
+ /*
294
+ * An instance without OAuth credentials must refuse to bind rather than
295
+ * quietly skipping verification — a soft fallback here would reopen the
296
+ * vulnerability for every self-hosted install that never set the vars.
297
+ */
298
+ test("refuses to exchange a code without client credentials", async () => {
299
+ jest.resetModules();
300
+ jest.doMock("../../../Server/EnvironmentConfig", () => {
301
+ return {
302
+ ...(jest.requireActual("../../../Server/EnvironmentConfig") as Record<
303
+ string,
304
+ unknown
305
+ >),
306
+ GitHubAppClientId: null,
307
+ GitHubAppClientSecret: null,
308
+ };
309
+ });
310
+
311
+ /*
312
+ * A re-require is the point here: the client credentials are read at
313
+ * module load, so the unconfigured branch can only be reached by reloading
314
+ * the module against a different mocked config.
315
+ */
316
+ const UnconfiguredGitHubUtil: typeof GitHubUtil =
317
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
318
+ require("../../../Server/Utils/CodeRepository/GitHub/GitHub").default;
319
+
320
+ const postSpy: jest.SpyInstance = jest.spyOn(API, "post");
321
+
322
+ let thrown: Error | null = null;
323
+
324
+ try {
325
+ await UnconfiguredGitHubUtil.exchangeOAuthCodeForUserAccessToken(
326
+ "the-code",
327
+ );
328
+ } catch (err) {
329
+ thrown = err as Error;
330
+ }
331
+
332
+ /*
333
+ * jest.resetModules() reloads the exception class too, so `instanceof`
334
+ * would compare two distinct copies of it — assert the contract instead.
335
+ */
336
+ expect(thrown).not.toBeNull();
337
+ expect(thrown!.message).toContain("GITHUB_APP_CLIENT_ID");
338
+ expect(postSpy).not.toHaveBeenCalled();
339
+ });
340
+ });
@@ -0,0 +1,250 @@
1
+ import GitHubUtil from "../../../Server/Utils/CodeRepository/GitHub/GitHub";
2
+ import API from "../../../Utils/API";
3
+ import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
4
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
5
+ import { JSONObject } from "../../../Types/JSON";
6
+ import * as crypto from "crypto";
7
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
8
+
9
+ /*
10
+ * Two hardening fixes found while remediating GHSA-xx95-gmcf-7q86. Neither is
11
+ * in the advisory; both sit on the same GitHub App code path.
12
+ *
13
+ * 1. verifyWebhookSignature used to return true when no webhook secret was
14
+ * configured, leaving POST /api/github/webhook unauthenticated on those
15
+ * instances — and its `installation.deleted` branch clears installation IDs
16
+ * across every tenant.
17
+ *
18
+ * 2. getRepositoryTreePaths caches a repository's full private file listing
19
+ * and reads that cache BEFORE minting a token, so the cache key is what
20
+ * stands in for access control on a hit. Keyed on org/repo/branch alone, it
21
+ * could serve one installation's file listing to another.
22
+ */
23
+
24
+ const WEBHOOK_SECRET: string = "test-webhook-secret";
25
+
26
+ function loadGitHubUtilWithWebhookSecret(
27
+ secret: string | null,
28
+ ): typeof GitHubUtil {
29
+ jest.resetModules();
30
+ jest.doMock("../../../Server/EnvironmentConfig", () => {
31
+ return {
32
+ ...(jest.requireActual("../../../Server/EnvironmentConfig") as Record<
33
+ string,
34
+ unknown
35
+ >),
36
+ GitHubAppWebhookSecret: secret,
37
+ };
38
+ });
39
+
40
+ /*
41
+ * A re-require is the point here: GitHubAppWebhookSecret is read at module
42
+ * load, so the only way to exercise the unconfigured branch is to reload the
43
+ * module against a different mocked config.
44
+ */
45
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
46
+ return require("../../../Server/Utils/CodeRepository/GitHub/GitHub")
47
+ .default as typeof GitHubUtil;
48
+ }
49
+
50
+ function signPayload(payload: string, secret: string): string {
51
+ return `sha256=${crypto
52
+ .createHmac("sha256", secret)
53
+ .update(payload)
54
+ .digest("hex")}`;
55
+ }
56
+
57
+ describe("GitHub webhook signature verification", () => {
58
+ const payload: string = JSON.stringify({
59
+ action: "deleted",
60
+ installation: { id: 12345678 },
61
+ });
62
+
63
+ afterEach(() => {
64
+ jest.resetModules();
65
+ jest.restoreAllMocks();
66
+ });
67
+
68
+ test("accepts a correctly signed payload", () => {
69
+ const util: typeof GitHubUtil =
70
+ loadGitHubUtilWithWebhookSecret(WEBHOOK_SECRET);
71
+
72
+ expect(
73
+ util.verifyWebhookSignature(
74
+ payload,
75
+ signPayload(payload, WEBHOOK_SECRET),
76
+ ),
77
+ ).toBe(true);
78
+ });
79
+
80
+ test("rejects a payload signed with the wrong secret", () => {
81
+ const util: typeof GitHubUtil =
82
+ loadGitHubUtilWithWebhookSecret(WEBHOOK_SECRET);
83
+
84
+ expect(
85
+ util.verifyWebhookSignature(payload, signPayload(payload, "wrong")),
86
+ ).toBe(false);
87
+ });
88
+
89
+ test("rejects a tampered payload", () => {
90
+ const util: typeof GitHubUtil =
91
+ loadGitHubUtilWithWebhookSecret(WEBHOOK_SECRET);
92
+
93
+ const signature: string = signPayload(payload, WEBHOOK_SECRET);
94
+ const tampered: string = JSON.stringify({
95
+ action: "deleted",
96
+ installation: { id: 99999999 },
97
+ });
98
+
99
+ expect(util.verifyWebhookSignature(tampered, signature)).toBe(false);
100
+ });
101
+
102
+ test("rejects a garbage signature without throwing", () => {
103
+ const util: typeof GitHubUtil =
104
+ loadGitHubUtilWithWebhookSecret(WEBHOOK_SECRET);
105
+
106
+ expect(util.verifyWebhookSignature(payload, "not-a-signature")).toBe(false);
107
+ });
108
+
109
+ /*
110
+ * The fix. Previously this returned true — so on an instance with no secret
111
+ * configured, anyone who could reach the endpoint could send an
112
+ * `installation.deleted` for any installation id and have OneUptime clear
113
+ * that installation from every project that had it.
114
+ */
115
+ test("fails CLOSED when no webhook secret is configured", () => {
116
+ const util: typeof GitHubUtil = loadGitHubUtilWithWebhookSecret(null);
117
+
118
+ expect(
119
+ util.verifyWebhookSignature(
120
+ payload,
121
+ signPayload(payload, WEBHOOK_SECRET),
122
+ ),
123
+ ).toBe(false);
124
+ });
125
+
126
+ test("an unconfigured instance rejects even a well-formed signature", () => {
127
+ const util: typeof GitHubUtil = loadGitHubUtilWithWebhookSecret("");
128
+
129
+ expect(util.verifyWebhookSignature(payload, signPayload(payload, ""))).toBe(
130
+ false,
131
+ );
132
+ });
133
+ });
134
+
135
+ describe("Repository tree cache is scoped to the installation", () => {
136
+ const TREE_RESPONSE: JSONObject = {
137
+ tree: [
138
+ { path: "src/secret.ts", type: "blob" },
139
+ { path: "src", type: "tree" },
140
+ ],
141
+ };
142
+
143
+ let getCacheSpy: jest.SpyInstance;
144
+ let setCacheSpy: jest.SpyInstance;
145
+
146
+ beforeEach(() => {
147
+ jest
148
+ .spyOn(GitHubUtil, "getInstallationAccessToken")
149
+ .mockResolvedValue({ token: "ghs_token", expiresAt: new Date() });
150
+
151
+ jest
152
+ .spyOn(API, "get")
153
+ .mockResolvedValue(new HTTPResponse<JSONObject>(200, TREE_RESPONSE, {}));
154
+
155
+ getCacheSpy = jest.spyOn(GlobalCache, "getStringArray");
156
+ getCacheSpy.mockResolvedValue(null);
157
+
158
+ setCacheSpy = jest.spyOn(GlobalCache, "setStringArray");
159
+ setCacheSpy.mockResolvedValue(undefined);
160
+ });
161
+
162
+ afterEach(() => {
163
+ jest.restoreAllMocks();
164
+ });
165
+
166
+ function argsFor(installationId: string): {
167
+ installationId: string;
168
+ organizationName: string;
169
+ repositoryName: string;
170
+ branchName: string;
171
+ } {
172
+ return {
173
+ installationId: installationId,
174
+ organizationName: "victim-org",
175
+ repositoryName: "private-repo",
176
+ branchName: "main",
177
+ };
178
+ }
179
+
180
+ test("includes the installation id in the cache key", async () => {
181
+ await GitHubUtil.getRepositoryTreePaths(argsFor("11111111"));
182
+
183
+ const cacheKey: string = getCacheSpy.mock.calls[0]![1] as string;
184
+ expect(cacheKey).toContain("11111111");
185
+ });
186
+
187
+ /*
188
+ * The point of the fix: the same org/repo/branch reached through a different
189
+ * installation must not collide, because a hit is served without minting a
190
+ * token or calling GitHub at all.
191
+ */
192
+ test("two installations naming the same repository use different keys", async () => {
193
+ await GitHubUtil.getRepositoryTreePaths(argsFor("11111111"));
194
+ await GitHubUtil.getRepositoryTreePaths(argsFor("99999999"));
195
+
196
+ const firstKey: string = getCacheSpy.mock.calls[0]![1] as string;
197
+ const secondKey: string = getCacheSpy.mock.calls[1]![1] as string;
198
+
199
+ expect(firstKey).not.toBe(secondKey);
200
+ });
201
+
202
+ test("writes back under the same installation-scoped key it read", async () => {
203
+ await GitHubUtil.getRepositoryTreePaths(argsFor("11111111"));
204
+
205
+ const readKey: string = getCacheSpy.mock.calls[0]![1] as string;
206
+ const writeKey: string = setCacheSpy.mock.calls[0]![1] as string;
207
+
208
+ expect(writeKey).toBe(readKey);
209
+ });
210
+
211
+ test("still distinguishes repositories and branches within one installation", async () => {
212
+ await GitHubUtil.getRepositoryTreePaths({
213
+ installationId: "11111111",
214
+ organizationName: "org",
215
+ repositoryName: "repo-a",
216
+ branchName: "main",
217
+ });
218
+ await GitHubUtil.getRepositoryTreePaths({
219
+ installationId: "11111111",
220
+ organizationName: "org",
221
+ repositoryName: "repo-b",
222
+ branchName: "main",
223
+ });
224
+ await GitHubUtil.getRepositoryTreePaths({
225
+ installationId: "11111111",
226
+ organizationName: "org",
227
+ repositoryName: "repo-a",
228
+ branchName: "develop",
229
+ });
230
+
231
+ const keys: Array<string> = getCacheSpy.mock.calls.map(
232
+ (call: Array<unknown>) => {
233
+ return call[1] as string;
234
+ },
235
+ );
236
+
237
+ expect(new Set(keys).size).toBe(3);
238
+ });
239
+
240
+ test("serves a cache hit without minting a token", async () => {
241
+ getCacheSpy.mockResolvedValue(["src/cached.ts"]);
242
+
243
+ const paths: Array<string> = await GitHubUtil.getRepositoryTreePaths(
244
+ argsFor("11111111"),
245
+ );
246
+
247
+ expect(paths).toEqual(["src/cached.ts"]);
248
+ expect(GitHubUtil.getInstallationAccessToken).not.toHaveBeenCalled();
249
+ });
250
+ });