@oneuptime/common 12.0.27 → 12.0.29

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 (204) hide show
  1. package/Models/AnalyticsModels/RumSessionChunk.ts +52 -0
  2. package/Models/DatabaseModels/CloudResource.ts +17 -1
  3. package/Models/DatabaseModels/DashboardDomain.ts +43 -0
  4. package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
  5. package/Models/DatabaseModels/RumApplication.ts +35 -2
  6. package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
  7. package/Server/API/DashboardDomainAPI.ts +74 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +81 -180
  9. package/Server/API/StatusPageDomainAPI.ts +74 -0
  10. package/Server/API/TelemetryAPI.ts +104 -15
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  14. package/Server/Middleware/ProjectAuthorization.ts +6 -1
  15. package/Server/Services/DashboardDomainService.ts +131 -0
  16. package/Server/Services/LogPipelineProcessorService.ts +88 -0
  17. package/Server/Services/MonitorTemplateService.ts +70 -11
  18. package/Server/Services/StatusPageDomainService.ts +163 -3
  19. package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
  20. package/Server/Types/Domain.ts +17 -0
  21. package/Server/Utils/APIKey/AccessPermission.ts +24 -0
  22. package/Server/Utils/Greenlock/Greenlock.ts +156 -58
  23. package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
  24. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
  25. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
  26. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +12 -5
  27. package/Server/Utils/SessionReplay/SessionReplayIdentity.ts +102 -0
  28. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +11 -2
  29. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
  30. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
  31. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
  32. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
  33. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
  34. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
  35. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
  36. package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
  37. package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
  38. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +81 -0
  39. package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
  40. package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
  41. package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
  42. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
  43. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
  44. package/Tests/Server/API/SessionReplayAPI.test.ts +232 -0
  45. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
  46. package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
  47. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
  48. package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
  49. package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
  50. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
  51. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
  52. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
  53. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
  54. package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
  55. package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
  56. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
  57. package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
  58. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
  59. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
  60. package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
  61. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
  62. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
  63. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
  64. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
  65. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
  66. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
  67. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
  68. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
  69. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
  70. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
  71. package/Tests/Types/API/HTTPResponse.test.ts +175 -0
  72. package/Tests/Types/API/Route.test.ts +36 -4
  73. package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
  74. package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
  75. package/Tests/Types/DateLocalTimeString.test.ts +259 -0
  76. package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
  77. package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
  78. package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
  79. package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
  80. package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
  81. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
  82. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
  83. package/Tests/UI/Utils/Navigation.test.ts +82 -0
  84. package/Tests/Utils/Array.test.ts +100 -0
  85. package/Tests/Utils/CertificateReissue.test.ts +225 -0
  86. package/Tests/Utils/Grok/Grok.test.ts +492 -0
  87. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
  88. package/Types/API/Route.ts +20 -5
  89. package/Types/Date.ts +143 -13
  90. package/Types/Rum/SessionReplay.ts +76 -1
  91. package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
  92. package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
  93. package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
  94. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
  95. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
  96. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
  97. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
  98. package/UI/Utils/Navigation.ts +42 -0
  99. package/Utils/Array.ts +37 -0
  100. package/Utils/CertificateReissue.ts +128 -0
  101. package/Utils/Grok/Grok.ts +461 -0
  102. package/Utils/Grok/GrokPatterns.ts +118 -0
  103. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
  104. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +48 -0
  105. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/CloudResource.js +19 -2
  107. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
  109. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
  111. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/RumApplication.js +37 -3
  113. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
  115. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  116. package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
  117. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  118. package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
  119. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  120. package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
  121. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  122. package/build/dist/Server/API/TelemetryAPI.js +67 -14
  123. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  130. package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
  131. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  132. package/build/dist/Server/Services/DashboardDomainService.js +104 -0
  133. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  134. package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
  135. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
  136. package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
  137. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  138. package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
  139. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  140. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
  141. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  142. package/build/dist/Server/Types/Domain.js +16 -0
  143. package/build/dist/Server/Types/Domain.js.map +1 -1
  144. package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
  145. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  146. package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
  147. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  148. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
  149. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
  150. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
  151. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  152. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
  153. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  154. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  155. package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js +59 -0
  156. package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js.map +1 -0
  157. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +9 -2
  158. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  159. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
  160. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  161. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
  162. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  163. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
  164. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
  165. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
  166. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  167. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
  168. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  169. package/build/dist/Types/API/Route.js +16 -5
  170. package/build/dist/Types/API/Route.js.map +1 -1
  171. package/build/dist/Types/Date.js +115 -13
  172. package/build/dist/Types/Date.js.map +1 -1
  173. package/build/dist/Types/Rum/SessionReplay.js +38 -0
  174. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  175. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
  176. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
  177. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
  178. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  179. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
  180. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
  181. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
  182. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  183. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
  184. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  185. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
  186. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  187. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
  188. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
  189. package/build/dist/UI/Utils/Navigation.js +30 -0
  190. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  191. package/build/dist/Utils/Array.js +25 -0
  192. package/build/dist/Utils/Array.js.map +1 -1
  193. package/build/dist/Utils/CertificateReissue.js +97 -0
  194. package/build/dist/Utils/CertificateReissue.js.map +1 -0
  195. package/build/dist/Utils/Grok/Grok.js +324 -0
  196. package/build/dist/Utils/Grok/Grok.js.map +1 -0
  197. package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
  198. package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
  199. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
  200. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  201. package/package.json +1 -1
  202. package/UI/Utils/JsonWebToken.ts +0 -14
  203. package/build/dist/UI/Utils/JsonWebToken.js +0 -10
  204. package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
