Procházet zdrojové kódy

feat: check-in feature integrates Turnstile security check

Seefs před 1 měsícem
rodič
revize
c33ac97c71

+ 1 - 1
router/api-router.go

@@ -96,7 +96,7 @@ func SetApiRouter(router *gin.Engine) {
 
 				// Check-in routes
 				selfRoute.GET("/checkin", controller.GetCheckinStatus)
-				selfRoute.POST("/checkin", controller.DoCheckin)
+				selfRoute.POST("/checkin", middleware.TurnstileCheck(), controller.DoCheckin)
 			}
 
 			adminRoute := userRoute.Group("/")

+ 6 - 1
web/src/components/settings/PersonalSetting.jsx

@@ -451,7 +451,12 @@ const PersonalSetting = () => {
           {/* 签到日历 - 仅在启用时显示 */}
           {status?.checkin_enabled && (
             <div className='mt-4 md:mt-6'>
-              <CheckinCalendar t={t} status={status} />
+              <CheckinCalendar
+                t={t}
+                status={status}
+                turnstileEnabled={turnstileEnabled}
+                turnstileSiteKey={turnstileSiteKey}
+              />
             </div>
           )}
 

+ 57 - 5
web/src/components/settings/personal/cards/CheckinCalendar.jsx

@@ -27,6 +27,7 @@ import {
   Spin,
   Tooltip,
   Collapsible,
+  Modal,
 } from '@douyinfe/semi-ui';
 import {
   CalendarCheck,
@@ -35,11 +36,14 @@ import {
   ChevronDown,
   ChevronUp,
 } from 'lucide-react';
+import Turnstile from 'react-turnstile';
 import { API, showError, showSuccess, renderQuota } from '../../../../helpers';
 
-const CheckinCalendar = ({ t, status }) => {
+const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
   const [loading, setLoading] = useState(false);
   const [checkinLoading, setCheckinLoading] = useState(false);
+  const [turnstileModalVisible, setTurnstileModalVisible] = useState(false);
+  const [turnstileWidgetKey, setTurnstileWidgetKey] = useState(0);
   const [checkinData, setCheckinData] = useState({
     enabled: false,
     stats: {
@@ -109,11 +113,23 @@ const CheckinCalendar = ({ t, status }) => {
     }
   };
 
-  // 执行签到
-  const doCheckin = async () => {
+  const postCheckin = async (token) => {
+    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);
     try {
-      const res = await API.post('/api/user/checkin');
+      const res = await postCheckin(token);
       const { success, data, message } = res.data;
       if (success) {
         showSuccess(
@@ -121,7 +137,19 @@ const CheckinCalendar = ({ t, status }) => {
         );
         // 刷新签到状态
         fetchCheckinStatus(currentMonth);
+        setTurnstileModalVisible(false);
       } 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('签到失败'));
       }
     } catch (error) {
@@ -186,6 +214,30 @@ const CheckinCalendar = ({ t, status }) => {
 
   return (
     <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
@@ -221,7 +273,7 @@ const CheckinCalendar = ({ t, status }) => {
           type='primary'
           theme='solid'
           icon={<Gift size={16} />}
-          onClick={doCheckin}
+          onClick={() => doCheckin()}
           loading={checkinLoading || !initialLoaded}
           disabled={!initialLoaded || checkinData.stats?.checked_in_today}
           className='!bg-green-600 hover:!bg-green-700'

+ 6 - 6
web/src/services/secureVerification.js

@@ -42,12 +42,12 @@ export class SecureVerificationService {
           isPasskeySupported(),
         ]);
 
-      console.log('=== DEBUGGING VERIFICATION METHODS ===');
-      console.log('2FA Response:', JSON.stringify(twoFAResponse, null, 2));
-      console.log(
-        'Passkey Response:',
-        JSON.stringify(passkeyResponse, null, 2),
-      );
+      // console.log('=== DEBUGGING VERIFICATION METHODS ===');
+      // console.log('2FA Response:', JSON.stringify(twoFAResponse, null, 2));
+      // console.log(
+      //   'Passkey Response:',
+      //   JSON.stringify(passkeyResponse, null, 2),
+      // );
 
       const has2FA =
         twoFAResponse.data?.success &&