mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-19 05:57:28 +00:00
feat: check-in feature integrates Turnstile security check
This commit is contained in:
@@ -96,7 +96,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
|
|
||||||
// Check-in routes
|
// Check-in routes
|
||||||
selfRoute.GET("/checkin", controller.GetCheckinStatus)
|
selfRoute.GET("/checkin", controller.GetCheckinStatus)
|
||||||
selfRoute.POST("/checkin", controller.DoCheckin)
|
selfRoute.POST("/checkin", middleware.TurnstileCheck(), controller.DoCheckin)
|
||||||
}
|
}
|
||||||
|
|
||||||
adminRoute := userRoute.Group("/")
|
adminRoute := userRoute.Group("/")
|
||||||
|
|||||||
@@ -451,7 +451,12 @@ const PersonalSetting = () => {
|
|||||||
{/* 签到日历 - 仅在启用时显示 */}
|
{/* 签到日历 - 仅在启用时显示 */}
|
||||||
{status?.checkin_enabled && (
|
{status?.checkin_enabled && (
|
||||||
<div className='mt-4 md:mt-6'>
|
<div className='mt-4 md:mt-6'>
|
||||||
<CheckinCalendar t={t} status={status} />
|
<CheckinCalendar
|
||||||
|
t={t}
|
||||||
|
status={status}
|
||||||
|
turnstileEnabled={turnstileEnabled}
|
||||||
|
turnstileSiteKey={turnstileSiteKey}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Collapsible,
|
Collapsible,
|
||||||
|
Modal,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import {
|
import {
|
||||||
CalendarCheck,
|
CalendarCheck,
|
||||||
@@ -35,11 +36,14 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import Turnstile from 'react-turnstile';
|
||||||
import { API, showError, showSuccess, renderQuota } from '../../../../helpers';
|
import { API, showError, showSuccess, renderQuota } from '../../../../helpers';
|
||||||
|
|
||||||
const CheckinCalendar = ({ t, status }) => {
|
const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [checkinLoading, setCheckinLoading] = useState(false);
|
const [checkinLoading, setCheckinLoading] = useState(false);
|
||||||
|
const [turnstileModalVisible, setTurnstileModalVisible] = useState(false);
|
||||||
|
const [turnstileWidgetKey, setTurnstileWidgetKey] = useState(0);
|
||||||
const [checkinData, setCheckinData] = useState({
|
const [checkinData, setCheckinData] = useState({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
stats: {
|
stats: {
|
||||||
@@ -109,11 +113,23 @@ const CheckinCalendar = ({ t, status }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 执行签到
|
const postCheckin = async (token) => {
|
||||||
const doCheckin = async () => {
|
const url = token
|
||||||
|
? `/api/user/checkin?turnstile=${encodeURIComponent(token)}`
|
||||||
|
: '/api/user/checkin';
|
||||||
|
return API.post(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldTriggerTurnstile = (message) => {
|
||||||
|
if (!turnstileEnabled) return false;
|
||||||
|
if (typeof message !== 'string') return true;
|
||||||
|
return message.includes('Turnstile');
|
||||||
|
};
|
||||||
|
|
||||||
|
const doCheckin = async (token) => {
|
||||||
setCheckinLoading(true);
|
setCheckinLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await API.post('/api/user/checkin');
|
const res = await postCheckin(token);
|
||||||
const { success, data, message } = res.data;
|
const { success, data, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
showSuccess(
|
showSuccess(
|
||||||
@@ -121,7 +137,19 @@ const CheckinCalendar = ({ t, status }) => {
|
|||||||
);
|
);
|
||||||
// 刷新签到状态
|
// 刷新签到状态
|
||||||
fetchCheckinStatus(currentMonth);
|
fetchCheckinStatus(currentMonth);
|
||||||
|
setTurnstileModalVisible(false);
|
||||||
} else {
|
} else {
|
||||||
|
if (!token && shouldTriggerTurnstile(message)) {
|
||||||
|
if (!turnstileSiteKey) {
|
||||||
|
showError('Turnstile is enabled but site key is empty.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTurnstileModalVisible(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (token && shouldTriggerTurnstile(message)) {
|
||||||
|
setTurnstileWidgetKey((v) => v + 1);
|
||||||
|
}
|
||||||
showError(message || t('签到失败'));
|
showError(message || t('签到失败'));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -186,6 +214,30 @@ const CheckinCalendar = ({ t, status }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='!rounded-2xl'>
|
<Card className='!rounded-2xl'>
|
||||||
|
<Modal
|
||||||
|
title='Security Check'
|
||||||
|
visible={turnstileModalVisible}
|
||||||
|
footer={null}
|
||||||
|
centered
|
||||||
|
onCancel={() => {
|
||||||
|
setTurnstileModalVisible(false);
|
||||||
|
setTurnstileWidgetKey((v) => v + 1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='flex justify-center py-2'>
|
||||||
|
<Turnstile
|
||||||
|
key={turnstileWidgetKey}
|
||||||
|
sitekey={turnstileSiteKey}
|
||||||
|
onVerify={(token) => {
|
||||||
|
doCheckin(token);
|
||||||
|
}}
|
||||||
|
onExpire={() => {
|
||||||
|
setTurnstileWidgetKey((v) => v + 1);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
{/* 卡片头部 */}
|
{/* 卡片头部 */}
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div
|
<div
|
||||||
@@ -221,7 +273,7 @@ const CheckinCalendar = ({ t, status }) => {
|
|||||||
type='primary'
|
type='primary'
|
||||||
theme='solid'
|
theme='solid'
|
||||||
icon={<Gift size={16} />}
|
icon={<Gift size={16} />}
|
||||||
onClick={doCheckin}
|
onClick={() => doCheckin()}
|
||||||
loading={checkinLoading || !initialLoaded}
|
loading={checkinLoading || !initialLoaded}
|
||||||
disabled={!initialLoaded || checkinData.stats?.checked_in_today}
|
disabled={!initialLoaded || checkinData.stats?.checked_in_today}
|
||||||
className='!bg-green-600 hover:!bg-green-700'
|
className='!bg-green-600 hover:!bg-green-700'
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ export class SecureVerificationService {
|
|||||||
isPasskeySupported(),
|
isPasskeySupported(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log('=== DEBUGGING VERIFICATION METHODS ===');
|
// console.log('=== DEBUGGING VERIFICATION METHODS ===');
|
||||||
console.log('2FA Response:', JSON.stringify(twoFAResponse, null, 2));
|
// console.log('2FA Response:', JSON.stringify(twoFAResponse, null, 2));
|
||||||
console.log(
|
// console.log(
|
||||||
'Passkey Response:',
|
// 'Passkey Response:',
|
||||||
JSON.stringify(passkeyResponse, null, 2),
|
// JSON.stringify(passkeyResponse, null, 2),
|
||||||
);
|
// );
|
||||||
|
|
||||||
const has2FA =
|
const has2FA =
|
||||||
twoFAResponse.data?.success &&
|
twoFAResponse.data?.success &&
|
||||||
|
|||||||
Reference in New Issue
Block a user