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,
|
client,
|
||||||
loading: false,
|
loading: false,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
isPasswordlessUser: false,
|
isPasswordlessUser: !!session.passwordlessUser,
|
||||||
isGuest: false,
|
isGuest: false,
|
||||||
userName: client.getUserIdLocalpart(),
|
userName: client.getUserIdLocalpart(),
|
||||||
});
|
});
|
||||||
|
@ -805,46 +805,68 @@ export function useInteractiveRegistration() {
|
||||||
const privacyPolicyUrl =
|
const privacyPolicyUrl =
|
||||||
error.data?.params["m.login.terms"]?.policies?.privacy_policy?.en?.url;
|
error.data?.params["m.login.terms"]?.policies?.privacy_policy?.en?.url;
|
||||||
|
|
||||||
if (privacyPolicyUrl) {
|
const recaptchaKey = error.data?.params["m.login.recaptcha"]?.public_key;
|
||||||
setState((prev) => ({ ...prev, privacyPolicyUrl }));
|
|
||||||
|
if (privacyPolicyUrl || recaptchaKey) {
|
||||||
|
setState((prev) => ({ ...prev, privacyPolicyUrl, recaptchaKey }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const register = useCallback(async (username, password) => {
|
const register = useCallback(
|
||||||
const interactiveAuth = new InteractiveAuth({
|
async (username, password, recaptchaResponse, passwordlessUser) => {
|
||||||
matrixClient: authClientRef.current,
|
const interactiveAuth = new InteractiveAuth({
|
||||||
busyChanged(loading) {
|
matrixClient: authClientRef.current,
|
||||||
setState((prev) => ({ ...prev, loading }));
|
busyChanged(loading) {
|
||||||
},
|
setState((prev) => ({ ...prev, loading }));
|
||||||
async doRequest(auth, _background) {
|
},
|
||||||
return authClientRef.current.registerRequest({
|
async doRequest(auth, _background) {
|
||||||
username,
|
return authClientRef.current.registerRequest({
|
||||||
password,
|
username,
|
||||||
auth: auth || undefined,
|
password,
|
||||||
});
|
auth: auth || undefined,
|
||||||
},
|
});
|
||||||
stateUpdated(nextStage, status) {
|
},
|
||||||
if (nextStage === "m.login.terms") {
|
stateUpdated(nextStage, status) {
|
||||||
interactiveAuth.submitAuthDict({ type: "m.login.terms" });
|
if (status.error) {
|
||||||
}
|
throw new Error(error);
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const { user_id, access_token, device_id } =
|
if (nextStage === "m.login.terms") {
|
||||||
await interactiveAuth.attemptAuth();
|
interactiveAuth.submitAuthDict({
|
||||||
|
type: "m.login.terms",
|
||||||
|
});
|
||||||
|
} else if (nextStage === "m.login.recaptcha") {
|
||||||
|
interactiveAuth.submitAuthDict({
|
||||||
|
type: "m.login.recaptcha",
|
||||||
|
response: recaptchaResponse,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const client = await initClient({
|
const { user_id, access_token, device_id } =
|
||||||
baseUrl: defaultHomeserver,
|
await interactiveAuth.attemptAuth();
|
||||||
accessToken: access_token,
|
|
||||||
userId: user_id,
|
|
||||||
deviceId: device_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
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];
|
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 { useModalTriggerState } from "./Modal";
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||||
|
import { RecaptchaInput } from "./RecaptchaInput";
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
const {
|
const {
|
||||||
|
@ -46,7 +47,9 @@ export function Home() {
|
||||||
client,
|
client,
|
||||||
} = useClient();
|
} = useClient();
|
||||||
|
|
||||||
const [{ privacyPolicyUrl }, register] = useInteractiveRegistration();
|
const [{ privacyPolicyUrl, recaptchaKey }, register] =
|
||||||
|
useInteractiveRegistration();
|
||||||
|
const [recaptchaResponse, setRecaptchaResponse] = useState();
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [creatingRoom, setCreatingRoom] = useState(false);
|
const [creatingRoom, setCreatingRoom] = useState(false);
|
||||||
|
@ -64,8 +67,17 @@ export function Home() {
|
||||||
async function onCreateRoom() {
|
async function onCreateRoom() {
|
||||||
let _client = client;
|
let _client = client;
|
||||||
|
|
||||||
|
if (!recaptchaResponse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_client || isGuest) {
|
if (!_client || isGuest) {
|
||||||
_client = await register(userName, randomString(16), true);
|
_client = await register(
|
||||||
|
userName,
|
||||||
|
randomString(16),
|
||||||
|
recaptchaResponse,
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomIdOrAlias = await createRoom(_client, roomName);
|
const roomIdOrAlias = await createRoom(_client, roomName);
|
||||||
|
@ -90,7 +102,7 @@ export function Home() {
|
||||||
setCreatingRoom(false);
|
setCreatingRoom(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[client, history, register, isGuest]
|
[client, history, register, isGuest, recaptchaResponse]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onJoinRoom = useCallback(
|
const onJoinRoom = useCallback(
|
||||||
|
@ -121,6 +133,8 @@ export function Home() {
|
||||||
creatingRoom={creatingRoom}
|
creatingRoom={creatingRoom}
|
||||||
onJoinRoom={onJoinRoom}
|
onJoinRoom={onJoinRoom}
|
||||||
privacyPolicyUrl={privacyPolicyUrl}
|
privacyPolicyUrl={privacyPolicyUrl}
|
||||||
|
recaptchaKey={recaptchaKey}
|
||||||
|
setRecaptchaResponse={setRecaptchaResponse}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RegisteredView
|
<RegisteredView
|
||||||
|
@ -147,6 +161,8 @@ function UnregisteredView({
|
||||||
creatingRoom,
|
creatingRoom,
|
||||||
onJoinRoom,
|
onJoinRoom,
|
||||||
privacyPolicyUrl,
|
privacyPolicyUrl,
|
||||||
|
recaptchaKey,
|
||||||
|
setRecaptchaResponse,
|
||||||
}) {
|
}) {
|
||||||
const acceptTermsRef = useRef();
|
const acceptTermsRef = useRef();
|
||||||
const [acceptTerms, setAcceptTerms] = useState(false);
|
const [acceptTerms, setAcceptTerms] = useState(false);
|
||||||
|
@ -237,6 +253,14 @@ function UnregisteredView({
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</a>
|
</a>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
{recaptchaKey && (
|
||||||
|
<FieldRow>
|
||||||
|
<RecaptchaInput
|
||||||
|
publicKey={recaptchaKey}
|
||||||
|
onResponse={setRecaptchaResponse}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
{createRoomError && (
|
{createRoomError && (
|
||||||
<FieldRow className={styles.fieldRow}>
|
<FieldRow className={styles.fieldRow}>
|
||||||
<ErrorMessage>{createRoomError.message}</ErrorMessage>
|
<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 React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useHistory, useLocation, Link } from "react-router-dom";
|
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 { Button } from "./button";
|
||||||
import {
|
import {
|
||||||
useClient,
|
useClient,
|
||||||
|
@ -26,6 +26,7 @@ import {
|
||||||
import styles from "./LoginPage.module.css";
|
import styles from "./LoginPage.module.css";
|
||||||
import { ReactComponent as Logo } from "./icons/LogoLarge.svg";
|
import { ReactComponent as Logo } from "./icons/LogoLarge.svg";
|
||||||
import { LoadingView } from "./FullScreenView";
|
import { LoadingView } from "./FullScreenView";
|
||||||
|
import { RecaptchaInput } from "./RecaptchaInput";
|
||||||
|
|
||||||
export function RegisterPage() {
|
export function RegisterPage() {
|
||||||
const {
|
const {
|
||||||
|
@ -44,7 +45,9 @@ export function RegisterPage() {
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [passwordConfirmation, setPasswordConfirmation] = useState("");
|
const [passwordConfirmation, setPasswordConfirmation] = useState("");
|
||||||
const [acceptTerms, setAcceptTerms] = useState(false);
|
const [acceptTerms, setAcceptTerms] = useState(false);
|
||||||
const [{ privacyPolicyUrl }, register] = useInteractiveRegistration();
|
const [{ privacyPolicyUrl, recaptchaKey }, register] =
|
||||||
|
useInteractiveRegistration();
|
||||||
|
const [recaptchaResponse, setRecaptchaResponse] = useState();
|
||||||
|
|
||||||
const onSubmitRegisterForm = useCallback(
|
const onSubmitRegisterForm = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
@ -55,13 +58,13 @@ export function RegisterPage() {
|
||||||
const passwordConfirmation = data.get("passwordConfirmation");
|
const passwordConfirmation = data.get("passwordConfirmation");
|
||||||
const acceptTerms = data.get("acceptTerms");
|
const acceptTerms = data.get("acceptTerms");
|
||||||
|
|
||||||
if (password !== passwordConfirmation || !acceptTerms) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRegistering(true);
|
|
||||||
|
|
||||||
if (isPasswordlessUser) {
|
if (isPasswordlessUser) {
|
||||||
|
if (password !== passwordConfirmation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRegistering(true);
|
||||||
|
|
||||||
changePassword(password)
|
changePassword(password)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (location.state && location.state.from) {
|
if (location.state && location.state.from) {
|
||||||
|
@ -75,7 +78,17 @@ export function RegisterPage() {
|
||||||
setRegistering(false);
|
setRegistering(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
register(userName, password)
|
if (
|
||||||
|
password !== passwordConfirmation ||
|
||||||
|
!acceptTerms ||
|
||||||
|
!recaptchaResponse
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRegistering(true);
|
||||||
|
|
||||||
|
register(userName, password, recaptchaResponse)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (location.state && location.state.from) {
|
if (location.state && location.state.from) {
|
||||||
history.push(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(() => {
|
useEffect(() => {
|
||||||
|
@ -177,20 +197,30 @@ export function RegisterPage() {
|
||||||
ref={confirmPasswordRef}
|
ref={confirmPasswordRef}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<FieldRow>
|
{!isPasswordlessUser && (
|
||||||
<InputField
|
<FieldRow>
|
||||||
id="acceptTerms"
|
<InputField
|
||||||
type="checkbox"
|
id="acceptTerms"
|
||||||
name="acceptTerms"
|
type="checkbox"
|
||||||
onChange={(e) => setAcceptTerms(e.target.checked)}
|
name="acceptTerms"
|
||||||
checked={acceptTerms}
|
onChange={(e) => setAcceptTerms(e.target.checked)}
|
||||||
label="Accept Privacy Policy"
|
checked={acceptTerms}
|
||||||
ref={acceptTermsRef}
|
label="Accept Privacy Policy"
|
||||||
/>
|
ref={acceptTermsRef}
|
||||||
<a target="_blank" href={privacyPolicyUrl}>
|
/>
|
||||||
Privacy Policy
|
<a target="_blank" href={privacyPolicyUrl}>
|
||||||
</a>
|
Privacy Policy
|
||||||
</FieldRow>
|
</a>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
|
{!isPasswordlessUser && recaptchaKey && (
|
||||||
|
<FieldRow>
|
||||||
|
<RecaptchaInput
|
||||||
|
publicKey={recaptchaKey}
|
||||||
|
onResponse={setRecaptchaResponse}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<ErrorMessage>{error.message}</ErrorMessage>
|
<ErrorMessage>{error.message}</ErrorMessage>
|
||||||
|
|
Loading…
Add table
Reference in a new issue