@oneuptime/common 7.0.4263 → 7.0.4298
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/StatusPage.ts +39 -0
- package/Server/API/OpenAPI.ts +29 -0
- package/Server/API/StatusPageAPI.ts +85 -114
- package/Server/EnvironmentConfig.ts +1 -8
- package/Server/Infrastructure/Postgres/SchemaMigrations/1748456937826-MigrationName.ts +15 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/BillingInvoiceService.ts +5 -3
- package/Server/Services/DomainService.ts +16 -0
- package/Server/Services/StatusPageDomainService.ts +114 -41
- package/Server/Services/StatusPageService.ts +89 -24
- package/Server/Types/ConfigLogLevel.ts +9 -0
- package/Server/Types/Domain.ts +25 -0
- package/Server/Utils/Logger.ts +2 -1
- package/Server/Utils/OpenAPI.ts +370 -0
- package/Tests/Server/API/BaseAPI.test.ts +4 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +7 -6
- package/Types/Billing/SubscriptionStatus.ts +1 -0
- package/Types/Exception/ExceptionCode.ts +1 -0
- package/Types/Exception/ForbiddenException.ts +8 -0
- package/Types/IP/IP.ts +88 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +0 -2
- package/UI/Utils/API/API.ts +15 -5
- package/UI/Utils/Cookie.ts +9 -0
- package/UI/Utils/User.ts +1 -0
- package/Utils/Schema/ModelSchema.ts +247 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js +40 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/OpenAPI.js +14 -0
- package/build/dist/Server/API/OpenAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +78 -62
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -8
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1748456937826-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1748456937826-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/BillingInvoiceService.js +3 -3
- package/build/dist/Server/Services/BillingInvoiceService.js.map +1 -1
- package/build/dist/Server/Services/DomainService.js +13 -0
- package/build/dist/Server/Services/DomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +87 -30
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +66 -21
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Types/ConfigLogLevel.js +10 -0
- package/build/dist/Server/Types/ConfigLogLevel.js.map +1 -0
- package/build/dist/Server/Types/Domain.js +22 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +2 -1
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/OpenAPI.js +334 -0
- package/build/dist/Server/Utils/OpenAPI.js.map +1 -0
- package/build/dist/Tests/Server/API/BaseAPI.test.js +4 -0
- package/build/dist/Tests/Server/API/BaseAPI.test.js.map +1 -1
- package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js +7 -6
- package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js.map +1 -1
- package/build/dist/Types/Billing/SubscriptionStatus.js +1 -0
- package/build/dist/Types/Billing/SubscriptionStatus.js.map +1 -1
- package/build/dist/Types/Exception/ExceptionCode.js +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/ForbiddenException.js +8 -0
- package/build/dist/Types/Exception/ForbiddenException.js.map +1 -0
- package/build/dist/Types/IP/IP.js +66 -0
- package/build/dist/Types/IP/IP.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Utils/API/API.js +11 -5
- package/build/dist/UI/Utils/API/API.js.map +1 -1
- package/build/dist/UI/Utils/Cookie.js +8 -0
- package/build/dist/UI/Utils/Cookie.js.map +1 -1
- package/build/dist/UI/Utils/User.js +1 -0
- package/build/dist/UI/Utils/User.js.map +1 -1
- package/build/dist/Utils/Schema/ModelSchema.js +181 -0
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenAPIRegistry,
|
|
3
|
+
OpenApiGeneratorV3,
|
|
4
|
+
} from "@asteasolutions/zod-to-openapi";
|
|
5
|
+
import DatabaseBaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
6
|
+
import Models from "../../Models/DatabaseModels/Index";
|
|
7
|
+
import { JSONObject } from "../../Types/JSON";
|
|
8
|
+
|
|
9
|
+
export default class OpenAPIUtil {
|
|
10
|
+
public static generateOpenAPISpec(): JSONObject {
|
|
11
|
+
const registry: OpenAPIRegistry = new OpenAPIRegistry();
|
|
12
|
+
|
|
13
|
+
// Register schemas and paths for all models
|
|
14
|
+
for (const ModelClass of Models) {
|
|
15
|
+
const model: DatabaseBaseModel = new ModelClass();
|
|
16
|
+
const modelName: string = model.constructor.name;
|
|
17
|
+
const basePath: string = `/api/${modelName.toLowerCase()}`;
|
|
18
|
+
// Use a plain object for paths
|
|
19
|
+
const paths: Record<string, Record<string, any>> = {};
|
|
20
|
+
|
|
21
|
+
// List endpoints (POST and GET)
|
|
22
|
+
paths[`${basePath}/get-list`] = {
|
|
23
|
+
post: this.generateListApiSpec({ modelType: ModelClass }),
|
|
24
|
+
get: this.generateListApiSpec({ modelType: ModelClass }),
|
|
25
|
+
};
|
|
26
|
+
// Count endpoint
|
|
27
|
+
paths[`${basePath}/count`] = {
|
|
28
|
+
post: this.generateCountApiSpec({ modelType: ModelClass }),
|
|
29
|
+
};
|
|
30
|
+
// Create endpoint
|
|
31
|
+
paths[basePath] = {
|
|
32
|
+
post: this.generateCreateApiSpec({ modelType: ModelClass }),
|
|
33
|
+
};
|
|
34
|
+
// Get item endpoints (POST and GET)
|
|
35
|
+
paths[`${basePath}/{id}/get-item`] = {
|
|
36
|
+
post: this.generateGetApiSpec({ modelType: ModelClass }),
|
|
37
|
+
get: this.generateGetApiSpec({ modelType: ModelClass }),
|
|
38
|
+
};
|
|
39
|
+
// Update endpoints (PUT, POST, GET)
|
|
40
|
+
if (!paths[`${basePath}/{id}`]) {
|
|
41
|
+
paths[`${basePath}/{id}`] = {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
paths[`${basePath}/{id}`]!["put"] = this.generateUpdateApiSpec({
|
|
45
|
+
modelType: ModelClass,
|
|
46
|
+
});
|
|
47
|
+
paths[`${basePath}/{id}/update-item`] = {
|
|
48
|
+
post: this.generateUpdateApiSpec({ modelType: ModelClass }),
|
|
49
|
+
get: this.generateUpdateApiSpec({ modelType: ModelClass }),
|
|
50
|
+
};
|
|
51
|
+
// Delete endpoints (DELETE, POST, GET)
|
|
52
|
+
if (!paths[`${basePath}/{id}`]) {
|
|
53
|
+
paths[`${basePath}/{id}`] = {};
|
|
54
|
+
}
|
|
55
|
+
paths[`${basePath}/{id}`]!["delete"] = this.generateDeleteApiSpec({
|
|
56
|
+
modelType: ModelClass,
|
|
57
|
+
});
|
|
58
|
+
paths[`${basePath}/{id}/delete-item`] = {
|
|
59
|
+
post: this.generateDeleteApiSpec({ modelType: ModelClass }),
|
|
60
|
+
get: this.generateDeleteApiSpec({ modelType: ModelClass }),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Register the paths in the registry
|
|
64
|
+
for (const path in paths) {
|
|
65
|
+
if (paths[path]) {
|
|
66
|
+
const methods: Record<string, any> | undefined = paths[path];
|
|
67
|
+
if (typeof methods === "object" && methods !== null) {
|
|
68
|
+
for (const method in methods) {
|
|
69
|
+
if (methods[method]) {
|
|
70
|
+
const spec: any = methods[method];
|
|
71
|
+
registry.registerPath({
|
|
72
|
+
method: method as any,
|
|
73
|
+
path,
|
|
74
|
+
...spec,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const generator: OpenApiGeneratorV3 = new OpenApiGeneratorV3(
|
|
84
|
+
registry.definitions,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const components: Pick<any, "components"> = generator.generateComponents();
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
openapi: "3.0.0",
|
|
91
|
+
info: {
|
|
92
|
+
title: "API Documentation",
|
|
93
|
+
version: "1.0.0",
|
|
94
|
+
description: "API documentation generated from models",
|
|
95
|
+
},
|
|
96
|
+
components: components,
|
|
97
|
+
} as unknown as JSONObject;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public static generateListApiSpec(data: {
|
|
101
|
+
modelType: new () => DatabaseBaseModel;
|
|
102
|
+
}): JSONObject {
|
|
103
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
104
|
+
const model: DatabaseBaseModel = new modelType();
|
|
105
|
+
return {
|
|
106
|
+
summary: `List ${model.constructor.name}`,
|
|
107
|
+
description: `Endpoint to list all ${model.constructor.name} items`,
|
|
108
|
+
requestBody: {
|
|
109
|
+
required: false,
|
|
110
|
+
content: {
|
|
111
|
+
"application/json": {
|
|
112
|
+
schema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
query: { type: "object" },
|
|
116
|
+
select: { type: "object" },
|
|
117
|
+
sort: { type: "object" },
|
|
118
|
+
groupBy: { type: "object" },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
responses: {
|
|
125
|
+
"200": {
|
|
126
|
+
description: "Successful response",
|
|
127
|
+
content: {
|
|
128
|
+
"application/json": {
|
|
129
|
+
schema: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
data: {
|
|
133
|
+
type: "array",
|
|
134
|
+
items: {
|
|
135
|
+
$ref: `#/components/schemas/${model.constructor.name}`,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
count: { type: "number" },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public static generateCountApiSpec(data: {
|
|
149
|
+
modelType: new () => DatabaseBaseModel;
|
|
150
|
+
}): JSONObject {
|
|
151
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
152
|
+
const model: DatabaseBaseModel = new modelType();
|
|
153
|
+
return {
|
|
154
|
+
summary: `Count ${model.constructor.name}`,
|
|
155
|
+
description: `Endpoint to count ${model.constructor.name} items`,
|
|
156
|
+
requestBody: {
|
|
157
|
+
required: false,
|
|
158
|
+
content: {
|
|
159
|
+
"application/json": {
|
|
160
|
+
schema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
query: { type: "object" },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
responses: {
|
|
170
|
+
"200": {
|
|
171
|
+
description: "Successful response",
|
|
172
|
+
content: {
|
|
173
|
+
"application/json": {
|
|
174
|
+
schema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
count: { type: "number" },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public static generateCreateApiSpec(data: {
|
|
188
|
+
modelType: new () => DatabaseBaseModel;
|
|
189
|
+
}): JSONObject {
|
|
190
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
191
|
+
const model: DatabaseBaseModel = new modelType();
|
|
192
|
+
return {
|
|
193
|
+
summary: `Create ${model.constructor.name}`,
|
|
194
|
+
description: `Endpoint to create a new ${model.constructor.name}`,
|
|
195
|
+
requestBody: {
|
|
196
|
+
required: true,
|
|
197
|
+
content: {
|
|
198
|
+
"application/json": {
|
|
199
|
+
schema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
data: {
|
|
203
|
+
$ref: `#/components/schemas/${model.constructor.name}Input`,
|
|
204
|
+
},
|
|
205
|
+
miscDataProps: { type: "object" },
|
|
206
|
+
},
|
|
207
|
+
required: ["data"],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
responses: {
|
|
213
|
+
"201": {
|
|
214
|
+
description: "Created successfully",
|
|
215
|
+
content: {
|
|
216
|
+
"application/json": {
|
|
217
|
+
schema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
data: {
|
|
221
|
+
$ref: `#/components/schemas/${model.constructor.name}`,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
"400": {
|
|
229
|
+
description: "Bad request",
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public static generateGetApiSpec(data: {
|
|
236
|
+
modelType: new () => DatabaseBaseModel;
|
|
237
|
+
}): JSONObject {
|
|
238
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
239
|
+
const model: DatabaseBaseModel = new modelType();
|
|
240
|
+
return {
|
|
241
|
+
summary: `Get ${model.constructor.name}`,
|
|
242
|
+
description: `Endpoint to retrieve a single ${model.constructor.name} by ID`,
|
|
243
|
+
parameters: [
|
|
244
|
+
{
|
|
245
|
+
name: "id",
|
|
246
|
+
in: "path",
|
|
247
|
+
required: true,
|
|
248
|
+
schema: {
|
|
249
|
+
type: "string",
|
|
250
|
+
format: "uuid",
|
|
251
|
+
},
|
|
252
|
+
description: `ID of the ${model.constructor.name} to retrieve`,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
responses: {
|
|
256
|
+
"200": {
|
|
257
|
+
description: "Successful response",
|
|
258
|
+
content: {
|
|
259
|
+
"application/json": {
|
|
260
|
+
schema: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
data: {
|
|
264
|
+
$ref: `#/components/schemas/${model.constructor.name}`,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
"404": {
|
|
272
|
+
description: "Resource not found",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public static generateUpdateApiSpec(data: {
|
|
279
|
+
modelType: new () => DatabaseBaseModel;
|
|
280
|
+
}): JSONObject {
|
|
281
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
282
|
+
const model: DatabaseBaseModel = new modelType();
|
|
283
|
+
return {
|
|
284
|
+
summary: `Update ${model.constructor.name}`,
|
|
285
|
+
description: `Endpoint to update an existing ${model.constructor.name}`,
|
|
286
|
+
parameters: [
|
|
287
|
+
{
|
|
288
|
+
name: "id",
|
|
289
|
+
in: "path",
|
|
290
|
+
required: true,
|
|
291
|
+
schema: {
|
|
292
|
+
type: "string",
|
|
293
|
+
format: "uuid",
|
|
294
|
+
},
|
|
295
|
+
description: `ID of the ${model.constructor.name} to update`,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
requestBody: {
|
|
299
|
+
required: true,
|
|
300
|
+
content: {
|
|
301
|
+
"application/json": {
|
|
302
|
+
schema: {
|
|
303
|
+
type: "object",
|
|
304
|
+
properties: {
|
|
305
|
+
data: {
|
|
306
|
+
$ref: `#/components/schemas/${model.constructor.name}UpdateInput`,
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
required: ["data"],
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
responses: {
|
|
315
|
+
"200": {
|
|
316
|
+
description: "Updated successfully",
|
|
317
|
+
content: {
|
|
318
|
+
"application/json": {
|
|
319
|
+
schema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
data: {
|
|
323
|
+
$ref: `#/components/schemas/${model.constructor.name}`,
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
"404": {
|
|
331
|
+
description: "Resource not found",
|
|
332
|
+
},
|
|
333
|
+
"400": {
|
|
334
|
+
description: "Bad request",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
public static generateDeleteApiSpec(data: {
|
|
341
|
+
modelType: new () => DatabaseBaseModel;
|
|
342
|
+
}): JSONObject {
|
|
343
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
344
|
+
const model: DatabaseBaseModel = new modelType();
|
|
345
|
+
return {
|
|
346
|
+
summary: `Delete ${model.constructor.name}`,
|
|
347
|
+
description: `Endpoint to delete a ${model.constructor.name}`,
|
|
348
|
+
parameters: [
|
|
349
|
+
{
|
|
350
|
+
name: "id",
|
|
351
|
+
in: "path",
|
|
352
|
+
required: true,
|
|
353
|
+
schema: {
|
|
354
|
+
type: "string",
|
|
355
|
+
format: "uuid",
|
|
356
|
+
},
|
|
357
|
+
description: `ID of the ${model.constructor.name} to delete`,
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
responses: {
|
|
361
|
+
"204": {
|
|
362
|
+
description: "Deleted successfully",
|
|
363
|
+
},
|
|
364
|
+
"404": {
|
|
365
|
+
description: "Resource not found",
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -25,6 +25,7 @@ import { UserPermission } from "../../../Types/Permission";
|
|
|
25
25
|
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
26
26
|
import UserType from "../../../Types/UserType";
|
|
27
27
|
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
28
|
+
import ConfigLogLevel from "../../../Server/Types/ConfigLogLevel";
|
|
28
29
|
|
|
29
30
|
jest.mock("../../../Server/Utils/Express", () => {
|
|
30
31
|
return {
|
|
@@ -104,6 +105,9 @@ jest.mock("../../../Server/Services/ProjectService", () => {
|
|
|
104
105
|
jest.mock("../../../Server/EnvironmentConfig", () => {
|
|
105
106
|
return {
|
|
106
107
|
IsBillingEnabled: true,
|
|
108
|
+
LogLevel: ConfigLogLevel.INFO, // Or any other appropriate default for tests
|
|
109
|
+
ConfigLogLevel: ConfigLogLevel,
|
|
110
|
+
DisableTelemetry: true,
|
|
107
111
|
};
|
|
108
112
|
});
|
|
109
113
|
|
|
@@ -342,12 +342,13 @@ describe("StatementGenerator", () => {
|
|
|
342
342
|
/* eslint-disable prettier/prettier */
|
|
343
343
|
const expectedStatement: Statement = SQL`
|
|
344
344
|
CREATE TABLE IF NOT EXISTS ${'oneuptime'}.${'<table-name>'}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
345
|
+
(
|
|
346
|
+
<columns-create-statement>
|
|
347
|
+
)
|
|
348
|
+
ENGINE = MergeTree
|
|
349
|
+
PARTITION BY (column_ObjectID)
|
|
350
|
+
PRIMARY KEY (${'column_ObjectID'})
|
|
351
|
+
ORDER BY (${'column_ObjectID'})
|
|
351
352
|
`;
|
|
352
353
|
/* eslint-enable prettier/prettier */
|
|
353
354
|
|
package/Types/IP/IP.ts
CHANGED
|
@@ -11,6 +11,94 @@ export default class IP extends DatabaseProperty {
|
|
|
11
11
|
public get ip(): string {
|
|
12
12
|
return this._ip;
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
public static isInWhitelist(data: {
|
|
16
|
+
ips: Array<string>;
|
|
17
|
+
whitelist: string[];
|
|
18
|
+
}): boolean {
|
|
19
|
+
for (const ip of data.ips) {
|
|
20
|
+
// If whitelist is empty, return false
|
|
21
|
+
if (!data.whitelist || data.whitelist.length === 0) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check if IP is valid
|
|
26
|
+
if (!IP.isIP(ip)) {
|
|
27
|
+
throw new BadDataException("Invalid IP address");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check each whitelist entry
|
|
31
|
+
for (const entry of data.whitelist) {
|
|
32
|
+
// Skip empty entries
|
|
33
|
+
if (!entry || entry.trim() === "") {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Direct IP match
|
|
38
|
+
if (entry === ip) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// CIDR notation check (IPv4 only for now)
|
|
43
|
+
if (entry.includes("/") && IP.isIPv4(ip)) {
|
|
44
|
+
try {
|
|
45
|
+
const [network, prefixStr] = entry.split("/");
|
|
46
|
+
|
|
47
|
+
if (!network || !prefixStr) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!IP.isIPv4(network)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const prefix: number = parseInt(prefixStr, 10);
|
|
56
|
+
if (isNaN(prefix) || prefix < 0 || prefix > 32) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Convert IPs to integers for comparison
|
|
61
|
+
const ipInt: number = this._ipv4ToInt(ip);
|
|
62
|
+
const networkInt: number = this._ipv4ToInt(network);
|
|
63
|
+
|
|
64
|
+
// Create mask from prefix
|
|
65
|
+
const mask: number = ~((1 << (32 - prefix)) - 1) >>> 0;
|
|
66
|
+
|
|
67
|
+
// Check if IP is in network
|
|
68
|
+
if ((ipInt & mask) === (networkInt & mask)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Helper method to convert IPv4 to integer
|
|
82
|
+
private static _ipv4ToInt(ip: string): number {
|
|
83
|
+
const octets: number[] = ip.split(".").map(Number);
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
octets.length !== 4 ||
|
|
87
|
+
octets.some((octet: number) => {
|
|
88
|
+
return isNaN(octet) || octet < 0 || octet > 255;
|
|
89
|
+
})
|
|
90
|
+
) {
|
|
91
|
+
throw new BadDataException("Invalid IPv4 address");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
((octets[0]! << 24) >>> 0) +
|
|
96
|
+
((octets[1]! << 16) >>> 0) +
|
|
97
|
+
((octets[2]! << 8) >>> 0) +
|
|
98
|
+
octets[3]!
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
14
102
|
public set ip(value: string) {
|
|
15
103
|
if (IP.isIPv4(value)) {
|
|
16
104
|
this._ip = value;
|
|
@@ -4,8 +4,6 @@ import { GetReactElementFunction } from "../../Types/FunctionTypes";
|
|
|
4
4
|
import SelectEntityField from "../../Types/SelectEntityField";
|
|
5
5
|
import API from "../../Utils/API/API";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
7
|
import Query from "../../../Types/BaseDatabase/Query";
|
|
10
8
|
import GroupBy from "../../../Types/BaseDatabase/GroupBy";
|
|
11
9
|
import Sort from "../../../Types/BaseDatabase/Sort";
|
package/UI/Utils/API/API.ts
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
UserTenantAccessPermission,
|
|
18
18
|
} from "../../../Types/Permission";
|
|
19
19
|
import API from "../../../Utils/API";
|
|
20
|
-
import Cookies from "universal-cookie";
|
|
21
20
|
|
|
22
21
|
class BaseAPI extends API {
|
|
23
22
|
public constructor(protocol: Protocol, hostname: Hostname, route?: Route) {
|
|
@@ -97,16 +96,14 @@ class BaseAPI extends API {
|
|
|
97
96
|
): HTTPErrorResponse | APIException {
|
|
98
97
|
// 405 Status - Tenant not found. If Project was deleted.
|
|
99
98
|
// 401 Status - User is not logged in.
|
|
99
|
+
// 403 Status - Forbidden. If the IP address is not whitelisted (for example).
|
|
100
100
|
if (
|
|
101
101
|
error instanceof HTTPErrorResponse &&
|
|
102
102
|
(error.statusCode === 401 || error.statusCode === 405)
|
|
103
103
|
) {
|
|
104
104
|
const loginRoute: Route = this.getLoginRoute();
|
|
105
105
|
|
|
106
|
-
User.
|
|
107
|
-
const cookies: Cookies = new Cookies();
|
|
108
|
-
cookies.remove("admin-data", { path: "/" });
|
|
109
|
-
cookies.remove("data", { path: "/" });
|
|
106
|
+
User.logout();
|
|
110
107
|
|
|
111
108
|
if (Navigation.getQueryStringByName("token")) {
|
|
112
109
|
Navigation.navigate(loginRoute.addRouteParam("sso", "true"), {
|
|
@@ -119,6 +116,15 @@ class BaseAPI extends API {
|
|
|
119
116
|
}
|
|
120
117
|
}
|
|
121
118
|
|
|
119
|
+
if (
|
|
120
|
+
error instanceof HTTPErrorResponse &&
|
|
121
|
+
error.statusCode === 403 &&
|
|
122
|
+
Navigation.getCurrentRoute().toString() !==
|
|
123
|
+
this.getForbiddenRoute().toString()
|
|
124
|
+
) {
|
|
125
|
+
Navigation.navigate(this.getForbiddenRoute(), { forceNavigate: true });
|
|
126
|
+
}
|
|
127
|
+
|
|
122
128
|
return error;
|
|
123
129
|
}
|
|
124
130
|
|
|
@@ -126,6 +132,10 @@ class BaseAPI extends API {
|
|
|
126
132
|
return new Route("/accounts/login");
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
protected static getForbiddenRoute(): Route {
|
|
136
|
+
return new Route("/accounts/forbidden");
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
public static getFriendlyMessage(
|
|
130
140
|
err: HTTPErrorResponse | Exception | unknown,
|
|
131
141
|
): string {
|
package/UI/Utils/Cookie.ts
CHANGED
|
@@ -9,6 +9,15 @@ import UniversalCookies, { CookieSetOptions } from "universal-cookie";
|
|
|
9
9
|
import CookieName from "../../Types/CookieName";
|
|
10
10
|
|
|
11
11
|
export default class Cookie {
|
|
12
|
+
public static clearAllCookies(): void {
|
|
13
|
+
const cookies: UniversalCookies = new UniversalCookies();
|
|
14
|
+
|
|
15
|
+
// Remove all cookies defined in CookieName enum
|
|
16
|
+
Object.values(CookieName).forEach((cookieName: string) => {
|
|
17
|
+
cookies.remove(cookieName, { path: "/" });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
public static setItem(
|
|
13
22
|
key: CookieName | string,
|
|
14
23
|
value: JSONValue | Email | URL,
|
package/UI/Utils/User.ts
CHANGED
|
@@ -174,6 +174,7 @@ export default class UserUtil {
|
|
|
174
174
|
await API.post(URL.fromString(IDENTITY_URL.toString()).addRoute("/logout"));
|
|
175
175
|
LocalStorage.clear();
|
|
176
176
|
SessionStorage.clear();
|
|
177
|
+
Cookie.clearAllCookies();
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
public static getUtmParams(): Dictionary<string> {
|