fix(auth): add submit Turnstile widget in email verify flow

- 邮箱验证码流程中,提交注册前要求重新完成 Turnstile 验证
- 修复发送验证码后 token 被清空导致注册时缺少 turnstile_token 的问题
- submit/resend 两个 widget 通过 showResendTurnstile 互斥显示
This commit is contained in:
erio
2026-03-01 13:02:12 +08:00
parent 99663a3f20
commit 51e7f262bd
2 changed files with 44 additions and 5 deletions

View File

@@ -113,8 +113,7 @@ func (h *AuthHandler) Register(c *gin.Context) {
return return
} }
// Turnstile 验证 — 始终执行,防止绕过 // Turnstile 验证 — 始终执行,防止机器人自动化注册
// TODO: 确认前端在提交邮箱验证码注册时也传递了 turnstile_token
if err := h.authService.VerifyTurnstile(c.Request.Context(), req.TurnstileToken, ip.GetClientIP(c)); err != nil { if err := h.authService.VerifyTurnstile(c.Request.Context(), req.TurnstileToken, ip.GetClientIP(c)); err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return

View File

@@ -69,6 +69,20 @@
</div> </div>
</div> </div>
<!-- Turnstile Widget for Submit -->
<div v-if="turnstileEnabled && turnstileSiteKey && !showResendTurnstile">
<TurnstileWidget
ref="submitTurnstileRef"
:site-key="turnstileSiteKey"
@verify="onSubmitTurnstileVerify"
@expire="onSubmitTurnstileExpire"
@error="onSubmitTurnstileError"
/>
<p v-if="errors.submitTurnstile" class="input-error-text mt-2 text-center">
{{ errors.submitTurnstile }}
</p>
</div>
<!-- Turnstile Widget for Resend --> <!-- Turnstile Widget for Resend -->
<div v-if="turnstileEnabled && turnstileSiteKey && showResendTurnstile"> <div v-if="turnstileEnabled && turnstileSiteKey && showResendTurnstile">
<TurnstileWidget <TurnstileWidget
@@ -101,7 +115,7 @@
</transition> </transition>
<!-- Submit Button --> <!-- Submit Button -->
<button type="submit" :disabled="isLoading || !verifyCode" class="btn btn-primary w-full"> <button type="submit" :disabled="isLoading || !verifyCode || (turnstileEnabled && !submitTurnstileToken)" class="btn btn-primary w-full">
<svg <svg
v-if="isLoading" v-if="isLoading"
class="-ml-1 mr-2 h-4 w-4 animate-spin text-white" class="-ml-1 mr-2 h-4 w-4 animate-spin text-white"
@@ -209,6 +223,10 @@ const turnstileEnabled = ref<boolean>(false)
const turnstileSiteKey = ref<string>('') const turnstileSiteKey = ref<string>('')
const siteName = ref<string>('Sub2API') const siteName = ref<string>('Sub2API')
// Turnstile for submit
const submitTurnstileRef = ref<InstanceType<typeof TurnstileWidget> | null>(null)
const submitTurnstileToken = ref<string>('')
// Turnstile for resend // Turnstile for resend
const turnstileRef = ref<InstanceType<typeof TurnstileWidget> | null>(null) const turnstileRef = ref<InstanceType<typeof TurnstileWidget> | null>(null)
const resendTurnstileToken = ref<string>('') const resendTurnstileToken = ref<string>('')
@@ -216,7 +234,8 @@ const showResendTurnstile = ref<boolean>(false)
const errors = ref({ const errors = ref({
code: '', code: '',
turnstile: '' turnstile: '',
submitTurnstile: ''
}) })
// ==================== Lifecycle ==================== // ==================== Lifecycle ====================
@@ -284,6 +303,21 @@ function startCountdown(seconds: number): void {
// ==================== Turnstile Handlers ==================== // ==================== Turnstile Handlers ====================
function onSubmitTurnstileVerify(token: string): void {
submitTurnstileToken.value = token
errors.value.submitTurnstile = ''
}
function onSubmitTurnstileExpire(): void {
submitTurnstileToken.value = ''
errors.value.submitTurnstile = 'Verification expired, please try again'
}
function onSubmitTurnstileError(): void {
submitTurnstileToken.value = ''
errors.value.submitTurnstile = 'Verification failed, please try again'
}
function onTurnstileVerify(token: string): void { function onTurnstileVerify(token: string): void {
resendTurnstileToken.value = token resendTurnstileToken.value = token
errors.value.turnstile = '' errors.value.turnstile = ''
@@ -385,7 +419,7 @@ async function handleVerify(): Promise<void> {
email: email.value, email: email.value,
password: password.value, password: password.value,
verify_code: verifyCode.value.trim(), verify_code: verifyCode.value.trim(),
turnstile_token: initialTurnstileToken.value || undefined, turnstile_token: submitTurnstileToken.value || undefined,
promo_code: promoCode.value || undefined, promo_code: promoCode.value || undefined,
invitation_code: invitationCode.value || undefined invitation_code: invitationCode.value || undefined
}) })
@@ -399,6 +433,12 @@ async function handleVerify(): Promise<void> {
// Redirect to dashboard // Redirect to dashboard
await router.push('/dashboard') await router.push('/dashboard')
} catch (error: unknown) { } catch (error: unknown) {
// Reset submit turnstile on error
if (submitTurnstileRef.value) {
submitTurnstileRef.value.reset()
submitTurnstileToken.value = ''
}
const err = error as { message?: string; response?: { data?: { detail?: string } } } const err = error as { message?: string; response?: { data?: { detail?: string } } }
if (err.response?.data?.detail) { if (err.response?.data?.detail) {