@oneuptime/common 12.0.14 → 12.0.15

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 (195) hide show
  1. package/Models/DatabaseModels/GlobalOidc.ts +20 -0
  2. package/Models/DatabaseModels/GlobalSso.ts +20 -0
  3. package/Models/DatabaseModels/MarketingConversion.ts +19 -9
  4. package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
  5. package/Models/DatabaseModels/StatusPage.ts +7 -4
  6. package/Server/API/StatusPageAPI.ts +29 -4
  7. package/Server/EnvironmentConfig.ts +7 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  11. package/Server/Middleware/SlackAuthorization.ts +43 -6
  12. package/Server/Middleware/UserAuthorization.ts +236 -26
  13. package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
  14. package/Server/Services/GlobalOidcProjectService.ts +126 -1
  15. package/Server/Services/GlobalOidcService.ts +132 -0
  16. package/Server/Services/GlobalSsoProjectService.ts +126 -1
  17. package/Server/Services/GlobalSsoService.ts +132 -0
  18. package/Server/Services/UserEmailService.ts +4 -4
  19. package/Server/Utils/CronTab.ts +73 -0
  20. package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
  21. package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
  22. package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
  23. package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
  24. package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
  25. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
  26. package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
  27. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
  28. package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
  29. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
  30. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
  31. package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
  32. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
  33. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
  34. package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
  35. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
  36. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
  37. package/Server/Utils/Monitor/MonitorResource.ts +161 -21
  38. package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
  39. package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
  40. package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
  41. package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
  42. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
  43. package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
  44. package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
  45. package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
  46. package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
  47. package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
  48. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
  49. package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
  50. package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
  51. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
  52. package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
  53. package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
  54. package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
  55. package/Tests/Server/Utils/CronTab.test.ts +116 -0
  56. package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
  57. package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
  58. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
  59. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
  60. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
  61. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
  62. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
  63. package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
  64. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
  65. package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
  66. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
  67. package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
  68. package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
  69. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
  70. package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
  71. package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
  72. package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
  73. package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
  74. package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
  75. package/Tests/Utils/Analytics.test.ts +187 -0
  76. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
  77. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
  78. package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
  79. package/Types/Marketing/MarketingConversion.ts +12 -0
  80. package/Types/Monitor/CriteriaFilter.ts +9 -0
  81. package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
  82. package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
  83. package/UI/Components/Forms/BasicForm.tsx +13 -1
  84. package/UI/Components/Forms/BasicModelForm.tsx +2 -1
  85. package/UI/Components/Forms/ModelForm.tsx +10 -1
  86. package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
  87. package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
  88. package/Utils/Analytics.ts +55 -2
  89. package/Utils/Monitor/MonitorMetricType.ts +40 -0
  90. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
  91. package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
  92. package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
  93. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
  95. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
  97. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
  99. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
  101. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  102. package/build/dist/Server/API/StatusPageAPI.js +25 -4
  103. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  104. package/build/dist/Server/EnvironmentConfig.js +6 -0
  105. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  112. package/build/dist/Server/Middleware/SlackAuthorization.js +28 -4
  113. package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
  114. package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
  115. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  116. package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
  117. package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
  118. package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
  119. package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
  120. package/build/dist/Server/Services/GlobalOidcService.js +127 -0
  121. package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
  122. package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
  123. package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
  124. package/build/dist/Server/Services/GlobalSsoService.js +127 -0
  125. package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
  126. package/build/dist/Server/Services/UserEmailService.js +4 -4
  127. package/build/dist/Server/Utils/CronTab.js +66 -0
  128. package/build/dist/Server/Utils/CronTab.js.map +1 -1
  129. package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
  130. package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
  131. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
  132. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
  133. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
  134. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
  135. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
  136. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
  137. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
  138. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
  139. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
  140. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
  141. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
  142. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
  143. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
  144. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  145. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
  146. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
  147. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
  148. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  149. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
  150. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  151. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
  152. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
  153. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
  154. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  155. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
  156. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  157. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
  158. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
  159. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
  160. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  161. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
  162. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  163. package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
  164. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  165. package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
  166. package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
  167. package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
  168. package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
  169. package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
  170. package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
  171. package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
  172. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  173. package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
  174. package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
  175. package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
  176. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  177. package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
  178. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  179. package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
  180. package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
  181. package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
  182. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  183. package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
  184. package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
  185. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
  186. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  187. package/build/dist/Utils/Analytics.js +50 -2
  188. package/build/dist/Utils/Analytics.js.map +1 -1
  189. package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
  190. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  191. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
  192. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
  193. package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
  194. package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
  195. package/package.json +1 -1
