@oneuptime/common 11.3.19 → 11.3.21
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/DatabaseModels/EnterpriseLicenseInstance.ts +173 -0
- package/Models/DatabaseModels/GlobalConfig.ts +39 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Server/API/DashboardAPI.ts +221 -0
- package/Server/API/EnterpriseLicenseAPI.ts +279 -12
- package/Server/API/GlobalConfigAPI.ts +81 -16
- package/Server/API/StatusPageAPI.ts +894 -791
- package/Server/EnvironmentConfig.ts +9 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783161406136-AddInstanceIdToGlobalConfig.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/EnterpriseLicenseInstanceService.ts +10 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTDeviceService.ts +99 -4
- package/Server/Services/StatusPageService.ts +104 -45
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +6 -4
- package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +7 -6
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +6 -4
- package/Server/Utils/Telemetry/IoTSnapshotScan.ts +45 -7
- package/ServiceRoute.ts +4 -0
- package/Tests/Server/Services/IoTDeviceService.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/Criteria/TelemetryStepMetricAlias.test.ts +390 -0
- package/Tests/Server/Utils/Telemetry/IoTSnapshotScan.test.ts +202 -0
- package/Tests/Utils/EnterpriseLicenseUsage.test.ts +165 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +18 -0
- package/Types/Monitor/MonitorStep.ts +32 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +172 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +125 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +197 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +39 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +166 -16
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +203 -13
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +62 -15
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +74 -31
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783161406136-AddInstanceIdToGlobalConfig.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783161406136-AddInstanceIdToGlobalConfig.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.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/Services/EnterpriseLicenseInstanceService.js +9 -0
- package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js.map +1 -0
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +66 -4
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +91 -43
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +4 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +6 -5
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +33 -2
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
- package/build/dist/ServiceRoute.js +1 -0
- package/build/dist/ServiceRoute.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.js +2 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStep.js +25 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +92 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +83 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import EnterpriseLicense from "../../Models/DatabaseModels/EnterpriseLicense";
|
|
2
|
+
import EnterpriseLicenseInstance from "../../Models/DatabaseModels/EnterpriseLicenseInstance";
|
|
2
3
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
3
4
|
import { JSONObject } from "../../Types/JSON";
|
|
5
|
+
import PartialEntity from "../../Types/Database/PartialEntity";
|
|
6
|
+
import EnterpriseLicenseInstanceSummary from "../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
|
|
7
|
+
import EnterpriseLicenseUsageUtil from "../../Utils/EnterpriseLicense/EnterpriseLicenseUsage";
|
|
8
|
+
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
|
9
|
+
import ObjectID from "../../Types/ObjectID";
|
|
10
|
+
import PositiveNumber from "../../Types/PositiveNumber";
|
|
11
|
+
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
4
12
|
import EnterpriseLicenseService, {
|
|
5
13
|
Service as EnterpriseLicenseServiceType,
|
|
6
14
|
} from "../Services/EnterpriseLicenseService";
|
|
15
|
+
import EnterpriseLicenseInstanceService from "../Services/EnterpriseLicenseInstanceService";
|
|
7
16
|
import UserMiddleware from "../Middleware/UserAuthorization";
|
|
8
17
|
import JSONWebToken from "../Utils/JsonWebToken";
|
|
9
18
|
import OneUptimeDate from "../../Types/Date";
|
|
@@ -16,6 +25,23 @@ import {
|
|
|
16
25
|
import BaseAPI from "./BaseAPI";
|
|
17
26
|
// import { Host } from "../EnvironmentConfig";
|
|
18
27
|
|
|
28
|
+
export interface LicenseInstanceUpsert {
|
|
29
|
+
licenseId: ObjectID;
|
|
30
|
+
instanceId: string;
|
|
31
|
+
host: string | undefined;
|
|
32
|
+
// Usage fields are only set on report-user-count, not on validate.
|
|
33
|
+
userCount?: number | undefined;
|
|
34
|
+
userEmailHashes?: Array<string> | undefined;
|
|
35
|
+
lastReportedAt?: Date | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* Bounds how many instance rows one license key can register. Real customers
|
|
40
|
+
* run a handful of instances; this stops a leaked key from being used to
|
|
41
|
+
* fill the table with junk rows.
|
|
42
|
+
*/
|
|
43
|
+
const MAX_INSTANCES_PER_LICENSE: number = 100;
|
|
44
|
+
|
|
19
45
|
export default class EnterpriseLicenseAPI extends BaseAPI<
|
|
20
46
|
EnterpriseLicense,
|
|
21
47
|
EnterpriseLicenseServiceType
|
|
@@ -50,6 +76,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
|
|
|
50
76
|
licenseKey: licenseKey,
|
|
51
77
|
},
|
|
52
78
|
select: {
|
|
79
|
+
_id: true,
|
|
53
80
|
companyName: true,
|
|
54
81
|
expiresAt: true,
|
|
55
82
|
licenseKey: true,
|
|
@@ -80,6 +107,27 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
|
|
|
80
107
|
throw new BadDataException("License key has expired");
|
|
81
108
|
}
|
|
82
109
|
|
|
110
|
+
/*
|
|
111
|
+
* The validating instance sends its instanceId and host so it
|
|
112
|
+
* shows up in the instance list right away (usage is reported
|
|
113
|
+
* later by the daily job on the instance).
|
|
114
|
+
*/
|
|
115
|
+
const instanceId: string = this.parseShortText(
|
|
116
|
+
req.body["instanceId"],
|
|
117
|
+
);
|
|
118
|
+
const instanceHost: string = this.parseShortText(req.body["host"]);
|
|
119
|
+
|
|
120
|
+
if (instanceId) {
|
|
121
|
+
await this.upsertLicenseInstance({
|
|
122
|
+
licenseId: license.id!,
|
|
123
|
+
instanceId: instanceId,
|
|
124
|
+
host: instanceHost || undefined,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const instances: Array<EnterpriseLicenseInstance> =
|
|
129
|
+
await this.findLicenseInstances(license.id!);
|
|
130
|
+
|
|
83
131
|
const payload: JSONObject = {
|
|
84
132
|
companyName: license.companyName || "",
|
|
85
133
|
expiresAt: license.expiresAt.toISOString(),
|
|
@@ -105,6 +153,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
|
|
|
105
153
|
userCountUpdatedAt: license.userCountUpdatedAt
|
|
106
154
|
? license.userCountUpdatedAt.toISOString()
|
|
107
155
|
: null,
|
|
156
|
+
instances: this.getInstanceSummaries(instances),
|
|
108
157
|
token,
|
|
109
158
|
});
|
|
110
159
|
} catch (err) {
|
|
@@ -158,23 +207,67 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
|
|
|
158
207
|
|
|
159
208
|
const reportedAt: Date = OneUptimeDate.getCurrentDate();
|
|
160
209
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
210
|
+
const instanceId: string = this.parseShortText(
|
|
211
|
+
req.body["instanceId"],
|
|
212
|
+
);
|
|
213
|
+
const instanceHost: string = this.parseShortText(req.body["host"]);
|
|
214
|
+
const userEmailHashes: Array<string> =
|
|
215
|
+
EnterpriseLicenseUsageUtil.sanitizeUserEmailHashes(
|
|
216
|
+
req.body["userEmailHashes"],
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (instanceId) {
|
|
220
|
+
/*
|
|
221
|
+
* Multi-instance report: track usage per instance and count
|
|
222
|
+
* users uniquely across all instances of this license.
|
|
223
|
+
*/
|
|
224
|
+
await this.upsertLicenseInstance({
|
|
225
|
+
licenseId: license.id!,
|
|
226
|
+
instanceId: instanceId,
|
|
227
|
+
host: instanceHost || undefined,
|
|
228
|
+
userCount: userCount,
|
|
229
|
+
userEmailHashes: userEmailHashes,
|
|
230
|
+
lastReportedAt: reportedAt,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const instances: Array<EnterpriseLicenseInstance> =
|
|
235
|
+
await this.findLicenseInstances(license.id!);
|
|
236
|
+
|
|
237
|
+
let currentUserCount: number = userCount;
|
|
238
|
+
|
|
239
|
+
if (instances.length > 0) {
|
|
240
|
+
currentUserCount = EnterpriseLicenseUsageUtil.getUniqueUserCount(
|
|
241
|
+
instances,
|
|
242
|
+
reportedAt,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/*
|
|
247
|
+
* Legacy reports (no instanceId) only drive the license-wide count
|
|
248
|
+
* while no instance has registered — otherwise they would stomp
|
|
249
|
+
* the deduplicated multi-instance count.
|
|
250
|
+
*/
|
|
251
|
+
if (instanceId || instances.length === 0) {
|
|
252
|
+
await EnterpriseLicenseService.updateOneById({
|
|
253
|
+
id: license.id!,
|
|
254
|
+
data: {
|
|
255
|
+
currentUserCount: currentUserCount,
|
|
256
|
+
userCountUpdatedAt: reportedAt,
|
|
257
|
+
},
|
|
258
|
+
props: {
|
|
259
|
+
isRoot: true,
|
|
260
|
+
ignoreHooks: true,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
172
264
|
|
|
173
265
|
return Response.sendJsonObjectResponse(req, res, {
|
|
174
|
-
currentUserCount:
|
|
266
|
+
currentUserCount: currentUserCount,
|
|
175
267
|
userCountUpdatedAt: reportedAt.toISOString(),
|
|
176
268
|
userLimit:
|
|
177
269
|
typeof license.userLimit === "number" ? license.userLimit : null,
|
|
270
|
+
instances: this.getInstanceSummaries(instances),
|
|
178
271
|
});
|
|
179
272
|
} catch (err) {
|
|
180
273
|
next(err);
|
|
@@ -182,4 +275,178 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
|
|
|
182
275
|
},
|
|
183
276
|
);
|
|
184
277
|
}
|
|
278
|
+
|
|
279
|
+
private parseShortText(value: unknown): string {
|
|
280
|
+
if (typeof value !== "string") {
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return value.trim().substring(0, 100);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private async findLicenseInstances(
|
|
288
|
+
licenseId: ObjectID,
|
|
289
|
+
): Promise<Array<EnterpriseLicenseInstance>> {
|
|
290
|
+
return EnterpriseLicenseInstanceService.findBy({
|
|
291
|
+
query: {
|
|
292
|
+
enterpriseLicenseId: licenseId,
|
|
293
|
+
},
|
|
294
|
+
select: {
|
|
295
|
+
_id: true,
|
|
296
|
+
instanceId: true,
|
|
297
|
+
host: true,
|
|
298
|
+
userCount: true,
|
|
299
|
+
userEmailHashes: true,
|
|
300
|
+
lastReportedAt: true,
|
|
301
|
+
},
|
|
302
|
+
sort: {
|
|
303
|
+
createdAt: SortOrder.Ascending,
|
|
304
|
+
},
|
|
305
|
+
skip: 0,
|
|
306
|
+
limit: LIMIT_MAX,
|
|
307
|
+
props: {
|
|
308
|
+
isRoot: true,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private getInstanceSummaries(
|
|
314
|
+
instances: Array<EnterpriseLicenseInstance>,
|
|
315
|
+
): Array<EnterpriseLicenseInstanceSummary> {
|
|
316
|
+
return instances.map(
|
|
317
|
+
(
|
|
318
|
+
instance: EnterpriseLicenseInstance,
|
|
319
|
+
): EnterpriseLicenseInstanceSummary => {
|
|
320
|
+
return {
|
|
321
|
+
instanceId: instance.instanceId || "",
|
|
322
|
+
host: instance.host || null,
|
|
323
|
+
userCount:
|
|
324
|
+
typeof instance.userCount === "number" ? instance.userCount : null,
|
|
325
|
+
lastReportedAt: instance.lastReportedAt
|
|
326
|
+
? instance.lastReportedAt.toISOString()
|
|
327
|
+
: null,
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private async upsertLicenseInstance(
|
|
334
|
+
data: LicenseInstanceUpsert,
|
|
335
|
+
): Promise<void> {
|
|
336
|
+
const updated: boolean = await this.updateLicenseInstanceIfExists(data);
|
|
337
|
+
|
|
338
|
+
if (updated) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const instanceCount: PositiveNumber =
|
|
343
|
+
await EnterpriseLicenseInstanceService.countBy({
|
|
344
|
+
query: {
|
|
345
|
+
enterpriseLicenseId: data.licenseId,
|
|
346
|
+
},
|
|
347
|
+
props: {
|
|
348
|
+
isRoot: true,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (instanceCount.toNumber() >= MAX_INSTANCES_PER_LICENSE) {
|
|
353
|
+
throw new BadDataException(
|
|
354
|
+
"Too many instances are registered for this license. Please contact support@oneuptime.com.",
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const newInstance: EnterpriseLicenseInstance =
|
|
359
|
+
new EnterpriseLicenseInstance();
|
|
360
|
+
newInstance.enterpriseLicenseId = data.licenseId;
|
|
361
|
+
newInstance.instanceId = data.instanceId;
|
|
362
|
+
|
|
363
|
+
if (data.host !== undefined) {
|
|
364
|
+
newInstance.host = data.host;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (data.userCount !== undefined) {
|
|
368
|
+
newInstance.userCount = data.userCount;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (data.userEmailHashes !== undefined) {
|
|
372
|
+
newInstance.userEmailHashes = data.userEmailHashes;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (data.lastReportedAt !== undefined) {
|
|
376
|
+
newInstance.lastReportedAt = data.lastReportedAt;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
await EnterpriseLicenseInstanceService.create({
|
|
381
|
+
data: newInstance,
|
|
382
|
+
props: {
|
|
383
|
+
isRoot: true,
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
} catch (err) {
|
|
387
|
+
/*
|
|
388
|
+
* A concurrent request created the row between our check and this
|
|
389
|
+
* insert — the unique index on (enterpriseLicenseId, instanceId)
|
|
390
|
+
* rejected the duplicate. Apply the report as an update instead.
|
|
391
|
+
*/
|
|
392
|
+
const retried: boolean = await this.updateLicenseInstanceIfExists(data);
|
|
393
|
+
|
|
394
|
+
if (!retried) {
|
|
395
|
+
throw err;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private async updateLicenseInstanceIfExists(
|
|
401
|
+
data: LicenseInstanceUpsert,
|
|
402
|
+
): Promise<boolean> {
|
|
403
|
+
const existingInstance: EnterpriseLicenseInstance | null =
|
|
404
|
+
await EnterpriseLicenseInstanceService.findOneBy({
|
|
405
|
+
query: {
|
|
406
|
+
enterpriseLicenseId: data.licenseId,
|
|
407
|
+
instanceId: data.instanceId,
|
|
408
|
+
},
|
|
409
|
+
select: {
|
|
410
|
+
_id: true,
|
|
411
|
+
},
|
|
412
|
+
props: {
|
|
413
|
+
isRoot: true,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (!existingInstance) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const updateData: PartialEntity<EnterpriseLicenseInstance> = {};
|
|
422
|
+
|
|
423
|
+
if (data.host !== undefined) {
|
|
424
|
+
updateData.host = data.host;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (data.userCount !== undefined) {
|
|
428
|
+
updateData.userCount = data.userCount;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (data.userEmailHashes !== undefined) {
|
|
432
|
+
updateData.userEmailHashes = data.userEmailHashes;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (data.lastReportedAt !== undefined) {
|
|
436
|
+
updateData.lastReportedAt = data.lastReportedAt;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (Object.keys(updateData).length > 0) {
|
|
440
|
+
await EnterpriseLicenseInstanceService.updateOneById({
|
|
441
|
+
id: existingInstance.id!,
|
|
442
|
+
data: updateData,
|
|
443
|
+
props: {
|
|
444
|
+
isRoot: true,
|
|
445
|
+
ignoreHooks: true,
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
185
452
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ExpressRequest,
|
|
6
6
|
ExpressResponse,
|
|
7
7
|
NextFunction,
|
|
8
|
+
OneUptimeRequest,
|
|
8
9
|
} from "../Utils/Express";
|
|
9
10
|
import Response from "../Utils/Response";
|
|
10
11
|
import BaseAPI from "./BaseAPI";
|
|
@@ -16,7 +17,8 @@ import API from "../../Utils/API";
|
|
|
16
17
|
import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
|
|
17
18
|
import HTTPResponse from "../../Types/API/HTTPResponse";
|
|
18
19
|
import PartialEntity from "../../Types/Database/PartialEntity";
|
|
19
|
-
import { EnterpriseLicenseValidationUrl } from "../EnvironmentConfig";
|
|
20
|
+
import { EnterpriseLicenseValidationUrl, Host } from "../EnvironmentConfig";
|
|
21
|
+
import EnterpriseLicenseInstanceSummary from "../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
|
|
20
22
|
import UserMiddleware from "../Middleware/UserAuthorization";
|
|
21
23
|
|
|
22
24
|
export default class GlobalConfigAPI extends BaseAPI<
|
|
@@ -54,6 +56,7 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
54
56
|
|
|
55
57
|
this.router.get(
|
|
56
58
|
`${new this.entityType().getCrudApiPath()?.toString()}/license`,
|
|
59
|
+
UserMiddleware.getUserMiddleware,
|
|
57
60
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
58
61
|
try {
|
|
59
62
|
const config: GlobalConfig | null =
|
|
@@ -67,19 +70,42 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
67
70
|
enterpriseLicenseUserLimit: true,
|
|
68
71
|
enterpriseLicenseCurrentUserCount: true,
|
|
69
72
|
enterpriseLicenseUserCountUpdatedAt: true,
|
|
73
|
+
enterpriseLicenseInstances: true,
|
|
74
|
+
instanceId: true,
|
|
70
75
|
},
|
|
71
76
|
props: {
|
|
72
77
|
isRoot: true,
|
|
73
78
|
},
|
|
74
79
|
});
|
|
75
80
|
|
|
81
|
+
/*
|
|
82
|
+
* This route also serves the login page (before sign-in), so it
|
|
83
|
+
* cannot require authentication outright. Anonymous callers only
|
|
84
|
+
* get enough to render the edition pill — the license key, token
|
|
85
|
+
* and instance topology require a signed-in user.
|
|
86
|
+
*/
|
|
87
|
+
const isAuthenticatedUser: boolean = Boolean(
|
|
88
|
+
(req as OneUptimeRequest).userAuthorization?.userId,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const licenseValid: boolean = Boolean(
|
|
92
|
+
config?.enterpriseLicenseToken &&
|
|
93
|
+
config?.enterpriseLicenseExpiresAt &&
|
|
94
|
+
config.enterpriseLicenseExpiresAt.getTime() > Date.now(),
|
|
95
|
+
);
|
|
96
|
+
|
|
76
97
|
const responseBody: JSONObject = {
|
|
77
98
|
companyName: config?.enterpriseCompanyName || null,
|
|
78
99
|
expiresAt: config?.enterpriseLicenseExpiresAt
|
|
79
100
|
? config.enterpriseLicenseExpiresAt.toISOString()
|
|
80
101
|
: null,
|
|
81
|
-
licenseKey:
|
|
82
|
-
|
|
102
|
+
licenseKey: isAuthenticatedUser
|
|
103
|
+
? config?.enterpriseLicenseKey || null
|
|
104
|
+
: null,
|
|
105
|
+
token: isAuthenticatedUser
|
|
106
|
+
? config?.enterpriseLicenseToken || null
|
|
107
|
+
: null,
|
|
108
|
+
licenseValid: licenseValid,
|
|
83
109
|
userLimit:
|
|
84
110
|
typeof config?.enterpriseLicenseUserLimit === "number"
|
|
85
111
|
? config.enterpriseLicenseUserLimit
|
|
@@ -91,6 +117,15 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
91
117
|
userCountUpdatedAt: config?.enterpriseLicenseUserCountUpdatedAt
|
|
92
118
|
? config.enterpriseLicenseUserCountUpdatedAt.toISOString()
|
|
93
119
|
: null,
|
|
120
|
+
instances:
|
|
121
|
+
isAuthenticatedUser &&
|
|
122
|
+
Array.isArray(config?.enterpriseLicenseInstances)
|
|
123
|
+
? config.enterpriseLicenseInstances
|
|
124
|
+
: [],
|
|
125
|
+
instanceId:
|
|
126
|
+
isAuthenticatedUser && config?.instanceId
|
|
127
|
+
? config.instanceId.toString()
|
|
128
|
+
: null,
|
|
94
129
|
};
|
|
95
130
|
|
|
96
131
|
return Response.sendJsonObjectResponse(req, res, responseBody);
|
|
@@ -112,12 +147,38 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
112
147
|
throw new BadDataException("License key is required");
|
|
113
148
|
}
|
|
114
149
|
|
|
150
|
+
const globalConfigId: ObjectID = ObjectID.getZeroObjectID();
|
|
151
|
+
|
|
152
|
+
const existingConfig: GlobalConfig | null =
|
|
153
|
+
await GlobalConfigService.findOneById({
|
|
154
|
+
id: globalConfigId,
|
|
155
|
+
select: {
|
|
156
|
+
_id: true,
|
|
157
|
+
instanceId: true,
|
|
158
|
+
},
|
|
159
|
+
props: {
|
|
160
|
+
isRoot: true,
|
|
161
|
+
ignoreHooks: true,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* Send this instance's id and host along with the key so the
|
|
167
|
+
* license server registers this instance against the license and
|
|
168
|
+
* it shows up in the instance list on all instances that share
|
|
169
|
+
* the license.
|
|
170
|
+
*/
|
|
171
|
+
const instanceId: ObjectID =
|
|
172
|
+
existingConfig?.instanceId || ObjectID.generate();
|
|
173
|
+
|
|
115
174
|
const validationResponse:
|
|
116
175
|
| HTTPResponse<JSONObject>
|
|
117
176
|
| HTTPErrorResponse = await API.post<JSONObject>({
|
|
118
177
|
url: EnterpriseLicenseValidationUrl,
|
|
119
178
|
data: {
|
|
120
179
|
licenseKey,
|
|
180
|
+
instanceId: instanceId.toString(),
|
|
181
|
+
host: Host,
|
|
121
182
|
},
|
|
122
183
|
});
|
|
123
184
|
|
|
@@ -178,6 +239,13 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
178
239
|
}
|
|
179
240
|
}
|
|
180
241
|
|
|
242
|
+
const instances: Array<EnterpriseLicenseInstanceSummary> =
|
|
243
|
+
Array.isArray(payload["instances"])
|
|
244
|
+
? (payload[
|
|
245
|
+
"instances"
|
|
246
|
+
] as Array<EnterpriseLicenseInstanceSummary>)
|
|
247
|
+
: [];
|
|
248
|
+
|
|
181
249
|
const updatePayload: PartialEntity<GlobalConfig> = {
|
|
182
250
|
enterpriseCompanyName: companyNameRaw || null,
|
|
183
251
|
enterpriseLicenseKey: licenseKeyRaw || null,
|
|
@@ -186,21 +254,13 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
186
254
|
enterpriseLicenseUserLimit: userLimit,
|
|
187
255
|
enterpriseLicenseCurrentUserCount: currentUserCount,
|
|
188
256
|
enterpriseLicenseUserCountUpdatedAt: userCountUpdatedAt,
|
|
257
|
+
enterpriseLicenseInstances: instances,
|
|
189
258
|
};
|
|
190
259
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
id: globalConfigId,
|
|
196
|
-
select: {
|
|
197
|
-
_id: true,
|
|
198
|
-
},
|
|
199
|
-
props: {
|
|
200
|
-
isRoot: true,
|
|
201
|
-
ignoreHooks: true,
|
|
202
|
-
},
|
|
203
|
-
});
|
|
260
|
+
if (!existingConfig?.instanceId) {
|
|
261
|
+
// Installs that predate instance ids: persist the one we generated.
|
|
262
|
+
updatePayload.instanceId = instanceId;
|
|
263
|
+
}
|
|
204
264
|
|
|
205
265
|
if (existingConfig) {
|
|
206
266
|
await GlobalConfigService.updateOneById({
|
|
@@ -244,6 +304,9 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
244
304
|
userCountUpdatedAt;
|
|
245
305
|
}
|
|
246
306
|
|
|
307
|
+
newConfig.enterpriseLicenseInstances = instances;
|
|
308
|
+
newConfig.instanceId = instanceId;
|
|
309
|
+
|
|
247
310
|
await GlobalConfigService.create({
|
|
248
311
|
data: newConfig,
|
|
249
312
|
props: {
|
|
@@ -263,6 +326,8 @@ export default class GlobalConfigAPI extends BaseAPI<
|
|
|
263
326
|
userCountUpdatedAt: userCountUpdatedAt
|
|
264
327
|
? userCountUpdatedAt.toISOString()
|
|
265
328
|
: null,
|
|
329
|
+
instances: instances,
|
|
330
|
+
instanceId: instanceId.toString(),
|
|
266
331
|
});
|
|
267
332
|
} catch (err) {
|
|
268
333
|
next(err);
|