@@ -239,5 +239,79 @@ export default class StatusPageDomainAPI extends BaseAPI<
239
239
  }
240
240
  },
241
241
  );
242
+
243
+ /*
244
+ * Reissue SSL API. Backs the "Reissue SSL Certificate" button in the
245
+ * dashboard, for a customer who wants a fresh certificate now rather than
246
+ * whenever the renewal cron next gets to this domain.
247
+ *
248
+ * The throttle that keeps this from becoming a way to spend the shared
249
+ * Let's Encrypt allowance lives in the service, alongside the write that
250
+ * claims it - a check here would be a check the automated callers do not
251
+ * share, and one an extra caller could forget.
252
+ */
253
+ this.router.get(
254
+ `${new this.entityType().getCrudApiPath()?.toString()}/reissue-ssl/:id`,
255
+ UserMiddleware.getUserMiddleware,
256
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
257
+ try {
258
+ if (!StatusPageCNameRecord) {
259
+ return Response.sendErrorResponse(
260
+ req,
261
+ res,
262
+ new BadDataException(
263
+ `Custom Domains not enabled for this
264
+ OneUptime installation. Please contact
265
+ your server admin to enable this
266
+ feature.`,
267
+ ),
268
+ );
269
+ }
270
+
271
+ const databaseProps: DatabaseCommonInteractionProps =
272
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
273
+
274
+ const id: ObjectID = new ObjectID(req.params["id"] as string);
275
+
276
+ /*
277
+ * Scoped by the caller's own props, so a domain in someone else's
278
+ * project is indistinguishable from one that does not exist.
279
+ */
280
+ const domainCount: PositiveNumber =
281
+ await StatusPageDomainService.countBy({
282
+ query: {
283
+ _id: id.toString(),
284
+ },
285
+ props: databaseProps,
286
+ });
287
+
288
+ if (domainCount.toNumber() === 0) {
289
+ return Response.sendErrorResponse(
290
+ req,
291
+ res,
292
+ new BadDataException(
293
+ "The domain does not exist or user does not have access to it.",
294
+ ),
295
+ );
296
+ }
297
+
298
+ logger.debug(
299
+ "Reissuing SSL",
300
+ getLogAttributesFromRequest(req as any),
301
+ );
302
+
303
+ await StatusPageDomainService.reissueCert(id);
304
+
305
+ logger.debug(
306
+ "SSL reissued for domain id - " + id.toString(),
307
+ getLogAttributesFromRequest(req as any),
308
+ );
309
+
310
+ return Response.sendEmptySuccessResponse(req, res);
311
+ } catch (e) {
312
+ next(e);
313
+ }
314
+ },
315
+ );
242
316
  }
243
317
  }
@@ -115,6 +115,7 @@ import RumApplication from "../../Models/DatabaseModels/RumApplication";
115
115
  import RumApplicationService from "../Services/RumApplicationService";
116
116
  import Project from "../../Models/DatabaseModels/Project";
