Add recaptcha
This commit is contained in:
parent
d45d37b18a
commit
ab73a351f8
4 changed files with 182 additions and 60 deletions
|
@ -394,7 +394,7 @@ export function ClientProvider({ children }) {
|
|||
client,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: false,
|
||||
isPasswordlessUser: !!session.passwordlessUser,
|
||||
isGuest: false,
|
||||
userName: client.getUserIdLocalpart(),
|
||||
});
|
||||
|
@ -805,46 +805,68 @@ export function useInteractiveRegistration() {
|
|||
const privacyPolicyUrl =
|
||||
error.data?.params["m.login.terms"]?.policies?.privacy_policy?.en?.url;
|
||||
|
||||
if (privacyPolicyUrl) {
|
||||
setState((prev) => ({ ...prev, privacyPolicyUrl }));
|
||||
const recaptchaKey = error.data?.params["m.login.recaptcha"]?.public_key;
|
||||
|
||||
if (privacyPolicyUrl || recaptchaKey) {
|
||||
setState((prev) => ({ ...prev, privacyPolicyUrl, recaptchaKey }));
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const register = useCallback(async (username, password) => {
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClientRef.current,
|
||||
busyChanged(loading) {
|
||||
setState((prev) => ({ ...prev, loading }));
|
||||
},
|
||||
async doRequest(auth, _background) {
|
||||
return authClientRef.current.registerRequest({
|
||||
username,
|
||||
password,
|
||||
auth: auth || undefined,
|
||||
});
|
||||
},
|
||||
stateUpdated(nextStage, status) {
|
||||
if (nextStage === "m.login.terms") {
|
||||
interactiveAuth.submitAuthDict({ type: "m.login.terms" });
|
||||
}
|
||||
},
|
||||
});
|
||||
const register = useCallback(
|
||||
async (username, password, recaptchaResponse, passwordlessUser) => {
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClientRef.current,
|
||||
busyChanged(loading) {
|
||||
setState((prev) => ({ ...prev, loading }));
|
||||
},
|
||||
async doRequest(auth, _background) {
|
||||
return authClientRef.current.registerRequest({
|
||||
username,
|
||||
password,
|
||||
auth: auth || undefined,
|
||||
});
|
||||
},
|
||||
stateUpdated(nextStage, status) {
|
||||
if (status.error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const { user_id, access_token, device_id } =
|
||||
await interactiveAuth.attemptAuth();
|
||||
if (nextStage === "m.login.terms") {
|
||||
interactiveAuth.submitAuthDict({
|
||||
type: "m.login.terms",
|
||||
});
|
||||
} else if (nextStage === "m.login.recaptcha") {
|
||||
interactiveAuth.submitAuthDict({
|
||||
type: "m.login.recaptcha",
|
||||
response: recaptchaResponse,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const client = await initClient({
|
||||
baseUrl: defaultHomeserver,
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
});
|
||||
const { user_id, access_token, device_id } =
|
||||
await interactiveAuth.attemptAuth();
|
||||
|
||||
setClient(client, { user_id, access_token, device_id });
|
||||
const client = await initClient({
|
||||
baseUrl: defaultHomeserver,
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
});
|
||||
|
||||
return client;
|
||||
}, []);
|
||||
const session = { user_id, device_id, access_token, passwordlessUser };
|
||||
|
||||
if (passwordlessUser) {
|
||||
session.tempPassword = password;
|
||||
}
|
||||
|
||||
setClient(client, session);
|
||||
|
||||
return client;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return [state, register];
|
||||
}
|
||||
|
|
30
src/Home.jsx
30
src/Home.jsx
|
@ -35,6 +35,7 @@ import { ErrorView, LoadingView } from "./FullScreenView";
|
|||
import { useModalTriggerState } from "./Modal";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||
import { RecaptchaInput } from "./RecaptchaInput";
|
||||
|
||||
export function Home() {
|
||||
const {
|
||||
|
@ -46,7 +47,9 @@ export function Home() {
|
|||
client,
|
||||
} = useClient();
|
||||
|
||||
const [{ privacyPolicyUrl }, register] = useInteractiveRegistration();
|
||||
const [{ privacyPolicyUrl, recaptchaKey }, register] =
|
||||
useInteractiveRegistration();
|
||||
const [recaptchaResponse, setRecaptchaResponse] = useState();
|
||||
|
||||
const history = useHistory();
|
||||
const [creatingRoom, setCreatingRoom] = useState(false);
|
||||
|
@ -64,8 +67,17 @@ export function Home() {
|
|||
async function onCreateRoom() {
|
||||
let _client = client;
|
||||
|
||||
if (!recaptchaResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_client || isGuest) {
|
||||
_client = await register(userName, randomString(16), true);
|
||||
_client = await register(
|
||||
userName,
|
||||
randomString(16),
|
||||
recaptchaResponse,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
const roomIdOrAlias = await createRoom(_client, roomName);
|
||||
|
@ -90,7 +102,7 @@ export function Home() {
|
|||
setCreatingRoom(false);
|
||||
});
|
||||
},
|
||||
[client, history, register, isGuest]
|
||||
[client, history, register, isGuest, recaptchaResponse]
|
||||
);
|
||||
|
||||
const onJoinRoom = useCallback(
|
||||
|
@ -121,6 +133,8 @@ export function Home() {
|
|||
creatingRoom={creatingRoom}
|
||||
onJoinRoom={onJoinRoom}
|
||||
privacyPolicyUrl={privacyPolicyUrl}
|
||||
recaptchaKey={recaptchaKey}
|
||||
setRecaptchaResponse={setRecaptchaResponse}
|
||||
/>
|
||||
) : (
|
||||
<RegisteredView
|
||||
|
@ -147,6 +161,8 @@ function UnregisteredView({
|
|||
creatingRoom,
|
||||
onJoinRoom,
|
||||
privacyPolicyUrl,
|
||||
recaptchaKey,
|
||||
setRecaptchaResponse,
|
||||
}) {
|
||||
const acceptTermsRef = useRef();
|
||||
const [acceptTerms, setAcceptTerms] = useState(false);
|
||||
|
@ -237,6 +253,14 @@ function UnregisteredView({
|
|||
Privacy Policy
|
||||
</a>
|
||||
</FieldRow>
|
||||
{recaptchaKey && (
|
||||
<FieldRow>
|
||||
<RecaptchaInput
|
||||
publicKey={recaptchaKey}
|
||||
onResponse={setRecaptchaResponse}
|
||||
/>
|
||||
</FieldRow>
|
||||
)}
|
||||
{createRoomError && (
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<ErrorMessage>{createRoomError.message}</ErrorMessage>
|
||||
|
|
46
src/RecaptchaInput.jsx
Normal file
46
src/RecaptchaInput.jsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
export function RecaptchaInput({ publicKey, onResponse }) {
|
||||
const containerRef = useRef();
|
||||
const recaptchaRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
const onRecaptchaLoaded = () => {
|
||||
if (!recaptchaRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.grecaptcha.render(recaptchaRef.current, {
|
||||
sitekey: publicKey,
|
||||
callback: (response) => {
|
||||
if (!recaptchaRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
onResponse(response);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
typeof window.grecaptcha !== "undefined" &&
|
||||
typeof window.grecaptcha.render === "function"
|
||||
) {
|
||||
onRecaptchaLoaded();
|
||||
} else {
|
||||
window.mxOnRecaptchaLoaded = onRecaptchaLoaded;
|
||||
const scriptTag = document.createElement("script");
|
||||
scriptTag.setAttribute(
|
||||
"src",
|
||||
`https://www.recaptcha.net/recaptcha/api.js?onload=mxOnRecaptchaLoaded&render=explicit`
|
||||
);
|
||||
containerRef.current.appendChild(scriptTag);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<div ref={recaptchaRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useHistory, useLocation, Link } from "react-router-dom";
|
||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||
import { FieldRow, InputField, ErrorMessage, Field } from "./Input";
|
||||
import { Button } from "./button";
|
||||
import {
|
||||
useClient,
|
||||
|
@ -26,6 +26,7 @@ import {
|
|||
import styles from "./LoginPage.module.css";
|
||||
import { ReactComponent as Logo } from "./icons/LogoLarge.svg";
|
||||
import { LoadingView } from "./FullScreenView";
|
||||
import { RecaptchaInput } from "./RecaptchaInput";
|
||||
|
||||
export function RegisterPage() {
|
||||
const {
|
||||
|
@ -44,7 +45,9 @@ export function RegisterPage() {
|
|||
const [password, setPassword] = useState("");
|
||||
const [passwordConfirmation, setPasswordConfirmation] = useState("");
|
||||
const [acceptTerms, setAcceptTerms] = useState(false);
|
||||
const [{ privacyPolicyUrl }, register] = useInteractiveRegistration();
|
||||
const [{ privacyPolicyUrl, recaptchaKey }, register] =
|
||||
useInteractiveRegistration();
|
||||
const [recaptchaResponse, setRecaptchaResponse] = useState();
|
||||
|
||||
const onSubmitRegisterForm = useCallback(
|
||||
(e) => {
|
||||
|
@ -55,13 +58,13 @@ export function RegisterPage() {
|
|||
const passwordConfirmation = data.get("passwordConfirmation");
|
||||
const acceptTerms = data.get("acceptTerms");
|
||||
|
||||
if (password !== passwordConfirmation || !acceptTerms) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRegistering(true);
|
||||
|
||||
if (isPasswordlessUser) {
|
||||
if (password !== passwordConfirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRegistering(true);
|
||||
|
||||
changePassword(password)
|
||||
.then(() => {
|
||||
if (location.state && location.state.from) {
|
||||
|
@ -75,7 +78,17 @@ export function RegisterPage() {
|
|||
setRegistering(false);
|
||||
});
|
||||
} else {
|
||||
register(userName, password)
|
||||
if (
|
||||
password !== passwordConfirmation ||
|
||||
!acceptTerms ||
|
||||
!recaptchaResponse
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRegistering(true);
|
||||
|
||||
register(userName, password, recaptchaResponse)
|
||||
.then(() => {
|
||||
if (location.state && location.state.from) {
|
||||
history.push(location.state.from);
|
||||
|
@ -89,7 +102,14 @@ export function RegisterPage() {
|
|||
});
|
||||
}
|
||||
},
|
||||
[register, changePassword, location, history, isPasswordlessUser]
|
||||
[
|
||||
register,
|
||||
changePassword,
|
||||
location,
|
||||
history,
|
||||
isPasswordlessUser,
|
||||
recaptchaResponse,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -177,20 +197,30 @@ export function RegisterPage() {
|
|||
ref={confirmPasswordRef}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="acceptTerms"
|
||||
type="checkbox"
|
||||
name="acceptTerms"
|
||||
onChange={(e) => setAcceptTerms(e.target.checked)}
|
||||
checked={acceptTerms}
|
||||
label="Accept Privacy Policy"
|
||||
ref={acceptTermsRef}
|
||||
/>
|
||||
<a target="_blank" href={privacyPolicyUrl}>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</FieldRow>
|
||||
{!isPasswordlessUser && (
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="acceptTerms"
|
||||
type="checkbox"
|
||||
name="acceptTerms"
|
||||
onChange={(e) => setAcceptTerms(e.target.checked)}
|
||||
checked={acceptTerms}
|
||||
label="Accept Privacy Policy"
|
||||
ref={acceptTermsRef}
|
||||
/>
|
||||
<a target="_blank" href={privacyPolicyUrl}>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</FieldRow>
|
||||
)}
|
||||
{!isPasswordlessUser && recaptchaKey && (
|
||||
<FieldRow>
|
||||
<RecaptchaInput
|
||||
publicKey={recaptchaKey}
|
||||
onResponse={setRecaptchaResponse}
|
||||
/>
|
||||
</FieldRow>
|
||||
)}
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
|
|
Loading…
Reference in a new issue