Skip to content

Commit a733c1c

Browse files
committed
replace reCAPTCHA with MTCaptcha
1 parent a3cd03d commit a733c1c

File tree

15 files changed

+224
-88
lines changed

15 files changed

+224
-88
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
BEGIN;
2+
3+
ALTER TABLE supporter ALTER COLUMN contact_email DROP NOT NULL;
4+
5+
COMMIT;

config.py.example

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ MAIL_FROM_DOMAIN = "metabrainz.org"
110110

111111
DEBUG_TB_INTERCEPT_REDIRECTS = False
112112

113-
# reCAPTCHA (https://www.google.com/recaptcha/)
114-
RECAPTCHA_PUBLIC_KEY = ""
115-
RECAPTCHA_PRIVATE_KEY = ""
113+
# MTCaptcha (https://www.mtcaptcha.com/)
114+
MTCAPTCHA_PUBLIC_KEY = ""
115+
MTCAPTCHA_PRIVATE_KEY = ""
116116

117117
# List of email addresses
118118
NOTIFICATION_RECIPIENTS = [

consul_config.py.ctmpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ LOG_SENTRY = {
8181
'release': os.getenv('GIT_SHA', None),
8282
}
8383

84-
# reCAPTCHA (https://www.google.com/recaptcha/)
85-
RECAPTCHA_PUBLIC_KEY = '''{{template "KEY" "recaptcha/public_key"}}'''
86-
RECAPTCHA_PRIVATE_KEY = '''{{template "KEY" "recaptcha/private_key"}}'''
84+
# MTCaptcha (https://www.mtcaptcha.com/)
85+
MTCAPTCHA_PUBLIC_KEY = '''{{template "KEY" "mtcaptcha/public_key"}}'''
86+
MTCAPTCHA_PRIVATE_KEY = '''{{template "KEY" "mtcaptcha/private_key"}}'''
8787

8888
{{if service "metabrainz-org.exim-relay"}}
8989
{{with index (service "metabrainz-org.exim-relay") 0}}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React, { useEffect, useRef, JSX } from "react";
2+
import { useField, useFormikContext } from "formik";
3+
4+
declare global {
5+
interface Window {
6+
mtcaptchaConfig?: {
7+
sitekey: string;
8+
renderQueue?: string[];
9+
"verified-callback"?: string;
10+
"verifyexpired-callback"?: string;
11+
"error-callback"?: string;
12+
};
13+
mtcaptcha?: any;
14+
}
15+
}
16+
17+
type MTCaptchaProps = {
18+
sitekey: string;
19+
size?: "compact" | "normal";
20+
fieldName: string;
21+
};
22+
23+
function MTCaptcha({
24+
sitekey,
25+
size = "normal",
26+
fieldName,
27+
}: MTCaptchaProps): JSX.Element {
28+
const [field] = useField(fieldName);
29+
const { setFieldValue } = useFormikContext();
30+
const containerRef = useRef<HTMLDivElement>(null);
31+
32+
const callbackIdRef = useRef<string>(
33+
`mtcaptcha_${Math.random().toString(36).substring(7)}`
34+
);
35+
36+
useEffect(() => {
37+
const callbackId = callbackIdRef.current;
38+
39+
(window as any)[`${callbackId}_verified`] = (token: string) => {
40+
const { verifiedToken } = token as any;
41+
setFieldValue(fieldName, verifiedToken);
42+
};
43+
44+
(window as any)[`${callbackId}_expired`] = () => {
45+
setFieldValue(fieldName, null);
46+
};
47+
48+
(window as any)[`${callbackId}_error`] = () => {
49+
setFieldValue(fieldName, null);
50+
};
51+
52+
if (!document.querySelector('script[src*="mtcaptcha.min.js"]')) {
53+
const script1 = document.createElement("script");
54+
script1.src =
55+
"https://service.mtcaptcha.com/mtcv1/client/mtcaptcha.min.js";
56+
script1.async = true;
57+
const targetElement =
58+
document.getElementsByTagName("head")[0] ||
59+
document.getElementsByTagName("body")[0];
60+
targetElement.appendChild(script1);
61+
62+
const script2 = document.createElement("script");
63+
script2.src =
64+
"https://service2.mtcaptcha.com/mtcv1/client/mtcaptcha2.min.js";
65+
script2.async = true;
66+
targetElement.appendChild(script2);
67+
68+
script2.onload = () => {
69+
if (containerRef.current) {
70+
containerRef.current.classList.add("mtcaptcha");
71+
}
72+
};
73+
} else if (containerRef.current) {
74+
containerRef.current.classList.add("mtcaptcha");
75+
}
76+
77+
return () => {
78+
delete (window as any)[`${callbackId}_verified`];
79+
delete (window as any)[`${callbackId}_expired`];
80+
delete (window as any)[`${callbackId}_error`];
81+
};
82+
}, [sitekey]);
83+
return (
84+
<>
85+
<input type="hidden" {...field} />
86+
<div
87+
ref={containerRef}
88+
className={size === "compact" ? "mtcaptcha-compact" : ""}
89+
data-sitekey={sitekey}
90+
data-verified-callback={`${callbackIdRef.current}_verified`}
91+
data-verifyexpired-callback={`${callbackIdRef.current}_expired`}
92+
data-error-callback={`${callbackIdRef.current}_error`}
93+
/>
94+
</>
95+
);
96+
}
97+
98+
export default MTCaptcha;

frontend/js/src/forms/SignupCommercial.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Formik, useField, FieldConfig } from "formik";
22
import React, { JSX } from "react";
33
import { createRoot } from "react-dom/client";
4-
import ReCAPTCHA from "react-google-recaptcha";
54
import * as Yup from "yup";
5+
import MTCaptcha from "./MTCaptcha";
66
import { getPageProps } from "../utils";
77
import {
88
AuthCardContainer,
@@ -49,7 +49,7 @@ type SignupCommercialProps = {
4949
name: string;
5050
price: number;
5151
};
52-
recaptcha_site_key: string;
52+
mtcaptcha_site_key: string;
5353
csrf_token: string;
5454
initial_form_data: any;
5555
initial_errors: any;
@@ -58,7 +58,7 @@ type SignupCommercialProps = {
5858
function SignupCommercial({
5959
tier,
6060
csrf_token,
61-
recaptcha_site_key,
61+
mtcaptcha_site_key,
6262
initial_form_data,
6363
initial_errors,
6464
}: SignupCommercialProps): JSX.Element {
@@ -96,7 +96,7 @@ function SignupCommercial({
9696
usage_desc: initial_form_data.usage_desc ?? "",
9797
contact_name: initial_form_data.contact_name ?? "",
9898
agreement: false,
99-
recaptcha: "",
99+
mtcaptcha: "",
100100
csrf_token,
101101
}}
102102
initialErrors={initial_errors}
@@ -153,11 +153,11 @@ function SignupCommercial({
153153
agreement: Yup.boolean()
154154
.required("You need to accept the agreement!")
155155
.oneOf([true], "You need to accept the agreement!"),
156-
recaptcha: Yup.string().required(),
156+
mtcaptcha: Yup.string().required(),
157157
})}
158158
onSubmit={() => {}}
159159
>
160-
{({ errors, setFieldValue, isValid, dirty }) => (
160+
{({ errors, isValid, dirty }) => (
161161
<form method="POST">
162162
<div className="form-group">
163163
<div className="col-sm-offset-4 col-sm-5">
@@ -434,10 +434,10 @@ function SignupCommercial({
434434
width: "fit-content",
435435
}}
436436
>
437-
<ReCAPTCHA
438-
sitekey={recaptcha_site_key}
439-
onChange={(value) => setFieldValue("recaptcha", value)}
437+
<MTCaptcha
438+
sitekey={mtcaptcha_site_key}
440439
size="compact"
440+
fieldName="mtcaptcha"
441441
/>
442442
</div>
443443

@@ -471,7 +471,7 @@ document.addEventListener("DOMContentLoaded", () => {
471471
const { domContainer, reactProps } = getPageProps();
472472
const {
473473
tier,
474-
recaptcha_site_key,
474+
mtcaptcha_site_key,
475475
csrf_token,
476476
initial_form_data,
477477
initial_errors,
@@ -481,7 +481,7 @@ document.addEventListener("DOMContentLoaded", () => {
481481
renderRoot.render(
482482
<SignupCommercial
483483
tier={tier}
484-
recaptcha_site_key={recaptcha_site_key}
484+
mtcaptcha_site_key={mtcaptcha_site_key}
485485
csrf_token={csrf_token}
486486
initial_form_data={initial_form_data}
487487
initial_errors={initial_errors}

frontend/js/src/forms/SignupNonCommercial.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Formik } from "formik";
22
import React, { JSX } from "react";
33
import { createRoot } from "react-dom/client";
4-
import ReCAPTCHA from "react-google-recaptcha";
54
import * as Yup from "yup";
5+
import MTCaptcha from "./MTCaptcha";
66
import { getPageProps } from "../utils";
77
import {
88
AuthCardContainer,
@@ -15,7 +15,7 @@ import {
1515

1616
type SignupNonCommercialProps = {
1717
datasets: Dataset[];
18-
recaptcha_site_key: string;
18+
mtcaptcha_site_key: string;
1919
csrf_token: string;
2020
initial_form_data: any;
2121
initial_errors: any;
@@ -24,7 +24,7 @@ type SignupNonCommercialProps = {
2424
function SignupNonCommercial({
2525
datasets,
2626
csrf_token,
27-
recaptcha_site_key,
27+
mtcaptcha_site_key,
2828
initial_form_data,
2929
initial_errors,
3030
}: SignupNonCommercialProps): JSX.Element {
@@ -52,7 +52,7 @@ function SignupNonCommercial({
5252
usage_desc: initial_form_data.usage_desc ?? "",
5353
contact_name: initial_form_data.contact_name ?? "",
5454
agreement: false,
55-
recaptcha: "",
55+
mtcaptcha: "",
5656
csrf_token,
5757
}}
5858
initialErrors={initial_errors}
@@ -81,11 +81,11 @@ function SignupNonCommercial({
8181
agreement: Yup.boolean()
8282
.required("You need to accept the agreement!")
8383
.oneOf([true], "You need to accept the agreement!"),
84-
recaptcha: Yup.string().required(),
84+
mtcaptcha: Yup.string().required(),
8585
})}
8686
onSubmit={() => {}}
8787
>
88-
{({ errors, setFieldValue, isValid, dirty }) => (
88+
{({ errors, isValid, dirty }) => (
8989
<form method="POST" className="d-flex flex-column">
9090
<div className="form-group">
9191
<div className="col-sm-offset-4 col-sm-5">
@@ -223,10 +223,10 @@ function SignupNonCommercial({
223223
width: "fit-content",
224224
}}
225225
>
226-
<ReCAPTCHA
227-
sitekey={recaptcha_site_key}
228-
onChange={(value) => setFieldValue("recaptcha", value)}
226+
<MTCaptcha
227+
sitekey={mtcaptcha_site_key}
229228
size="compact"
229+
fieldName="mtcaptcha"
230230
/>
231231
</div>
232232

@@ -261,7 +261,7 @@ document.addEventListener("DOMContentLoaded", () => {
261261
const { domContainer, reactProps } = getPageProps();
262262
const {
263263
datasets,
264-
recaptcha_site_key,
264+
mtcaptcha_site_key,
265265
csrf_token,
266266
initial_form_data,
267267
initial_errors,
@@ -271,7 +271,7 @@ document.addEventListener("DOMContentLoaded", () => {
271271
renderRoot.render(
272272
<SignupNonCommercial
273273
datasets={datasets}
274-
recaptcha_site_key={recaptcha_site_key}
274+
mtcaptcha_site_key={mtcaptcha_site_key}
275275
csrf_token={csrf_token}
276276
initial_form_data={initial_form_data}
277277
initial_errors={initial_errors}

frontend/js/src/forms/SignupUser.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Formik } from "formik";
22
import React, { JSX } from "react";
33
import { createRoot } from "react-dom/client";
4-
import ReCAPTCHA from "react-google-recaptcha";
54
import * as Yup from "yup";
5+
import MTCaptcha from "./MTCaptcha";
66
import { getPageProps } from "../utils";
77
import {
88
AuthCardContainer,
@@ -12,15 +12,15 @@ import {
1212
import ConditionsModal from "./ConditionsModal";
1313

1414
type SignupUserProps = {
15-
recaptcha_site_key: string;
15+
mtcaptcha_site_key: string;
1616
csrf_token: string;
1717
initial_form_data: any;
1818
initial_errors: any;
1919
};
2020

2121
function SignupUser({
2222
csrf_token,
23-
recaptcha_site_key,
23+
mtcaptcha_site_key,
2424
initial_form_data,
2525
initial_errors,
2626
}: SignupUserProps): JSX.Element {
@@ -36,7 +36,7 @@ function SignupUser({
3636
email: initial_form_data.email ?? "",
3737
password: initial_form_data.password ?? "",
3838
confirm_password: initial_form_data.confirm_password ?? "",
39-
recaptcha: "",
39+
mtcaptcha: "",
4040
csrf_token,
4141
}}
4242
initialErrors={initial_errors}
@@ -58,11 +58,11 @@ function SignupUser({
5858
[Yup.ref("password")],
5959
"Confirm Password should match password!"
6060
),
61-
recaptcha: Yup.string().required(),
61+
mtcaptcha: Yup.string().required(),
6262
})}
6363
onSubmit={() => {}}
6464
>
65-
{({ errors, setFieldValue, isValid, dirty }) => (
65+
{({ errors, setFieldValue, isValid, dirty, values }) => (
6666
<form method="POST">
6767
<div className="form-group">
6868
<div className="col-sm-offset-4 col-sm-5">
@@ -141,10 +141,10 @@ function SignupUser({
141141
width: "fit-content",
142142
}}
143143
>
144-
<ReCAPTCHA
145-
sitekey={recaptcha_site_key}
146-
onChange={(value) => setFieldValue("recaptcha", value)}
144+
<MTCaptcha
145+
sitekey={mtcaptcha_site_key}
147146
size="compact"
147+
fieldName="mtcaptcha"
148148
/>
149149
</div>
150150

@@ -170,13 +170,13 @@ function SignupUser({
170170

171171
document.addEventListener("DOMContentLoaded", () => {
172172
const { domContainer, reactProps } = getPageProps();
173-
const { recaptcha_site_key, csrf_token, initial_form_data, initial_errors } =
173+
const { mtcaptcha_site_key, csrf_token, initial_form_data, initial_errors } =
174174
reactProps;
175175

176176
const renderRoot = createRoot(domContainer!);
177177
renderRoot.render(
178178
<SignupUser
179-
recaptcha_site_key={recaptcha_site_key}
179+
mtcaptcha_site_key={mtcaptcha_site_key}
180180
csrf_token={csrf_token}
181181
initial_form_data={initial_form_data}
182182
initial_errors={initial_errors}

0 commit comments

Comments
 (0)