117
117
  import ProjectService from "../Services/ProjectService";
118
+ import SessionReplayIdentity from "../Utils/SessionReplay/SessionReplayIdentity";
118
119
  import SessionReplayTargeting from "../Utils/SessionReplay/SessionReplayTargeting";
119
120
  import SessionReplayUsage from "../Utils/SessionReplay/SessionReplayUsage";
120
121
  import RumSessionReplayView from "../../Models/DatabaseModels/RumSessionReplayView";
@@ -134,6 +135,7 @@ import {
134
135
  DEFAULT_SESSION_REPLAY_MAX_BYTES_PER_PROJECT_PER_DAY,
135
136
  MAX_SESSION_REPLAY_CHUNKS_PER_READ,
136
137
  MAX_SESSION_REPLAY_READ_BYTES,
138
+ SESSION_REPLAY_MAX_USER_REF_LENGTH,
137
139
  } from "../../Types/Rum/SessionReplay";
138
140
 
139
141
  const router: ExpressRouter = Express.getRouter();
@@ -3495,6 +3497,14 @@ router.post(
3495
3497
  /*
3496
3498
  * Listing sessions. Mirrors RumSession's table-level read ACL exactly:
3497
3499
  * knowing WHICH sessions errored is triage.
3500
+ *
3501
+ * Including the PAYLOAD permission, which that ACL also carries and this
3502
+ * guard used to omit. Watching implies listing: the payload routes authorize
3503
+ * on ReadRumSessionReplayPayload alone, so a role granted only "Watch
3504
+ * Session Replays" could play back any session whose id it was handed while
3505
+ * being 401'd on the list, the manifest and the exception page's replay card
3506
+ * - an incoherent grant rather than a safer one, and exactly what
3507
+ * RumSession's own comment says the ACL exists to prevent.
3498
3508
  */
3499
3509
  const requireSessionReplayListAccess: Array<RequestHandler> = [
3500
3510
  UserMiddleware.getUserMiddleware,
@@ -3505,6 +3515,7 @@ const requireSessionReplayListAccess: Array<RequestHandler> = [
3505
3515
  Permission.ProjectAdmin,
3506
3516
  Permission.TelemetryAdmin,
3507
3517
  Permission.ReadRumSessionReplay,
3518
+ Permission.ReadRumSessionReplayPayload,
3508
3519
  ],
3509
3520
  }),
3510
3521
  ];
@@ -3534,6 +3545,20 @@ const SESSION_REPLAY_LIST_PERMISSIONS: Array<Permission> = [
3534
3545
  Permission.ProjectAdmin,
3535
3546
  Permission.TelemetryAdmin,
3536
3547
  Permission.ReadRumSessionReplay,
3548
+ /*
3549
+ * Watching implies listing.
3550
+ *
3551
+ * RumSession's own table read ACL contains this permission for a reason it
3552
+ * states outright: a role granted only the watch permission could fetch
3553
+ * payloads (the payload routes authorize on it alone) while being 401'd on
3554
+ * the manifest and the list - an incoherent grant rather than a safer one.
3555
+ * Leaving it out here meant a support-engineer role built from "Watch
3556
+ * Session Replays" + "Read RUM Application" - the natural pairing, and the
3557
+ * one the permission's own description suggests - got a permission error
3558
+ * on the session list and a silently missing "Watch what the user saw"
3559
+ * card on every exception page.
3560
+ */
3561
+ Permission.ReadRumSessionReplayPayload,
3537
3562
  ];
3538
3563
 
