@oneuptime/common 10.4.12 → 10.4.14
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/Alert.ts +2 -0
- package/Models/DatabaseModels/AlertFeed.ts +1 -0
- package/Models/DatabaseModels/CallLog.ts +2 -0
- package/Models/DatabaseModels/DockerHost.ts +34 -0
- package/Models/DatabaseModels/EmailLog.ts +2 -0
- package/Models/DatabaseModels/Host.ts +34 -0
- package/Models/DatabaseModels/Incident.ts +1 -0
- package/Models/DatabaseModels/IncidentFeed.ts +1 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +34 -0
- package/Models/DatabaseModels/MonitorFeed.ts +1 -0
- package/Models/DatabaseModels/MonitorProbe.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +3 -0
- package/Models/DatabaseModels/SmsLog.ts +2 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +2 -0
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +1 -0
- package/Models/DatabaseModels/WorkflowLog.ts +1 -0
- package/Server/API/ProjectAPI.ts +52 -15
- package/Server/EnvironmentConfig.ts +69 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +26 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.ts +160 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Infrastructure/PostgresDatabase.ts +2 -5
- package/Server/Middleware/ProjectAuthorization.ts +31 -53
- package/Server/Middleware/UserAuthorization.ts +106 -64
- package/Server/Services/ApiKeyService.ts +100 -1
- package/Server/Services/DockerHostService.ts +5 -0
- package/Server/Services/HostService.ts +6 -0
- package/Server/Services/KubernetesClusterService.ts +33 -10
- package/Server/Services/MonitorService.ts +10 -3
- package/Server/Services/ProjectService.ts +72 -1
- package/Server/Services/TeamMemberService.ts +36 -0
- package/Server/Services/UserService.ts +38 -0
- package/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.ts +108 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +25 -0
- package/Types/Monitor/CriteriaFilter.ts +13 -0
- package/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.ts +69 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
- package/Types/Monitor/MonitorStep.ts +39 -0
- package/Types/Monitor/MonitorStepDnssecMonitor.ts +59 -0
- package/Types/Monitor/MonitorType.ts +17 -1
- package/Types/Probe/ProbeMonitorResponse.ts +2 -0
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +51 -0
- package/Utils/Monitor/MonitorMetricType.ts +1 -0
- package/build/dist/Models/DatabaseModels/Alert.js +3 -1
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CallLog.js +4 -1
- package/build/dist/Models/DatabaseModels/CallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +35 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EmailLog.js +4 -1
- package/build/dist/Models/DatabaseModels/EmailLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +35 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +35 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/MonitorFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorProbe.js +2 -0
- package/build/dist/Models/DatabaseModels/MonitorProbe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +3 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/SmsLog.js +4 -1
- package/build/dist/Models/DatabaseModels/SmsLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +4 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +3 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +2 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Server/API/ProjectAPI.js +42 -14
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +41 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +20 -2
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.js +63 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.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/Infrastructure/PostgresDatabase.js +2 -2
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +21 -39
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +83 -50
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyService.js +86 -0
- package/build/dist/Server/Services/ApiKeyService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +5 -1
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +5 -1
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +21 -11
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +8 -3
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +65 -1
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +24 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +36 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.js +94 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -3
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +12 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +26 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js +42 -0
- package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +15 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +47 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +1 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -132,6 +132,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
132
132
|
extra?: {
|
|
133
133
|
osType?: string | undefined;
|
|
134
134
|
osVersion?: string | undefined;
|
|
135
|
+
agentVersion?: string | undefined;
|
|
135
136
|
},
|
|
136
137
|
): Promise<void> {
|
|
137
138
|
const cacheKey: string = hostId.toString();
|
|
@@ -141,6 +142,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
141
142
|
JSON.stringify({
|
|
142
143
|
osType: extra?.osType ?? null,
|
|
143
144
|
osVersion: extra?.osVersion ?? null,
|
|
145
|
+
agentVersion: extra?.agentVersion ?? null,
|
|
144
146
|
}),
|
|
145
147
|
)
|
|
146
148
|
.digest("hex");
|
|
@@ -173,6 +175,9 @@ export class Service extends DatabaseService<Model> {
|
|
|
173
175
|
if (extra?.osVersion) {
|
|
174
176
|
data.osVersion = extra.osVersion;
|
|
175
177
|
}
|
|
178
|
+
if (extra?.agentVersion) {
|
|
179
|
+
data.agentVersion = extra.agentVersion;
|
|
180
|
+
}
|
|
176
181
|
|
|
177
182
|
await this.updateOneById({
|
|
178
183
|
id: hostId,
|
|
@@ -135,6 +135,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
135
135
|
containerRuntime?: string | undefined;
|
|
136
136
|
dockerHostId?: ObjectID | undefined;
|
|
137
137
|
kubernetesClusterId?: ObjectID | undefined;
|
|
138
|
+
agentVersion?: string | undefined;
|
|
138
139
|
},
|
|
139
140
|
): Promise<void> {
|
|
140
141
|
/*
|
|
@@ -204,6 +205,9 @@ export class Service extends DatabaseService<Model> {
|
|
|
204
205
|
if (extra?.kubernetesClusterId) {
|
|
205
206
|
data.kubernetesClusterId = extra.kubernetesClusterId;
|
|
206
207
|
}
|
|
208
|
+
if (extra?.agentVersion) {
|
|
209
|
+
data.agentVersion = extra.agentVersion;
|
|
210
|
+
}
|
|
207
211
|
|
|
208
212
|
await this.updateOneById({
|
|
209
213
|
id: hostId,
|
|
@@ -227,6 +231,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
227
231
|
containerRuntime?: string | undefined;
|
|
228
232
|
dockerHostId?: ObjectID | undefined;
|
|
229
233
|
kubernetesClusterId?: ObjectID | undefined;
|
|
234
|
+
agentVersion?: string | undefined;
|
|
230
235
|
}): string {
|
|
231
236
|
const normalized: Record<string, string | number | null> = {
|
|
232
237
|
osType: extra?.osType ?? null,
|
|
@@ -241,6 +246,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
241
246
|
containerRuntime: extra?.containerRuntime ?? null,
|
|
242
247
|
dockerHostId: extra?.dockerHostId?.toString() ?? null,
|
|
243
248
|
kubernetesClusterId: extra?.kubernetesClusterId?.toString() ?? null,
|
|
249
|
+
agentVersion: extra?.agentVersion ?? null,
|
|
244
250
|
};
|
|
245
251
|
|
|
246
252
|
return crypto
|
|
@@ -127,28 +127,51 @@ export class Service extends DatabaseService<Model> {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
@CaptureSpan()
|
|
130
|
-
public async updateLastSeen(
|
|
130
|
+
public async updateLastSeen(
|
|
131
|
+
clusterId: ObjectID,
|
|
132
|
+
extra?: {
|
|
133
|
+
agentVersion?: string | undefined;
|
|
134
|
+
},
|
|
135
|
+
): Promise<void> {
|
|
131
136
|
const cacheKey: string = clusterId.toString();
|
|
137
|
+
const extrasFingerprint: string = crypto
|
|
138
|
+
.createHash("sha1")
|
|
139
|
+
.update(
|
|
140
|
+
JSON.stringify({
|
|
141
|
+
agentVersion: extra?.agentVersion ?? null,
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
.digest("hex");
|
|
132
145
|
|
|
133
146
|
const cached: string | null = await GlobalCache.getString(
|
|
134
147
|
LAST_SEEN_CACHE_NAMESPACE,
|
|
135
148
|
cacheKey,
|
|
136
149
|
);
|
|
137
150
|
|
|
138
|
-
if (cached) {
|
|
139
|
-
return; //
|
|
151
|
+
if (cached === extrasFingerprint) {
|
|
152
|
+
return; // same data was written recently
|
|
140
153
|
}
|
|
141
154
|
|
|
142
|
-
await GlobalCache.setString(
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
await GlobalCache.setString(
|
|
156
|
+
LAST_SEEN_CACHE_NAMESPACE,
|
|
157
|
+
cacheKey,
|
|
158
|
+
extrasFingerprint,
|
|
159
|
+
{ expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
|
+
const data: any = {
|
|
164
|
+
lastSeenAt: OneUptimeDate.getCurrentDate(),
|
|
165
|
+
otelCollectorStatus: "connected",
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (extra?.agentVersion) {
|
|
169
|
+
data.agentVersion = extra.agentVersion;
|
|
170
|
+
}
|
|
145
171
|
|
|
146
172
|
await this.updateOneById({
|
|
147
173
|
id: clusterId,
|
|
148
|
-
data:
|
|
149
|
-
lastSeenAt: OneUptimeDate.getCurrentDate(),
|
|
150
|
-
otelCollectorStatus: "connected",
|
|
151
|
-
},
|
|
174
|
+
data: data,
|
|
152
175
|
props: {
|
|
153
176
|
isRoot: true,
|
|
154
177
|
},
|
|
@@ -1563,9 +1563,16 @@ ${createdItem.description?.trim() || "No description provided."}
|
|
|
1563
1563
|
return;
|
|
1564
1564
|
}
|
|
1565
1565
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1566
|
+
/*
|
|
1567
|
+
* Each monitor appears at most once for a given probeId (composite
|
|
1568
|
+
* unique on MonitorProbe), so concurrent refreshes operate on
|
|
1569
|
+
* disjoint rows and are safe to run in parallel.
|
|
1570
|
+
*/
|
|
1571
|
+
await Promise.all(
|
|
1572
|
+
monitorProbes.map((monitorProbe: MonitorProbe) => {
|
|
1573
|
+
return this.refreshMonitorProbeStatus(monitorProbe.monitorId!);
|
|
1574
|
+
}),
|
|
1575
|
+
);
|
|
1569
1576
|
}
|
|
1570
1577
|
|
|
1571
1578
|
@CaptureSpan()
|
|
@@ -82,6 +82,7 @@ import DatabaseConfig from "../DatabaseConfig";
|
|
|
82
82
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
83
83
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
84
84
|
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
|
|
85
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
85
86
|
|
|
86
87
|
export interface CurrentPlan {
|
|
87
88
|
plan: PlanType | null;
|
|
@@ -89,6 +90,20 @@ export interface CurrentPlan {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
export class ProjectService extends DatabaseService<Model> {
|
|
93
|
+
/*
|
|
94
|
+
* Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
|
|
95
|
+
* staleness on "last seen" is acceptable; an UPDATE per request is not.
|
|
96
|
+
*/
|
|
97
|
+
private lastActiveCache: InMemoryTTLCache<true> = new InMemoryTTLCache(
|
|
98
|
+
10_000,
|
|
99
|
+
);
|
|
100
|
+
/*
|
|
101
|
+
* Caches the `requireSsoForLogin` flag per project so middleware can skip a
|
|
102
|
+
* Postgres findOneById on every authenticated request.
|
|
103
|
+
*/
|
|
104
|
+
private requireSsoForLoginCache: InMemoryTTLCache<boolean> =
|
|
105
|
+
new InMemoryTTLCache(10_000);
|
|
106
|
+
|
|
92
107
|
public constructor() {
|
|
93
108
|
super(Model);
|
|
94
109
|
}
|
|
@@ -318,6 +333,14 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
318
333
|
protected override async onBeforeUpdate(
|
|
319
334
|
updateBy: UpdateBy<Model>,
|
|
320
335
|
): Promise<OnUpdate<Model>> {
|
|
336
|
+
/*
|
|
337
|
+
* Any project field could have changed; invalidate the in-process cache
|
|
338
|
+
* of the SSO flag. Cheap to refetch on the next request.
|
|
339
|
+
*/
|
|
340
|
+
if (updateBy.data.requireSsoForLogin !== undefined) {
|
|
341
|
+
this.requireSsoForLoginCache.clear();
|
|
342
|
+
}
|
|
343
|
+
|
|
321
344
|
if (IsBillingEnabled) {
|
|
322
345
|
if (
|
|
323
346
|
updateBy.data.businessDetails ||
|
|
@@ -1251,7 +1274,21 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1251
1274
|
|
|
1252
1275
|
@CaptureSpan()
|
|
1253
1276
|
public async updateLastActive(projectId: ObjectID): Promise<void> {
|
|
1254
|
-
|
|
1277
|
+
const key: string = projectId.toString();
|
|
1278
|
+
if (this.lastActiveCache.has(key)) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
/*
|
|
1282
|
+
* Set BEFORE the await so a burst of concurrent requests collapses to one
|
|
1283
|
+
* UPDATE per node per 60s window instead of all firing in parallel.
|
|
1284
|
+
*/
|
|
1285
|
+
this.lastActiveCache.set(key, true, 60_000);
|
|
1286
|
+
|
|
1287
|
+
/*
|
|
1288
|
+
* Fire-and-forget — `lastActive` is a soft-real-time field and the
|
|
1289
|
+
* caller (auth middleware) shouldn't pay a Postgres round-trip for it.
|
|
1290
|
+
*/
|
|
1291
|
+
void this.updateOneById({
|
|
1255
1292
|
id: projectId,
|
|
1256
1293
|
data: {
|
|
1257
1294
|
lastActive: OneUptimeDate.getCurrentDate(),
|
|
@@ -1259,9 +1296,43 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1259
1296
|
props: {
|
|
1260
1297
|
isRoot: true,
|
|
1261
1298
|
},
|
|
1299
|
+
}).catch((err: Error) => {
|
|
1300
|
+
// Drop the cache entry so a retry can fire within the same TTL window.
|
|
1301
|
+
this.lastActiveCache.delete(key);
|
|
1302
|
+
logger.error(
|
|
1303
|
+
`Failed to update Project.lastActive for ${key}: ${err.message}`,
|
|
1304
|
+
);
|
|
1262
1305
|
});
|
|
1263
1306
|
}
|
|
1264
1307
|
|
|
1308
|
+
/**
|
|
1309
|
+
* Returns whether the given project requires SSO for login. Cached for
|
|
1310
|
+
* 60s in-process — middleware calls this on every authenticated request.
|
|
1311
|
+
*/
|
|
1312
|
+
@CaptureSpan()
|
|
1313
|
+
public async getRequireSsoForLogin(projectId: ObjectID): Promise<boolean> {
|
|
1314
|
+
const key: string = projectId.toString();
|
|
1315
|
+
const cached: boolean | undefined = this.requireSsoForLoginCache.get(key);
|
|
1316
|
+
if (cached !== undefined) {
|
|
1317
|
+
return cached;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const project: Model | null = await this.findOneById({
|
|
1321
|
+
id: projectId,
|
|
1322
|
+
select: { requireSsoForLogin: true },
|
|
1323
|
+
props: { isRoot: true },
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
if (!project) {
|
|
1327
|
+
// Don't cache "not found" — let the caller decide how to handle it.
|
|
1328
|
+
throw new BadDataException("Project not found");
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const value: boolean = Boolean(project.requireSsoForLogin);
|
|
1332
|
+
this.requireSsoForLoginCache.set(key, value, 60_000);
|
|
1333
|
+
return value;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1265
1336
|
@CaptureSpan()
|
|
1266
1337
|
public async getOwners(projectId: ObjectID): Promise<Array<User>> {
|
|
1267
1338
|
if (!projectId) {
|
|
@@ -37,8 +37,19 @@ import User from "../../Models/DatabaseModels/User";
|
|
|
37
37
|
import OnCallDutyPolicyTimeLogService from "./OnCallDutyPolicyTimeLogService";
|
|
38
38
|
import OneUptimeDate from "../../Types/Date";
|
|
39
39
|
import ProjectSCIMService from "./ProjectSCIMService";
|
|
40
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
40
41
|
|
|
41
42
|
export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
43
|
+
/*
|
|
44
|
+
* Caches the user's accepted team memberships per project. Auth middleware
|
|
45
|
+
* calls this on every authenticated request to evaluate the `Owned`
|
|
46
|
+
* permission scope; without the cache it's a Postgres findBy per request.
|
|
47
|
+
* 60s of staleness on team membership changes is acceptable; we also
|
|
48
|
+
* invalidate proactively when team membership writes happen.
|
|
49
|
+
*/
|
|
50
|
+
private teamIdsForUserCache: InMemoryTTLCache<Array<string>> =
|
|
51
|
+
new InMemoryTTLCache(10_000);
|
|
52
|
+
|
|
42
53
|
public constructor() {
|
|
43
54
|
super(TeamMember);
|
|
44
55
|
}
|
|
@@ -215,6 +226,14 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
215
226
|
userId: ObjectID,
|
|
216
227
|
projectId: ObjectID,
|
|
217
228
|
): Promise<void> {
|
|
229
|
+
/*
|
|
230
|
+
* Invalidate the in-process cache of this user's team memberships in
|
|
231
|
+
* this project — membership just changed.
|
|
232
|
+
*/
|
|
233
|
+
this.teamIdsForUserCache.delete(
|
|
234
|
+
`${userId.toString()}:${projectId.toString()}`,
|
|
235
|
+
);
|
|
236
|
+
|
|
218
237
|
/// Refresh tokens.
|
|
219
238
|
await AccessTokenService.refreshUserGlobalAccessPermission(userId);
|
|
220
239
|
|
|
@@ -545,6 +564,15 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
545
564
|
userId: ObjectID,
|
|
546
565
|
projectId: ObjectID,
|
|
547
566
|
): Promise<Array<ObjectID>> {
|
|
567
|
+
const cacheKey: string = `${userId.toString()}:${projectId.toString()}`;
|
|
568
|
+
const cached: Array<string> | undefined =
|
|
569
|
+
this.teamIdsForUserCache.get(cacheKey);
|
|
570
|
+
if (cached !== undefined) {
|
|
571
|
+
return cached.map((id: string) => {
|
|
572
|
+
return new ObjectID(id);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
548
576
|
const members: Array<TeamMember> = await this.findBy({
|
|
549
577
|
query: {
|
|
550
578
|
userId: userId,
|
|
@@ -570,6 +598,14 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
570
598
|
teamIds.push(id);
|
|
571
599
|
}
|
|
572
600
|
}
|
|
601
|
+
|
|
602
|
+
this.teamIdsForUserCache.set(
|
|
603
|
+
cacheKey,
|
|
604
|
+
teamIds.map((id: ObjectID) => {
|
|
605
|
+
return id.toString();
|
|
606
|
+
}),
|
|
607
|
+
60_000,
|
|
608
|
+
);
|
|
573
609
|
return teamIds;
|
|
574
610
|
}
|
|
575
611
|
}
|
|
@@ -39,12 +39,50 @@ import BadDataException from "../../Types/Exception/BadDataException";
|
|
|
39
39
|
import Name from "../../Types/Name";
|
|
40
40
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
41
41
|
import Timezone from "../../Types/Timezone";
|
|
42
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
42
43
|
|
|
43
44
|
export class Service extends DatabaseService<Model> {
|
|
45
|
+
/*
|
|
46
|
+
* Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
|
|
47
|
+
* staleness on "last seen" is acceptable; an UPDATE per request is not.
|
|
48
|
+
*/
|
|
49
|
+
private lastActiveCache: InMemoryTTLCache<true> = new InMemoryTTLCache(
|
|
50
|
+
10_000,
|
|
51
|
+
);
|
|
52
|
+
|
|
44
53
|
public constructor() {
|
|
45
54
|
super(Model);
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Debounced fire-and-forget update of `User.lastActive`. The auth
|
|
59
|
+
* middleware calls this on every authenticated request; without the cache
|
|
60
|
+
* we'd issue one Postgres UPDATE per request per user.
|
|
61
|
+
*/
|
|
62
|
+
@CaptureSpan()
|
|
63
|
+
public async updateLastActive(userId: ObjectID): Promise<void> {
|
|
64
|
+
const key: string = userId.toString();
|
|
65
|
+
if (this.lastActiveCache.has(key)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
/*
|
|
69
|
+
* Set BEFORE the await so a burst of concurrent requests collapses to one
|
|
70
|
+
* UPDATE per node per 60s window.
|
|
71
|
+
*/
|
|
72
|
+
this.lastActiveCache.set(key, true, 60_000);
|
|
73
|
+
|
|
74
|
+
void this.updateOneById({
|
|
75
|
+
id: userId,
|
|
76
|
+
data: { lastActive: OneUptimeDate.getCurrentDate() },
|
|
77
|
+
props: { isRoot: true },
|
|
78
|
+
}).catch((err: Error) => {
|
|
79
|
+
this.lastActiveCache.delete(key);
|
|
80
|
+
logger.error(
|
|
81
|
+
`Failed to update User.lastActive for ${key}: ${err.message}`,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
48
86
|
@CaptureSpan()
|
|
49
87
|
public async getUserMarkdownString(data: {
|
|
50
88
|
userId: ObjectID;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import DataToProcess from "../DataToProcess";
|
|
2
|
+
import CompareCriteria from "./CompareCriteria";
|
|
3
|
+
import {
|
|
4
|
+
CheckOn,
|
|
5
|
+
CriteriaFilter,
|
|
6
|
+
FilterType,
|
|
7
|
+
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
8
|
+
import DnssecMonitorResponse from "../../../../Types/Monitor/DnssecMonitor/DnssecMonitorResponse";
|
|
9
|
+
import ProbeMonitorResponse from "../../../../Types/Probe/ProbeMonitorResponse";
|
|
10
|
+
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
11
|
+
|
|
12
|
+
export default class DnssecMonitorCriteria {
|
|
13
|
+
@CaptureSpan()
|
|
14
|
+
public static async isMonitorInstanceCriteriaFilterMet(input: {
|
|
15
|
+
dataToProcess: DataToProcess;
|
|
16
|
+
criteriaFilter: CriteriaFilter;
|
|
17
|
+
}): Promise<string | null> {
|
|
18
|
+
const dataToProcess: ProbeMonitorResponse =
|
|
19
|
+
input.dataToProcess as ProbeMonitorResponse;
|
|
20
|
+
|
|
21
|
+
const dnssecResponse: DnssecMonitorResponse | undefined =
|
|
22
|
+
dataToProcess.dnssecResponse;
|
|
23
|
+
|
|
24
|
+
if (!dnssecResponse) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isTrue: boolean = input.criteriaFilter.filterType === FilterType.True;
|
|
29
|
+
const isFalse: boolean =
|
|
30
|
+
input.criteriaFilter.filterType === FilterType.False;
|
|
31
|
+
|
|
32
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecChainValid) {
|
|
33
|
+
if (dnssecResponse.isChainValid && isTrue) {
|
|
34
|
+
return `DNSSEC chain is valid for ${dnssecResponse.domainName}.`;
|
|
35
|
+
}
|
|
36
|
+
if (!dnssecResponse.isChainValid && isFalse) {
|
|
37
|
+
return `DNSSEC chain validation failed for ${dnssecResponse.domainName}.`;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecDnskeyExists) {
|
|
43
|
+
const exists: boolean = dnssecResponse.dnskeys.length > 0;
|
|
44
|
+
if (exists && isTrue) {
|
|
45
|
+
return `DNSKEY records present for ${dnssecResponse.domainName}.`;
|
|
46
|
+
}
|
|
47
|
+
if (!exists && isFalse) {
|
|
48
|
+
return `No DNSKEY records found for ${dnssecResponse.domainName}.`;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecDsExists) {
|
|
54
|
+
const exists: boolean = dnssecResponse.isParentDsPresent;
|
|
55
|
+
if (exists && isTrue) {
|
|
56
|
+
return `DS records present at parent zone for ${dnssecResponse.domainName}.`;
|
|
57
|
+
}
|
|
58
|
+
if (!exists && isFalse) {
|
|
59
|
+
return `No DS records found at the parent zone for ${dnssecResponse.domainName}.`;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecResolverConsensus) {
|
|
65
|
+
const consensus: boolean = dnssecResponse.resolverConsensusAd;
|
|
66
|
+
if (consensus && isTrue) {
|
|
67
|
+
return `All resolvers report DNSSEC-valid (AD flag) for ${dnssecResponse.domainName}.`;
|
|
68
|
+
}
|
|
69
|
+
if (!consensus && isFalse) {
|
|
70
|
+
return `Resolvers do not agree on DNSSEC validity for ${dnssecResponse.domainName}.`;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecNameserverConsistent) {
|
|
76
|
+
const consistent: boolean = dnssecResponse.isNameserverConsistent;
|
|
77
|
+
if (consistent && isTrue) {
|
|
78
|
+
return `Authoritative nameservers are consistent for ${dnssecResponse.domainName}.`;
|
|
79
|
+
}
|
|
80
|
+
if (!consistent && isFalse) {
|
|
81
|
+
return `Authoritative nameservers are inconsistent for ${dnssecResponse.domainName}.`;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (input.criteriaFilter.checkOn === CheckOn.DnssecSignatureExpiresInDays) {
|
|
87
|
+
const threshold: number | null = CompareCriteria.convertToNumber(
|
|
88
|
+
input.criteriaFilter.value,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (threshold === null || threshold === undefined) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (dnssecResponse.daysUntilSignatureExpiry === undefined) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return CompareCriteria.compareCriteriaNumbers({
|
|
100
|
+
value: dnssecResponse.daysUntilSignatureExpiry,
|
|
101
|
+
threshold: threshold,
|
|
102
|
+
criteriaFilter: input.criteriaFilter,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -18,6 +18,7 @@ import ProfileMonitorCriteria from "./Criteria/ProfileMonitorCriteria";
|
|
|
18
18
|
import SnmpMonitorCriteria from "./Criteria/SnmpMonitorCriteria";
|
|
19
19
|
import DnsMonitorCriteria from "./Criteria/DnsMonitorCriteria";
|
|
20
20
|
import DomainMonitorCriteria from "./Criteria/DomainMonitorCriteria";
|
|
21
|
+
import DnssecMonitorCriteria from "./Criteria/DnssecMonitorCriteria";
|
|
21
22
|
import ExternalStatusPageMonitorCriteria from "./Criteria/ExternalStatusPageMonitorCriteria";
|
|
22
23
|
import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
|
|
23
24
|
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
|
|
@@ -761,6 +762,18 @@ ${contextBlock}
|
|
|
761
762
|
}
|
|
762
763
|
}
|
|
763
764
|
|
|
765
|
+
if (input.monitor.monitorType === MonitorType.DNSSEC) {
|
|
766
|
+
const dnssecMonitorResult: string | null =
|
|
767
|
+
await DnssecMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
|
|
768
|
+
dataToProcess: input.dataToProcess,
|
|
769
|
+
criteriaFilter: input.criteriaFilter,
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
if (dnssecMonitorResult) {
|
|
773
|
+
return dnssecMonitorResult;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
764
777
|
if (input.monitor.monitorType === MonitorType.ExternalStatusPage) {
|
|
765
778
|
const externalStatusPageResult: string | null =
|
|
766
779
|
await ExternalStatusPageMonitorCriteria.isMonitorInstanceCriteriaFilterMet(
|
|
@@ -19,6 +19,7 @@ import DnsMonitorResponse, {
|
|
|
19
19
|
DnsRecordResponse,
|
|
20
20
|
} from "../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
|
|
21
21
|
import DomainMonitorResponse from "../../../Types/Monitor/DomainMonitor/DomainMonitorResponse";
|
|
22
|
+
import DnssecMonitorResponse from "../../../Types/Monitor/DnssecMonitor/DnssecMonitorResponse";
|
|
22
23
|
import ExternalStatusPageMonitorResponse, {
|
|
23
24
|
ExternalStatusPageComponentStatus,
|
|
24
25
|
} from "../../../Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse";
|
|
@@ -332,6 +333,30 @@ export default class MonitorTemplateUtil {
|
|
|
332
333
|
} as JSONObject;
|
|
333
334
|
}
|
|
334
335
|
|
|
336
|
+
if (data.monitorType === MonitorType.DNSSEC) {
|
|
337
|
+
const dnssecResponse: DnssecMonitorResponse | undefined = (
|
|
338
|
+
data.dataToProcess as ProbeMonitorResponse
|
|
339
|
+
).dnssecResponse;
|
|
340
|
+
|
|
341
|
+
storageMap = {
|
|
342
|
+
isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
|
|
343
|
+
responseTimeInMs: dnssecResponse?.responseTimeInMs,
|
|
344
|
+
failureCause: dnssecResponse?.failureCause,
|
|
345
|
+
domainName: dnssecResponse?.domainName,
|
|
346
|
+
isZoneSigned: dnssecResponse?.isZoneSigned,
|
|
347
|
+
isParentDsPresent: dnssecResponse?.isParentDsPresent,
|
|
348
|
+
isChainValid: dnssecResponse?.isChainValid,
|
|
349
|
+
resolverConsensusAd: dnssecResponse?.resolverConsensusAd,
|
|
350
|
+
isNameserverConsistent: dnssecResponse?.isNameserverConsistent,
|
|
351
|
+
earliestSignatureExpiration:
|
|
352
|
+
dnssecResponse?.earliestSignatureExpiration,
|
|
353
|
+
daysUntilSignatureExpiry: dnssecResponse?.daysUntilSignatureExpiry,
|
|
354
|
+
dnskeyCount: dnssecResponse?.dnskeys?.length,
|
|
355
|
+
dsRecordCount: dnssecResponse?.parentDsRecords?.length,
|
|
356
|
+
rrsigCount: dnssecResponse?.rrsigs?.length,
|
|
357
|
+
} as JSONObject;
|
|
358
|
+
}
|
|
359
|
+
|
|
335
360
|
if (
|
|
336
361
|
data.monitorType === MonitorType.Metrics ||
|
|
337
362
|
data.monitorType === MonitorType.Kubernetes ||
|
|
@@ -84,6 +84,14 @@ export enum CheckOn {
|
|
|
84
84
|
DomainStatusCode = "Domain Status Code",
|
|
85
85
|
DomainIsExpired = "Domain Is Expired",
|
|
86
86
|
|
|
87
|
+
// DNSSEC monitors.
|
|
88
|
+
DnssecChainValid = "DNSSEC Chain Is Valid",
|
|
89
|
+
DnssecDnskeyExists = "DNSSEC DNSKEY Record Exists",
|
|
90
|
+
DnssecDsExists = "DNSSEC DS Record Exists At Parent",
|
|
91
|
+
DnssecSignatureExpiresInDays = "DNSSEC Signature Expires In Days",
|
|
92
|
+
DnssecResolverConsensus = "DNSSEC Resolver Consensus (AD Flag)",
|
|
93
|
+
DnssecNameserverConsistent = "DNSSEC Nameservers Are Consistent",
|
|
94
|
+
|
|
87
95
|
// External Status Page monitors.
|
|
88
96
|
ExternalStatusPageIsOnline = "External Status Page Is Online",
|
|
89
97
|
ExternalStatusPageOverallStatus = "External Status Page Overall Status",
|
|
@@ -279,6 +287,11 @@ export class CriteriaFilterUtil {
|
|
|
279
287
|
checkOn === CheckOn.SnmpIsOnline ||
|
|
280
288
|
checkOn === CheckOn.DnsIsOnline ||
|
|
281
289
|
checkOn === CheckOn.DomainIsExpired ||
|
|
290
|
+
checkOn === CheckOn.DnssecChainValid ||
|
|
291
|
+
checkOn === CheckOn.DnssecDnskeyExists ||
|
|
292
|
+
checkOn === CheckOn.DnssecDsExists ||
|
|
293
|
+
checkOn === CheckOn.DnssecResolverConsensus ||
|
|
294
|
+
checkOn === CheckOn.DnssecNameserverConsistent ||
|
|
282
295
|
checkOn === CheckOn.ExternalStatusPageIsOnline
|
|
283
296
|
) {
|
|
284
297
|
return false;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface DnssecKeyRecord {
|
|
2
|
+
flags: number;
|
|
3
|
+
algorithm: number;
|
|
4
|
+
keyTag?: number | undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface DnssecDsRecord {
|
|
8
|
+
keyTag: number;
|
|
9
|
+
algorithm: number;
|
|
10
|
+
digestType: number;
|
|
11
|
+
digest: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DnssecRrsigRecord {
|
|
15
|
+
typeCovered: string;
|
|
16
|
+
algorithm: number;
|
|
17
|
+
signerName: string;
|
|
18
|
+
keyTag: number;
|
|
19
|
+
inception?: string | undefined;
|
|
20
|
+
expiration?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DnssecResolverCheck {
|
|
24
|
+
resolver: string;
|
|
25
|
+
adFlag: boolean;
|
|
26
|
+
servfailWhenValidating: boolean;
|
|
27
|
+
error?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DnssecNameserverCheck {
|
|
31
|
+
nameServer: string;
|
|
32
|
+
soaSerial?: string | undefined;
|
|
33
|
+
rrsigExpiration?: string | undefined;
|
|
34
|
+
error?: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default interface DnssecMonitorResponse {
|
|
38
|
+
isOnline: boolean;
|
|
39
|
+
responseTimeInMs: number;
|
|
40
|
+
failureCause: string;
|
|
41
|
+
domainName: string;
|
|
42
|
+
isTimeout?: boolean | undefined;
|
|
43
|
+
|
|
44
|
+
// Zone signed?
|
|
45
|
+
isZoneSigned: boolean;
|
|
46
|
+
|
|
47
|
+
// DNSKEY presence
|
|
48
|
+
dnskeys: Array<DnssecKeyRecord>;
|
|
49
|
+
|
|
50
|
+
// DS at parent
|
|
51
|
+
parentDsRecords: Array<DnssecDsRecord>;
|
|
52
|
+
isParentDsPresent: boolean;
|
|
53
|
+
|
|
54
|
+
// RRSIG over the A record (zone apex by default)
|
|
55
|
+
rrsigs: Array<DnssecRrsigRecord>;
|
|
56
|
+
earliestSignatureExpiration?: string | undefined;
|
|
57
|
+
daysUntilSignatureExpiry?: number | undefined;
|
|
58
|
+
|
|
59
|
+
// Resolver consensus (AD flag + CD-bit SERVFAIL test)
|
|
60
|
+
resolverChecks: Array<DnssecResolverCheck>;
|
|
61
|
+
resolverConsensusAd: boolean;
|
|
62
|
+
|
|
63
|
+
// Primary/secondary nameserver consistency
|
|
64
|
+
nameserverChecks: Array<DnssecNameserverCheck>;
|
|
65
|
+
isNameserverConsistent: boolean;
|
|
66
|
+
|
|
67
|
+
// Overall chain validity (DNSKEY exists, DS exists, RRSIG valid, AD across resolvers)
|
|
68
|
+
isChainValid: boolean;
|
|
69
|
+
}
|