@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
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { mockRouter } from "./Helpers";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
jest,
|
|
9
|
+
test,
|
|
10
|
+
} from "@jest/globals";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* The reissue-ssl route on both custom-domain APIs.
|
|
14
|
+
*
|
|
15
|
+
* The service owns the throttle; what this file protects is the layer above
|
|
16
|
+
* it, which is where the two mistakes that matter live:
|
|
17
|
+
*
|
|
18
|
+
* - The route must be behind UserMiddleware and must check access with the
|
|
19
|
+
* CALLER'S props, not with isRoot. Every other read on this route runs as
|
|
20
|
+
* root (it has to - the row carries fields the caller may not read), so
|
|
21
|
+
* the one scoped query is the whole tenancy boundary. Lose it and the
|
|
22
|
+
* route reissues certificates for any domain id in the fleet.
|
|
23
|
+
* - A caller who is refused must never reach the CA. Anything that spends
|
|
24
|
+
* the shared Let's Encrypt allowance before the access check is a way for
|
|
25
|
+
* a stranger to spend it.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const mockCNameRecord: string = "oneuptime.example.com";
|
|
29
|
+
|
|
30
|
+
jest.mock("../../../Server/Utils/Express", () => {
|
|
31
|
+
return {
|
|
32
|
+
__esModule: true,
|
|
33
|
+
default: {
|
|
34
|
+
getRouter: () => {
|
|
35
|
+
return mockRouter;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
getRouter: () => {
|
|
39
|
+
return mockRouter;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Custom domains ON. The routes read these at import time, so the value is
|
|
46
|
+
* fixed for the whole file; the "custom domains are switched off" case needs
|
|
47
|
+
* a fresh module graph and gets one at the bottom of this file.
|
|
48
|
+
*/
|
|
49
|
+
jest.mock("../../../Server/EnvironmentConfig", () => {
|
|
50
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
51
|
+
"../../../Server/EnvironmentConfig",
|
|
52
|
+
) as Record<string, unknown>;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
...actual,
|
|
56
|
+
__esModule: true,
|
|
57
|
+
StatusPageCNameRecord: mockCNameRecord,
|
|
58
|
+
DashboardCNameRecord: mockCNameRecord,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
jest.mock("../../../Server/Utils/Response", () => {
|
|
63
|
+
return {
|
|
64
|
+
__esModule: true,
|
|
65
|
+
default: {
|
|
66
|
+
sendEmptySuccessResponse: jest.fn(),
|
|
67
|
+
sendJsonObjectResponse: jest.fn(),
|
|
68
|
+
sendEntityResponse: jest.fn(),
|
|
69
|
+
sendEntityArrayResponse: jest.fn(),
|
|
70
|
+
sendErrorResponse: jest.fn(),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
jest.mock("../../../Server/Utils/Logger", () => {
|
|
76
|
+
return {
|
|
77
|
+
__esModule: true,
|
|
78
|
+
default: {
|
|
79
|
+
debug: jest.fn(),
|
|
80
|
+
info: jest.fn(),
|
|
81
|
+
warn: jest.fn(),
|
|
82
|
+
error: jest.fn(),
|
|
83
|
+
},
|
|
84
|
+
getLogAttributesFromRequest: jest.fn().mockReturnValue({}),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
import StatusPageDomainAPI from "../../../Server/API/StatusPageDomainAPI";
|
|
89
|
+
import DashboardDomainAPI from "../../../Server/API/DashboardDomainAPI";
|
|
90
|
+
import StatusPageDomainService from "../../../Server/Services/StatusPageDomainService";
|
|
91
|
+
import DashboardDomainService from "../../../Server/Services/DashboardDomainService";
|
|
92
|
+
import CommonAPI from "../../../Server/API/CommonAPI";
|
|
93
|
+
import Response from "../../../Server/Utils/Response";
|
|
94
|
+
import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
|
|
95
|
+
import {
|
|
96
|
+
ExpressRequest,
|
|
97
|
+
ExpressResponse,
|
|
98
|
+
NextFunction,
|
|
99
|
+
} from "../../../Server/Utils/Express";
|
|
100
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
101
|
+
import TooManyRequestsException from "../../../Types/Exception/TooManyRequestsException";
|
|
102
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
103
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
104
|
+
|
|
105
|
+
type MockedFn = ReturnType<typeof jest.fn>;
|
|
106
|
+
|
|
107
|
+
type ResponseModule = {
|
|
108
|
+
sendErrorResponse: MockedFn;
|
|
109
|
+
sendEmptySuccessResponse: MockedFn;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const sendEmptySuccessResponseMock: MockedFn =
|
|
113
|
+
Response.sendEmptySuccessResponse as unknown as MockedFn;
|
|
114
|
+
const sendErrorResponseMock: MockedFn =
|
|
115
|
+
Response.sendErrorResponse as unknown as MockedFn;
|
|
116
|
+
|
|
117
|
+
type Surface = {
|
|
118
|
+
name: string;
|
|
119
|
+
buildApi: () => void;
|
|
120
|
+
route: string;
|
|
121
|
+
service: typeof StatusPageDomainService | typeof DashboardDomainService;
|
|
122
|
+
// Module path re-required by the "custom domains switched off" case below.
|
|
123
|
+
apiModulePath: string;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const surfaces: Array<[string, Surface]> = [
|
|
127
|
+
[
|
|
128
|
+
"StatusPageDomainAPI",
|
|
129
|
+
{
|
|
130
|
+
name: "StatusPageDomainAPI",
|
|
131
|
+
buildApi: (): void => {
|
|
132
|
+
new StatusPageDomainAPI();
|
|
133
|
+
},
|
|
134
|
+
route: "/status-page-domain/reissue-ssl/:id",
|
|
135
|
+
service: StatusPageDomainService,
|
|
136
|
+
apiModulePath: "../../../Server/API/StatusPageDomainAPI",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
"DashboardDomainAPI",
|
|
141
|
+
{
|
|
142
|
+
name: "DashboardDomainAPI",
|
|
143
|
+
buildApi: (): void => {
|
|
144
|
+
new DashboardDomainAPI();
|
|
145
|
+
},
|
|
146
|
+
route: "/dashboard-domain/reissue-ssl/:id",
|
|
147
|
+
service: DashboardDomainService,
|
|
148
|
+
apiModulePath: "../../../Server/API/DashboardDomainAPI",
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
describe.each(surfaces)("%s reissue-ssl", (_name: string, surface: Surface) => {
|
|
154
|
+
const callerProps: DatabaseCommonInteractionProps = {
|
|
155
|
+
userId: ObjectID.generate(),
|
|
156
|
+
tenantId: ObjectID.generate(),
|
|
157
|
+
} as DatabaseCommonInteractionProps;
|
|
158
|
+
|
|
159
|
+
let domainId: ObjectID;
|
|
160
|
+
|
|
161
|
+
beforeAll(() => {
|
|
162
|
+
mockRouter.routes.length = 0;
|
|
163
|
+
surface.buildApi();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
jest.clearAllMocks();
|
|
168
|
+
|
|
169
|
+
domainId = ObjectID.generate();
|
|
170
|
+
|
|
171
|
+
jest
|
|
172
|
+
.spyOn(CommonAPI, "getDatabaseCommonInteractionProps")
|
|
173
|
+
.mockResolvedValue(callerProps);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
afterEach(() => {
|
|
177
|
+
jest.restoreAllMocks();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
type CallRouteResult = {
|
|
181
|
+
next: MockedFn;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
type CallRouteFunction = (data?: { id?: string }) => Promise<CallRouteResult>;
|
|
185
|
+
|
|
186
|
+
const callRoute: CallRouteFunction = async (
|
|
187
|
+
data: {
|
|
188
|
+
id?: string;
|
|
189
|
+
} = {},
|
|
190
|
+
): Promise<CallRouteResult> => {
|
|
191
|
+
const req: ExpressRequest = {
|
|
192
|
+
params: { id: data.id ?? domainId.toString() },
|
|
193
|
+
query: {},
|
|
194
|
+
body: {},
|
|
195
|
+
headers: {},
|
|
196
|
+
} as unknown as ExpressRequest;
|
|
197
|
+
|
|
198
|
+
const res: ExpressResponse = {} as ExpressResponse;
|
|
199
|
+
const next: MockedFn = jest.fn();
|
|
200
|
+
|
|
201
|
+
await mockRouter
|
|
202
|
+
.match("GET", surface.route)
|
|
203
|
+
.handlerFunction(req, res, next as unknown as NextFunction);
|
|
204
|
+
|
|
205
|
+
return { next };
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
describe("wiring", () => {
|
|
209
|
+
test("the route exists", () => {
|
|
210
|
+
expect(() => {
|
|
211
|
+
return mockRouter.match("GET", surface.route);
|
|
212
|
+
}).not.toThrow();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/*
|
|
216
|
+
* Without the auth middleware the handler still runs, and
|
|
217
|
+
* getDatabaseCommonInteractionProps would resolve for an unauthenticated
|
|
218
|
+
* caller - so the access check below would be checking nothing.
|
|
219
|
+
*/
|
|
220
|
+
test("it sits behind the user auth middleware", () => {
|
|
221
|
+
expect(mockRouter.match("GET", surface.route).middlewares).toContain(
|
|
222
|
+
UserMiddleware.getUserMiddleware,
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe("access control", () => {
|
|
228
|
+
test("scopes the existence check to the caller's own props", async () => {
|
|
229
|
+
const countSpy: MockedFn = jest
|
|
230
|
+
.spyOn(surface.service, "countBy")
|
|
231
|
+
.mockResolvedValue(new PositiveNumber(1)) as unknown as MockedFn;
|
|
232
|
+
|
|
233
|
+
jest.spyOn(surface.service, "reissueCert").mockResolvedValue(undefined);
|
|
234
|
+
|
|
235
|
+
await callRoute();
|
|
236
|
+
|
|
237
|
+
expect(countSpy).toHaveBeenCalledTimes(1);
|
|
238
|
+
|
|
239
|
+
const countArgs: { query: { _id: string }; props: unknown } = countSpy
|
|
240
|
+
.mock.calls[0]![0] as { query: { _id: string }; props: unknown };
|
|
241
|
+
|
|
242
|
+
expect(countArgs.query._id).toBe(domainId.toString());
|
|
243
|
+
|
|
244
|
+
/*
|
|
245
|
+
* The whole tenancy boundary. isRoot here would make every domain id in
|
|
246
|
+
* the fleet reissuable by any signed-in user.
|
|
247
|
+
*/
|
|
248
|
+
expect(countArgs.props).toBe(callerProps);
|
|
249
|
+
expect((countArgs.props as { isRoot?: boolean }).isRoot).toBeFalsy();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("a domain the caller cannot see is refused and never reaches the CA", async () => {
|
|
253
|
+
jest
|
|
254
|
+
.spyOn(surface.service, "countBy")
|
|
255
|
+
.mockResolvedValue(new PositiveNumber(0));
|
|
256
|
+
|
|
257
|
+
const reissueSpy: MockedFn = jest
|
|
258
|
+
.spyOn(surface.service, "reissueCert")
|
|
259
|
+
.mockResolvedValue(undefined) as unknown as MockedFn;
|
|
260
|
+
|
|
261
|
+
await callRoute();
|
|
262
|
+
|
|
263
|
+
expect(reissueSpy).not.toHaveBeenCalled();
|
|
264
|
+
expect(sendErrorResponseMock).toHaveBeenCalled();
|
|
265
|
+
expect(sendEmptySuccessResponseMock).not.toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("the refusal does not say whether the domain exists", async () => {
|
|
269
|
+
jest
|
|
270
|
+
.spyOn(surface.service, "countBy")
|
|
271
|
+
.mockResolvedValue(new PositiveNumber(0));
|
|
272
|
+
jest.spyOn(surface.service, "reissueCert").mockResolvedValue(undefined);
|
|
273
|
+
|
|
274
|
+
await callRoute();
|
|
275
|
+
|
|
276
|
+
const error: Error = sendErrorResponseMock.mock
|
|
277
|
+
.calls[0]![2] as unknown as Error;
|
|
278
|
+
|
|
279
|
+
expect(error.message).toContain("does not exist or user does not have");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe("the happy path", () => {
|
|
284
|
+
test("reissues the domain named in the url and answers success", async () => {
|
|
285
|
+
jest
|
|
286
|
+
.spyOn(surface.service, "countBy")
|
|
287
|
+
.mockResolvedValue(new PositiveNumber(1));
|
|
288
|
+
|
|
289
|
+
const reissueSpy: MockedFn = jest
|
|
290
|
+
.spyOn(surface.service, "reissueCert")
|
|
291
|
+
.mockResolvedValue(undefined) as unknown as MockedFn;
|
|
292
|
+
|
|
293
|
+
await callRoute();
|
|
294
|
+
|
|
295
|
+
expect(reissueSpy).toHaveBeenCalledTimes(1);
|
|
296
|
+
expect((reissueSpy.mock.calls[0]![0] as ObjectID).toString()).toBe(
|
|
297
|
+
domainId.toString(),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
expect(sendEmptySuccessResponseMock).toHaveBeenCalled();
|
|
301
|
+
expect(sendErrorResponseMock).not.toHaveBeenCalled();
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe("refusals from the service", () => {
|
|
306
|
+
/*
|
|
307
|
+
* The cooldown is a 429 raised inside the service. It has to reach the
|
|
308
|
+
* error handler intact: the message is the countdown the dashboard shows
|
|
309
|
+
* the customer, so swallowing it or replacing it with a generic 500 turns
|
|
310
|
+
* "try again in 3 hours" into "Server Error. Please try again".
|
|
311
|
+
*/
|
|
312
|
+
test("a cooldown refusal is passed on with its message", async () => {
|
|
313
|
+
jest
|
|
314
|
+
.spyOn(surface.service, "countBy")
|
|
315
|
+
.mockResolvedValue(new PositiveNumber(1));
|
|
316
|
+
|
|
317
|
+
const cooldown: TooManyRequestsException = new TooManyRequestsException(
|
|
318
|
+
"Please try again in 3 hours.",
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
jest
|
|
322
|
+
.spyOn(surface.service, "reissueCert")
|
|
323
|
+
.mockRejectedValue(cooldown as never);
|
|
324
|
+
|
|
325
|
+
const { next } = await callRoute();
|
|
326
|
+
|
|
327
|
+
expect(next).toHaveBeenCalledWith(cooldown);
|
|
328
|
+
expect(sendEmptySuccessResponseMock).not.toHaveBeenCalled();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("a failed order is not reported to the customer as a success", async () => {
|
|
332
|
+
jest
|
|
333
|
+
.spyOn(surface.service, "countBy")
|
|
334
|
+
.mockResolvedValue(new PositiveNumber(1));
|
|
335
|
+
|
|
336
|
+
jest
|
|
337
|
+
.spyOn(surface.service, "reissueCert")
|
|
338
|
+
.mockRejectedValue(new Error("CA refused the order") as never);
|
|
339
|
+
|
|
340
|
+
const { next } = await callRoute();
|
|
341
|
+
|
|
342
|
+
expect(next).toHaveBeenCalled();
|
|
343
|
+
expect(sendEmptySuccessResponseMock).not.toHaveBeenCalled();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* Custom domains switched off — a self-hosted installation that never set
|
|
350
|
+
* STATUS_PAGE_CNAME_RECORD / DASHBOARD_CNAME_RECORD.
|
|
351
|
+
*
|
|
352
|
+
* The routes read those values at import time, so this needs its own module
|
|
353
|
+
* graph rather than a value flipped at runtime: re-mocking and re-requiring is
|
|
354
|
+
* the only way to observe the branch the deployed binary would actually take.
|
|
355
|
+
* Without the guard the route would happily order a certificate for a domain
|
|
356
|
+
* that cannot possibly point at this cluster, spending a validation attempt
|
|
357
|
+
* against the shared Let's Encrypt account to do it.
|
|
358
|
+
*/
|
|
359
|
+
describe("reissue-ssl with custom domains switched off", () => {
|
|
360
|
+
type DisabledSurface = {
|
|
361
|
+
label: string;
|
|
362
|
+
apiModulePath: string;
|
|
363
|
+
serviceModulePath: string;
|
|
364
|
+
route: string;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const disabledSurfaces: Array<[string, DisabledSurface]> = [
|
|
368
|
+
[
|
|
369
|
+
"StatusPageDomainAPI",
|
|
370
|
+
{
|
|
371
|
+
label: "StatusPageDomainAPI",
|
|
372
|
+
apiModulePath: "../../../Server/API/StatusPageDomainAPI",
|
|
373
|
+
serviceModulePath: "../../../Server/Services/StatusPageDomainService",
|
|
374
|
+
route: "/status-page-domain/reissue-ssl/:id",
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
[
|
|
378
|
+
"DashboardDomainAPI",
|
|
379
|
+
{
|
|
380
|
+
label: "DashboardDomainAPI",
|
|
381
|
+
apiModulePath: "../../../Server/API/DashboardDomainAPI",
|
|
382
|
+
serviceModulePath: "../../../Server/Services/DashboardDomainService",
|
|
383
|
+
route: "/dashboard-domain/reissue-ssl/:id",
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
test.each(disabledSurfaces)(
|
|
389
|
+
"%s refuses without touching the CA",
|
|
390
|
+
async (_label: string, disabled: DisabledSurface) => {
|
|
391
|
+
jest.resetModules();
|
|
392
|
+
|
|
393
|
+
jest.doMock("../../../Server/EnvironmentConfig", () => {
|
|
394
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
395
|
+
"../../../Server/EnvironmentConfig",
|
|
396
|
+
) as Record<string, unknown>;
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
...actual,
|
|
400
|
+
__esModule: true,
|
|
401
|
+
StatusPageCNameRecord: "",
|
|
402
|
+
DashboardCNameRecord: "",
|
|
403
|
+
};
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
/*
|
|
407
|
+
* resetModules gives every module below a fresh instance, so the
|
|
408
|
+
* response spy and the service must be re-read from the same fresh
|
|
409
|
+
* graph the freshly built API is wired to.
|
|
410
|
+
*/
|
|
411
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
412
|
+
const FreshAPI: new () => unknown = require(
|
|
413
|
+
disabled.apiModulePath,
|
|
414
|
+
).default;
|
|
415
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
416
|
+
const freshService: { reissueCert: unknown; countBy: unknown } = require(
|
|
417
|
+
disabled.serviceModulePath,
|
|
418
|
+
).default;
|
|
419
|
+
const responseModulePath: string = "../../../Server/Utils/Response";
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
421
|
+
const freshResponse: ResponseModule = require(responseModulePath).default;
|
|
422
|
+
|
|
423
|
+
mockRouter.routes.length = 0;
|
|
424
|
+
new FreshAPI();
|
|
425
|
+
|
|
426
|
+
const reissueSpy: MockedFn = jest
|
|
427
|
+
.spyOn(freshService as never, "reissueCert")
|
|
428
|
+
.mockResolvedValue(undefined as never) as unknown as MockedFn;
|
|
429
|
+
|
|
430
|
+
const countSpy: MockedFn = jest
|
|
431
|
+
.spyOn(freshService as never, "countBy")
|
|
432
|
+
.mockResolvedValue(
|
|
433
|
+
new PositiveNumber(1) as never,
|
|
434
|
+
) as unknown as MockedFn;
|
|
435
|
+
|
|
436
|
+
freshResponse.sendErrorResponse.mockClear();
|
|
437
|
+
freshResponse.sendEmptySuccessResponse.mockClear();
|
|
438
|
+
|
|
439
|
+
const req: ExpressRequest = {
|
|
440
|
+
params: { id: ObjectID.generate().toString() },
|
|
441
|
+
query: {},
|
|
442
|
+
body: {},
|
|
443
|
+
headers: {},
|
|
444
|
+
} as unknown as ExpressRequest;
|
|
445
|
+
|
|
446
|
+
await mockRouter
|
|
447
|
+
.match("GET", disabled.route)
|
|
448
|
+
.handlerFunction(
|
|
449
|
+
req,
|
|
450
|
+
{} as ExpressResponse,
|
|
451
|
+
jest.fn() as unknown as NextFunction,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
expect(freshResponse.sendErrorResponse).toHaveBeenCalled();
|
|
455
|
+
expect(freshResponse.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
456
|
+
expect(reissueSpy).not.toHaveBeenCalled();
|
|
457
|
+
|
|
458
|
+
/*
|
|
459
|
+
* Refused before the row is even looked up — the switch is off for the
|
|
460
|
+
* whole installation, so there is nothing about this domain to check.
|
|
461
|
+
*/
|
|
462
|
+
expect(countSpy).not.toHaveBeenCalled();
|
|
463
|
+
|
|
464
|
+
jest.restoreAllMocks();
|
|
465
|
+
jest.dontMock("../../../Server/EnvironmentConfig");
|
|
466
|
+
jest.resetModules();
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
});
|