@oneuptime/common 9.1.0 → 9.1.1

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.
Files changed (40) hide show
  1. package/Models/DatabaseModels/BillingPaymentMethod.ts +45 -9
  2. package/Models/DatabaseModels/StatusPageDomain.ts +3 -2
  3. package/Models/DatabaseModels/User.ts +1 -1
  4. package/Server/API/BillingPaymentMethodAPI.ts +6 -7
  5. package/Server/API/StatusPageAPI.ts +1 -1
  6. package/Server/EnvironmentConfig.ts +11 -0
  7. package/Server/Services/StatusPageDomainService.ts +17 -10
  8. package/Server/Types/Workflow/Components/Email.ts +10 -1
  9. package/Server/Utils/Captcha.ts +98 -0
  10. package/Server/Utils/Greenlock/Greenlock.ts +10 -3
  11. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +16 -7
  12. package/UI/Components/Captcha/Captcha.tsx +75 -0
  13. package/UI/Config.ts +3 -0
  14. package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js +45 -9
  15. package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js.map +1 -1
  16. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +2 -2
  17. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  18. package/build/dist/Models/DatabaseModels/User.js +1 -1
  19. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  20. package/build/dist/Server/API/BillingPaymentMethodAPI.js +6 -5
  21. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  22. package/build/dist/Server/API/StatusPageAPI.js +1 -1
  23. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  24. package/build/dist/Server/EnvironmentConfig.js +6 -0
  25. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  26. package/build/dist/Server/Services/StatusPageDomainService.js +12 -9
  27. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  28. package/build/dist/Server/Types/Workflow/Components/Email.js +8 -1
  29. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  30. package/build/dist/Server/Utils/Captcha.js +58 -0
  31. package/build/dist/Server/Utils/Captcha.js.map +1 -0
  32. package/build/dist/Server/Utils/Greenlock/Greenlock.js +7 -2
  33. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  34. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +13 -8
  35. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  36. package/build/dist/UI/Components/Captcha/Captcha.js +37 -0
  37. package/build/dist/UI/Components/Captcha/Captcha.js.map +1 -0
  38. package/build/dist/UI/Config.js +2 -0
  39. package/build/dist/UI/Config.js.map +1 -1
  40. package/package.json +2 -1
@@ -20,7 +20,11 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
20
20
  @AllowAccessIfSubscriptionIsUnpaid()
21
21
  @TenantColumn("projectId")
22
22
  @TableAccessControl({
23
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
23
+ create: [
24
+ Permission.ProjectOwner,
25
+ Permission.ManageProjectBilling,
26
+ Permission.CreateBillingPaymentMethod,
27
+ ],
24
28
  read: [
25
29
  Permission.ProjectOwner,
26
30
  Permission.ProjectUser,
@@ -28,7 +32,11 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
28
32
  Permission.ProjectMember,
29
33
  Permission.ReadBillingPaymentMethod,
30
34
  ],
31
- delete: [Permission.ProjectOwner, Permission.DeleteBillingPaymentMethod],
35
+ delete: [
36
+ Permission.ProjectOwner,
37
+ Permission.ManageProjectBilling,
38
+ Permission.DeleteBillingPaymentMethod,
39
+ ],
32
40
  update: [],
33
41
  })
34
42
  @CrudApiEndpoint(new Route("/billing-payment-methods"))
@@ -45,7 +53,11 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
45
53
  })