@@ -24,6 +24,7 @@ import MonitorType, {
24
24
  } from "../../../Types/Monitor/MonitorType";
25
25
  import ServerMonitorResponse from "../../../Types/Monitor/ServerMonitor/ServerMonitorResponse";
26
26
  import ObjectID from "../../../Types/ObjectID";
27
+ import { JSONObject } from "../../../Types/JSON";
27
28
  import ProbeApiIngestResponse from "../../../Types/Probe/ProbeApiIngestResponse";
28
29
  import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
29
30
  import Monitor from "../../../Models/DatabaseModels/Monitor";
@@ -141,6 +142,13 @@ export default class MonitorResourceUtil {
141
142
  _id: true,
142
143
  name: true,
143
144
  minimumProbeAgreement: true,
145
+ /*
146
+ * How often this monitor is checked. Over-time criteria filters need
147
+ * it to work out how many samples a fully covered evaluation window
148
+ * should hold - without it a monitor that has only just started
149
+ * looks the same as one whose whole window is breaching.
150
+ */
151
+ monitoringInterval: true,
144
152
  /*
145
153
  * Recorded as oneuptime.label.* / oneuptime.customField.* attributes on
146
154
  * every monitor metric this result produces, so response time and
@@ -737,6 +745,7 @@ export default class MonitorResourceUtil {
737
745
  monitorStep: monitorStep,
738
746
  currentCriteriaMetId: response.criteriaMetId || null,
739
747
  currentRootCause: response.rootCause || null,
748
+ currentProbeId: (dataToProcess as ProbeMonitorResponse).probeId,
740
749
  monitorProbes: monitorProbesForMonitor || undefined,
741
750
  });
742
751
 
@@ -1238,12 +1247,115 @@ export default class MonitorResourceUtil {
1238
1247
  }
1239
1248
  }
1240
1249
 
1250
+ /**
1251
+ * Turn a probe response read back out of MonitorProbe.lastMonitoringLog
1252
+ * into one the criteria evaluators can actually use.
1253
+ *
1254
+ * The column stores `JSON.parse(JSON.stringify(response))`, which turns
1255
+ * every ObjectID into a plain `{ _type: "ObjectID", value: "..." }` object
1256
+ * and every Date into a string. Those plain objects are not `instanceof
1257
+ * ObjectID`, so the analytics query builder binds them as-is and the
1258
+ * "evaluate over time" lookup silently matches zero rows - which used to
1259
+ * be indistinguishable from "this monitor has no history" and sent the
1260
+ * evaluator down its instantaneous-value fallback. The result was a
1261
+ * decision that disagreed with the evaluation summary shown beside it.
1262
+ */
1263
+ public static hydrateStoredProbeResponse(
1264
+ probeResponse: ProbeMonitorResponse,
1265
+ ): ProbeMonitorResponse {
1266
+ const hydrated: ProbeMonitorResponse = {
1267
+ ...probeResponse,
1268
+ };
1269
+
1270
+ const toObjectID: (value: unknown) => ObjectID | undefined = (
1271
+ value: unknown,
1272
+ ): ObjectID | undefined => {
1273
+ if (!value) {
1274
+ return undefined;
1275
+ }
1276
+
1277
+ if (value instanceof ObjectID) {
1278
+ return value;
1279
+ }
1280
+
1281
+ if (typeof value === "string") {
1282
+ return new ObjectID(value);
1283
+ }
1284
+
1285
+ if (typeof value === "object") {
1286
+ const asJson: JSONObject = value as JSONObject;
1287
+
1288
+ if (asJson["value"]) {
1289
+ return new ObjectID(asJson["value"].toString());
1290
+ }
1291
+ }
1292
+
1293
+ return undefined;
1294
+ };
1295
+
1296
+ const toDate: (value: unknown) => Date | undefined = (
1297
+ value: unknown,
1298
+ ): Date | undefined => {
1299
+ if (!value) {
1300
+ return undefined;
1301
+ }
1302
+
1303
+ if (value instanceof Date) {
1304
+ return value;
1305
+ }
1306
+
1307
+ const parsed: Date = new Date(value as string);
1308
+
1309
+ return isNaN(parsed.getTime()) ? undefined : parsed;
1310
+ };
1311
+
1312
+ const projectId: ObjectID | undefined = toObjectID(probeResponse.projectId);
1313
+ if (projectId) {
1314
+ hydrated.projectId = projectId;
1315
+ }
1316
+
1317
+ const monitorId: ObjectID | undefined = toObjectID(probeResponse.monitorId);
1318
+ if (monitorId) {
1319
+ hydrated.monitorId = monitorId;
1320
+ }
1321
+
1322
+ const monitorStepId: ObjectID | undefined = toObjectID(
1323
+ probeResponse.monitorStepId,
1324
+ );
1325
+ if (monitorStepId) {
1326
+ hydrated.monitorStepId = monitorStepId;
1327
+ }
1328
+
1329
+ const probeId: ObjectID | undefined = toObjectID(probeResponse.probeId);
1330
+ if (probeId) {
1331
+ hydrated.probeId = probeId;
1332
+ }
1333
+
1334
+ const monitoredAt: Date | undefined = toDate(probeResponse.monitoredAt);
1335
+ if (monitoredAt) {
1336
+ hydrated.monitoredAt = monitoredAt;
1337
+ }
1338
+
1339
+ const ingestedAt: Date | undefined = toDate(probeResponse.ingestedAt);
1340
+ if (ingestedAt) {
1341
+ hydrated.ingestedAt = ingestedAt;
1342
+ }
1343
+
1344
+ return hydrated;
1345
+ }
1346
+
1241
1347
  @CaptureSpan()
1242
1348
  private static async checkProbeAgreement(input: {
1243
1349
  monitor: Monitor;
1244
1350
  monitorStep: MonitorStep;
1245
1351
  currentCriteriaMetId: string | null;
1246
1352
  currentRootCause: string | null;
1353
+ /*
1354
+ * Probe that produced the result being handled right now. Its verdict is
1355
+ * already in currentCriteriaMetId / currentRootCause, so it is reused
1356
+ * rather than recomputed - see the loop below.
1357
+ */
1358
+ currentProbeId?: ObjectID | undefined;
1247
1359
  /*
1248
1360
  * Pre-fetched MonitorProbe rows for this monitor (probeId, isEnabled,
1249
1361
  * lastMonitoringLog, probe.name/connectionStatus). The probe-result hot
@@ -1342,30 +1454,58 @@ export default class MonitorResourceUtil {
1342
1454
  continue;
1343
1455
  }
1344
1456
 
1345
- // Evaluate this probe's response against criteria
1346
- const tempResponse: ProbeApiIngestResponse = {
1347
- monitorId: monitor.id!,
1348
- criteriaMetId: undefined,
1349
- rootCause: null,
1350
- };
1457
+ let evaluatedCriteriaMetId: string | undefined = undefined;
1458
+ let evaluatedRootCause: string | null = null;
1351
1459
 
1352
- const tempEvaluationSummary: MonitorEvaluationSummary = {
1353
- evaluatedAt: OneUptimeDate.getCurrentDate(),
1354
- criteriaResults: [],
1355
- events: [],
1356
- };
1460
+ const isCurrentProbe: boolean = Boolean(
1461
+ input.currentProbeId &&
1462
+ monitorProbe.probeId?.toString() === input.currentProbeId.toString(),
1463
+ );
1357
1464
 
1358
- const evaluatedResponse: ProbeApiIngestResponse =
1359
- await MonitorCriteriaEvaluator.processMonitorStep({
1360
- dataToProcess: probeResponse as DataToProcess,
1361
- monitorStep: monitorStep,
1362
- monitor: monitor,
1363
- probeApiIngestResponse: tempResponse,
1364
- evaluationSummary: tempEvaluationSummary,
1365
- });
1465
+ if (isCurrentProbe) {
1466
+ /*
1467
+ * The probe whose result we are handling right now was already
1468
+ * evaluated by the caller, and that evaluation is the one the user
1469
+ * sees in the evaluation summary. Re-running it here would evaluate
1470
+ * a *different* window for any "evaluate over time" filter - seconds
1471
+ * have passed, and this pass reads the payload back out of
1472
+ * lastMonitoringLog rather than using the live object. That is how
1473
+ * an incident could be created citing a criteria the summary right
1474
+ * next to it reported as not met.
1475
+ */
1476
+ evaluatedCriteriaMetId = currentCriteriaMetId || undefined;
1477
+ evaluatedRootCause = currentRootCause;
1478
+ } else {
1479
+ // Evaluate this probe's response against criteria
1480
+ const tempResponse: ProbeApiIngestResponse = {
1481
+ monitorId: monitor.id!,
1482
+ criteriaMetId: undefined,
1483
+ rootCause: null,
1484
+ };
1485
+
1486
+ const tempEvaluationSummary: MonitorEvaluationSummary = {
1487
+ evaluatedAt: OneUptimeDate.getCurrentDate(),
1488
+ criteriaResults: [],
1489
+ events: [],
1490
+ };
1491
+
1492
+ const evaluatedResponse: ProbeApiIngestResponse =
1493
+ await MonitorCriteriaEvaluator.processMonitorStep({
1494
+ dataToProcess: MonitorResourceUtil.hydrateStoredProbeResponse(
1495
+ probeResponse,
1496
+ ) as DataToProcess,
1497
+ monitorStep: monitorStep,
1498
+ monitor: monitor,
1499
+ probeApiIngestResponse: tempResponse,
1500
+ evaluationSummary: tempEvaluationSummary,
1501
+ });
1502
+
1503
+ evaluatedCriteriaMetId = evaluatedResponse.criteriaMetId;
1504
+ evaluatedRootCause = evaluatedResponse.rootCause;
1505
+ }
1366
1506
 
1367
1507
  // Record the result
1368
- const criteriaKey: string = evaluatedResponse.criteriaMetId || "none";
1508
+ const criteriaKey: string = evaluatedCriteriaMetId || "none";
1369
1509
  const existing:
1370
1510
  | { count: number; rootCause: string | null; probeNames: Array<string> }
1371
1511
  | undefined = criteriaAgreements.get(criteriaKey);
@@ -1379,7 +1519,7 @@ export default class MonitorResourceUtil {
1379
1519
  } else {
1380
1520
  criteriaAgreements.set(criteriaKey, {
1381
1521
  count: 1,
1382
- rootCause: evaluatedResponse.rootCause,
1522
+ rootCause: evaluatedRootCause,
1383
1523
  probeNames: [probeName],
1384
1524
  });
1385
1525
  }
@@ -0,0 +1,56 @@
1
+ import { ExpressRequest, ExpressResponse } from "./Express";
2
+
3
+ export const STATUS_PAGE_CONTENT_SECURITY_POLICY_HEADER_NAME: string =
4
+ "Content-Security-Policy";
5
+
6
+ /*
7
+ * Fallback status pages share the authenticated application's origin. Custom
8
+ * domains do not, so this policy is deliberately limited to documents served
9
+ * below /status-page on the application host/path.
10
+ *
11
+ * The status-page template currently contains inline script elements for its
12
+ * Tailwind configuration and optional Google Tag Manager bootstrap. Keeping
13
+ * 'unsafe-inline' in script-src lets those elements continue to run, while
14
+ * script-src-attr 'none' uses CSP3's more-specific directive to block inline
15
+ * event handlers such as <img onerror>. Omitting 'unsafe-eval' separately
16
+ * blocks eval/new Function, which is the custom-JavaScript execution sink.
17
+ * HTTPS script sources remain available for Tag Manager and the tags it loads.
18
+ */
19
+ export const STATUS_PAGE_FALLBACK_CONTENT_SECURITY_POLICY: string = [
20
+ "script-src 'self' 'unsafe-inline' https:",
21
+ "script-src-attr 'none'",
22
+ "object-src 'none'",
23
+ "base-uri 'none'",
24
+ ].join("; ");
25
+
26
+ export const isStatusPageFallbackDocumentPath: (path: string) => boolean = (
27
+ path: string,
28
+ ): boolean => {
29
+ const normalizedPath: string = (path || "").split("?")[0] || "";
30
+
31
+ return (
32
+ normalizedPath === "/status-page" ||
33
+ normalizedPath === "/status-page/" ||
34
+ normalizedPath.startsWith("/status-page/")
35
+ );
36
+ };
37
+
38
+ /**
39
+ * Adds the fallback status page's CSP before its HTML document is rendered.
40
+ * Custom-domain documents (normally served from "/") are intentionally left
41
+ * alone because arbitrary customization remains a supported isolated-origin
42
+ * feature there.
43
+ */
44
+ export default function applyStatusPageContentSecurityPolicy(
45
+ req: ExpressRequest,
46
+ res: ExpressResponse,
47
+ ): void {
48
+ if (!isStatusPageFallbackDocumentPath(req.path || "") || res.headersSent) {
49
+ return;
50
+ }
51
+
52
+ res.set(
53
+ STATUS_PAGE_CONTENT_SECURITY_POLICY_HEADER_NAME,
54
+ STATUS_PAGE_FALLBACK_CONTENT_SECURITY_POLICY,
55
+ );
56
+ }
@@ -0,0 +1,117 @@
1
+ import Domain from "../../Models/DatabaseModels/Domain";
2
+ import StatusPageDomain from "../../Models/DatabaseModels/StatusPageDomain";
3
+ import Hostname from "../../Types/API/Hostname";
4
+ import ObjectID from "../../Types/ObjectID";
5
+ import StatusPageDomainService from "../Services/StatusPageDomainService";
6
+ import { ExpressRequest } from "./Express";
7
+
8
+ const BUILT_IN_PRIMARY_HOSTS: Set<string> = new Set<string>([
9
+ "localhost",
10
+ "ingress",
11
+ ]);
12
+
13
+ const INVALID_HOST_AUTHORITY_CHARACTER: RegExp = /[\s/?#]/;
14
+
15
+ /**
16
+ * Parse the HTTP Host authority without consulting Express' `req.hostname`.
17
+ *
18
+ * Express may derive `req.hostname` from X-Forwarded-Host when `trust proxy`
19
+ * is enabled. That header is caller-controlled on several of the Nginx routes
20
+ * in this repository, so it cannot decide whether tenant-controlled code is
21
+ * returned. Nginx overwrites the actual Host header with `$host`, which makes
22
+ * the raw Host the appropriate input to this security boundary.
23
+ */
24
+ export const normalizeStatusPageRequestHost: (
25
+ rawHost: string | undefined,
26
+ ) => string | null = (rawHost: string | undefined): string | null => {
27
+ const authority: string = rawHost?.trim() || "";
28
+
29
+ if (
30
+ !authority ||
31
+ authority.includes("@") ||
32
+ authority.includes(",") ||
33
+ INVALID_HOST_AUTHORITY_CHARACTER.test(authority) ||
34
+ !Hostname.isValid(authority)
35
+ ) {
36
+ return null;
37
+ }
38
+
39
+ try {
40
+ let hostname: string = Hostname.fromAuthority(authority)
41
+ .hostname.trim()
42
+ .toLowerCase();
43
+
44
+ // Treat the fully-qualified spelling of a DNS name as the same host.
45
+ if (hostname.endsWith(".")) {
46
+ hostname = hostname.slice(0, -1);
47
+ }
48
+
49
+ return hostname || null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ };
54
+
55
+ export const isPrimaryStatusPageHost: (hostname: string) => boolean = (
56
+ hostname: string,
57
+ ): boolean => {
58
+ if (BUILT_IN_PRIMARY_HOSTS.has(hostname)) {
59
+ return true;
60
+ }
61
+
62
+ const configuredHostValue: string = process.env["HOST"] || "localhost";
63
+ const configuredHost: string | null =
64
+ normalizeStatusPageRequestHost(configuredHostValue);
65
+
66
+ /*
67
+ * A malformed deployment HOST means we cannot prove that any incoming host
68
+ * is isolated from the authenticated application. Disable customizations
69
+ * globally until the configuration is corrected.
70
+ */
71
+ if (!configuredHost) {
72
+ return true;
73
+ }
74
+
75
+ return hostname === configuredHost;
76
+ };
77
+
78
+ export const canServeStatusPageCustomizations: (data: {
79
+ req: ExpressRequest;
80
+ statusPageId: ObjectID;
81
+ }) => Promise<boolean> = async (data: {
82
+ req: ExpressRequest;
83
+ statusPageId: ObjectID;
84
+ }): Promise<boolean> => {
85
+ const requestHost: string | null = normalizeStatusPageRequestHost(
86
+ data.req.get("host"),
87
+ );
88
+
89
+ if (!requestHost || isPrimaryStatusPageHost(requestHost)) {
90
+ return false;
91
+ }
92
+
93
+ try {
94
+ const statusPageDomain: StatusPageDomain | null =
95
+ await StatusPageDomainService.findOneBy({
96
+ query: {
97
+ fullDomain: requestHost,
98
+ statusPageId: data.statusPageId,
99
+ isCnameVerified: true,
100
+ domain: {
101
+ isVerified: true,
102
+ } as Domain,
103
+ },
104
+ select: {
105
+ _id: true,
106
+ },
107
+ props: {
108
+ isRoot: true,
109
+ },
110
+ });
111
+
112
+ return Boolean(statusPageDomain);
113
+ } catch {
114
+ // A lookup failure must never fall back to serving tenant-controlled code.
115
+ return false;
116
+ }
117
+ };