fix(captcha): close human-verification loop end-to-end

Rewrite captcha core around aksrt_captcha_config only, require captcha
on login/comment/SMS/phone auth and related gates, wire frontend token
collection, and remove dead dual-source security settings plus slider enqueue.
This commit is contained in:
kate522 committed 2026-07-13 19:52:30 +08:00
1 parent a0808bbe54
commit 02f77b756c
21 files changed
+1163 -1515

No files matched your search

+5 -2
View File
@@ -231,8 +231,11 @@
const state = window.AKSRTCaptchaState;
const scene = state && state.scenes && state.scenes.download;
if (!scene || typeof scene.verify !== 'function') {
// captcha 组件还未就绪,直接发,由后端兜底报错
callback();
// captcha 组件未就绪:不直发,避免绕过服务端门槛
if (window.console && console.warn) {
console.warn('[AKSRT] download captcha not ready');
}
alert(tt('captcha_loading', '验证码组件加载中,请稍候再试'));
return;
}
scene.verify(function () { callback(); });
+17 -3
View File
@@ -232,9 +232,15 @@
var replyInfo = document.getElementById('comment-reply-info');
if (replyInfo) replyInfo.style.display = 'none';
// Reset captcha
var captchaImg = form.querySelector('.imagecaptcha img');
if (captchaImg) captchaImg.click();
// Reset captcha after successful submit
try {
var captchaScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (captchaScene && typeof captchaScene.reset === 'function') {
captchaScene.reset();
}
} catch (e) { /* ignore */ }
// Wait time countdown
if (json.wait_time && json.wait_time > 0) {
@@ -251,6 +257,14 @@
if (json && json.code) {
showToast(json.message || '评论提交失败', 'error');
try {
var failScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (failScene && typeof failScene.reset === 'function') {
failScene.reset();
}
} catch (e) { /* ignore */ }
if (json.wait_time) startCountdown(json.wait_time);
else setLoading(false);
return;
+105 -64
View File
@@ -163,9 +163,33 @@
clearMessages();
}
function withCaptcha(scene, form, next) {
var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes
? window.AKSRTCaptchaState.scenes
: null;
var instance = store && store[scene] ? store[scene] : null;
if (instance && typeof instance.verify === 'function') {
instance.verify(function () {
next(serializeForm(form));
});
return;
}
next(serializeForm(form));
}
function resetCaptcha(scene) {
try {
var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes
? window.AKSRTCaptchaState.scenes
: null;
if (store && store[scene] && typeof store[scene].reset === 'function') {
store[scene].reset();
}
} catch (e) { /* ignore */ }
}
function handleLogin(form) {
var payload = serializeForm(form);
if (!payload.log || !payload.pwd) {
if (!form.querySelector('[name="log"]') || !form.querySelector('[name="pwd"]')) {
showMessage('login', '请输入用户名和密码', 'error');
return;
}
@@ -173,76 +197,93 @@
setLoading(form, true);
clearMessages();
request('/auth/modal-login', payload)
.then(function (result) {
if (result.ok && result.data.success) {
onAuthSuccess(result.data);
return;
}
var message = result.data.message || '登录失败';
if (result.data.verify_url) {
message += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('login', message, 'error');
})
.catch(function () {
showMessage('login', '网络错误,请稍后重试', 'error');
})
.finally(function () {
withCaptcha('login', form, function (payload) {
if (!payload.log || !payload.pwd) {
setLoading(form, false);
});
showMessage('login', '请输入用户名和密码', 'error');
return;
}
request('/auth/modal-login', payload)
.then(function (result) {
if (result.ok && result.data.success) {
onAuthSuccess(result.data);
return;
}
resetCaptcha('login');
var message = result.data.message || '登录失败';
if (result.data.verify_url) {
message += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('login', message, 'error');
})
.catch(function () {
resetCaptcha('login');
showMessage('login', '网络错误,请稍后重试', 'error');
})
.finally(function () {
setLoading(form, false);
});
});
}
function handleRegister(form) {
var payload = serializeForm(form);
if (!payload.user_login || !payload.user_email || !payload.user_pass || !payload.user_pass_confirm) {
showMessage('register', '请完整填写注册信息', 'error');
return;
}
if (payload.user_pass !== payload.user_pass_confirm) {
showMessage('register', '两次输入的密码不一致', 'error');
return;
}
setLoading(form, true);
clearMessages();
request('/auth/modal-register', payload)
.then(function (result) {
if (!result.ok || !result.data.success) {
showMessage('register', result.data.message || '注册失败', 'error');
return;
}
if (result.data.user) {
onAuthSuccess(result.data);
return;
}
if (result.data.verification_required) {
var verifyMessage = result.data.message || '注册成功,请完成邮箱验证';
if (result.data.verify_url) {
verifyMessage += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('register', verifyMessage, 'success');
form.reset();
return;
}
var loginMessage = result.data.message || '注册成功,请登录';
switchTab('login');
prefillLogin(result.data.user_login || result.data.user_email || '');
showMessage('login', loginMessage, 'success');
form.reset();
})
.catch(function () {
showMessage('register', '网络错误,请稍后重试', 'error');
})
.finally(function () {
withCaptcha('register', form, function (payload) {
if (!payload.user_login || !payload.user_email || !payload.user_pass || !payload.user_pass_confirm) {
setLoading(form, false);
});
showMessage('register', '请完整填写注册信息', 'error');
return;
}
if (payload.user_pass !== payload.user_pass_confirm) {
setLoading(form, false);
showMessage('register', '两次输入的密码不一致', 'error');
return;
}
request('/auth/modal-register', payload)
.then(function (result) {
if (!result.ok || !result.data.success) {
resetCaptcha('register');
showMessage('register', result.data.message || '注册失败', 'error');
return;
}
if (result.data.user) {
onAuthSuccess(result.data);
return;
}
if (result.data.verification_required) {
var verifyMessage = result.data.message || '注册成功,请完成邮箱验证';
if (result.data.verify_url) {
verifyMessage += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('register', verifyMessage, 'success');
form.reset();
resetCaptcha('register');
return;
}
var loginMessage = result.data.message || '注册成功,请登录';
switchTab('login');
prefillLogin(result.data.user_login || result.data.user_email || '');
showMessage('login', loginMessage, 'success');
form.reset();
resetCaptcha('register');
})
.catch(function () {
resetCaptcha('register');
showMessage('register', '网络错误,请稍后重试', 'error');
})
.finally(function () {
setLoading(form, false);
});
});
}
function serializeForm(form) {
+44 -24
View File
@@ -81,11 +81,12 @@
// 收集人机验证字段
const form = btn.closest('form')
const captchaData = collectCaptchaFields(form)
const scene = btn.dataset.aksrtSmsScene || 'register'
fetch(REST_BASE + '/auth/send-sms-code', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, ...captchaData }),
body: JSON.stringify({ phone, scene, ...captchaData }),
})
.then(r => r.json().then(data => ({ ok: r.ok, data })))
.then(({ ok, data }) => {
@@ -97,6 +98,12 @@
} else {
const msg = (data.data && data.data.message) || data.message || '发送失败'
showToast(msg, 'error')
try {
const captchaState = window.AKSRTCaptchaState
if (captchaState && captchaState.scenes && captchaState.scenes[scene]) {
captchaState.scenes[scene].reset()
}
} catch (e) { /* ignore */ }
const remaining = data.data && data.data.remaining_time
if (remaining && remaining > 0) {
sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + remaining * 1000))
@@ -265,33 +272,46 @@
body = { phone, password }
}
fetch(REST_BASE + endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
.then(r => r.json().then(data => ({ ok: r.ok, data })))
.then(({ ok, data }) => {
if (ok && data.success) {
showToast('登录成功', 'success')
setTimeout(() => {
window.location.href = data.data?.redirect || '/'
}, 500)
} else {
showToast(data.message || '登录失败', 'error')
const captchaState = window.AKSRTCaptchaState
const doLogin = () => {
const captchaData = collectCaptchaFields(form)
fetch(REST_BASE + endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...body, ...captchaData }),
})
.then(r => r.json().then(data => ({ ok: r.ok, data })))
.then(({ ok, data }) => {
if (ok && data.success) {
showToast('登录成功', 'success')
setTimeout(() => {
window.location.href = data.data?.redirect || '/'
}, 500)
} else {
showToast(data.message || '登录失败', 'error')
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
captchaState.scenes.login.reset()
}
if (submitBtn) {
submitBtn.disabled = false
submitBtn.textContent = '登录'
}
}
})
.catch(() => {
showToast('网络错误', 'error')
if (submitBtn) {
submitBtn.disabled = false
submitBtn.textContent = '登录'
}
}
})
.catch(() => {
showToast('网络错误', 'error')
if (submitBtn) {
submitBtn.disabled = false
submitBtn.textContent = '登录'
}
})
})
}
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
captchaState.scenes.login.verify(doLogin)
} else {
doLogin()
}
}
/* =================================================================