46
54
  export default class BillingPaymentMethod extends BaseModel {
47
55
  @ColumnAccessControl({
48
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
56
+ create: [
57
+ Permission.ProjectOwner,
58
+ Permission.ManageProjectBilling,
59
+ Permission.CreateBillingPaymentMethod,
60
+ ],
49
61
  read: [
50
62
  Permission.ProjectOwner,
51
63
  Permission.ProjectUser,
@@ -77,7 +89,11 @@ export default class BillingPaymentMethod extends BaseModel {
77
89
  public project?: Project = undefined;
78
90
 
79
91
  @ColumnAccessControl({
80
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
92
+ create: [
93
+ Permission.ProjectOwner,
94
+ Permission.ManageProjectBilling,
95
+ Permission.CreateBillingPaymentMethod,
96
+ ],
81
97
  read: [
82
98
  Permission.ProjectOwner,
83
99
  Permission.ProjectAdmin,
@@ -103,7 +119,11 @@ export default class BillingPaymentMethod extends BaseModel {
103
119
  public projectId?: ObjectID = undefined;
104
120
 
105
121
  @ColumnAccessControl({
106
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
122
+ create: [
123
+ Permission.ProjectOwner,
124
+ Permission.ManageProjectBilling,
125
+ Permission.CreateBillingPaymentMethod,
126
+ ],
107
127
  read: [
108
128
  Permission.ProjectOwner,
109
129
  Permission.ProjectAdmin,
@@ -136,7 +156,11 @@ export default class BillingPaymentMethod extends BaseModel {
136
156
  public createdByUser?: User = undefined;
137
157
 
138
158
  @ColumnAccessControl({
139
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
159
+ create: [
160
+ Permission.ProjectOwner,
161
+ Permission.ManageProjectBilling,
162
+ Permission.CreateBillingPaymentMethod,
163
+ ],
140
164
  read: [
141
165
  Permission.ProjectOwner,
142
166
  Permission.ProjectAdmin,
@@ -218,7 +242,11 @@ export default class BillingPaymentMethod extends BaseModel {
218
242
  public deletedByUserId?: ObjectID = undefined;
219
243
 
220
244
  @ColumnAccessControl({
221
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
245
+ create: [
246
+ Permission.ProjectOwner,
247
+ Permission.ManageProjectBilling,
248
+ Permission.CreateBillingPaymentMethod,
249
+ ],
222
250
  read: [
223
251
  Permission.ProjectOwner,
224
252
  Permission.ProjectAdmin,
@@ -278,7 +306,11 @@ export default class BillingPaymentMethod extends BaseModel {
278
306
  public paymentProviderCustomerId?: string = undefined;
279
307
 
280
308
  @ColumnAccessControl({
281
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
309
+ create: [
310
+ Permission.ProjectOwner,
311
+ Permission.ManageProjectBilling,
312
+ Permission.CreateBillingPaymentMethod,
313
+ ],
282
314
  read: [
283
315
  Permission.ProjectOwner,
284
316
  Permission.ProjectAdmin,
@@ -298,7 +330,11 @@ export default class BillingPaymentMethod extends BaseModel {
298
330
  public last4Digits?: string = undefined;
299
331
 
300
332
  @ColumnAccessControl({
301
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
333
+ create: [
334
+ Permission.ProjectOwner,
335
+ Permission.ManageProjectBilling,
336
+ Permission.CreateBillingPaymentMethod,
337
+ ],
302
338
  read: [
303
339
  Permission.ProjectOwner,
304
340
  Permission.ProjectAdmin,
@@ -282,8 +282,9 @@ export default class StatusPageDomain extends BaseModel {
282
282
  @TableColumn({
283
283
  required: true,
284
284
  type: TableColumnType.ShortText,
285
- title: "Sumdomain",
286
- description: "Subdomain of your status page - like (status)",
285
+ title: "Subdomain",
286
+ description:
287
+ "Subdomain label for your status page such as 'status'. Leave blank or enter @ to use the root domain.",
287
288
  })
288
289
  @Column({
289
290
  nullable: false,
@@ -30,7 +30,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
30
30
  })
31
31
  @AllowAccessIfSubscriptionIsUnpaid()
32
32
  @TableAccessControl({
33
- create: [Permission.Public],
33
+ create: [],
34
34
  read: [Permission.CurrentUser],
35
35
  delete: [Permission.CurrentUser],
36
36
  update: [Permission.CurrentUser],
@@ -42,17 +42,16 @@ export default class UserAPI extends BaseAPI<
42
42
  const userPermissions: Array<UserPermission> = (
43
43
  await this.getPermissionsForTenant(req)
44
44
  ).filter((permission: UserPermission) => {
45
- return (
46
- permission.permission.toString() ===
47
- Permission.ProjectOwner.toString() ||
48
- permission.permission.toString() ===
49
- Permission.CreateBillingPaymentMethod.toString()
50
- );
45
+ return [
46
+ Permission.ProjectOwner,
47
+ Permission.ManageProjectBilling,
48
+ Permission.CreateBillingPaymentMethod,
49
+ ].includes(permission.permission);
51
50
  });
52
51
 
53
52
  if (userPermissions.length === 0) {
54
53
  throw new BadDataException(
55
- "Only Project owner can add payment methods.",
54
+ "Only project owners or members with Manage Billing access can add payment methods.",
56
55
  );
57
56
  }
58
57
 
@@ -2809,7 +2809,7 @@ export default class StatusPageAPI extends BaseAPI<
2809
2809
  manageSubscriptionUrl: manageUrlink,
2810
2810
  },
2811
2811
  subject:
2812
- "Manage your Subscription for" +
2812
+ "Manage your Subscription for " +
2813
2813
  (statusPage.name || "Status Page"),
2814
2814
  },
2815
2815
  {
@@ -44,6 +44,8 @@ const FRONTEND_ENV_ALLOW_LIST: Array<string> = [
44
44
  "DISABLE_TELEMETRY",
45
45
  "SLACK_APP_CLIENT_ID",
46
46
  "MICROSOFT_TEAMS_APP_CLIENT_ID",
47
+ "CAPTCHA_ENABLED",
48
+ "CAPTCHA_SITE_KEY",
47
49
  ];
48
50
 
49
51
  const FRONTEND_ENV_ALLOW_PREFIXES: Array<string> = [
@@ -324,6 +326,13 @@ export const Host: string = process.env["HOST"] || "";
324
326
 
325
327
  export const ProvisionSsl: boolean = process.env["PROVISION_SSL"] === "true";
326
328
 
329
+ export const CaptchaEnabled: boolean =
330
+ process.env["CAPTCHA_ENABLED"] === "true";
331
+
332
+ export const CaptchaSecretKey: string = process.env["CAPTCHA_SECRET_KEY"] || "";
333
+
334
+ export const CaptchaSiteKey: string = process.env["CAPTCHA_SITE_KEY"] || "";
335
+
327
336
  export const WorkflowScriptTimeoutInMS: number = process.env[
328
337
  "WORKFLOW_SCRIPT_TIMEOUT_IN_MS"
329
338
  ]
@@ -446,6 +455,8 @@ export const MicrosoftTeamsAppClientId: string | null =
446
455
  process.env["MICROSOFT_TEAMS_APP_CLIENT_ID"] || null;
447
456
  export const MicrosoftTeamsAppClientSecret: string | null =
448
457
  process.env["MICROSOFT_TEAMS_APP_CLIENT_SECRET"] || null;
458
+ export const MicrosoftTeamsAppTenantId: string | null =
459
+ process.env["MICROSOFT_TEAMS_APP_TENANT_ID"] || null;
449
460
 
450
461
  // VAPID Configuration for Web Push Notifications
451
462
  export const VapidPublicKey: string | undefined =
@@ -48,19 +48,26 @@ export class Service extends DatabaseService<StatusPageDomain> {
48
48
  );
49
49
  }
50
50
 
51
- if (createBy.data.subdomain) {
52
- // trim and lowercase the subdomain.
53
- createBy.data.subdomain = createBy.data.subdomain.trim().toLowerCase();
51
+ let normalizedSubdomain: string =
52
+ createBy.data.subdomain?.trim().toLowerCase() || "";
53
+
54
+ if (normalizedSubdomain === "@") {
55
+ normalizedSubdomain = "";
54
56
  }
55
57
 
58
+ createBy.data.subdomain = normalizedSubdomain;
59
+
56
60
  if (domain) {
57
- createBy.data.fullDomain = (
58
- createBy.data.subdomain +
59
- "." +
60
- domain.domain?.toString()
61
- )
62
- .toLowerCase()
63
- .trim();
61
+ const baseDomain: string =
62
+ domain.domain?.toString().toLowerCase().trim() || "";
63
+
64
+ if (!baseDomain) {
65
+ throw new BadDataException("Please select a valid domain.");
66
+ }
67
+
68
+ createBy.data.fullDomain = normalizedSubdomain
69
+ ? `${normalizedSubdomain}.${baseDomain}`
70
+ : baseDomain;
64
71
  }
65
72
 
66
73
  createBy.data.cnameVerificationToken = ObjectID.generate().toString();
@@ -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,
@@ -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
- throw new ServerException(
329
- `Unable to order certificate for ${data.domain}. Please contact support at support@oneuptime.com for more information.`,
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
  }
@@ -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(microsoftAppTenantId: string): CloudAdapter {
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 (!microsoftAppTenantId) {
105
- throw new BadDataException("Microsoft Teams tenant ID is required");
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: ${microsoftAppTenantId}`);
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: 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(tenantId);
1153
+ const adapter: CloudAdapter = this.getBotAdapter();
1145
1154
 
1146
1155
  // Create conversation reference for the channel
1147
1156
  const conversationReference: ConversationReference = {
@@ -2564,7 +2573,7 @@ All monitoring checks are passing normally.`;
2564
2573
  }
2565
2574
 
2566
2575
  // Get Bot Framework adapter
2567
- const adapter: CloudAdapter = this.getBotAdapter(tenantId);
2576
+ const adapter: CloudAdapter = this.getBotAdapter();
2568
2577
 
2569
2578
  // Create custom activity handler class that extends TeamsActivityHandler
2570
2579
  class OneUptimeTeamsActivityHandler extends TeamsActivityHandler {
@@ -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
 
@@ -43,7 +43,11 @@ let BillingPaymentMethod = class BillingPaymentMethod extends BaseModel {
43
43
  };
44
44
  __decorate([
45
45
  ColumnAccessControl({
46
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
46
+ create: [
47
+ Permission.ProjectOwner,
48
+ Permission.ManageProjectBilling,
49
+ Permission.CreateBillingPaymentMethod,
50
+ ],
47
51
  read: [
48
52
  Permission.ProjectOwner,
49
53
  Permission.ProjectUser,
@@ -73,7 +77,11 @@ __decorate([
73
77
  ], BillingPaymentMethod.prototype, "project", void 0);
74
78
  __decorate([
75
79
  ColumnAccessControl({
76
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
80
+ create: [
81
+ Permission.ProjectOwner,
82
+ Permission.ManageProjectBilling,
83
+ Permission.CreateBillingPaymentMethod,
84
+ ],
77
85
  read: [
78
86
  Permission.ProjectOwner,
79
87
  Permission.ProjectAdmin,
@@ -100,7 +108,11 @@ __decorate([
100
108
  ], BillingPaymentMethod.prototype, "projectId", void 0);
101
109
  __decorate([
102
110
  ColumnAccessControl({
103
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
111
+ create: [
112
+ Permission.ProjectOwner,
113
+ Permission.ManageProjectBilling,
114
+ Permission.CreateBillingPaymentMethod,
115
+ ],
104
116
  read: [
105
117
  Permission.ProjectOwner,
106
118
  Permission.ProjectAdmin,
@@ -130,7 +142,11 @@ __decorate([
130
142
  ], BillingPaymentMethod.prototype, "createdByUser", void 0);
131
143
  __decorate([
132
144
  ColumnAccessControl({
133
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
145
+ create: [
146
+ Permission.ProjectOwner,
147
+ Permission.ManageProjectBilling,
148
+ Permission.CreateBillingPaymentMethod,
149
+ ],
134
150
  read: [
135
151
  Permission.ProjectOwner,
136
152
  Permission.ProjectAdmin,
@@ -209,7 +225,11 @@ __decorate([
209
225
  ], BillingPaymentMethod.prototype, "deletedByUserId", void 0);
210
226
  __decorate([
211
227
  ColumnAccessControl({
212
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
228
+ create: [
229
+ Permission.ProjectOwner,
230
+ Permission.ManageProjectBilling,
231
+ Permission.CreateBillingPaymentMethod,
232
+ ],
213
233
  read: [
214
234
  Permission.ProjectOwner,
215
235
  Permission.ProjectAdmin,
@@ -272,7 +292,11 @@ __decorate([
272
292
  ], BillingPaymentMethod.prototype, "paymentProviderCustomerId", void 0);
273
293
  __decorate([
274
294
  ColumnAccessControl({
275
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
295
+ create: [
296
+ Permission.ProjectOwner,
297
+ Permission.ManageProjectBilling,
298
+ Permission.CreateBillingPaymentMethod,
299
+ ],
276
300
  read: [
277
301
  Permission.ProjectOwner,
278
302
  Permission.ProjectAdmin,
@@ -293,7 +317,11 @@ __decorate([
293
317
  ], BillingPaymentMethod.prototype, "last4Digits", void 0);
294
318
  __decorate([
295
319
  ColumnAccessControl({
296
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
320
+ create: [
321
+ Permission.ProjectOwner,
322
+ Permission.ManageProjectBilling,
323
+ Permission.CreateBillingPaymentMethod,
324
+ ],
297
325
  read: [
298
326
  Permission.ProjectOwner,
299
327
  Permission.ProjectAdmin,
@@ -315,7 +343,11 @@ BillingPaymentMethod = __decorate([
315
343
  AllowAccessIfSubscriptionIsUnpaid(),
316
344
  TenantColumn("projectId"),
317
345
  TableAccessControl({
318
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
346
+ create: [
347
+ Permission.ProjectOwner,
348
+ Permission.ManageProjectBilling,
349
+ Permission.CreateBillingPaymentMethod,
350
+ ],
319
351
  read: [
320
352
  Permission.ProjectOwner,
321
353
  Permission.ProjectUser,
@@ -323,7 +355,11 @@ BillingPaymentMethod = __decorate([
323
355
  Permission.ProjectMember,
324
356
  Permission.ReadBillingPaymentMethod,
325
357
  ],
326
- delete: [Permission.ProjectOwner, Permission.DeleteBillingPaymentMethod],
358
+ delete: [
359
+ Permission.ProjectOwner,
360
+ Permission.ManageProjectBilling,
361
+ Permission.DeleteBillingPaymentMethod,
362
+ ],
327
363
  update: [],
328
364
  }),
329
365
  CrudApiEndpoint(new Route("/billing-payment-methods")),