@oneuptime/common 10.4.13 → 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/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/package.json +1 -1
package/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class AddPerformanceIndexes1779392970424 implements MigrationInterface {
|
|
4
|
+
public name: string = "AddPerformanceIndexes1779392970424";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
// Active-X badge counters on the dashboard.
|
|
8
|
+
await queryRunner.query(
|
|
9
|
+
`CREATE INDEX "IDX_d846ce00a02d1073efc07178fa" ON "Incident" ("projectId", "currentIncidentStateId") `,
|
|
10
|
+
);
|
|
11
|
+
await queryRunner.query(
|
|
12
|
+
`CREATE INDEX "IDX_0c7286dfa90fd7d8201ec6f217" ON "Alert" ("projectId", "currentAlertStateId") `,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
// Alert filtering by monitor (monitor detail page).
|
|
16
|
+
await queryRunner.query(
|
|
17
|
+
`CREATE INDEX "IDX_b57071fc2f1e27430e651382ee" ON "Alert" ("monitorId") `,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Notification log tables: filter by project + status / time range.
|
|
21
|
+
await queryRunner.query(
|
|
22
|
+
`CREATE INDEX "IDX_8a0f032f20cd845c9bb908f38c" ON "CallLog" ("projectId", "status") `,
|
|
23
|
+
);
|
|
24
|
+
await queryRunner.query(
|
|
25
|
+
`CREATE INDEX "IDX_b59ee5f702882c10066cdd1128" ON "CallLog" ("projectId", "createdAt") `,
|
|
26
|
+
);
|
|
27
|
+
await queryRunner.query(
|
|
28
|
+
`CREATE INDEX "IDX_8f4eea9e7f20eaf121625f5787" ON "EmailLog" ("projectId", "status") `,
|
|
29
|
+
);
|
|
30
|
+
await queryRunner.query(
|
|
31
|
+
`CREATE INDEX "IDX_33d38c24249a256f89001acf83" ON "EmailLog" ("projectId", "createdAt") `,
|
|
32
|
+
);
|
|
33
|
+
await queryRunner.query(
|
|
34
|
+
`CREATE INDEX "IDX_c37d2fcfcb591afb284dad27d9" ON "SmsLog" ("projectId", "status") `,
|
|
35
|
+
);
|
|
36
|
+
await queryRunner.query(
|
|
37
|
+
`CREATE INDEX "IDX_c6bbede6b3bb66781bc7c82463" ON "SmsLog" ("projectId", "createdAt") `,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Feed timelines on alert/incident/monitor detail pages.
|
|
41
|
+
await queryRunner.query(
|
|
42
|
+
`CREATE INDEX "IDX_6f97cb7c189e6339cf364a3608" ON "AlertFeed" ("alertId", "postedAt") `,
|
|
43
|
+
);
|
|
44
|
+
await queryRunner.query(
|
|
45
|
+
`CREATE INDEX "IDX_e26bf84ec503bbfb06bcde139e" ON "IncidentFeed" ("incidentId", "postedAt") `,
|
|
46
|
+
);
|
|
47
|
+
await queryRunner.query(
|
|
48
|
+
`CREATE INDEX "IDX_5771fc57305fb0f508153b53ce" ON "MonitorFeed" ("monitorId", "postedAt") `,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Monitor probe scheduler.
|
|
52
|
+
await queryRunner.query(
|
|
53
|
+
`CREATE INDEX "IDX_4bf4109d325af5e5b5a5665bc7" ON "MonitorProbe" ("probeId", "isEnabled", "nextPingAt") `,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// On-call duty time logs (active-on-call lookups).
|
|
57
|
+
await queryRunner.query(
|
|
58
|
+
`CREATE INDEX "IDX_43da7ffeee531e9452d36a89ba" ON "OnCallDutyPolicyTimeLog" ("userId") `,
|
|
59
|
+
);
|
|
60
|
+
await queryRunner.query(
|
|
61
|
+
`CREATE INDEX "IDX_977d907fb45cbc1a2067f490af" ON "OnCallDutyPolicyTimeLog" ("startsAt") `,
|
|
62
|
+
);
|
|
63
|
+
await queryRunner.query(
|
|
64
|
+
`CREATE INDEX "IDX_002727a958120be971790fd016" ON "OnCallDutyPolicyTimeLog" ("endsAt") `,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Telemetry exceptions dashboard.
|
|
68
|
+
await queryRunner.query(
|
|
69
|
+
`CREATE INDEX "IDX_fa102ae5073b428e514cc2ceea" ON "TelemetryException" ("occuranceCount") `,
|
|
70
|
+
);
|
|
71
|
+
await queryRunner.query(
|
|
72
|
+
`CREATE INDEX "IDX_3836772be478de8a9df86a938d" ON "TelemetryException" ("projectId", "isResolved", "isArchived") `,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Status page subscriber dedupe.
|
|
76
|
+
await queryRunner.query(
|
|
77
|
+
`CREATE INDEX "IDX_c28628545faa67976e1d462e69" ON "StatusPageSubscriber" ("statusPageId", "subscriberPhone") `,
|
|
78
|
+
);
|
|
79
|
+
await queryRunner.query(
|
|
80
|
+
`CREATE INDEX "IDX_3a2d83fc5107c639d10a7c5cc0" ON "StatusPageSubscriber" ("statusPageId", "subscriberEmail") `,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Worker sweeps.
|
|
84
|
+
await queryRunner.query(
|
|
85
|
+
`CREATE INDEX "IDX_094b044a3d6a79695ba754cdfb" ON "UserOnCallLog" ("status") `,
|
|
86
|
+
);
|
|
87
|
+
await queryRunner.query(
|
|
88
|
+
`CREATE INDEX "IDX_7a5dc4760803e57f2d0b363e6e" ON "WorkflowLog" ("workflowStatus", "createdAt") `,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
93
|
+
await queryRunner.query(
|
|
94
|
+
`DROP INDEX "public"."IDX_7a5dc4760803e57f2d0b363e6e"`,
|
|
95
|
+
);
|
|
96
|
+
await queryRunner.query(
|
|
97
|
+
`DROP INDEX "public"."IDX_094b044a3d6a79695ba754cdfb"`,
|
|
98
|
+
);
|
|
99
|
+
await queryRunner.query(
|
|
100
|
+
`DROP INDEX "public"."IDX_3a2d83fc5107c639d10a7c5cc0"`,
|
|
101
|
+
);
|
|
102
|
+
await queryRunner.query(
|
|
103
|
+
`DROP INDEX "public"."IDX_c28628545faa67976e1d462e69"`,
|
|
104
|
+
);
|
|
105
|
+
await queryRunner.query(
|
|
106
|
+
`DROP INDEX "public"."IDX_3836772be478de8a9df86a938d"`,
|
|
107
|
+
);
|
|
108
|
+
await queryRunner.query(
|
|
109
|
+
`DROP INDEX "public"."IDX_fa102ae5073b428e514cc2ceea"`,
|
|
110
|
+
);
|
|
111
|
+
await queryRunner.query(
|
|
112
|
+
`DROP INDEX "public"."IDX_002727a958120be971790fd016"`,
|
|
113
|
+
);
|
|
114
|
+
await queryRunner.query(
|
|
115
|
+
`DROP INDEX "public"."IDX_977d907fb45cbc1a2067f490af"`,
|
|
116
|
+
);
|
|
117
|
+
await queryRunner.query(
|
|
118
|
+
`DROP INDEX "public"."IDX_43da7ffeee531e9452d36a89ba"`,
|
|
119
|
+
);
|
|
120
|
+
await queryRunner.query(
|
|
121
|
+
`DROP INDEX "public"."IDX_4bf4109d325af5e5b5a5665bc7"`,
|
|
122
|
+
);
|
|
123
|
+
await queryRunner.query(
|
|
124
|
+
`DROP INDEX "public"."IDX_5771fc57305fb0f508153b53ce"`,
|
|
125
|
+
);
|
|
126
|
+
await queryRunner.query(
|
|
127
|
+
`DROP INDEX "public"."IDX_e26bf84ec503bbfb06bcde139e"`,
|
|
128
|
+
);
|
|
129
|
+
await queryRunner.query(
|
|
130
|
+
`DROP INDEX "public"."IDX_6f97cb7c189e6339cf364a3608"`,
|
|
131
|
+
);
|
|
132
|
+
await queryRunner.query(
|
|
133
|
+
`DROP INDEX "public"."IDX_c6bbede6b3bb66781bc7c82463"`,
|
|
134
|
+
);
|
|
135
|
+
await queryRunner.query(
|
|
136
|
+
`DROP INDEX "public"."IDX_c37d2fcfcb591afb284dad27d9"`,
|
|
137
|
+
);
|
|
138
|
+
await queryRunner.query(
|
|
139
|
+
`DROP INDEX "public"."IDX_33d38c24249a256f89001acf83"`,
|
|
140
|
+
);
|
|
141
|
+
await queryRunner.query(
|
|
142
|
+
`DROP INDEX "public"."IDX_8f4eea9e7f20eaf121625f5787"`,
|
|
143
|
+
);
|
|
144
|
+
await queryRunner.query(
|
|
145
|
+
`DROP INDEX "public"."IDX_b59ee5f702882c10066cdd1128"`,
|
|
146
|
+
);
|
|
147
|
+
await queryRunner.query(
|
|
148
|
+
`DROP INDEX "public"."IDX_8a0f032f20cd845c9bb908f38c"`,
|
|
149
|
+
);
|
|
150
|
+
await queryRunner.query(
|
|
151
|
+
`DROP INDEX "public"."IDX_b57071fc2f1e27430e651382ee"`,
|
|
152
|
+
);
|
|
153
|
+
await queryRunner.query(
|
|
154
|
+
`DROP INDEX "public"."IDX_0c7286dfa90fd7d8201ec6f217"`,
|
|
155
|
+
);
|
|
156
|
+
await queryRunner.query(
|
|
157
|
+
`DROP INDEX "public"."IDX_d846ce00a02d1073efc07178fa"`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -342,6 +342,8 @@ import { DropServiceDependencyTable1779277271302 } from "./1779277271302-DropSer
|
|
|
342
342
|
import { AddTelemetryRetentionToHostDockerKubernetes1779282769946 } from "./1779282769946-AddTelemetryRetentionToHostDockerKubernetes";
|
|
343
343
|
import { AttachKubernetesAndDockerToIncidentAndAlert1779302536475 } from "./1779302536475-AttachKubernetesAndDockerToIncidentAndAlert";
|
|
344
344
|
import { AttachServiceToIncidentAndAlert1779303924241 } from "./1779303924241-AttachServiceToIncidentAndAlert";
|
|
345
|
+
import { AddAgentVersionToKubernetesDockerHost1779392865146 } from "./1779392865146-AddAgentVersionToKubernetesDockerHost";
|
|
346
|
+
import { AddPerformanceIndexes1779392970424 } from "./1779392970424-AddPerformanceIndexes";
|
|
345
347
|
export default [
|
|
346
348
|
InitialMigration,
|
|
347
349
|
MigrationName1717678334852,
|
|
@@ -687,4 +689,6 @@ export default [
|
|
|
687
689
|
AddTelemetryRetentionToHostDockerKubernetes1779282769946,
|
|
688
690
|
AttachKubernetesAndDockerToIncidentAndAlert1779302536475,
|
|
689
691
|
AttachServiceToIncidentAndAlert1779303924241,
|
|
692
|
+
AddAgentVersionToKubernetesDockerHost1779392865146,
|
|
693
|
+
AddPerformanceIndexes1779392970424,
|
|
690
694
|
];
|
|
@@ -82,12 +82,9 @@ export default class Database {
|
|
|
82
82
|
|
|
83
83
|
@CaptureSpan()
|
|
84
84
|
public static async checkConnnectionStatus(): Promise<boolean> {
|
|
85
|
-
//
|
|
86
|
-
|
|
85
|
+
// SELECT 1 round-trips a connection without scanning any user table.
|
|
87
86
|
try {
|
|
88
|
-
const result: any = await this.dataSource?.query(
|
|
89
|
-
`SELECT COUNT(domain) FROM "AcmeChallenge"`,
|
|
90
|
-
); // this is a dummy query to check if the connection is still alive
|
|
87
|
+
const result: any = await this.dataSource?.query(`SELECT 1`);
|
|
91
88
|
|
|
92
89
|
if (!result) {
|
|
93
90
|
return false;
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import ApiKeyService from "../Services/ApiKeyService";
|
|
2
2
|
import GlobalConfigService from "../Services/GlobalConfigService";
|
|
3
3
|
import UserService from "../Services/UserService";
|
|
4
|
-
import QueryHelper from "../Types/Database/QueryHelper";
|
|
5
4
|
import {
|
|
6
5
|
ExpressRequest,
|
|
7
6
|
ExpressResponse,
|
|
8
7
|
NextFunction,
|
|
9
8
|
OneUptimeRequest,
|
|
10
9
|
} from "../Utils/Express";
|
|
11
|
-
import OneUptimeDate from "../../Types/Date";
|
|
12
10
|
import Dictionary from "../../Types/Dictionary";
|
|
13
11
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
14
12
|
import ObjectID from "../../Types/ObjectID";
|
|
15
13
|
import { UserTenantAccessPermission } from "../../Types/Permission";
|
|
16
14
|
import UserType from "../../Types/UserType";
|
|
17
|
-
import ApiKey from "../../Models/DatabaseModels/ApiKey";
|
|
18
15
|
import GlobalConfig from "../../Models/DatabaseModels/GlobalConfig";
|
|
19
16
|
import User from "../../Models/DatabaseModels/User";
|
|
20
17
|
import APIKeyAccessPermission from "../Utils/APIKey/AccessPermission";
|
|
@@ -89,62 +86,43 @@ export default class ProjectMiddleware {
|
|
|
89
86
|
);
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
|
|
89
|
+
/*
|
|
90
|
+
* Cached lookup — see ApiKeyService.findApiKey. Hot path for any
|
|
91
|
+
* automated caller hitting the API by key.
|
|
92
|
+
*/
|
|
93
|
+
const apiKeyRow: { id: ObjectID; projectId: ObjectID } | null =
|
|
94
|
+
await ApiKeyService.findApiKey(apiKey);
|
|
93
95
|
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
query: {
|
|
97
|
-
apiKey: apiKey,
|
|
98
|
-
expiresAt: QueryHelper.greaterThan(OneUptimeDate.getCurrentDate()),
|
|
99
|
-
},
|
|
100
|
-
select: {
|
|
101
|
-
_id: true,
|
|
102
|
-
projectId: true,
|
|
103
|
-
},
|
|
104
|
-
props: { isRoot: true },
|
|
105
|
-
});
|
|
96
|
+
if (apiKeyRow) {
|
|
97
|
+
tenantId = apiKeyRow.projectId;
|
|
106
98
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
(req as OneUptimeRequest).tenantId = tenantId;
|
|
100
|
+
(req as OneUptimeRequest).userType = UserType.API;
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
* TODO: Add API key permissions.
|
|
104
|
+
*/
|
|
105
|
+
(req as OneUptimeRequest).userGlobalAccessPermission =
|
|
106
|
+
await APIKeyAccessPermission.getDefaultApiGlobalPermission(tenantId);
|
|
107
|
+
|
|
108
|
+
const userTenantAccessPermission: UserTenantAccessPermission | null =
|
|
109
|
+
await APIKeyAccessPermission.getApiTenantAccessPermission(
|
|
110
|
+
tenantId,
|
|
111
|
+
apiKeyRow.id,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (userTenantAccessPermission) {
|
|
115
|
+
(req as OneUptimeRequest).userTenantAccessPermission = {};
|
|
116
|
+
(
|
|
117
|
+
(req as OneUptimeRequest)
|
|
118
|
+
.userTenantAccessPermission as Dictionary<UserTenantAccessPermission>
|
|
119
|
+
)[tenantId.toString()] = userTenantAccessPermission;
|
|
113
120
|
|
|
114
|
-
(
|
|
115
|
-
|
|
116
|
-
if (apiKeyModel) {
|
|
117
|
-
(req as OneUptimeRequest).userType = UserType.API;
|
|
118
|
-
/*
|
|
119
|
-
* TODO: Add API key permissions.
|
|
120
|
-
* (req as OneUptimeRequest).permissions =
|
|
121
|
-
* apiKeyModel.permissions || [];
|
|
122
|
-
*/
|
|
123
|
-
(req as OneUptimeRequest).userGlobalAccessPermission =
|
|
124
|
-
await APIKeyAccessPermission.getDefaultApiGlobalPermission(
|
|
125
|
-
tenantId,
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const userTenantAccessPermission: UserTenantAccessPermission | null =
|
|
129
|
-
await APIKeyAccessPermission.getApiTenantAccessPermission(
|
|
130
|
-
tenantId,
|
|
131
|
-
apiKeyModel.id!,
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
if (userTenantAccessPermission) {
|
|
135
|
-
(req as OneUptimeRequest).userTenantAccessPermission = {};
|
|
136
|
-
(
|
|
137
|
-
(req as OneUptimeRequest)
|
|
138
|
-
.userTenantAccessPermission as Dictionary<UserTenantAccessPermission>
|
|
139
|
-
)[tenantId.toString()] = userTenantAccessPermission;
|
|
140
|
-
|
|
141
|
-
return next();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
121
|
+
return next();
|
|
144
122
|
}
|
|
145
123
|
}
|
|
146
124
|
|
|
147
|
-
if (!
|
|
125
|
+
if (!apiKeyRow) {
|
|
148
126
|
// check master key.
|
|
149
127
|
const masterKeyGlobalConfig: GlobalConfig | null =
|
|
150
128
|
await GlobalConfigService.findOneBy({
|
|
@@ -17,7 +17,6 @@ import Response from "../Utils/Response";
|
|
|
17
17
|
import ProjectMiddleware from "./ProjectAuthorization";
|
|
18
18
|
import SpanUtil from "../Utils/Telemetry/SpanUtil";
|
|
19
19
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
20
|
-
import OneUptimeDate from "../../Types/Date";
|
|
21
20
|
import Dictionary from "../../Types/Dictionary";
|
|
22
21
|
import Exception from "../../Types/Exception/Exception";
|
|
23
22
|
import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException";
|
|
@@ -201,8 +200,12 @@ export default class UserMiddleware {
|
|
|
201
200
|
if (tenantId) {
|
|
202
201
|
oneuptimeRequest.tenantId = tenantId;
|
|
203
202
|
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
/*
|
|
204
|
+
* Fire-and-forget: lastActive write is debounced inside the service
|
|
205
|
+
* (60s in-process cache) and we don't need the result before
|
|
206
|
+
* continuing.
|
|
207
|
+
*/
|
|
208
|
+
void ProjectService.updateLastActive(tenantId);
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
if (ProjectMiddleware.hasApiKey(req)) {
|
|
@@ -253,31 +256,50 @@ export default class UserMiddleware {
|
|
|
253
256
|
: {}),
|
|
254
257
|
});
|
|
255
258
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
data: { lastActive: OneUptimeDate.getCurrentDate() },
|
|
262
|
-
});
|
|
259
|
+
/*
|
|
260
|
+
* Fire-and-forget: lastActive write is debounced inside the service
|
|
261
|
+
* (60s in-process cache) and we don't need the result before continuing.
|
|
262
|
+
*/
|
|
263
|
+
void UserService.updateLastActive(new ObjectID(userId));
|
|
263
264
|
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
/*
|
|
266
|
+
* Resolve global permission, tenant permission, and team membership in
|
|
267
|
+
* parallel. These were previously sequential awaits — each added an
|
|
268
|
+
* extra round-trip latency to every authenticated request. The original
|
|
269
|
+
* code wrapped only the tenant-side calls in try/catch (to convert
|
|
270
|
+
* SsoAuthorizationException etc. into an HTTP error response); we
|
|
271
|
+
* preserve that semantic by routing the rejection through the same
|
|
272
|
+
* catch only when tenantId is present.
|
|
273
|
+
*/
|
|
274
|
+
const userGlobalAccessPermissionPromise: Promise<UserGlobalAccessPermission | null> =
|
|
275
|
+
AccessTokenService.getUserGlobalAccessPermission(
|
|
266
276
|
oneuptimeRequest.userAuthorization.userId,
|
|
267
277
|
);
|
|
268
278
|
|
|
269
|
-
|
|
270
|
-
oneuptimeRequest.userGlobalAccessPermission = userGlobalAccessPermission;
|
|
271
|
-
}
|
|
279
|
+
let userGlobalAccessPermission: UserGlobalAccessPermission | null = null;
|
|
272
280
|
|
|
273
281
|
if (tenantId) {
|
|
274
282
|
try {
|
|
275
|
-
const userTenantAccessPermission:
|
|
276
|
-
|
|
283
|
+
const [globalPermission, userTenantAccessPermission, userTeamIds]: [
|
|
284
|
+
UserGlobalAccessPermission | null,
|
|
285
|
+
UserTenantAccessPermission | null,
|
|
286
|
+
Array<ObjectID>,
|
|
287
|
+
] = await Promise.all([
|
|
288
|
+
userGlobalAccessPermissionPromise,
|
|
289
|
+
UserMiddleware.getUserTenantAccessPermissionWithTenantId({
|
|
277
290
|
req,
|
|
278
291
|
tenantId,
|
|
279
292
|
userId: new ObjectID(userId),
|
|
280
|
-
})
|
|
293
|
+
}),
|
|
294
|
+
TeamMemberService.getTeamIdsForUser(new ObjectID(userId), tenantId),
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
userGlobalAccessPermission = globalPermission;
|
|
298
|
+
|
|
299
|
+
if (userGlobalAccessPermission) {
|
|
300
|
+
oneuptimeRequest.userGlobalAccessPermission =
|
|
301
|
+
userGlobalAccessPermission;
|
|
302
|
+
}
|
|
281
303
|
|
|
282
304
|
if (userTenantAccessPermission) {
|
|
283
305
|
oneuptimeRequest.userTenantAccessPermission = {};
|
|
@@ -291,14 +313,17 @@ export default class UserMiddleware {
|
|
|
291
313
|
* an extra DB roundtrip on every permission check. Absent for non-user
|
|
292
314
|
* callers (API keys, Probes); `Owned` then evaluates as `All`.
|
|
293
315
|
*/
|
|
294
|
-
oneuptimeRequest.userTeamIds =
|
|
295
|
-
await TeamMemberService.getTeamIdsForUser(
|
|
296
|
-
new ObjectID(userId),
|
|
297
|
-
tenantId,
|
|
298
|
-
);
|
|
316
|
+
oneuptimeRequest.userTeamIds = userTeamIds;
|
|
299
317
|
} catch (error) {
|
|
300
318
|
return Response.sendErrorResponse(req, res, error as Exception);
|
|
301
319
|
}
|
|
320
|
+
} else {
|
|
321
|
+
userGlobalAccessPermission = await userGlobalAccessPermissionPromise;
|
|
322
|
+
|
|
323
|
+
if (userGlobalAccessPermission) {
|
|
324
|
+
oneuptimeRequest.userGlobalAccessPermission =
|
|
325
|
+
userGlobalAccessPermission;
|
|
326
|
+
}
|
|
302
327
|
}
|
|
303
328
|
|
|
304
329
|
if (req.headers["is-multi-tenant-query"]) {
|
|
@@ -468,32 +493,36 @@ export default class UserMiddleware {
|
|
|
468
493
|
}): Promise<UserTenantAccessPermission | null> {
|
|
469
494
|
const { req, tenantId, userId } = data;
|
|
470
495
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
496
|
+
/*
|
|
497
|
+
* Resolve the SSO requirement and the tenant permission in parallel.
|
|
498
|
+
* `getRequireSsoForLogin` is cached in-process for 60s, so this is
|
|
499
|
+
* usually free; the tenant permission lookup is the expensive call.
|
|
500
|
+
*/
|
|
501
|
+
const [requireSsoForLogin, tenantPermission]: [
|
|
502
|
+
boolean,
|
|
503
|
+
UserTenantAccessPermission | null,
|
|
504
|
+
] = await Promise.all([
|
|
505
|
+
ProjectService.getRequireSsoForLogin(tenantId).catch((err: Error) => {
|
|
506
|
+
/*
|
|
507
|
+
* Preserve the original behavior of throwing a TenantNotFoundException
|
|
508
|
+
* for an unknown project. Any other error re-throws.
|
|
509
|
+
*/
|
|
510
|
+
if (err.message === "Project not found") {
|
|
511
|
+
throw new TenantNotFoundException("Invalid tenantId");
|
|
512
|
+
}
|
|
513
|
+
throw err;
|
|
514
|
+
}),
|
|
515
|
+
AccessTokenService.getUserTenantAccessPermission(userId, tenantId),
|
|
516
|
+
]);
|
|
484
517
|
|
|
485
518
|
if (
|
|
486
|
-
|
|
519
|
+
requireSsoForLogin &&
|
|
487
520
|
!UserMiddleware.doesSsoTokenForProjectExist(req, tenantId, userId)
|
|
488
521
|
) {
|
|
489
522
|
throw new SsoAuthorizationException();
|
|
490
523
|
}
|
|
491
524
|
|
|
492
|
-
|
|
493
|
-
return await AccessTokenService.getUserTenantAccessPermission(
|
|
494
|
-
userId,
|
|
495
|
-
tenantId,
|
|
496
|
-
);
|
|
525
|
+
return tenantPermission;
|
|
497
526
|
}
|
|
498
527
|
|
|
499
528
|
@CaptureSpan()
|
|
@@ -524,34 +553,47 @@ export default class UserMiddleware {
|
|
|
524
553
|
},
|
|
525
554
|
});
|
|
526
555
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
556
|
+
/*
|
|
557
|
+
* Resolve permissions for every project in parallel. With the previous
|
|
558
|
+
* for-await loop this scaled linearly with project count, adding one
|
|
559
|
+
* round-trip per project even on cache hits.
|
|
560
|
+
*/
|
|
561
|
+
const resolved: Array<{
|
|
562
|
+
projectId: ObjectID;
|
|
563
|
+
permission: UserTenantAccessPermission | null;
|
|
564
|
+
}> = await Promise.all(
|
|
565
|
+
projectIds.map(async (projectId: ObjectID) => {
|
|
566
|
+
if (
|
|
567
|
+
projects.find((p: Project) => {
|
|
568
|
+
return p._id === projectId.toString() && p.requireSsoForLogin;
|
|
569
|
+
}) &&
|
|
570
|
+
!UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId)
|
|
571
|
+
) {
|
|
572
|
+
return {
|
|
573
|
+
projectId,
|
|
574
|
+
permission:
|
|
575
|
+
UserPermissionUtil.getDefaultUserTenantAccessPermission(
|
|
576
|
+
projectId,
|
|
577
|
+
),
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
projectId,
|
|
582
|
+
permission: await AccessTokenService.getUserTenantAccessPermission(
|
|
545
583
|
userId,
|
|
546
584
|
projectId,
|
|
547
|
-
)
|
|
548
|
-
|
|
585
|
+
),
|
|
586
|
+
};
|
|
587
|
+
}),
|
|
588
|
+
);
|
|
549
589
|
|
|
550
|
-
|
|
590
|
+
let result: Dictionary<UserTenantAccessPermission> | null = null;
|
|
591
|
+
for (const { projectId, permission } of resolved) {
|
|
592
|
+
if (permission) {
|
|
551
593
|
if (!result) {
|
|
552
594
|
result = {};
|
|
553
595
|
}
|
|
554
|
-
result[projectId.toString()] =
|
|
596
|
+
result[projectId.toString()] = permission;
|
|
555
597
|
}
|
|
556
598
|
}
|
|
557
599
|
|
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
import CreateBy from "../Types/Database/CreateBy";
|
|
2
|
-
import
|
|
2
|
+
import DeleteBy from "../Types/Database/DeleteBy";
|
|
3
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
4
|
+
import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
|
|
3
5
|
import DatabaseService from "./DatabaseService";
|
|
6
|
+
import OneUptimeDate from "../../Types/Date";
|
|
4
7
|
import ObjectID from "../../Types/ObjectID";
|
|
8
|
+
import QueryHelper from "../Types/Database/QueryHelper";
|
|
5
9
|
import Model from "../../Models/DatabaseModels/ApiKey";
|
|
6
10
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
11
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* 60s is the worst-case staleness on any single API node after a key is
|
|
15
|
+
* revoked from the dashboard. We invalidate in-process immediately on
|
|
16
|
+
* delete/update; this TTL is the upper bound for *other* processes.
|
|
17
|
+
*/
|
|
18
|
+
const POSITIVE_TTL_MS: number = 60 * 1000;
|
|
19
|
+
/*
|
|
20
|
+
* Short TTL on misses so an invalid-key flood can't pin entries in the
|
|
21
|
+
* bounded cache for long while still absorbing repeat hits.
|
|
22
|
+
*/
|
|
23
|
+
const NEGATIVE_TTL_MS: number = 10 * 1000;
|
|
24
|
+
|
|
25
|
+
interface CachedApiKey {
|
|
26
|
+
id: string;
|
|
27
|
+
projectId: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
export class Service extends DatabaseService<Model> {
|
|
31
|
+
/*
|
|
32
|
+
* Cache of `apiKey -> { id, projectId }`. The project-auth middleware hits
|
|
33
|
+
* this on every API-key-authenticated request; without it that's a
|
|
34
|
+
* Postgres findOneBy per request for automated callers.
|
|
35
|
+
*/
|
|
36
|
+
private apiKeyCache: InMemoryTTLCache<CachedApiKey | null> =
|
|
37
|
+
new InMemoryTTLCache(10_000);
|
|
38
|
+
|
|
8
39
|
public constructor() {
|
|
9
40
|
super(Model);
|
|
10
41
|
}
|
|
@@ -16,6 +47,74 @@ export class Service extends DatabaseService<Model> {
|
|
|
16
47
|
createBy.data.apiKey = ObjectID.generate();
|
|
17
48
|
return { createBy, carryForward: null };
|
|
18
49
|
}
|
|
50
|
+
|
|
51
|
+
@CaptureSpan()
|
|
52
|
+
protected override async onBeforeUpdate(
|
|
53
|
+
updateBy: UpdateBy<Model>,
|
|
54
|
+
): Promise<OnUpdate<Model>> {
|
|
55
|
+
/*
|
|
56
|
+
* We don't know which keys are being updated without a query; updates
|
|
57
|
+
* are rare so clearing is cheap.
|
|
58
|
+
*/
|
|
59
|
+
this.apiKeyCache.clear();
|
|
60
|
+
return { updateBy, carryForward: null };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@CaptureSpan()
|
|
64
|
+
protected override async onBeforeDelete(
|
|
65
|
+
deleteBy: DeleteBy<Model>,
|
|
66
|
+
): Promise<OnDelete<Model>> {
|
|
67
|
+
this.apiKeyCache.clear();
|
|
68
|
+
return { deleteBy, carryForward: null };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolves an API key string to its row, with a short-lived in-process
|
|
73
|
+
* cache. Returns null for unknown or expired keys (also cached, for a
|
|
74
|
+
* shorter TTL). Use this from the auth middleware hot path instead of
|
|
75
|
+
* calling `findOneBy` directly.
|
|
76
|
+
*/
|
|
77
|
+
@CaptureSpan()
|
|
78
|
+
public async findApiKey(
|
|
79
|
+
apiKey: ObjectID,
|
|
80
|
+
): Promise<{ id: ObjectID; projectId: ObjectID } | null> {
|
|
81
|
+
const cacheKey: string = apiKey.toString();
|
|
82
|
+
const cached: CachedApiKey | null | undefined =
|
|
83
|
+
this.apiKeyCache.get(cacheKey);
|
|
84
|
+
if (cached !== undefined) {
|
|
85
|
+
if (cached === null) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
id: new ObjectID(cached.id),
|
|
90
|
+
projectId: new ObjectID(cached.projectId),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const row: Model | null = await this.findOneBy({
|
|
95
|
+
query: {
|
|
96
|
+
apiKey: apiKey,
|
|
97
|
+
expiresAt: QueryHelper.greaterThan(OneUptimeDate.getCurrentDate()),
|
|
98
|
+
},
|
|
99
|
+
select: {
|
|
100
|
+
_id: true,
|
|
101
|
+
projectId: true,
|
|
102
|
+
},
|
|
103
|
+
props: { isRoot: true },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!row || !row.id || !row.projectId) {
|
|
107
|
+
this.apiKeyCache.set(cacheKey, null, NEGATIVE_TTL_MS);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.apiKeyCache.set(
|
|
112
|
+
cacheKey,
|
|
113
|
+
{ id: row.id.toString(), projectId: row.projectId.toString() },
|
|
114
|
+
POSITIVE_TTL_MS,
|
|
115
|
+
);
|
|
116
|
+
return { id: row.id, projectId: row.projectId };
|
|
117
|
+
}
|
|
19
118
|
}
|
|
20
119
|
|
|
21
120
|
export default new Service();
|