3539
3564
  const SESSION_REPLAY_PAYLOAD_PERMISSIONS: Array<Permission> = [
@@ -4131,8 +4156,51 @@ router.post(
4131
4156
  ? OneUptimeDate.fromString(body["endTime"] as string)
4132
4157
  : OneUptimeDate.getCurrentDate();
4133
4158
 
4159
+ /*
4160
+ * The narrower identity ACL, resolved BEFORE the filters are built.
4161
+ *
4162
+ * It is enforced by simply not naming the column in the SELECT - there
4163
+ * is no ModelPermission on this path to strip it after the fact - and
4164
+ * it is decided against the application already loaded by the access
4165
+ * check, so a caller whose identity grant is label-scoped elsewhere
4166
+ * does not get named end users here.
4167
+ *
4168
+ * It gates the identity FILTER as well as the column. Without that,
4169
+ * a caller deliberately denied the label could still ask "does
4170
+ * jane@example.com have sessions here" and read every other field of
4171
+ * the answer - a dictionary attack that de-anonymises the list one
4172
+ * candidate at a time, and hands back identifiedUserKey as a stable
4173
+ * pseudonym to join against the route filter. The permission sets are
4174
+ * genuinely different: SESSION_REPLAY_IDENTITY_PERMISSIONS excludes
4175
+ * TelemetryAdmin and ReadRumSessionReplay, both of which can list.
4176
+ */
4177
+ const includeIdentifiedUserLabel: boolean = canReadIdentifiedUserLabel({
4178
+ databaseProps: databaseProps,
4179
+ application: application,
4180
+ });
4181
+
4134
4182
  const rawFilters: JSONObject = (body["filters"] as JSONObject) || {};
4135
4183
 
4184
+ /*
4185
+ * A reference the server cannot hash must be a 400, never a filter
4186
+ * that quietly disappears. Dropping it would return the WHOLE
4187
+ * unfiltered list with a 200 - the caller sees every session in the
4188
+ * project and has no way to tell that the person they asked about was
4189
+ * not the one being answered about.
4190
+ */
4191
+ if (
4192
+ rawFilters["identifiedUserRef"] !== undefined &&
4193
+ !SessionReplayIdentity.isUsableUserRef(rawFilters["identifiedUserRef"])
4194
+ ) {
4195
+ return Response.sendErrorResponse(
4196
+ req,
4197
+ res,
4198
+ new BadDataException(
4199
+ `identifiedUserRef must be a non-empty string of at most ${SESSION_REPLAY_MAX_USER_REF_LENGTH} characters.`,
4200
+ ),
4201
+ );
4202
+ }
4203
+
4136
4204
  const filters: SessionReplayListFilters = {
4137
4205
  ...(typeof rawFilters["hasError"] === "boolean" && {
4138
4206
  hasError: rawFilters["hasError"],
@@ -4158,9 +4226,42 @@ router.post(
4158
4226
  ...(readStringArrayFromBody(rawFilters, "countryCodes") && {
4159
4227
  countryCodes: readStringArrayFromBody(rawFilters, "countryCodes"),
4160
4228
  }),
4161
- ...(typeof rawFilters["identifiedUserKey"] === "string" && {
4162
- identifiedUserKey: rawFilters["identifiedUserKey"],
4163
- }),
4229
+ /*
4230
+ * The caller sends the end-user reference their own page supplied -
4231
+ * the value the session list displays - and the server derives the
4232
+ * digest with the same per-project HMAC the ingest used. Hashing
4233
+ * here rather than in the browser is what keeps the derivation (and
4234
+ * the EncryptionSecret it is keyed on) server-side, and it is the
4235
+ * only reason this filter can match anything: the raw key is
4236
+ * displayed nowhere in the product, so a user had no way to obtain
4237
+ * the value the field used to demand.
4238
+ *
4239
+ * Gated on the identity permission, and validated above so an
4240
+ * unusable reference is a 400 rather than a silently unfiltered
4241
+ * list.
4242
+ */
4243
+ ...(includeIdentifiedUserLabel &&
4244
+ SessionReplayIdentity.isUsableUserRef(
4245
+ rawFilters["identifiedUserRef"],
4246
+ ) && {
4247
+ identifiedUserKey: SessionReplayIdentity.buildUserKey({
4248
+ projectId: projectId,
4249
+ userRef: rawFilters["identifiedUserRef"] as string,
4250
+ }),
4251
+ }),
4252
+ /*
4253
+ * Still accepted, for API callers that already hold a digest (an
4254
+ * erasure workflow, a saved view). Ignored when a reference was also
4255
+ * sent, since the reference is the one a human typed. The digest is
4256
+ * not guessable and is already returned to every list-capable
4257
+ * caller, so it needs no identity gate of its own.
4258
+ */
4259
+ ...(typeof rawFilters["identifiedUserKey"] === "string" &&
4260
+ !SessionReplayIdentity.isUsableUserRef(
4261
+ rawFilters["identifiedUserRef"],
4262
+ ) && {
4263
+ identifiedUserKey: rawFilters["identifiedUserKey"],
4264
+ }),
4164
4265
  ...(typeof rawFilters["route"] === "string" && {
4165
4266
  route: rawFilters["route"],
4166
4267
  }),
@@ -4183,18 +4284,6 @@ router.post(
4183
4284
  }
4184
4285
  : undefined;
4185
4286
 
4186
- /*
4187
- * The narrower identity ACL is enforced by simply not naming the
4188
- * column in the SELECT. There is no ModelPermission on this path to
4189
- * strip it after the fact. Decided against the application already
4190
- * loaded by the access check, so a caller whose identity grant is
4191
- * label-scoped elsewhere does not get named end users here.
4192
- */
4193
- const includeIdentifiedUserLabel: boolean = canReadIdentifiedUserLabel({
4194
- databaseProps: databaseProps,
4195
- application: application,
4196
- });
4197
-
4198
4287
  const result: SessionReplayListResult =
4199
4288
  await SessionReplayReadService.listSessions({
4200
4289
  projectId: projectId,
@@ -0,0 +1,43 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * "Default Monitor Name" on a Monitor Template becomes optional (issue #3486).
5
+ *
6
+ * A Network Device auto-import rule names every monitor it provisions
7
+ * "<device> - <template's default monitor name>". While the column was NOT
8
+ * NULL there was nothing an operator could type that would not become a
9
+ * suffix on every imported device in the estate — "UN0660WANRTR01 - Unit
10
+ * Router" for a router whose name is already UN0660WANRTR01. Blank now means
11
+ * the monitor is named after the device alone.
12
+ *
13
+ * Nullable with no default and no backfill: every template that already
14
+ * exists keeps the name it was created with, so nothing about an existing
15
+ * project's monitor naming changes when this ships.
16
+ */
17
+ export class AllowNullMonitorNameOnMonitorTemplate1790200000000
18
+ implements MigrationInterface
19
+ {
20
+ public name: string = "AllowNullMonitorNameOnMonitorTemplate1790200000000";
21
+
22
+ public async up(queryRunner: QueryRunner): Promise<void> {
23
+ await queryRunner.query(
24
+ `ALTER TABLE "MonitorTemplate" ALTER COLUMN "monitorName" DROP NOT NULL`,
25
+ );
26
+ }
27
+
28
+ public async down(queryRunner: QueryRunner): Promise<void> {
29
+ /*
30
+ * Templates written while the column was nullable have no name, and
31
+ * restoring NOT NULL would fail on every one of them. "Monitor" is the
32
+ * exact string the pre-#3486 code substituted when a template's name was
33
+ * blank, so it is the one value that reconstructs the old behaviour
34
+ * rather than inventing a new one.
35
+ */
36
+ await queryRunner.query(
37
+ `UPDATE "MonitorTemplate" SET "monitorName" = 'Monitor' WHERE "monitorName" IS NULL`,
38
+ );
39
+ await queryRunner.query(
40
+ `ALTER TABLE "MonitorTemplate" ALTER COLUMN "monitorName" SET NOT NULL`,
41
+ );
42
+ }
43
+ }
@@ -0,0 +1,40 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * When a customer last asked us to reissue the Let's Encrypt certificate for a
5
+ * custom domain, on both the status page and the dashboard domain tables.
6
+ *
7
+ * This is the state behind the reissue cooldown: certificates are ordered
8
+ * against a Let's Encrypt account shared by every customer on the
9
+ * installation, so the dashboard button that spends that account's allowance
10
+ * is throttled per domain, and this column is what the throttle reads.
11
+ *
12
+ * Nullable with no default and no backfill. Null means "never reissued", which
13
+ * is exactly true of every domain that exists when this ships, and it is the
14
+ * value that lets the first press of the button through rather than making
15
+ * every existing customer wait out a cooldown they never triggered.
16
+ */
17
+ export class AddCertificateReissueRequestedAtToDomains1790300000000
18
+ implements MigrationInterface
19
+ {
20
+ public name: string =
21
+ "AddCertificateReissueRequestedAtToDomains1790300000000";
22
+
23
+ public async up(queryRunner: QueryRunner): Promise<void> {
24
+ await queryRunner.query(
25
+ `ALTER TABLE "StatusPageDomain" ADD "certificateReissueRequestedAt" TIMESTAMP WITH TIME ZONE`,
26
+ );
27
+ await queryRunner.query(
28
+ `ALTER TABLE "DashboardDomain" ADD "certificateReissueRequestedAt" TIMESTAMP WITH TIME ZONE`,
29
+ );
30
+ }
31
+
32
+ public async down(queryRunner: QueryRunner): Promise<void> {
33
+ await queryRunner.query(
34
+ `ALTER TABLE "DashboardDomain" DROP COLUMN "certificateReissueRequestedAt"`,
35
+ );
36
+ await queryRunner.query(
37
+ `ALTER TABLE "StatusPageDomain" DROP COLUMN "certificateReissueRequestedAt"`,
38
+ );
39
+ }
40
+ }
@@ -561,6 +561,8 @@ import { AddTelemetrySourceMap1790000000000 } from "./1790000000000-AddTelemetry
561
561
  import { AddSnmpConfigsToNetworkDeviceDiscoveryScan1790000000001 } from "./1790000000001-AddSnmpConfigsToNetworkDeviceDiscoveryScan";
562
562
  import { AddSnmpEnabledToNetworkDeviceDiscoveryScan1790003445000 } from "./1790003445000-AddSnmpEnabledToNetworkDeviceDiscoveryScan";
563
563
  import { AddResourceActivityFeeds1790100000000 } from "./1790100000000-AddResourceActivityFeeds";
564
+ import { AllowNullMonitorNameOnMonitorTemplate1790200000000 } from "./1790200000000-AllowNullMonitorNameOnMonitorTemplate";
565
+ import { AddCertificateReissueRequestedAtToDomains1790300000000 } from "./1790300000000-AddCertificateReissueRequestedAtToDomains";
564
566
 
565
567
  export default [
566
568
  InitialMigration,
@@ -1126,4 +1128,6 @@ export default [
1126
1128
  AddSnmpConfigsToNetworkDeviceDiscoveryScan1790000000001,
1127
1129
  AddSnmpEnabledToNetworkDeviceDiscoveryScan1790003445000,
1128
1130
  AddResourceActivityFeeds1790100000000,
1131
+ AllowNullMonitorNameOnMonitorTemplate1790200000000,
1132
+ AddCertificateReissueRequestedAtToDomains1790300000000,
1129
1133
  ];
@@ -199,7 +199,12 @@ export default class ProjectMiddleware {
199
199
  (req as OneUptimeRequest).userType = UserType.API;
200
200
 
201
201
  /*
202
- * TODO: Add API key permissions.
202
+ * The two halves of an API key's authority. The global half is the
203
+ * fixed marker set every key carries (see APIKeyAccessPermission); the
204
+ * per-key grants an administrator actually configured are read from
205
+ * ApiKeyPermission just below. That second half is what the long-lived
206
+ * "TODO: Add API key permissions" here was asking for, and it is done -
207
+ * the TODO outlived the work.
203
208
  */
204
209
  (req as OneUptimeRequest).userGlobalAccessPermission =
205
210
  await APIKeyAccessPermission.getDefaultApiGlobalPermission(tenantId);
@@ -20,6 +20,10 @@ import Telemetry, { Span } from "../Utils/Telemetry";
20
20
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
21
21
  import { DashboardCNameRecord } from "../EnvironmentConfig";
22
22
  import Domain from "../Types/Domain";
23
+ import OneUptimeDate from "../../Types/Date";
24
+ import CertificateReissueUtil from "../../Utils/CertificateReissue";
25
+ import TooManyRequestsException from "../../Types/Exception/TooManyRequestsException";
26
+ import QueryHelper from "../Types/Database/QueryHelper";
23
27
 
24
28
  const DASHBOARD_DOMAIN_EGRESS_LABEL: string = "Dashboard domain";
25
29
 
@@ -247,6 +251,133 @@ export class Service extends DatabaseService<DashboardDomain> {
247
251
  });
248
252
  }
249
253
 
254
+ /*
255
+ * Reissue this domain's Let's Encrypt certificate because a customer asked
256
+ * for it, rather than because it is close to expiring.
257
+ *
258
+ * Every order spends from a Let's Encrypt account that is shared by the
259
+ * whole installation - including by the cron that keeps everybody else's
260
+ * certificates alive - so this is throttled to one request per domain per
261
+ * CertificateReissueUtil.COOLDOWN_IN_HOURS.
262
+ *
263
+ * Throws with a customer-readable reason for every refusal, so the API layer
264
+ * can hand the message straight back without classifying the failure:
265
+ * BadDataException for a domain that is not eligible at all, and
266
+ * TooManyRequestsException (429) for one that is simply too soon.
267
+ */
268
+ @CaptureSpan()
269
+ public async reissueCert(domainId: ObjectID): Promise<void> {
270
+ const now: Date = OneUptimeDate.getCurrentDate();
271
+
272
+ const dashboardDomain: DashboardDomain | null = await this.findOneBy({
273
+ query: {
274
+ _id: domainId.toString(),
275
+ },
276
+ select: {
277
+ _id: true,
278
+ fullDomain: true,
279
+ isCnameVerified: true,
280
+ isSslOrdered: true,
281
+ isCustomCertificate: true,
282
+ certificateReissueRequestedAt: true,
283
+ },
284
+ props: {
285
+ isRoot: true,
286
+ },
287
+ });
288
+
289
+ if (!dashboardDomain) {
290
+ throw new BadDataException("Domain not found.");
291
+ }
292
+
293
+ if (!dashboardDomain.fullDomain) {
294
+ throw new BadDataException(
295
+ "Unable to reissue certificate because domain is null",
296
+ );
297
+ }
298
+
299
+ /*
300
+ * Nothing to reissue: the certificate on a custom-certificate domain is
301
+ * the one the customer uploaded, and we have no way to obtain another.
302
+ */
303
+ if (dashboardDomain.isCustomCertificate) {
304
+ throw new BadDataException(
305
+ "This domain uses a certificate you uploaded yourself, so there is no Let's Encrypt certificate to reissue. Please edit this domain and upload a new certificate instead.",
306
+ );
307
+ }
308
+
309
+ if (!dashboardDomain.isCnameVerified) {
310
+ throw new BadDataException(
311
+ "CNAME is not verified. Please verify CNAME first before you reissue the SSL certificate.",
312
+ );
313
+ }
314
+
315
+ /*
316
+ * A domain that never ordered a certificate has nothing to reissue - the
317
+ * dashboard shows "Order Free SSL" for it, which is the correct button.
318
+ */
319
+ if (!dashboardDomain.isSslOrdered) {
320
+ throw new BadDataException(
321
+ "No SSL certificate has been ordered for this domain yet. Please order one first.",
322
+ );
323
+ }
324
+
325
+ /*
326
+ * Claim the cooldown before ordering anything, and claim it by putting the
327
+ * cooldown condition in the WHERE clause rather than in an `if` above the
328
+ * write. Two clicks that arrive together both read a row that is not
329
+ * cooling down; only the write can decide which one of them gets to spend
330
+ * the CA's allowance.
331
+ *
332
+ * The stamp goes down BEFORE the order and is never rolled back on
333
+ * failure. A rejected order still costs a validation attempt against the
334
+ * shared account, and a domain whose order fails is exactly the domain
335
+ * somebody presses again - so a failed attempt has to start the clock the
336
+ * same way a successful one does.
337
+ */
338
+ const claimedRowCount: number = await this.updateOneBy({
339
+ query: {
340
+ _id: domainId.toString(),
341
+ certificateReissueRequestedAt: QueryHelper.lessThanEqualToOrNull(
342
+ CertificateReissueUtil.getCooldownCutoff(now),
343
+ ),
344
+ },
345
+ data: {
346
+ certificateReissueRequestedAt: now,
347
+ },
348
+ props: {
349
+ isRoot: true,
350
+ },
351
+ });
352
+
353
+ if (claimedRowCount === 0) {
354
+ if (dashboardDomain.certificateReissueRequestedAt) {
355
+ throw new TooManyRequestsException(
356
+ CertificateReissueUtil.getCooldownMessage(
357
+ dashboardDomain.certificateReissueRequestedAt,
358
+ now,
359
+ ),
360
+ );
361
+ }
362
+
363
+ /*
364
+ * The row matched the cooldown a moment ago and does not now: another
365
+ * request claimed it in between, or the domain was deleted. Either way
366
+ * the caller must not order.
367
+ */
368
+ throw new BadDataException(
369
+ "Could not start a certificate reissue for this domain. Please refresh the page and try again.",
370
+ );
371
+ }
372
+
373
+ logger.debug(
374
+ "Reissuing SSL certificate for domain: " + dashboardDomain.fullDomain,
375
+ { fullDomain: dashboardDomain.fullDomain } as LogAttributes,
376
+ );
377
+
378
+ await this.orderCert(dashboardDomain);
379
+ }
380
+
250
381
  @CaptureSpan()
251
382
  public async updateSslProvisioningStatusForAllDomains(): Promise<void> {
252
383
  const domains: Array<DashboardDomain> = await this.findBy({
@@ -1,10 +1,98 @@
1
1
  import DatabaseService from "./DatabaseService";
2
2
  import Model from "../../Models/DatabaseModels/LogPipelineProcessor";
3
+ import CreateBy from "../Types/Database/CreateBy";
4
+ import UpdateBy from "../Types/Database/UpdateBy";
5
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
6
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
7
+ import { JSONObject } from "../../Types/JSON";
8
+ import { validateLogPipelineProcessor } from "../Utils/LogPipelineProcessorValidation";
9
+
10
+ /*
11
+ * `PartialEntity` values may be `() => string` raw SQL expressions rather
12
+ * than literals. One cannot be evaluated here, so an update that sets a
13
+ * validated field to an expression is skipped - validating a stringified
14
+ * function would be worse than not validating at all. Nothing in the
15
+ * product writes processor fields this way.
16
+ */
17
+ function isSqlExpressionValue(value: unknown): boolean {
18
+ return typeof value === "function";
19
+ }
3
20
 
4
21
  export class Service extends DatabaseService<Model> {
5
22
  public constructor() {
6
23
  super(Model);
7
24
  }
25
+
26
+ protected override async onBeforeCreate(
27
+ createBy: CreateBy<Model>,
28
+ ): Promise<OnCreate<Model>> {
29
+ validateLogPipelineProcessor({
30
+ processorType: createBy.data.processorType,
31
+ configuration: createBy.data.configuration as JSONObject | undefined,
32
+ });
33
+
34
+ return { createBy, carryForward: null };
35
+ }
36
+
37
+ protected override async onBeforeUpdate(
38
+ updateBy: UpdateBy<Model>,
39
+ ): Promise<OnUpdate<Model>> {
40
+ /*
41
+ * An edit can break a row without naming both fields: switching
42
+ * `processorType` to GrokParser while the stored configuration is
43
+ * still a severity-remapper blob, or replacing the pattern on a row
44
+ * that is already a GrokParser. So validate the MERGED row.
45
+ *
46
+ * Only pay for the SELECT when the update touches one of them.
47
+ * Processor edits are rare, human-driven config changes - this is
48
+ * not a hot path.
49
+ */
50
+ const incomingProcessorType: unknown = updateBy.data.processorType;
51
+ const incomingConfiguration: unknown = updateBy.data.configuration;
52
+
53
+ if (
54
+ incomingProcessorType === undefined &&
55
+ incomingConfiguration === undefined
56
+ ) {
57
+ return { updateBy, carryForward: null };
58
+ }
59
+
60
+ if (
61
+ isSqlExpressionValue(incomingProcessorType) ||
62
+ isSqlExpressionValue(incomingConfiguration)
63
+ ) {
64
+ return { updateBy, carryForward: null };
65
+ }
66
+
67
+ const existingRows: Array<Model> = await this.findBy({
68
+ query: updateBy.query,
69
+ skip: 0,
70
+ limit: LIMIT_MAX,
71
+ select: {
72
+ _id: true,
73
+ processorType: true,
74
+ configuration: true,
75
+ },
76
+ props: {
77
+ isRoot: true,
78
+ },
79
+ });
80
+
81
+ for (const existing of existingRows) {
82
+ validateLogPipelineProcessor({
83
+ processorType:
84
+ incomingProcessorType !== undefined
85
+ ? (incomingProcessorType as string | null)
86
+ : existing.processorType,
87
+ configuration:
88
+ incomingConfiguration !== undefined
89
+ ? (incomingConfiguration as JSONObject | string | null)
90
+ : (existing.configuration as JSONObject | undefined),
91
+ });
92
+ }
93
+
94
+ return { updateBy, carryForward: null };
95
+ }
8
96
  }
9
97
 
10
98
  export default new Service();