最新的demofuclaude修改了跨域请求的策略导致一系列问题,现在我糊了一个简单的解决方案,
支持对话隔离,支持多账户切换,方便几个朋友间拼车使用,强烈谴责商人滥用始皇公共资源
基于
做法就是通过前台脚本来直接操作demo.fuclaude.com,这样就不用跨域请求了(天才的想法)
这样就做了一个cloudflare的worker后端和油猴或者脚本猫脚本的前端
worker的部署和key的设置参考了上面的佬友办法,其中your-encryption-key 是一个简单的加密(我也不知道安不安全,假装安全了)设置前后端一致即可,SITE_PASSWORD是站点密码,这是后端的,应该存储在kv里的。
下面是浏览器的脚本文件,好像触发了什么关键词,所以只能上传文件了,并且后缀由js改成了txt,放进油猴或者脚本猫
tampermonkey_script_opensource.txt (23.9 KB)
下面是worker代码,粘贴进cloudflare然后参照上面佬友的那个kv设置kv
var KV = kv_shared_storage;
const HOST = 'demo.example.com';
const BASE_URL = `https://${HOST}`;
const AUTH_ENDPOINT = '/manage-api/auth/oauth_token';
const ENCRYPTION_KEY = 'your-encryption-key';
// Simple encryption function (matches frontend)
function simpleEncrypt(text, key) {
try {
let encrypted = '';
for (let i = 0; i < text.length; i++) {
const keyChar = key.charCodeAt(i % key.length);
const textChar = text.charCodeAt(i);
const encryptedChar = textChar ^ keyChar;
encrypted += encryptedChar.toString(16).padStart(2, '0');
}
return encrypted;
} catch (e) {
console.error('Encryption failed:', e);
return text; // Return original text if encryption fails
}
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
// Filter accounts function - customize as needed
function filterAccounts(accounts) {
const filtered = {};
for (const [key, value] of Object.entries(accounts)) {
// Add your filtering logic here
// Example: if (key !== 'restricted_account') { filtered[key] = value; }
filtered[key] = value;
}
return filtered;
}
async function generateLoginHtml(accounts, showAllAccounts = false) {
// Filter accounts based on permissions
const filteredAccounts = showAllAccounts ? accounts : filterAccounts(accounts);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Account Switcher</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
padding: 20px;
}
form, .response-container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
max-width: 600px;
margin: 20px auto;
}
h1 {
text-align: center;
}
h2 {
text-align: center;
}
p {
display: block;
margin-bottom: 10px;
font-size: 16px;
}
input[type="text"], textarea {
width: calc(100% - 22px);
padding: 10px;
margin-top: 5px;
margin-bottom: 20px;
border-radius: 5px;
border: 1px solid #ccc;
}
textarea {
font-family: 'Courier New', monospace;
background-color: #f8f8f8;
height: 150px;
}
button {
background-color: #000000;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
font-weight:600;
width:100% !important
}
button:hover {
background-color: #1e293b;
}
@media (max-width: 768px) {
body, form, .response-container {
padding: 10px;
}
}
.checkbox-group {
display: flex;
justify-content: space-between;
}
.checkbox-group input[type="checkbox"] {
margin-right: 5px;
}
.checkbox-group label {
margin-right: 10px;
}
select {
width: calc(100% - 22px);
padding: 10px;
margin-top: 5px;
margin-bottom: 20px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 16px;
background-color: white;
}
select:focus {
outline: none;
border-color: #000;
}
</style>
</head>
<body>
<h1>Account Switcher</h1>
<form method="POST">
<label for="account-select">Please select an account:</label>
<select id="account-select" name="account">
<option value="" disabled selected>Please select an account</option>
${Object.keys(filteredAccounts).map((nickname) => `
<option value="${nickname}">${nickname}</option>
`).join('')}
</select>
<br/>
<label for="site_password">Please enter site password:</label>
<input type="text" id="site_password" name="site_password" placeholder="Site password">
<button type="submit">Access</button>
</form>
<script>
document.querySelector('form').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(e.target);
const account = formData.get('account');
const sitePassword = formData.get('site_password');
if (!account) {
alert('Please select an account');
return;
}
try {
const response = await fetch(window.location.href, {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
if (result.session_key) {
alert('Verification successful! Please install the Tampermonkey script for automatic account switching.\\n\\nThe script will show an account switching panel on the target domain.');
} else {
alert('Failed to get session_key');
}
} else {
const errorText = await response.text();
alert('Error: ' + errorText);
}
} catch (error) {
alert('Request failed: ' + error.message);
}
});
</script>
</body>
</html>`;
}
async function handleRequest(request) {
const requestURL = new URL(request.url);
const accountsJsonStr = await KV.get('account_nickname_to_session_keys');
const accounts = JSON.parse(accountsJsonStr);
// Return accounts list
if (request.method === "GET") {
// Check password in URL parameters
const urlPassword = requestURL.searchParams.get('password');
const ADMIN_PASSWORD = 'admin-password'; // Replace with your admin password
const showAllAccounts = urlPassword === ADMIN_PASSWORD;
const html = await generateLoginHtml(accounts, showAllAccounts);
return new Response(html, {
headers: {
'Content-Type': 'text/html; charset=utf-8'
},
});
}
// Handle account login
if (request.method === "POST") {
try {
const formData = await request.formData();
const account = formData.get("account");
const sitePassword = formData.get('site_password') || '';
// Admin password check
const ADMIN_PASSWORD = 'admin-password'; // Replace with your admin password
const showAllAccounts = sitePassword === ADMIN_PASSWORD;
// KV storage password check
const SITE_PASSWORD = await KV.get('SITE_PASSWORD') || '';
if (sitePassword !== SITE_PASSWORD && sitePassword !== ADMIN_PASSWORD) {
return new Response('Incorrect password', { status: 401 });
}
// Check account permissions (customize as needed)
if (!showAllAccounts) {
// Add your permission logic here
// Example: if (account === 'restricted_account') { return new Response('No permission', { status: 403 }); }
}
const sessionKey = accounts[account];
if (!sessionKey) {
return new Response('Account not found', { status: 400 });
}
// Encrypt session_key before returning
const encryptedSessionKey = simpleEncrypt(sessionKey, ENCRYPTION_KEY);
return new Response(JSON.stringify({
session_key: encryptedSessionKey,
message: 'success'
}), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': `https://${HOST}`,
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
},
});
} catch (error) {
return new Response(`Processing error: ${error.message}`, { status: 500 });
}
}
}

