mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2025-12-06 06:58:54 +08:00
1
exedata/capjs/capversion
Normal file
1
exedata/capjs/capversion
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.0.6
|
||||||
@@ -1,335 +1,357 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0" />
|
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0" />
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
<meta http-equiv="Expires" content="0" />
|
<meta http-equiv="Expires" content="0" />
|
||||||
<title id="page-title">进行验证</title>
|
<title id="page-title">进行验证</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #3C3C3C;
|
color: #3C3C3C;
|
||||||
background: #EBF3FB;
|
background: #EBF3FB;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-switch {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #3C3C3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-switch:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
color: #5eaa2f;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-family: system, -apple-system, "BlinkMacSystemFont",
|
||||||
|
".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI",
|
||||||
|
"Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #ed4630;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-family: system, -apple-system, "BlinkMacSystemFont",
|
||||||
|
".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI",
|
||||||
|
"Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast样式 */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.success {
|
||||||
|
background: #f0f9eb;
|
||||||
|
border: 1px solid #dcf9cc;
|
||||||
|
color: #5eaa2f;
|
||||||
|
box-shadow: 1px 1px 10px #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.error {
|
||||||
|
background: #fef0f0;
|
||||||
|
border: 1px solid #fcd6d6;
|
||||||
|
color: #ed4630;
|
||||||
|
box-shadow: 1px 1px 10px #e0e0e0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button id="lang-switch" class="lang-switch">English</button>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- 添加提示信息区域 -->
|
||||||
|
<div class="info-message" id="info-message">
|
||||||
|
<h2 id="info-title">安全验证</h2>
|
||||||
|
<p id="info-text">为了确保您的访问安全,请完成以下验证</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<cap-widget
|
||||||
|
id="cap"
|
||||||
|
data-cap-api-endpoint="/samwaf_captcha/"
|
||||||
|
data-cap-i18n-verifying-label="验证中..."
|
||||||
|
data-cap-i18n-initial-state="我是人类"
|
||||||
|
data-cap-i18n-solved-label="我是人类"
|
||||||
|
data-cap-i18n-error-label="错误"
|
||||||
|
></cap-widget>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 语言资源
|
||||||
|
const i18n = {
|
||||||
|
'zh': {
|
||||||
|
title: '进行验证',
|
||||||
|
verifySuccess: '验证成功,页面即将刷新...',
|
||||||
|
verifyFail: '验证失败',
|
||||||
|
validationError: '验证异常',
|
||||||
|
switchLang: 'English',
|
||||||
|
// 添加提示信息的中文翻译
|
||||||
|
infoTitle: '安全验证',
|
||||||
|
infoText: '为了确保您的访问安全,请完成以下验证',
|
||||||
|
// Cap.js widget 多语言
|
||||||
|
capWidget: {
|
||||||
|
verifyingLabel: '验证中...',
|
||||||
|
initialState: '我是人类',
|
||||||
|
solvedLabel: '我是人类',
|
||||||
|
errorLabel: '错误'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'en': {
|
||||||
|
title: 'Verification',
|
||||||
|
verifySuccess: 'Verification successful, page will refresh...',
|
||||||
|
verifyFail: 'Verification failed',
|
||||||
|
validationError: 'Validation error',
|
||||||
|
switchLang: '中文',
|
||||||
|
// 添加提示信息的英文翻译
|
||||||
|
infoTitle: 'Security Verification',
|
||||||
|
infoText: 'To ensure the security of your access, please complete the following verification',
|
||||||
|
// Cap.js widget 多语言
|
||||||
|
capWidget: {
|
||||||
|
verifyingLabel: 'Verifying...',
|
||||||
|
initialState: "I'm a human",
|
||||||
|
solvedLabel: "I'm a human",
|
||||||
|
errorLabel: 'Error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 语言管理
|
||||||
|
const langManager = (function() {
|
||||||
|
// Cookie 操作函数
|
||||||
|
function setCookie(name, value, days) {
|
||||||
|
let expires = "";
|
||||||
|
if (days) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
const nameEQ = name + "=";
|
||||||
|
const ca = document.cookie.split(';');
|
||||||
|
for(let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测浏览器语言
|
||||||
|
function detectBrowserLang() {
|
||||||
|
const browserLang = navigator.language || navigator.userLanguage;
|
||||||
|
return browserLang.toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentLang = getCookie('samwaf_lang') || detectBrowserLang();
|
||||||
|
|
||||||
|
function setLanguage(lang) {
|
||||||
|
currentLang = lang;
|
||||||
|
setCookie('samwaf_lang', lang, 30); // 保存30天
|
||||||
|
applyLanguage();
|
||||||
|
return currentLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLang() {
|
||||||
|
return currentLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getText(key) {
|
||||||
|
return i18n[currentLang][key] || i18n['en'][key] || key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCapWidgetText(key) {
|
||||||
|
return i18n[currentLang].capWidget[key] || i18n['en'].capWidget[key] || key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyLanguage() {
|
||||||
|
// 更新页面标题
|
||||||
|
document.getElementById('page-title').textContent = getText('title');
|
||||||
|
// 更新语言切换按钮
|
||||||
|
document.getElementById('lang-switch').textContent = getText('switchLang');
|
||||||
|
|
||||||
|
// 更新提示信息的多语言内容
|
||||||
|
const infoTitle = document.getElementById('info-title');
|
||||||
|
const infoText = document.getElementById('info-text');
|
||||||
|
if (infoTitle) {
|
||||||
|
infoTitle.textContent = getText('infoTitle');
|
||||||
|
}
|
||||||
|
if (infoText) {
|
||||||
|
infoText.textContent = getText('infoText');
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
// 更新cap-widget的多语言属性
|
||||||
display: flex;
|
const capWidget = document.getElementById('cap');
|
||||||
flex-direction: column;
|
if (capWidget) {
|
||||||
align-items: center;
|
capWidget.setAttribute('data-cap-i18n-verifying-label', getCapWidgetText('verifyingLabel'));
|
||||||
justify-content: center;
|
capWidget.setAttribute('data-cap-i18n-initial-state', getCapWidgetText('initialState'));
|
||||||
padding: 40px 0;
|
capWidget.setAttribute('data-cap-i18n-solved-label', getCapWidgetText('solvedLabel'));
|
||||||
|
capWidget.setAttribute('data-cap-i18n-error-label', getCapWidgetText('errorLabel'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.lang-switch {
|
return {
|
||||||
position: absolute;
|
setLanguage,
|
||||||
top: 10px;
|
getCurrentLang,
|
||||||
right: 10px;
|
getText,
|
||||||
background: #fff;
|
getCapWidgetText,
|
||||||
border: 1px solid #ddd;
|
applyLanguage
|
||||||
border-radius: 4px;
|
};
|
||||||
padding: 5px 10px;
|
})();
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #3C3C3C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lang-switch:hover {
|
// Toast提示函数
|
||||||
background: #f5f5f5;
|
function showToast(message, type = 'success') {
|
||||||
}
|
// 移除已存在的toast
|
||||||
|
const existingToast = document.querySelector('.toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
.success-message {
|
const toast = document.createElement('div');
|
||||||
color: #5eaa2f;
|
toast.className = `toast ${type}`;
|
||||||
text-align: center;
|
toast.textContent = message;
|
||||||
font-size: 18px;
|
document.body.appendChild(toast);
|
||||||
margin-top: 20px;
|
|
||||||
font-family: system, -apple-system, "BlinkMacSystemFont",
|
|
||||||
".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI",
|
|
||||||
"Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
// 显示toast
|
||||||
color: #ed4630;
|
setTimeout(() => {
|
||||||
text-align: center;
|
toast.classList.add('show');
|
||||||
font-size: 16px;
|
}, 100);
|
||||||
margin-top: 20px;
|
|
||||||
font-family: system, -apple-system, "BlinkMacSystemFont",
|
|
||||||
".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI",
|
|
||||||
"Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toast样式 */
|
// 隐藏toast
|
||||||
.toast {
|
setTimeout(() => {
|
||||||
position: fixed;
|
toast.classList.remove('show');
|
||||||
top: 50%;
|
setTimeout(() => {
|
||||||
left: 50%;
|
if (toast.parentNode) {
|
||||||
transform: translate(-50%, -50%);
|
toast.parentNode.removeChild(toast);
|
||||||
padding: 12px 24px;
|
}
|
||||||
border-radius: 6px;
|
}, 300);
|
||||||
font-size: 14px;
|
}, 2000);
|
||||||
z-index: 1000;
|
}
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.show {
|
// 初始化语言
|
||||||
opacity: 1;
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
}
|
langManager.applyLanguage();
|
||||||
|
});
|
||||||
|
|
||||||
.toast.success {
|
// 语言切换按钮事件
|
||||||
background: #f0f9eb;
|
document.getElementById('lang-switch').addEventListener('click', function() {
|
||||||
border: 1px solid #dcf9cc;
|
const newLang = langManager.getCurrentLang() === 'zh' ? 'en' : 'zh';
|
||||||
color: #5eaa2f;
|
langManager.setLanguage(newLang);
|
||||||
box-shadow: 1px 1px 10px #e0e0e0;
|
// 刷新页面以重新初始化验证码
|
||||||
}
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
.toast.error {
|
// 动态获取当前页面的协议、域名和端口
|
||||||
background: #fef0f0;
|
const currentProtocol = window.location.protocol; // http: 或 https:
|
||||||
border: 1px solid #fcd6d6;
|
const currentHost = window.location.host; // 包含域名和端口
|
||||||
color: #ed4630;
|
window.CAP_CUSTOM_WASM_URL = `${currentProtocol}//${currentHost}/samwaf_captcha/cap_wasm.min.js`;
|
||||||
box-shadow: 1px 1px 10px #e0e0e0;
|
</script>
|
||||||
}
|
<script src="/samwaf_captcha/widget.js"></script>
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<button id="lang-switch" class="lang-switch">English</button>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<cap-widget
|
|
||||||
id="cap"
|
|
||||||
data-cap-api-endpoint="/samwaf_captcha/"
|
|
||||||
data-cap-i18n-verifying-label="验证中..."
|
|
||||||
data-cap-i18n-initial-state="我是人类"
|
|
||||||
data-cap-i18n-solved-label="我是人类"
|
|
||||||
data-cap-i18n-error-label="错误"
|
|
||||||
></cap-widget>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 语言资源
|
const widget = document.querySelector("#cap");
|
||||||
const i18n = {
|
|
||||||
'zh': {
|
widget.addEventListener("solve", function (e) {
|
||||||
title: '进行验证',
|
const token = e.detail.token;
|
||||||
verifySuccess: '验证成功,页面即将刷新...',
|
// console.log("Captcha solved!");
|
||||||
verifyFail: '验证失败',
|
//console.log("Token:" + token); // Token is returned by the server
|
||||||
validationError: '验证异常',
|
|
||||||
switchLang: 'English',
|
// Submit token to backend for validation
|
||||||
// Cap.js widget 多语言
|
validateToken(token);
|
||||||
capWidget: {
|
});
|
||||||
verifyingLabel: '验证中...',
|
|
||||||
initialState: '我是人类',
|
// Function to validate token with backend
|
||||||
solvedLabel: '我是人类',
|
async function validateToken(token) {
|
||||||
errorLabel: '错误'
|
try {
|
||||||
}
|
const response = await fetch('/samwaf_captcha/validate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
'en': {
|
body: JSON.stringify({ token: token })
|
||||||
title: 'Verification',
|
});
|
||||||
verifySuccess: 'Verification successful, page will refresh...',
|
|
||||||
verifyFail: 'Verification failed',
|
|
||||||
validationError: 'Validation error',
|
|
||||||
switchLang: '中文',
|
|
||||||
// Cap.js widget 多语言
|
|
||||||
capWidget: {
|
|
||||||
verifyingLabel: 'Verifying...',
|
|
||||||
initialState: "I'm a human",
|
|
||||||
solvedLabel: "I'm a human",
|
|
||||||
errorLabel: 'Error'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 语言管理
|
if (!response.ok) {
|
||||||
const langManager = (function() {
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
// Cookie 操作函数
|
}
|
||||||
function setCookie(name, value, days) {
|
|
||||||
let expires = "";
|
|
||||||
if (days) {
|
|
||||||
const date = new Date();
|
|
||||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
||||||
expires = "; expires=" + date.toUTCString();
|
|
||||||
}
|
|
||||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCookie(name) {
|
const result = await response.json();
|
||||||
const nameEQ = name + "=";
|
|
||||||
const ca = document.cookie.split(';');
|
|
||||||
for(let i = 0; i < ca.length; i++) {
|
|
||||||
let c = ca[i];
|
|
||||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
|
||||||
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测浏览器语言
|
if (result.success) {
|
||||||
function detectBrowserLang() {
|
// 隐藏验证码组件
|
||||||
const browserLang = navigator.language || navigator.userLanguage;
|
document.querySelector('#cap').style.display = 'none';
|
||||||
return browserLang.toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
// 隐藏语言切换按钮
|
||||||
}
|
document.getElementById('lang-switch').style.display = 'none';
|
||||||
|
|
||||||
let currentLang = getCookie('samwaf_lang') || detectBrowserLang();
|
// 显示成功消息
|
||||||
|
const successEl = document.createElement("div");
|
||||||
|
successEl.className = "success-message";
|
||||||
|
successEl.textContent = langManager.getText('verifySuccess');
|
||||||
|
document.querySelector(".container").appendChild(successEl);
|
||||||
|
|
||||||
function setLanguage(lang) {
|
// 显示成功toast
|
||||||
currentLang = lang;
|
showToast(langManager.getText('verifySuccess'), 'success');
|
||||||
setCookie('samwaf_lang', lang, 30); // 保存30天
|
|
||||||
applyLanguage();
|
|
||||||
return currentLang;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrentLang() {
|
// 2秒后刷新页面
|
||||||
return currentLang;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getText(key) {
|
|
||||||
return i18n[currentLang][key] || i18n['en'][key] || key;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCapWidgetText(key) {
|
|
||||||
return i18n[currentLang].capWidget[key] || i18n['en'].capWidget[key] || key;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyLanguage() {
|
|
||||||
// 更新页面标题
|
|
||||||
document.getElementById('page-title').textContent = getText('title');
|
|
||||||
// 更新语言切换按钮
|
|
||||||
document.getElementById('lang-switch').textContent = getText('switchLang');
|
|
||||||
|
|
||||||
// 更新cap-widget的多语言属性
|
|
||||||
const capWidget = document.getElementById('cap');
|
|
||||||
if (capWidget) {
|
|
||||||
capWidget.setAttribute('data-cap-i18n-verifying-label', getCapWidgetText('verifyingLabel'));
|
|
||||||
capWidget.setAttribute('data-cap-i18n-initial-state', getCapWidgetText('initialState'));
|
|
||||||
capWidget.setAttribute('data-cap-i18n-solved-label', getCapWidgetText('solvedLabel'));
|
|
||||||
capWidget.setAttribute('data-cap-i18n-error-label', getCapWidgetText('errorLabel'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
setLanguage,
|
|
||||||
getCurrentLang,
|
|
||||||
getText,
|
|
||||||
getCapWidgetText,
|
|
||||||
applyLanguage
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Toast提示函数
|
|
||||||
function showToast(message, type = 'success') {
|
|
||||||
// 移除已存在的toast
|
|
||||||
const existingToast = document.querySelector('.toast');
|
|
||||||
if (existingToast) {
|
|
||||||
existingToast.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = `toast ${type}`;
|
|
||||||
toast.textContent = message;
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
// 显示toast
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.classList.add('show');
|
window.location.reload();
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// 隐藏toast
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.classList.remove('show');
|
|
||||||
setTimeout(() => {
|
|
||||||
if (toast.parentNode) {
|
|
||||||
toast.parentNode.removeChild(toast);
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
// 显示失败toast
|
||||||
|
showToast(langManager.getText('verifyFail'), 'error');
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
// 初始化语言
|
console.error('Validation error:', error);
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
// 显示错误toast
|
||||||
langManager.applyLanguage();
|
showToast(langManager.getText('validationError') + ': ' + error.message, 'error');
|
||||||
});
|
}
|
||||||
|
}
|
||||||
// 语言切换按钮事件
|
</script>
|
||||||
document.getElementById('lang-switch').addEventListener('click', function() {
|
</html>
|
||||||
const newLang = langManager.getCurrentLang() === 'zh' ? 'en' : 'zh';
|
|
||||||
langManager.setLanguage(newLang);
|
|
||||||
// 刷新页面以重新初始化验证码
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 动态获取当前页面的协议、域名和端口
|
|
||||||
const currentProtocol = window.location.protocol; // http: 或 https:
|
|
||||||
const currentHost = window.location.host; // 包含域名和端口
|
|
||||||
window.CAP_CUSTOM_WASM_URL = `${currentProtocol}//${currentHost}/samwaf_captcha/cap_wasm.min.js`;
|
|
||||||
</script>
|
|
||||||
<script src="/samwaf_captcha/widget.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const widget = document.querySelector("#cap");
|
|
||||||
|
|
||||||
widget.addEventListener("solve", function (e) {
|
|
||||||
const token = e.detail.token;
|
|
||||||
// console.log("Captcha solved!");
|
|
||||||
//console.log("Token:" + token); // Token is returned by the server
|
|
||||||
|
|
||||||
// Submit token to backend for validation
|
|
||||||
validateToken(token);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to validate token with backend
|
|
||||||
async function validateToken(token) {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/samwaf_captcha/validate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ token: token })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
// 隐藏验证码组件
|
|
||||||
document.querySelector('#cap').style.display = 'none';
|
|
||||||
// 隐藏语言切换按钮
|
|
||||||
document.getElementById('lang-switch').style.display = 'none';
|
|
||||||
|
|
||||||
// 显示成功消息
|
|
||||||
const successEl = document.createElement("div");
|
|
||||||
successEl.className = "success-message";
|
|
||||||
successEl.textContent = langManager.getText('verifySuccess');
|
|
||||||
document.querySelector(".container").appendChild(successEl);
|
|
||||||
|
|
||||||
// 显示成功toast
|
|
||||||
showToast(langManager.getText('verifySuccess'), 'success');
|
|
||||||
|
|
||||||
// 2秒后刷新页面
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
// 显示失败toast
|
|
||||||
showToast(langManager.getText('verifyFail'), 'error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Validation error:', error);
|
|
||||||
// 显示错误toast
|
|
||||||
showToast(langManager.getText('validationError') + ': ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
@@ -1 +1 @@
|
|||||||
0.0.6
|
1.0.1
|
||||||
@@ -77,6 +77,14 @@ type CaptchaConfig struct {
|
|||||||
ChallengeSize int `json:"challengeSize,omitempty"` // Size of each challenge in bytes (default: 32)
|
ChallengeSize int `json:"challengeSize,omitempty"` // Size of each challenge in bytes (default: 32)
|
||||||
ChallengeDifficulty int `json:"challengeDifficulty,omitempty"` // Difficulty level (default: 4)
|
ChallengeDifficulty int `json:"challengeDifficulty,omitempty"` // Difficulty level (default: 4)
|
||||||
ExpiresMs int `json:"expiresMs,omitempty"` // Expiration time in milliseconds (default: 600000)
|
ExpiresMs int `json:"expiresMs,omitempty"` // Expiration time in milliseconds (default: 600000)
|
||||||
|
InfoTitle struct {
|
||||||
|
En string `json:"en,omitempty"` // English title
|
||||||
|
Zh string `json:"zh,omitempty"` // Chinese title
|
||||||
|
} `json:"infoTitle,omitempty"` // Multi-language info title
|
||||||
|
InfoText struct {
|
||||||
|
En string `json:"en,omitempty"` // English text
|
||||||
|
Zh string `json:"zh,omitempty"` // Chinese text
|
||||||
|
} `json:"infoText,omitempty"` // Multi-language info text
|
||||||
} `json:"cap_js_config"`
|
} `json:"cap_js_config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +105,14 @@ func ParseCaptchaConfig(captchaJSON string) CaptchaConfig {
|
|||||||
config.CapJsConfig.ChallengeDifficulty = 4 // 默认难度级别4
|
config.CapJsConfig.ChallengeDifficulty = 4 // 默认难度级别4
|
||||||
config.CapJsConfig.ExpiresMs = 600000 // 默认过期时间600秒(10分钟)
|
config.CapJsConfig.ExpiresMs = 600000 // 默认过期时间600秒(10分钟)
|
||||||
|
|
||||||
|
// 初始化InfoTitle默认值
|
||||||
|
config.CapJsConfig.InfoTitle.Zh = "安全验证"
|
||||||
|
config.CapJsConfig.InfoTitle.En = "Security Verification"
|
||||||
|
|
||||||
|
// 初始化InfoText默认值
|
||||||
|
config.CapJsConfig.InfoText.Zh = "为了确保您的访问安全,请完成以下验证"
|
||||||
|
config.CapJsConfig.InfoText.En = "To ensure the security of your access, please complete the following verification"
|
||||||
|
|
||||||
// 如果JSON不为空,则解析覆盖默认值
|
// 如果JSON不为空,则解析覆盖默认值
|
||||||
if captchaJSON != "" {
|
if captchaJSON != "" {
|
||||||
err := json.Unmarshal([]byte(captchaJSON), &config)
|
err := json.Unmarshal([]byte(captchaJSON), &config)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/wenlng/go-captcha/v2/base/option"
|
"github.com/wenlng/go-captcha/v2/base/option"
|
||||||
"github.com/wenlng/go-captcha/v2/click"
|
"github.com/wenlng/go-captcha/v2/click"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -512,8 +513,49 @@ func (s *CaptchaService) ShowCaptchaHomePage(w http.ResponseWriter, r *http.Requ
|
|||||||
// 从指定目录加载index.html
|
// 从指定目录加载index.html
|
||||||
http.ServeFile(w, r, utils.GetCurrentDir()+"/data/captcha/index.html")
|
http.ServeFile(w, r, utils.GetCurrentDir()+"/data/captcha/index.html")
|
||||||
} else if configStruct.EngineType == "capJs" {
|
} else if configStruct.EngineType == "capJs" {
|
||||||
// 从指定目录加载index.html
|
// 读取HTML模板文件
|
||||||
http.ServeFile(w, r, utils.GetCurrentDir()+"/data/capjs/index.html")
|
htmlPath := utils.GetCurrentDir() + "/data/capjs/index.html"
|
||||||
|
htmlContent, err := ioutil.ReadFile(htmlPath)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to load page", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备替换的数据
|
||||||
|
htmlStr := string(htmlContent)
|
||||||
|
|
||||||
|
// 替换中文提示信息
|
||||||
|
zhInfoTitle := configStruct.CapJsConfig.InfoTitle.Zh
|
||||||
|
zhInfoText := configStruct.CapJsConfig.InfoText.Zh
|
||||||
|
if zhInfoTitle == "" {
|
||||||
|
zhInfoTitle = "安全验证"
|
||||||
|
}
|
||||||
|
if zhInfoText == "" {
|
||||||
|
zhInfoText = "为了确保您的访问安全,请完成以下验证"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换英文提示信息
|
||||||
|
enInfoTitle := configStruct.CapJsConfig.InfoTitle.En
|
||||||
|
enInfoText := configStruct.CapJsConfig.InfoText.En
|
||||||
|
if enInfoTitle == "" {
|
||||||
|
enInfoTitle = "Security Verification"
|
||||||
|
}
|
||||||
|
if enInfoText == "" {
|
||||||
|
enInfoText = "To ensure the security of your access, please complete the following verification"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用strings.Replace替换HTML中的静态文本
|
||||||
|
htmlStr = strings.Replace(htmlStr, "infoTitle: '安全验证',", fmt.Sprintf("infoTitle: '%s',", zhInfoTitle), 1)
|
||||||
|
htmlStr = strings.Replace(htmlStr, "infoText: '为了确保您的访问安全,请完成以下验证',", fmt.Sprintf("infoText: '%s',", zhInfoText), 1)
|
||||||
|
htmlStr = strings.Replace(htmlStr, "infoTitle: 'Security Verification',", fmt.Sprintf("infoTitle: '%s',", enInfoTitle), 1)
|
||||||
|
htmlStr = strings.Replace(htmlStr, "infoText: 'To ensure the security of your access, please complete the following verification',", fmt.Sprintf("infoText: '%s',", enInfoText), 1)
|
||||||
|
|
||||||
|
// 同时替换HTML中的默认显示文本
|
||||||
|
htmlStr = strings.Replace(htmlStr, "<h2 id=\"info-title\">安全验证</h2>", fmt.Sprintf("<h2 id=\"info-title\">%s</h2>", zhInfoTitle), 1)
|
||||||
|
htmlStr = strings.Replace(htmlStr, "<p id=\"info-text\">为了确保您的访问安全,请完成以下验证</p>", fmt.Sprintf("<p id=\"info-text\">%s</p>", zhInfoText), 1)
|
||||||
|
|
||||||
|
// 输出修改后的HTML
|
||||||
|
w.Write([]byte(htmlStr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user