@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.
- package/Models/AnalyticsModels/RumSessionChunk.ts +52 -0
- package/Models/DatabaseModels/CloudResource.ts +17 -1
- package/Models/DatabaseModels/DashboardDomain.ts +43 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/RumApplication.ts +35 -2
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/API/TelemetryAPI.ts +104 -15
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +12 -5
- package/Server/Utils/SessionReplay/SessionReplayIdentity.ts +102 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +11 -2
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +81 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +232 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Date.ts +143 -13
- package/Types/Rum/SessionReplay.ts +76 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +48 -0
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +19 -2
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +37 -3
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +67 -14
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js +59 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +9 -2
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- 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
|
-
|
|
4162
|
-
|
|
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
|
-
*
|
|
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();
|