Use cloudflare turnstile.

This commit is contained in:
2025-05-15 15:01:39 +08:00
parent 578aab36c3
commit f4e82092eb
11 changed files with 134 additions and 23 deletions

View File

@@ -74,11 +74,12 @@ class Network {
}
}
async register(username: string, password: string): Promise<Response<UserWithToken>> {
async register(username: string, password: string, cfToken: string): Promise<Response<UserWithToken>> {
try {
const response = await axios.postForm(`${this.apiBaseUrl}/user/register`, {
username,
password
password,
cf_token: cfToken
})
return response.data
} catch (e: any) {
@@ -599,8 +600,8 @@ class Network {
}
}
getFileDownloadLink(fileId: string): string {
return `${this.apiBaseUrl}/files/download/${fileId}`;
getFileDownloadLink(fileId: string, cfToken: string): string {
return `${this.apiBaseUrl}/files/download/${fileId}?cf_token=${cfToken}`;
}
async createComment(resourceID: number, content: string): Promise<Response<any>> {

View File

@@ -3,6 +3,7 @@ import {network} from "../network/network.ts";
import {app} from "../app.ts";
import {useNavigate} from "react-router";
import {useTranslation} from "react-i18next";
import {Turnstile} from "@marsidev/react-turnstile";
export default function RegisterPage() {
const {t} = useTranslation();
@@ -11,11 +12,17 @@ export default function RegisterPage() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [cfToken, setCfToken] = useState("");
const navigate = useNavigate();
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError(null);
if (app.cloudflareTurnstileSiteKey && !cfToken) {
setError(t("Please complete the captcha"));
return;
}
if (!username || !password) {
setError(t("Username and password cannot be empty"));
return;
@@ -25,7 +32,7 @@ export default function RegisterPage() {
return;
}
setLoading(true);
const res = await network.register(username, password);
const res = await network.register(username, password, cfToken);
if (res.success) {
app.user = res.data!;
app.token = res.data!.token;
@@ -37,9 +44,9 @@ export default function RegisterPage() {
}
};
useEffect(() => {
document.title = t("Register");
}, [])
useEffect(() => {
document.title = t("Register");
}, [t])
return <div className={"flex items-center justify-center w-full h-full bg-base-200"} id={"register-page"}>
<div className={"w-96 card card-border bg-base-100 border-base-300"}>
@@ -60,12 +67,21 @@ export default function RegisterPage() {
</fieldset>
<fieldset className="fieldset w-full">
<legend className="fieldset-legend">{t("Password")}</legend>
<input type="password" className="input w-full" value={password} onChange={(e) => setPassword(e.target.value)}/>
<input type="password" className="input w-full" value={password}
onChange={(e) => setPassword(e.target.value)}/>
</fieldset>
<fieldset className="fieldset w-full">
<legend className="fieldset-legend">{t("Confirm Password")}</legend>
<input type="password" className="input w-full" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)}/>
<input type="password" className="input w-full" value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}/>
</fieldset>
{
app.cloudflareTurnstileSiteKey && <Turnstile
siteKey={app.cloudflareTurnstileSiteKey}
onSuccess={setCfToken}
onExpire={() => setCfToken("")}
/>
}
<button className={"btn my-4 btn-primary"} type={"submit"}>
{isLoading && <span className="loading loading-spinner"></span>}
{t("Continue")}

View File

@@ -1,5 +1,5 @@
import { useNavigate, useParams } from "react-router";
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import {createContext, createRef, useCallback, useContext, useEffect, useRef, useState} from "react";
import { ResourceDetails, RFile, Storage, Comment } from "../network/models.ts";
import { network } from "../network/network.ts";
import showToast from "../components/toast.ts";
@@ -12,6 +12,8 @@ import { uploadingManager } from "../network/uploading.ts";
import { ErrorAlert } from "../components/alert.tsx";
import { useTranslation } from "react-i18next";
import Pagination from "../components/pagination.tsx";
import showPopup, {useClosePopup} from "../components/popup.tsx";
import {Turnstile} from "@marsidev/react-turnstile";
export default function ResourcePage() {
const params = useParams()
@@ -39,7 +41,7 @@ export default function ResourcePage() {
useEffect(() => {
document.title = t("Resource Details");
}, [])
}, [t])
useEffect(() => {
if (!isNaN(id)) {
@@ -155,6 +157,8 @@ function Article({ article }: { article: string }) {
}
function FileTile({ file }: { file: RFile }) {
const buttonRef = createRef<HTMLButtonElement>()
return <div className={"card card-border border-base-300 my-2"}>
<div className={"p-4 flex flex-row items-center"}>
<div className={"grow"}>
@@ -162,9 +166,13 @@ function FileTile({ file }: { file: RFile }) {
<p className={"text-sm"}>{file.description}</p>
</div>
<div>
<button className={"btn btn-primary btn-soft btn-square"} onClick={() => {
const link = network.getFileDownloadLink(file.id);
window.open(link, "_blank");
<button ref={buttonRef} className={"btn btn-primary btn-soft btn-square"} onClick={() => {
if (!app.cloudflareTurnstileSiteKey) {
const link = network.getFileDownloadLink(file.id, "");
window.open(link, "_blank");
} else {
showPopup(<CloudflarePopup file={file}/>, buttonRef.current!)
}
}}>
<MdOutlineDownload size={24} />
</button>
@@ -173,6 +181,18 @@ function FileTile({ file }: { file: RFile }) {
</div>
}
function CloudflarePopup({ file }: { file: RFile }) {
const closePopup = useClosePopup()
return <div className={"menu bg-base-100 rounded-box z-1 w-80 p-2 shadow-sm h-20"}>
<Turnstile siteKey={app.cloudflareTurnstileSiteKey!} onSuccess={(token) => {
closePopup();
const link = network.getFileDownloadLink(file.id, token);
window.open(link, "_blank");
}}></Turnstile>
</div>
}
function Files({ files, resourceID }: { files: RFile[], resourceID: number }) {
return <div>
{