@oneuptime/common 9.1.0 → 9.1.2
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/BillingPaymentMethod.ts +45 -9
- package/Models/DatabaseModels/Incident.ts +37 -0
- package/Models/DatabaseModels/StatusPageDomain.ts +3 -2
- package/Models/DatabaseModels/User.ts +1 -1
- package/Server/API/BillingPaymentMethodAPI.ts +6 -7
- package/Server/API/StatusPageAPI.ts +6 -1
- package/Server/EnvironmentConfig.ts +11 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/IncidentService.ts +38 -0
- package/Server/Services/MonitorService.ts +49 -31
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +18 -0
- package/Server/Services/StatusPageDomainService.ts +17 -10
- package/Server/Services/TeamMemberService.ts +8 -7
- package/Server/Services/TeamService.ts +2 -1
- package/Server/Types/Workflow/Components/Email.ts +10 -1
- package/Server/Utils/Browser.ts +28 -2
- package/Server/Utils/Captcha.ts +98 -0
- package/Server/Utils/Greenlock/Greenlock.ts +10 -3
- package/Server/Utils/Telemetry.ts +10 -2
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +4 -1
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +23 -10
- package/Tests/Server/Services/TeamMemberService.test.ts +119 -0
- package/UI/Components/Captcha/Captcha.tsx +75 -0
- package/UI/Config.ts +3 -0
- package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js +45 -9
- package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +39 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +2 -2
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +1 -1
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/BillingPaymentMethodAPI.js +6 -5
- package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +6 -1
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.js +17 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-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/IncidentService.js +32 -1
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +44 -24
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +12 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +12 -9
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +9 -8
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +2 -1
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +8 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Utils/Browser.js +24 -2
- package/build/dist/Server/Utils/Browser.js.map +1 -1
- package/build/dist/Server/Utils/Captcha.js +58 -0
- package/build/dist/Server/Utils/Captcha.js.map +1 -0
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +7 -2
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +19 -11
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Tests/Server/Services/TeamMemberService.test.js +79 -0
- package/build/dist/Tests/Server/Services/TeamMemberService.test.js.map +1 -1
- package/build/dist/UI/Components/Captcha/Captcha.js +37 -0
- package/build/dist/UI/Components/Captcha/Captcha.js.map +1 -0
- package/build/dist/UI/Config.js +2 -0
- package/build/dist/UI/Config.js.map +1 -1
- package/package.json +2 -1
|
@@ -44,10 +44,11 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
@CaptureSpan()
|
|
47
|
-
private async
|
|
47
|
+
private async isSCIMPushGroupsEnabled(projectId: ObjectID): Promise<boolean> {
|
|
48
48
|
const count: PositiveNumber = await ProjectSCIMService.countBy({
|
|
49
49
|
query: {
|
|
50
50
|
projectId: projectId,
|
|
51
|
+
enablePushGroups: true,
|
|
51
52
|
},
|
|
52
53
|
props: {
|
|
53
54
|
isRoot: true,
|
|
@@ -63,12 +64,12 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
63
64
|
// Check if SCIM is enabled for the project
|
|
64
65
|
if (
|
|
65
66
|
!createBy.props.isRoot &&
|
|
66
|
-
(await this.
|
|
67
|
+
(await this.isSCIMPushGroupsEnabled(
|
|
67
68
|
createBy.data.projectId! || createBy.props.tenantId,
|
|
68
69
|
))
|
|
69
70
|
) {
|
|
70
71
|
throw new BadDataException(
|
|
71
|
-
"Cannot invite team members
|
|
72
|
+
"Cannot invite team members while SCIM Push Groups is enabled for this project. Disable Push Groups to manage members from OneUptime.",
|
|
72
73
|
);
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -311,10 +312,10 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
311
312
|
!deleteBy.props.isRoot &&
|
|
312
313
|
members.length > 0 &&
|
|
313
314
|
members[0]?.projectId &&
|
|
314
|
-
(await this.
|
|
315
|
+
(await this.isSCIMPushGroupsEnabled(members[0].projectId))
|
|
315
316
|
) {
|
|
316
317
|
throw new BadDataException(
|
|
317
|
-
"Cannot delete team members
|
|
318
|
+
"Cannot delete team members while SCIM Push Groups is enabled for this project. Disable Push Groups to manage members from OneUptime.",
|
|
318
319
|
);
|
|
319
320
|
}
|
|
320
321
|
|
|
@@ -346,11 +347,11 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
346
347
|
});
|
|
347
348
|
|
|
348
349
|
// Skip the one-member guard when SCIM manages membership for the project.
|
|
349
|
-
const
|
|
350
|
+
const isPushGroupsManaged: boolean = await this.isSCIMPushGroupsEnabled(
|
|
350
351
|
member.projectId!,
|
|
351
352
|
);
|
|
352
353
|
|
|
353
|
-
if (!
|
|
354
|
+
if (!isPushGroupsManaged && membersInTeam.toNumber() <= 1) {
|
|
354
355
|
throw new BadDataException(
|
|
355
356
|
Errors.TeamMemberService.ONE_MEMBER_REQUIRED,
|
|
356
357
|
);
|
|
@@ -71,6 +71,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
71
71
|
const scimCount: PositiveNumber = await ProjectSCIMService.countBy({
|
|
72
72
|
query: {
|
|
73
73
|
projectId: projectId,
|
|
74
|
+
enablePushGroups: true,
|
|
74
75
|
},
|
|
75
76
|
skip: new PositiveNumber(0),
|
|
76
77
|
limit: new PositiveNumber(1),
|
|
@@ -82,7 +83,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
82
83
|
|
|
83
84
|
if (scimCount.toNumber() > 0) {
|
|
84
85
|
throw new BadDataException(
|
|
85
|
-
`Cannot ${data.action} teams
|
|
86
|
+
`Cannot ${data.action} teams while SCIM Push Groups is enabled for this project. Disable Push Groups to manage teams from OneUptime.`,
|
|
86
87
|
);
|
|
87
88
|
}
|
|
88
89
|
}
|
|
@@ -115,9 +115,18 @@ export default class Email extends ComponentCode {
|
|
|
115
115
|
const smtpTransport: SMTPTransport.Options = {
|
|
116
116
|
host: args["smtp-host"]?.toString(),
|
|
117
117
|
port: args["smtp-port"] as number,
|
|
118
|
-
secure: Boolean(args["secure"]),
|
|
119
118
|
};
|
|
120
119
|
|
|
120
|
+
if (
|
|
121
|
+
args["secure"] === true ||
|
|
122
|
+
args["secure"] === "true" ||
|
|
123
|
+
args["secure"] === 1
|
|
124
|
+
) {
|
|
125
|
+
smtpTransport.secure = true;
|
|
126
|
+
} else {
|
|
127
|
+
smtpTransport.secure = false;
|
|
128
|
+
}
|
|
129
|
+
|
|
121
130
|
if (username && password) {
|
|
122
131
|
smtpTransport.auth = {
|
|
123
132
|
user: username,
|
package/Server/Utils/Browser.ts
CHANGED
|
@@ -166,7 +166,20 @@ export default class BrowserUtil {
|
|
|
166
166
|
throw new BadDataException("Chrome executable path not found.");
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
const chromeExecutableCandidates: Array<string> = [
|
|
170
|
+
`/root/.cache/ms-playwright/${chromeInstallationName}/chrome-linux/chrome`,
|
|
171
|
+
`/root/.cache/ms-playwright/${chromeInstallationName}/chrome-linux64/chrome`,
|
|
172
|
+
`/root/.cache/ms-playwright/${chromeInstallationName}/chrome64/chrome`,
|
|
173
|
+
`/root/.cache/ms-playwright/${chromeInstallationName}/chrome/chrome`,
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
for (const executablePath of chromeExecutableCandidates) {
|
|
177
|
+
if (await LocalFile.doesFileExist(executablePath)) {
|
|
178
|
+
return executablePath;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
throw new BadDataException("Chrome executable path not found.");
|
|
170
183
|
}
|
|
171
184
|
|
|
172
185
|
@CaptureSpan()
|
|
@@ -197,6 +210,19 @@ export default class BrowserUtil {
|
|
|
197
210
|
throw new BadDataException("Firefox executable path not found.");
|
|
198
211
|
}
|
|
199
212
|
|
|
200
|
-
|
|
213
|
+
const firefoxExecutableCandidates: Array<string> = [
|
|
214
|
+
`/root/.cache/ms-playwright/${firefoxInstallationName}/firefox/firefox`,
|
|
215
|
+
`/root/.cache/ms-playwright/${firefoxInstallationName}/firefox-linux64/firefox`,
|
|
216
|
+
`/root/.cache/ms-playwright/${firefoxInstallationName}/firefox64/firefox`,
|
|
217
|
+
`/root/.cache/ms-playwright/${firefoxInstallationName}/firefox-64/firefox`,
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
for (const executablePath of firefoxExecutableCandidates) {
|
|
221
|
+
if (await LocalFile.doesFileExist(executablePath)) {
|
|
222
|
+
return executablePath;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
throw new BadDataException("Firefox executable path not found.");
|
|
201
227
|
}
|
|
202
228
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import axios, { AxiosError, AxiosResponse } from "axios";
|
|
2
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
3
|
+
import logger from "./Logger";
|
|
4
|
+
import { CaptchaEnabled, CaptchaSecretKey } from "../EnvironmentConfig";
|
|
5
|
+
|
|
6
|
+
export interface VerifyCaptchaOptions {
|
|
7
|
+
token: string | null | undefined;
|
|
8
|
+
remoteIp?: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const REQUEST_TIMEOUT_MS: number = 5000;
|
|
12
|
+
const GENERIC_ERROR_MESSAGE: string =
|
|
13
|
+
"Captcha verification failed. Please try again.";
|
|
14
|
+
|
|
15
|
+
type HCaptchaResponse = {
|
|
16
|
+
success?: boolean;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class CaptchaUtil {
|
|
21
|
+
public static isCaptchaEnabled(): boolean {
|
|
22
|
+
return CaptchaEnabled && Boolean(CaptchaSecretKey);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static async verifyCaptcha(
|
|
26
|
+
options: VerifyCaptchaOptions,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
if (!CaptchaEnabled) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!CaptchaSecretKey) {
|
|
33
|
+
logger.error(
|
|
34
|
+
"Captcha is enabled but CAPTCHA_SECRET_KEY is not configured.",
|
|
35
|
+
);
|
|
36
|
+
throw new BadDataException(GENERIC_ERROR_MESSAGE);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const token: string = (options.token || "").trim();
|
|
40
|
+
|
|
41
|
+
if (!token) {
|
|
42
|
+
throw new BadDataException(
|
|
43
|
+
"Captcha token is missing. Please complete the verification challenge.",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await this.verifyHCaptcha(token, options.remoteIp || undefined);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (axios.isAxiosError(err)) {
|
|
51
|
+
const axiosError: AxiosError = err as AxiosError;
|
|
52
|
+
logger.error(
|
|
53
|
+
`Captcha provider verification failure: ${axiosError.message}`,
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
logger.error(
|
|
57
|
+
`Captcha provider verification failure: ${(err as Error).message}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new BadDataException(GENERIC_ERROR_MESSAGE);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private static async verifyHCaptcha(
|
|
66
|
+
token: string,
|
|
67
|
+
remoteIp?: string,
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const params: URLSearchParams = new URLSearchParams();
|
|
70
|
+
params.append("secret", CaptchaSecretKey);
|
|
71
|
+
params.append("response", token);
|
|
72
|
+
|
|
73
|
+
if (remoteIp) {
|
|
74
|
+
params.append("remoteip", remoteIp);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const response: AxiosResponse<HCaptchaResponse> =
|
|
78
|
+
await axios.post<HCaptchaResponse>(
|
|
79
|
+
"https://hcaptcha.com/siteverify",
|
|
80
|
+
params.toString(),
|
|
81
|
+
{
|
|
82
|
+
headers: {
|
|
83
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
84
|
+
},
|
|
85
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (!response.data?.success) {
|
|
90
|
+
logger.warn(
|
|
91
|
+
`hCaptcha verification failed: ${JSON.stringify(response.data || {})}`,
|
|
92
|
+
);
|
|
93
|
+
throw new BadDataException(GENERIC_ERROR_MESSAGE);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default CaptchaUtil;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
IsBillingEnabled,
|
|
2
3
|
LetsEncryptAccountKey,
|
|
3
4
|
LetsEncryptNotificationEmail,
|
|
4
5
|
} from "../../../Server/EnvironmentConfig";
|
|
@@ -325,9 +326,15 @@ export default class GreenlockUtil {
|
|
|
325
326
|
throw e;
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
if (IsBillingEnabled) {
|
|
330
|
+
throw new ServerException(
|
|
331
|
+
`Unable to order certificate for ${data.domain}. Please contact support at support@oneuptime.com for more information.`,
|
|
332
|
+
);
|
|
333
|
+
} else {
|
|
334
|
+
throw new ServerException(
|
|
335
|
+
`Unable to order certificate for ${data.domain}. Please make sure that your server can be accessed publicly over port 80 (HTTP) and port 443 (HTTPS). If the problem persists, please refer to server logs for more information. Please also set up LOG_LEVEL=DEBUG to get more detailed server logs.`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
331
338
|
}
|
|
332
339
|
}
|
|
333
340
|
}
|
|
@@ -226,7 +226,11 @@ export default class Telemetry {
|
|
|
226
226
|
};
|
|
227
227
|
|
|
228
228
|
if (logRecordProcessors.length > 0) {
|
|
229
|
-
|
|
229
|
+
(
|
|
230
|
+
loggerProviderConfig as LoggerProviderConfig & {
|
|
231
|
+
processors?: Array<LogRecordProcessor>;
|
|
232
|
+
}
|
|
233
|
+
).processors = logRecordProcessors;
|
|
230
234
|
}
|
|
231
235
|
|
|
232
236
|
this.loggerProvider = new LoggerProvider(loggerProviderConfig);
|
|
@@ -254,7 +258,11 @@ export default class Telemetry {
|
|
|
254
258
|
*/
|
|
255
259
|
|
|
256
260
|
if (logRecordProcessors.length > 0) {
|
|
257
|
-
|
|
261
|
+
(
|
|
262
|
+
nodeSdkConfiguration as opentelemetry.NodeSDKConfiguration & {
|
|
263
|
+
logRecordProcessors?: Array<LogRecordProcessor>;
|
|
264
|
+
}
|
|
265
|
+
).logRecordProcessors = logRecordProcessors;
|
|
258
266
|
}
|
|
259
267
|
|
|
260
268
|
const sdk: opentelemetry.NodeSDK = new opentelemetry.NodeSDK(
|
|
@@ -320,6 +320,7 @@ export default class MicrosoftTeamsIncidentActions {
|
|
|
320
320
|
name: true,
|
|
321
321
|
},
|
|
322
322
|
createdAt: true,
|
|
323
|
+
declaredAt: true,
|
|
323
324
|
},
|
|
324
325
|
props: {
|
|
325
326
|
isRoot: true,
|
|
@@ -331,7 +332,9 @@ export default class MicrosoftTeamsIncidentActions {
|
|
|
331
332
|
return;
|
|
332
333
|
}
|
|
333
334
|
|
|
334
|
-
const
|
|
335
|
+
const declaredAt: Date | undefined =
|
|
336
|
+
incident.declaredAt || incident.createdAt || undefined;
|
|
337
|
+
const message: string = `**Incident Details**\n\n**Title:** ${incident.title}\n**Description:** ${incident.description || "No description"}\n**State:** ${incident.currentIncidentState?.name || "Unknown"}\n**Severity:** ${incident.incidentSeverity?.name || "Unknown"}\n**Declared At:** ${declaredAt ? new Date(declaredAt).toLocaleString() : "Unknown"}`;
|
|
335
338
|
|
|
336
339
|
await turnContext.sendActivity(message);
|
|
337
340
|
return;
|
|
@@ -43,6 +43,7 @@ import OneUptimeDate from "../../../../Types/Date";
|
|
|
43
43
|
import {
|
|
44
44
|
MicrosoftTeamsAppClientId,
|
|
45
45
|
MicrosoftTeamsAppClientSecret,
|
|
46
|
+
MicrosoftTeamsAppTenantId,
|
|
46
47
|
} from "../../../EnvironmentConfig";
|
|
47
48
|
|
|
48
49
|
// Import services for bot commands
|
|
@@ -91,18 +92,25 @@ const MICROSOFT_TEAMS_APP_TYPE: string = "SingleTenant";
|
|
|
91
92
|
const MICROSOFT_TEAMS_MAX_PAGES: number = 500;
|
|
92
93
|
|
|
93
94
|
export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
95
|
+
private static cachedAdapter: CloudAdapter | null = null;
|
|
94
96
|
private static readonly WELCOME_CARD_STATE_KEY: string =
|
|
95
97
|
"oneuptime.microsoftTeams.welcomeCardSent";
|
|
96
98
|
// Get or create Bot Framework adapter for a specific tenant
|
|
97
|
-
private static getBotAdapter(
|
|
99
|
+
private static getBotAdapter(): CloudAdapter {
|
|
100
|
+
if (this.cachedAdapter) {
|
|
101
|
+
return this.cachedAdapter;
|
|
102
|
+
}
|
|
103
|
+
|
|
98
104
|
if (!MicrosoftTeamsAppClientId || !MicrosoftTeamsAppClientSecret) {
|
|
99
105
|
throw new BadDataException(
|
|
100
106
|
"Microsoft Teams App credentials not configured",
|
|
101
107
|
);
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
if (!
|
|
105
|
-
throw new BadDataException(
|
|
110
|
+
if (!MicrosoftTeamsAppTenantId) {
|
|
111
|
+
throw new BadDataException(
|
|
112
|
+
"Microsoft Teams app tenant ID is not configured",
|
|
113
|
+
);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
logger.debug(
|
|
@@ -110,18 +118,19 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
110
118
|
);
|
|
111
119
|
logger.debug(`App ID: ${MicrosoftTeamsAppClientId}`);
|
|
112
120
|
logger.debug(`App Type: ${MICROSOFT_TEAMS_APP_TYPE}`);
|
|
113
|
-
logger.debug(`Tenant ID: ${
|
|
121
|
+
logger.debug(`Tenant ID: ${MicrosoftTeamsAppTenantId}`);
|
|
114
122
|
|
|
115
123
|
const authConfig: ConfigurationBotFrameworkAuthenticationOptions = {
|
|
116
124
|
MicrosoftAppId: MicrosoftTeamsAppClientId,
|
|
117
125
|
MicrosoftAppPassword: MicrosoftTeamsAppClientSecret,
|
|
118
126
|
MicrosoftAppType: MICROSOFT_TEAMS_APP_TYPE,
|
|
119
|
-
MicrosoftAppTenantId:
|
|
127
|
+
MicrosoftAppTenantId: MicrosoftTeamsAppTenantId,
|
|
120
128
|
};
|
|
121
129
|
|
|
122
130
|
const botFrameworkAuthentication: ConfigurationBotFrameworkAuthentication =
|
|
123
131
|
new ConfigurationBotFrameworkAuthentication(authConfig);
|
|
124
132
|
const adapter: CloudAdapter = new CloudAdapter(botFrameworkAuthentication);
|
|
133
|
+
this.cachedAdapter = adapter;
|
|
125
134
|
|
|
126
135
|
logger.debug("Bot Framework adapter created successfully");
|
|
127
136
|
return adapter;
|
|
@@ -1141,7 +1150,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1141
1150
|
logger.debug(`Using bot ID: ${miscData.botId}`);
|
|
1142
1151
|
|
|
1143
1152
|
// Get Bot Framework adapter
|
|
1144
|
-
const adapter: CloudAdapter = this.getBotAdapter(
|
|
1153
|
+
const adapter: CloudAdapter = this.getBotAdapter();
|
|
1145
1154
|
|
|
1146
1155
|
// Create conversation reference for the channel
|
|
1147
1156
|
const conversationReference: ConversationReference = {
|
|
@@ -1920,11 +1929,13 @@ Just type any of these commands to get the information you need!`;
|
|
|
1920
1929
|
color: true,
|
|
1921
1930
|
},
|
|
1922
1931
|
createdAt: true,
|
|
1932
|
+
declaredAt: true,
|
|
1923
1933
|
monitors: {
|
|
1924
1934
|
name: true,
|
|
1925
1935
|
},
|
|
1926
1936
|
},
|
|
1927
1937
|
sort: {
|
|
1938
|
+
declaredAt: SortOrder.Descending,
|
|
1928
1939
|
createdAt: SortOrder.Descending,
|
|
1929
1940
|
},
|
|
1930
1941
|
limit: 10,
|
|
@@ -1949,8 +1960,10 @@ If you need to report an incident or check historical incidents, please visit th
|
|
|
1949
1960
|
for (const incident of activeIncidents) {
|
|
1950
1961
|
const severity: string = incident.incidentSeverity?.name || "Unknown";
|
|
1951
1962
|
const state: string = incident.currentIncidentState?.name || "Unknown";
|
|
1952
|
-
const
|
|
1953
|
-
|
|
1963
|
+
const declaredAt: Date | undefined =
|
|
1964
|
+
incident.declaredAt || incident.createdAt;
|
|
1965
|
+
const declaredAtText: string = declaredAt
|
|
1966
|
+
? OneUptimeDate.getDateAsFormattedString(declaredAt)
|
|
1954
1967
|
: "Unknown";
|
|
1955
1968
|
|
|
1956
1969
|
const severityIcon: string = ["Critical", "Major"].includes(severity)
|
|
@@ -1968,7 +1981,7 @@ If you need to report an incident or check historical incidents, please visit th
|
|
|
1968
1981
|
message += `${severityIcon} **[Incident #${incident.incidentNumber}: ${incident.title}](${incidentUrl.toString()})**
|
|
1969
1982
|
• **Severity:** ${severity}
|
|
1970
1983
|
• **Status:** ${state}
|
|
1971
|
-
• **
|
|
1984
|
+
• **Declared:** ${declaredAtText}
|
|
1972
1985
|
`;
|
|
1973
1986
|
|
|
1974
1987
|
if (incident.monitors && incident.monitors.length > 0) {
|
|
@@ -2564,7 +2577,7 @@ All monitoring checks are passing normally.`;
|
|
|
2564
2577
|
}
|
|
2565
2578
|
|
|
2566
2579
|
// Get Bot Framework adapter
|
|
2567
|
-
const adapter: CloudAdapter = this.getBotAdapter(
|
|
2580
|
+
const adapter: CloudAdapter = this.getBotAdapter();
|
|
2568
2581
|
|
|
2569
2582
|
// Create custom activity handler class that extends TeamsActivityHandler
|
|
2570
2583
|
class OneUptimeTeamsActivityHandler extends TeamsActivityHandler {
|
|
@@ -5,6 +5,8 @@ import MailService from "../../../Server/Services/MailService";
|
|
|
5
5
|
import TeamMemberService from "../../../Server/Services/TeamMemberService";
|
|
6
6
|
import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
|
|
7
7
|
import UserNotificationSettingService from "../../../Server/Services/UserNotificationSettingService";
|
|
8
|
+
import ProjectSCIMService from "../../../Server/Services/ProjectSCIMService";
|
|
9
|
+
import ProjectSCIM from "../../../Models/DatabaseModels/ProjectSCIM";
|
|
8
10
|
import Errors from "../../../Server/Utils/Errors";
|
|
9
11
|
import "../TestingUtils/Init";
|
|
10
12
|
import ProjectServiceHelper from "../TestingUtils/Services/ProjectServiceHelper";
|
|
@@ -334,6 +336,123 @@ describe("TeamMemberService", () => {
|
|
|
334
336
|
},
|
|
335
337
|
);
|
|
336
338
|
});
|
|
339
|
+
|
|
340
|
+
it("should block inviting users when SCIM push groups is enabled", async () => {
|
|
341
|
+
const owner: User = await UserServiceHelper.genrateAndSaveRandomUser(
|
|
342
|
+
null,
|
|
343
|
+
{
|
|
344
|
+
isRoot: true,
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const project: Project =
|
|
349
|
+
await ProjectServiceHelper.generateAndSaveRandomProject(null, {
|
|
350
|
+
isRoot: true,
|
|
351
|
+
userId: owner.id!,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const team: Team = await TeamServiceHelper.generateAndSaveRandomTeam(
|
|
355
|
+
{
|
|
356
|
+
projectId: new ObjectID(project.id!),
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
isRoot: true,
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const memberUser: User =
|
|
364
|
+
await UserServiceHelper.genrateAndSaveRandomUser(null, {
|
|
365
|
+
isRoot: true,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const scimWithPushGroups: ProjectSCIM = new ProjectSCIM();
|
|
369
|
+
scimWithPushGroups.projectId = new ObjectID(project._id!);
|
|
370
|
+
scimWithPushGroups.name = "Test SCIM Push Groups";
|
|
371
|
+
scimWithPushGroups.bearerToken = ObjectID.generate().toString();
|
|
372
|
+
scimWithPushGroups.enablePushGroups = true;
|
|
373
|
+
|
|
374
|
+
await ProjectSCIMService.create({
|
|
375
|
+
data: scimWithPushGroups,
|
|
376
|
+
props: {
|
|
377
|
+
isRoot: true,
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const tm: TeamMember = TeamMemberServiceHelper.generateRandomTeamMember(
|
|
382
|
+
{
|
|
383
|
+
projectId: new ObjectID(project._id!),
|
|
384
|
+
userId: new ObjectID(memberUser._id!),
|
|
385
|
+
teamId: new ObjectID(team._id!),
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
await expect(
|
|
390
|
+
TeamMemberService.create({
|
|
391
|
+
data: tm,
|
|
392
|
+
props: { isRoot: false, tenantId: project.id! },
|
|
393
|
+
}),
|
|
394
|
+
).rejects.toThrow(/SCIM Push Groups/i);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("should allow inviting users when SCIM push groups is disabled", async () => {
|
|
398
|
+
const owner: User = await UserServiceHelper.genrateAndSaveRandomUser(
|
|
399
|
+
null,
|
|
400
|
+
{
|
|
401
|
+
isRoot: true,
|
|
402
|
+
},
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const project: Project =
|
|
406
|
+
await ProjectServiceHelper.generateAndSaveRandomProject(null, {
|
|
407
|
+
isRoot: true,
|
|
408
|
+
userId: owner.id!,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const team: Team = await TeamServiceHelper.generateAndSaveRandomTeam(
|
|
412
|
+
{
|
|
413
|
+
projectId: new ObjectID(project.id!),
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
isRoot: true,
|
|
417
|
+
},
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const memberUser: User =
|
|
421
|
+
await UserServiceHelper.genrateAndSaveRandomUser(null, {
|
|
422
|
+
isRoot: true,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const scimWithoutPushGroups: ProjectSCIM = new ProjectSCIM();
|
|
426
|
+
scimWithoutPushGroups.projectId = new ObjectID(project._id!);
|
|
427
|
+
scimWithoutPushGroups.name = "Test SCIM without Push Groups";
|
|
428
|
+
scimWithoutPushGroups.bearerToken = ObjectID.generate().toString();
|
|
429
|
+
scimWithoutPushGroups.enablePushGroups = false;
|
|
430
|
+
|
|
431
|
+
await ProjectSCIMService.create({
|
|
432
|
+
data: scimWithoutPushGroups,
|
|
433
|
+
props: {
|
|
434
|
+
isRoot: true,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const tm: TeamMember = TeamMemberServiceHelper.generateRandomTeamMember(
|
|
439
|
+
{
|
|
440
|
+
projectId: new ObjectID(project._id!),
|
|
441
|
+
userId: new ObjectID(memberUser._id!),
|
|
442
|
+
teamId: new ObjectID(team._id!),
|
|
443
|
+
},
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const teamMember: TeamMember = await TeamMemberService.create({
|
|
447
|
+
data: tm,
|
|
448
|
+
props: { isRoot: false, tenantId: project.id! },
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
expect(teamMember).toBeDefined();
|
|
452
|
+
expect(teamMember.projectId?.toString()).toEqual(
|
|
453
|
+
project._id?.toString(),
|
|
454
|
+
);
|
|
455
|
+
});
|
|
337
456
|
});
|
|
338
457
|
|
|
339
458
|
describe("onCreateSuccess", () => {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
export interface CaptchaProps {
|
|
5
|
+
siteKey: string;
|
|
6
|
+
resetSignal?: number | undefined;
|
|
7
|
+
error?: string | undefined;
|
|
8
|
+
onTokenChange?: (token: string) => void;
|
|
9
|
+
onBlur?: (() => void) | undefined;
|
|
10
|
+
className?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Captcha: React.FC<CaptchaProps> = ({
|
|
14
|
+
siteKey,
|
|
15
|
+
resetSignal = 0,
|
|
16
|
+
error,
|
|
17
|
+
onTokenChange,
|
|
18
|
+
onBlur,
|
|
19
|
+
className,
|
|
20
|
+
}: CaptchaProps): JSX.Element => {
|
|
21
|
+
const captchaRef: React.MutableRefObject<HCaptcha | null> =
|
|
22
|
+
React.useRef<HCaptcha | null>(null);
|
|
23
|
+
const onTokenChangeRef: React.MutableRefObject<
|
|
24
|
+
CaptchaProps["onTokenChange"]
|
|
25
|
+
> = React.useRef<CaptchaProps["onTokenChange"]>(onTokenChange);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
onTokenChangeRef.current = onTokenChange;
|
|
29
|
+
}, [onTokenChange]);
|
|
30
|
+
|
|
31
|
+
const handleTokenChange: (token: string | null) => void = React.useCallback(
|
|
32
|
+
(token: string | null) => {
|
|
33
|
+
onTokenChangeRef.current?.(token || "");
|
|
34
|
+
},
|
|
35
|
+
[],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
captchaRef.current?.resetCaptcha();
|
|
40
|
+
handleTokenChange("");
|
|
41
|
+
}, [resetSignal, handleTokenChange]);
|
|
42
|
+
|
|
43
|
+
if (!siteKey) {
|
|
44
|
+
return (
|
|
45
|
+
<div className={className || "text-center text-sm text-red-500"}>
|
|
46
|
+
Captcha is not configured.
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className={className || "flex flex-col items-center gap-2"}>
|
|
53
|
+
<HCaptcha
|
|
54
|
+
sitekey={siteKey}
|
|
55
|
+
ref={captchaRef}
|
|
56
|
+
onVerify={(token: string) => {
|
|
57
|
+
handleTokenChange(token);
|
|
58
|
+
onBlur?.();
|
|
59
|
+
}}
|
|
60
|
+
onExpire={() => {
|
|
61
|
+
handleTokenChange(null);
|
|
62
|
+
captchaRef.current?.resetCaptcha();
|
|
63
|
+
onBlur?.();
|
|
64
|
+
}}
|
|
65
|
+
onError={() => {
|
|
66
|
+
handleTokenChange(null);
|
|
67
|
+
onBlur?.();
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
{error && <span className="text-sm text-red-500">{error}</span>}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default Captcha;
|
package/UI/Config.ts
CHANGED
|
@@ -51,6 +51,9 @@ export const IS_ENTERPRISE_EDITION: boolean =
|
|
|
51
51
|
env("IS_ENTERPRISE_EDITION") === "true";
|
|
52
52
|
export const BILLING_PUBLIC_KEY: string = env("BILLING_PUBLIC_KEY") || "";
|
|
53
53
|
|
|
54
|
+
export const CAPTCHA_ENABLED: boolean = env("CAPTCHA_ENABLED") === "true";
|
|
55
|
+
export const CAPTCHA_SITE_KEY: string = env("CAPTCHA_SITE_KEY") || "";
|
|
56
|
+
|
|
54
57
|
// VAPID Configuration for Push Notifications
|
|
55
58
|
export const VAPID_PUBLIC_KEY: string = env("VAPID_PUBLIC_KEY") || "";
|
|
56
59
|
|