feat: add recaptcha to sign in and forget password

This commit is contained in:
DIYgod 2025-03-07 21:28:11 +08:00
parent 00c807f86d
commit ddd3d39204
No known key found for this signature in database
8 changed files with 53 additions and 31 deletions

View File

@ -64,9 +64,10 @@ export const loginHandler = async (
args?: {
email?: string
password?: string
headers?: Record<string, string>
},
) => {
const { email, password } = args ?? {}
const { email, password, headers } = args ?? {}
if (IN_ELECTRON && provider !== "credential") {
window.open(`${WEB_URL}/login?provider=${provider}`)
} else {
@ -75,7 +76,7 @@ export const loginHandler = async (
window.location.href = "/login"
return
}
return signIn.email({ email, password })
return signIn.email({ email, password }, { headers })
}
signIn.social({

View File

@ -42,10 +42,16 @@ export function LoginWithPassword({ runtime }: { runtime: LoginRuntime }) {
const { present } = useModalStack()
const { dismiss } = useCurrentModal()
const recaptchaRef = useRef<ReCAPTCHA>(null)
async function onSubmit(values: z.infer<typeof formSchema>) {
const token = await recaptchaRef.current?.executeAsync()
const res = await loginHandler("credential", runtime, {
email: values.email,
password: values.password,
headers: {
"x-token": `r2:${token}`,
},
})
if (res?.error) {
toast.error(res.error.message)
@ -117,6 +123,7 @@ export function LoginWithPassword({ runtime }: { runtime: LoginRuntime }) {
)}
/>
<div className="flex flex-col space-y-3">
<ReCAPTCHA ref={recaptchaRef} sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY} size="invisible" />
<Button
type="submit"
isLoading={form.formState.isSubmitting}
@ -235,13 +242,7 @@ function RegisterForm() {
</FormItem>
)}
/>
{env.VITE_RECAPTCHA_V2_SITE_KEY && (
<ReCAPTCHA
ref={recaptchaRef}
sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY}
size="invisible"
/>
)}
<ReCAPTCHA ref={recaptchaRef} sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY} size="invisible" />
<Button disabled={!isValid} type="submit" className="w-full" size="lg">
{t("register.submit")}
</Button>

View File

@ -23,13 +23,7 @@ export const ClaimDailyReward = () => {
<Tooltip>
<TooltipTrigger asChild>
<>
{env.VITE_RECAPTCHA_V2_SITE_KEY && (
<ReCAPTCHA
ref={recaptchaRef}
sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY}
size="invisible"
/>
)}
<ReCAPTCHA ref={recaptchaRef} sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY} size="invisible" />
<Button
variant="primary"
isLoading={mutation.isPending}

View File

@ -62,16 +62,17 @@ export const loginHandler = async (
args?: {
email?: string
password?: string
headers?: Record<string, string>
},
) => {
const { email, password } = args ?? {}
const { email, password, headers } = args ?? {}
if (provider === "credential") {
if (!email || !password) {
window.location.href = "/login"
return
}
return signIn.email({ email, password })
return signIn.email({ email, password }, { headers })
}
signIn.social({

View File

@ -18,9 +18,11 @@ import { Input } from "@follow/components/ui/input/index.js"
import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
import { authProvidersConfig } from "@follow/constants"
import { DEEPLINK_SCHEME } from "@follow/shared/constants"
import { env } from "@follow/shared/env"
import { cn } from "@follow/utils/utils"
import { zodResolver } from "@hookform/resolvers/zod"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import ReCAPTCHA from "react-google-recaptcha"
import { useForm } from "react-hook-form"
import { Trans, useTranslation } from "react-i18next"
import { Link, useLocation, useNavigate } from "react-router"
@ -225,6 +227,8 @@ function LoginWithPassword() {
const { isSubmitting } = form.formState
const navigate = useNavigate()
const recaptchaRef = useRef<ReCAPTCHA>(null)
async function onSubmit(values: z.infer<typeof formSchema>) {
if (needTwoFactor && values.code) {
const res = await twoFactor.verifyTotp({ code: values.code })
@ -236,7 +240,13 @@ function LoginWithPassword() {
return
}
const res = await loginHandler("credential", "app", values)
const token = await recaptchaRef.current?.executeAsync()
const res = await loginHandler("credential", "app", {
...values,
headers: {
"x-token": `r2:${token}`,
},
})
if (res?.error) {
toast.error(res.error.message)
return
@ -303,6 +313,7 @@ function LoginWithPassword() {
)}
/>
)}
<ReCAPTCHA ref={recaptchaRef} sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY} size="invisible" />
<Button type="submit" buttonClassName="!mt-3 w-full" isLoading={isSubmitting} size="lg">
{t("login.continueWith", { provider: t("words.email") })}
</Button>

View File

@ -19,6 +19,8 @@ import { Input } from "@follow/components/ui/input/index.js"
import { env } from "@follow/shared/env"
import { zodResolver } from "@hookform/resolvers/zod"
import { useMutation } from "@tanstack/react-query"
import { useRef } from "react"
import ReCAPTCHA from "react-google-recaptcha"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router"
@ -38,13 +40,23 @@ export function Component() {
},
})
const recaptchaRef = useRef<ReCAPTCHA>(null)
const { isValid } = form.formState
const updateMutation = useMutation({
mutationFn: async (values: z.infer<typeof forgetPasswordFormSchema>) => {
const res = await forgetPassword({
email: values.email,
redirectTo: `${env.VITE_WEB_URL}/reset-password`,
})
const token = await recaptchaRef.current?.executeAsync()
const res = await forgetPassword(
{
email: values.email,
redirectTo: `${env.VITE_WEB_URL}/reset-password`,
},
{
headers: {
"x-token": `r2:${token}`,
},
},
)
if (res.error) {
throw new Error(res.error.message)
}
@ -98,6 +110,11 @@ export function Component() {
</FormItem>
)}
/>
<ReCAPTCHA
ref={recaptchaRef}
sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY}
size="invisible"
/>
<div className="text-right">
<Button disabled={!isValid} type="submit" isLoading={updateMutation.isPending}>
{t("login.submit")}

View File

@ -10,7 +10,7 @@ import {
FormMessage,
} from "@follow/components/ui/form/index.jsx"
import { Input } from "@follow/components/ui/input/index.js"
import { env } from "@follow/shared/env.desktop"
import { env } from "@follow/shared/env"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRef } from "react"
import ReCAPTCHA from "react-google-recaptcha"
@ -138,13 +138,7 @@ function RegisterForm() {
</FormItem>
)}
/>
{env.VITE_RECAPTCHA_V2_SITE_KEY && (
<ReCAPTCHA
ref={recaptchaRef}
sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY}
size="invisible"
/>
)}
<ReCAPTCHA ref={recaptchaRef} sitekey={env.VITE_RECAPTCHA_V2_SITE_KEY} size="invisible" />
<Button disabled={!isValid} type="submit" className="w-full" size="lg">
{t("register.submit")}
</Button>

View File

@ -20,6 +20,9 @@ export const envSchema = {
VITE_EXTERNAL_API_URL: z.string().optional(),
VITE_WEB_PROD_URL: z.string().optional(),
VITE_WEB_DEV_URL: z.string().optional(),
VITE_RECAPTCHA_V2_SITE_KEY: z.string().default("6LdxQ-sqAAAAAN-_za3hUdFkJEO_cu2xHSpLKVan"),
VITE_RECAPTCHA_V3_SITE_KEY: z.string().default("6LdG-asqAAAAAEXr96565MKbRvxGEv31XEykRSHV"),
}
export const isDev = false