Fixed hardcoded sitekey, fixed hasCaptcha being ignored, fixed setState while unmounted

This commit is contained in:
mrjvs 2023-11-17 19:10:02 +01:00
parent a5512b95e5
commit 328414ab06
6 changed files with 40 additions and 19 deletions

View File

@ -5,6 +5,7 @@ export interface MetaResponse {
name: string;
description?: string;
hasCaptcha: boolean;
captchaClientKey?: string;
}
export async function getBackendMeta(url: string): Promise<MetaResponse> {

View File

@ -1,5 +1,5 @@
import { useRef, useState } from "react";
import { useCopyToClipboard } from "react-use";
import { useCopyToClipboard, useMountedState } from "react-use";
import { Icon, Icons } from "./Icon";
@ -9,6 +9,7 @@ export function PassphaseDisplay(props: { mnemonic: string }) {
const [, copy] = useCopyToClipboard();
const [hasCopied, setHasCopied] = useState(false);
const isMounted = useMountedState();
const timeout = useRef<ReturnType<typeof setTimeout>>();
@ -16,7 +17,7 @@ export function PassphaseDisplay(props: { mnemonic: string }) {
copy(props.mnemonic);
setHasCopied(true);
if (timeout.current) clearTimeout(timeout.current);
timeout.current = setTimeout(() => setHasCopied(false), 500);
timeout.current = setTimeout(() => isMounted() && setHasCopied(false), 500);
}
return (

View File

@ -19,7 +19,7 @@ import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { useAuthStore } from "@/stores/auth";
export interface RegistrationData {
recaptchaToken: string;
recaptchaToken?: string;
mnemonic: string;
userData: {
device: string;

View File

@ -1,6 +1,7 @@
import { useState } from "react";
import { GoogleReCaptchaProvider } from "react-google-recaptcha-v3";
import { MetaResponse } from "@/backend/accounts/meta";
import { SubPageLayout } from "@/pages/layouts/SubPageLayout";
import {
AccountCreatePart,
@ -9,20 +10,38 @@ import {
import { PassphraseGeneratePart } from "@/pages/parts/auth/PassphraseGeneratePart";
import { TrustBackendPart } from "@/pages/parts/auth/TrustBackendPart";
import { VerifyPassphrase } from "@/pages/parts/auth/VerifyPassphrasePart";
import { conf } from "@/setup/config";
function CaptchaProvider(props: {
siteKey: string | null;
children: JSX.Element;
}) {
if (!props.siteKey) return props.children;
return (
<GoogleReCaptchaProvider reCaptchaKey={props.siteKey}>
{props.children}
</GoogleReCaptchaProvider>
);
}
export function RegisterPage() {
const [step, setStep] = useState(0);
const [mnemonic, setMnemonic] = useState<null | string>(null);
const [account, setAccount] = useState<null | AccountProfile>(null);
const reCaptchaKey = conf().RECAPTCHA_SITE_KEY;
const [siteKey, setSiteKey] = useState<string | null>(null);
// TODO because of user data loading (in useAuthRestore()), the register page gets unmounted before finishing the register flow
return (
<GoogleReCaptchaProvider reCaptchaKey={reCaptchaKey}>
<CaptchaProvider siteKey={siteKey}>
<SubPageLayout>
{step === 0 ? (
<TrustBackendPart
onNext={() => {
onNext={(meta: MetaResponse) => {
setSiteKey(
meta.hasCaptcha && meta.captchaClientKey
? meta.captchaClientKey
: null
);
setStep(1);
}}
/>
@ -45,6 +64,7 @@ export function RegisterPage() {
) : null}
{step === 3 ? (
<VerifyPassphrase
hasCaptcha={!!siteKey}
mnemonic={mnemonic}
userData={account}
onNext={() => {
@ -54,6 +74,6 @@ export function RegisterPage() {
) : null}
{step === 4 ? <p>Success, account now exists</p> : null}
</SubPageLayout>
</GoogleReCaptchaProvider>
</CaptchaProvider>
);
}

View File

@ -15,6 +15,7 @@ import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
interface VerifyPassphraseProps {
mnemonic: string | null;
hasCaptcha?: boolean;
userData: AccountProfile | null;
onNext?: () => void;
}
@ -27,18 +28,20 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
const [result, execute] = useAsyncFn(
async (inputMnemonic: string) => {
const recaptchaToken = executeRecaptcha
? await executeRecaptcha()
: undefined;
if (!props.mnemonic || !props.userData)
throw new Error("Data is not valid");
if (!recaptchaToken) throw new Error("ReCaptcha validation failed");
let recaptchaToken: string | undefined;
if (props.hasCaptcha) {
recaptchaToken = executeRecaptcha
? await executeRecaptcha()
: undefined;
if (!recaptchaToken) throw new Error("ReCaptcha validation failed");
}
if (inputMnemonic !== props.mnemonic)
throw new Error("Passphrase doesn't match");
// TODO captcha?
await register({
mnemonic: inputMnemonic,
userData: props.userData,

View File

@ -8,7 +8,6 @@ interface Config {
CORS_PROXY_URL: string;
NORMAL_ROUTER: boolean;
BACKEND_URL: string;
RECAPTCHA_SITE_KEY: string;
}
export interface RuntimeConfig {
@ -19,7 +18,6 @@ export interface RuntimeConfig {
NORMAL_ROUTER: boolean;
PROXY_URLS: string[];
BACKEND_URL: string;
RECAPTCHA_SITE_KEY: string;
}
const env: Record<keyof Config, undefined | string> = {
@ -30,7 +28,6 @@ const env: Record<keyof Config, undefined | string> = {
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
RECAPTCHA_SITE_KEY: import.meta.env.VITE_RECAPTCHA_SITE_KEY,
};
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
@ -56,6 +53,5 @@ export function conf(): RuntimeConfig {
.split(",")
.map((v) => v.trim()),
NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true",
RECAPTCHA_SITE_KEY: getKey("RECAPTCHA_SITE_KEY"),
};
}