该贡献站服务由三个worker组成
语音
export default {
async fetch(request, env) {
const url = new URL(request.url);
url.host = 'voice.oaifree.com';
return fetch(new Request(url, request));
}
}
镜像
export default {
async fetch(request, env) {
const url = new URL(request.url);
url.host = 'new.oaifree.com';
if(url.pathname === "/auth/login_auth0" || url.pathname === "/auth/login"){
return Response.redirect("https://主程序自定义域/", 301);
}
const modifiedRequest = new Request(url, request);
modifiedRequest.headers.set('X-Voice-Base', 'https://语音自定义域');
return fetch(modifiedRequest);
}
}
主程序
需要绑定KV,绑定名称 KV
,KV配置见后文
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request));
});
// 在文件开头添加加密相关函数
async function encryptData(data) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(await KV.get("COOKIE_SECRET")),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
return btoa(JSON.stringify({
data: btoa(data),
signature: Array.from(new Uint8Array(signature)).map(b => b.toString(16).padStart(2, '0')).join('')
}));
}
async function decryptData(encrypted) {
try {
const { data, signature } = JSON.parse(atob(encrypted));
const originalData = atob(data);
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(originalData);
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(await KV.get("COOKIE_SECRET")),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signatureArray = new Uint8Array(signature.match(/.{2}/g).map(byte => parseInt(byte, 16)));
const isValid = await crypto.subtle.verify("HMAC", key, signatureArray, dataBuffer);
return isValid ? originalData : null;
} catch (e) {
return null;
}
}
// 修改主请求处理函数
async function handleRequest(request) {
if (request.method === "POST") {
const url = new URL(request.url);
if (url.pathname === "/accounts") {
return handleAccountSelection(request);
} else if (url.pathname === "/logout") {
return handleLogout(request);
} else {
return handleFormSubmission(request);
}
} else {
return handleLoginPage(request);
}
}
// 修改处理登录页面的函数
async function handleLoginPage(request) {
const cookie = request.headers.get('Cookie') || '';
const userCookie = cookie.split(';').find(c => c.trim().startsWith('user='));
if (userCookie) {
const encryptedData = userCookie.split('=')[1].trim();
const decryptedData = await decryptData(encryptedData);
if (decryptedData) {
try {
const userData = JSON.parse(decryptedData);
const user = await validateUserWithoutPassword(userData.unique_name);
if (user) {
// 直接复用 handleFormSubmission 中的账户获取逻辑
const groupList = user.group;
const itemList = await getGroupAccounts(groupList);
user.account = {
...user.account,
...itemList
};
return displayAccountPage(user);
}
} catch (error) {
// Cookie 无效,继续显示登录页面
}
}
}
return displayLoginPage();
}
// 修改免密码验证函数
async function validateUserWithoutPassword(unique_name) {
const userJson = await KV.get(`${unique_name}`);
if (!userJson) {
return null;
}
const user = JSON.parse(userJson);
return user;
}
// 修改表单提交处理函数
async function handleFormSubmission(request) {
const formData = await request.formData();
try {
const user = await validateUser(formData);
const groupList = user.group;
const accounts = await getGroupAccounts(groupList);
user.account = {
...user.account,
...accounts
}
// 生成加密的 cookie
const cookieData = await encryptData(JSON.stringify({
unique_name: user.name,
timestamp: Date.now()
}));
const response = await displayAccountPage(user);
response.headers.set('Set-Cookie', `user=${cookieData}; Path=/; HttpOnly; Secure; Max-Age=604800`); // 7天有效期
return response;
} catch (error) {
return displayErrorPage(error.message);
}
}
// 修改注销处理函数
async function handleLogout(request) {
const formData = await request.formData();
const unique_name = formData.get("unique_name");
const response = new Response(displayTransitionPage("注销中", "正在安全退出您的账户...").body, {
headers: {
"Content-Type": "text/html; charset=utf-8",
"Set-Cookie": "user=; Path=/; HttpOnly; Secure; Max-Age=0"
}
});
return response;
}
//获取组和组内账户
async function getGroupAccounts(groups, specificAccountKey = null) {
// 验证 groups 参数
if (!Array.isArray(groups) || groups.length === 0) {
throw new Error("无效的用户组信息");
}
// 获取所有组的账户信息
const groupPromises = groups.map(async groupName => {
const groupJson = await KV.get(groupName);
if (!groupJson) return {};
try {
const groupAccounts = JSON.parse(groupJson);
// 如果指定了特定账户,检查该账户是否在允许的组中
if (specificAccountKey) {
const account = groupAccounts[specificAccountKey];
if (account) {
return { [specificAccountKey]: { ...account, group: groupName } };
}
return {};
}
// 为每个账号添加组信息
const accountsWithGroup = {};
Object.entries(groupAccounts).forEach(([key, value]) => {
accountsWithGroup[key] = { ...value, group: groupName };
});
return accountsWithGroup;
} catch (error) {
console.error(`解析组 ${groupName} 的数据时出错:`, error);
return {};
}
});
const results = await Promise.all(groupPromises);
// 如果是查找特定账户,返回第一个匹配的账户
if (specificAccountKey) {
const foundAccount = results.find(result => result[specificAccountKey]);
return foundAccount ? foundAccount[specificAccountKey] : null;
}
// 合并所有账户并返回
return Object.assign({}, ...results);
}
// 处理账户选择
async function handleAccountSelection(request) {
const formData = await request.formData();
const unique_name = formData.get("unique_name");
const account_key = formData.get("account_key");
try {
// 验证用户数据
const userJson = await KV.get(`${unique_name}`);
if (!userJson) {
return displayErrorPage("用户信息已失效,请重新登录");
}
const user = JSON.parse(userJson);
// 获取账户信息
const selectedAccount = user.account[account_key] || await getGroupAccounts(user.group, account_key);
if (!selectedAccount) {
return displayErrorPage("该账户不存在或您没有访问权限");
}
// 获取访问令牌
let access_token;
try {
access_token = selectedAccount.access_token || await getValidToken(selectedAccount);
} catch (error) {
return displayErrorPage("获取访问令牌失败,请稍后重试");
}
// 生成共享令牌
const shareToken = await generateShareToken(unique_name, access_token);
if (!shareToken || shareToken === "未找到 Share_token") {
return displayErrorPage("生成共享令牌失败,请稍后重试");
}
// 获取域名并生成跳转链接
const YOUR_DOMAIN = (await KV.get("YOUR_DOMAIN")) || new URL(request.url).host;
const oauthLink = await getOAuthLink(shareToken, YOUR_DOMAIN);
if (!oauthLink) {
return displayErrorPage("生成登录链接失败,请稍后重试");
}
return Response.redirect(oauthLink, 302);
} catch (error) {
console.error("账户选择处理错误:", error);
return displayErrorPage(error.message || "处理请求时发生错误,请稍后重试");
}
}
// 验证用户身份
async function validateUser(formData) {
const unique_name = formData.get("unique_name") || "";
const site_password = formData.get("site_password") || "";
const userJson = await KV.get(`${unique_name}`);
if (!userJson) {
throw new Error("用户不存在");
}
const user = JSON.parse(userJson);
if (site_password !== user.password) {
throw new Error("密码错误");
}
return user;
}
// 获取有效令牌
async function getValidToken(account) {
if (!account.refresh_token) {
throw new Error("访问令牌不存在,请联系管理员重新配置账户");
}
try {
const token = await refreshToken(account.refresh_token);
if (!token) {
throw new Error("获取访问令牌失败");
}
account.access_token = token;
return token;
} catch (error) {
if (error.message.includes("Error fetching access token")) {
throw new Error("刷新访问令牌失败,请稍后重试");
}
throw error; // 抛出其他错误
}
}
// 刷新 Token
async function refreshToken(refreshToken) {
try {
const url = "https://token.oaifree.com/api/auth/refresh";
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
body: `refresh_token=${refreshToken}`,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`刷新令牌请求失败: ${response.status} - ${errorText}`);
}
const data = await response.json();
if (!data.access_token) {
throw new Error("返回的数据中没有访问令牌");
}
return data.access_token;
} catch (error) {
console.error("刷新令牌时出错:", error);
throw new Error("刷新访问令牌失败,请检查网络连接或联系管理员");
}
}
// 生成共享令牌
async function generateShareToken(unique_name, access_token) {
const url = "https://chat.oaifree.com/token/register";
const show_conversations = (await KV.get("show_conversations")) || "true";
const temporary_chat = (await KV.get("temporary_chat")) || "false";
const reset_limit = (await KV.get("reset_limit")) || "false";
const body = new URLSearchParams({
unique_name,
access_token,
site_limit: "",
expires_in: "0",
gpt35_limit: "-1",
gpt4_limit: "-1",
show_conversations,
temporary_chat,
reset_limit,
});
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: body.toString(),
});
const data = await response.json();
return data.token_key || "未找到 Share_token";
}
// 获取 OAuth 链接
async function getOAuthLink(shareToken, proxiedDomain) {
const url = `https://${proxiedDomain}/api/auth/oauth_token`;
const response = await fetch(url, {
method: "POST",
headers: {
Origin: `https://${proxiedDomain}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ share_token: shareToken }),
});
const data = await response.json();
return data.login_url;
}
// 显示登录页面
async function displayLoginPage() {
const TURNSTILE_SITE_KEY = await KV.get("TURNSTILE_SITE_KEY");
const formHtml = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>欢迎使用</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.0/dist/tailwind.min.css" rel="stylesheet">
<link rel="icon" type="image/png" href="https://img.pub/p/8efdf03e4c4a5f057ac6.jpg">
<style>
body { background-image: url('https://img.pub/p/2539593fdcda772068e2.png'); background-size: cover; background-position: center; background-attachment: fixed; }
input::placeholder { color: transparent; }
input:not(:placeholder-shown) + label, input:focus + label { top: -20px; padding: 0 5px; left: 12px; z-index: 10; background: rgba(255, 255, 255, 0); }
.container { background: rgba(255, 255, 255, 0.85); border-radius: 15px; padding: 30px; max-width: 400px; margin: auto; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
.logo { margin: 0 auto 20px; width: 120px; height: auto; }
.title { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 15px; }
.form { margin-top: 20px; }
.input-field { width: 100%; height: 45px; padding: 10px; border-radius: 5px; border: 1px solid #ccc; margin-bottom: 20px; }
.button { background-color: #48bb78; color: white; padding: 12px 20px; border-radius: 5px; text-decoration: none; display: inline-block; transition: background-color 0.3s; width: 100%; }
.button:hover { background-color: #38a169; }
</style>
</head>
<body class="flex justify-center items-center min-h-screen m-0 bg-black bg-opacity-60">
<div class="container">
<img src="https://img.pub/p/dd92c1e0a8b081befd3d.jpg" alt="LINUX DO" class="logo">
<h1 class="title">欢迎使用</h1>
<form method="POST" class="form">
<div class="relative mb-4">
<input type="text" id="unique_name" name="unique_name" placeholder=" " required class="input-field">
<label for="unique_name" class="absolute left-5 top-1 text-green-500 text-sm transition-all duration-300">用户名</label>
</div>
<div class="relative mb-4">
<input type="password" id="site_password" name="site_password" placeholder=" " class="input-field">
<label for="site_password" class="absolute left-5 top-1 text-green-500 text-sm transition-all duration-300">口令</label>
</div>
<div class="cf-turnstile my-4 flex justify-center" data-sitekey="${TURNSTILE_SITE_KEY}" data-callback="onTurnstileCallback"></div>
<input type="hidden" id="cf-turnstile-response" name="cf-turnstile-response" required>
<button type="submit" class="w-full h-12 bg-green-500 hover:bg-green-600 text-white font-bold rounded-lg transition-colors duration-300">点击登录</button>
</form>
</div>
</body>
<script>
function onTurnstileCallback(token) {
document.getElementById('cf-turnstile-response').value = token;
}
document.querySelector('form').addEventListener('submit', function(event) {
if (!document.getElementById('cf-turnstile-response').value) {
alert('请完成验证。');
event.preventDefault();
}
});
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</html>
`;
return new Response(formHtml, {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
// 显示账户选择页面
function displayAccountPage(user) {
const htmlContent = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>选择账户</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.0/dist/tailwind.min.css" rel="stylesheet">
<link rel="icon" type="image/png" href="https://img.pub/p/8efdf03e4c4a5f057ac6.jpg">
<style>
body { background-image: url('https://img.pub/p/2539593fdcda772068e2.png'); background-size: cover; background-position: center; background-attachment: fixed; }
.container { background: rgba(255, 255, 255, 0.85); border-radius: 15px; padding: 30px; max-width: 400px; margin: auto; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
.logo { margin: 0 auto 20px; width: 120px; height: auto; }
.select { width: 100%; height: 45px; padding: 10px; border-radius: 5px; border: 1px solid #ccc; margin-bottom: 20px; }
.button { background-color: #48bb78; color: white; padding: 12px 20px; border-radius: 5px; text-decoration: none; display: inline-block; transition: background-color 0.3s; width: 100%; }
.button:hover { background-color: #38a169; }
</style>
</head>
<body class="flex justify-center items-center min-h-screen m-0 bg-black bg-opacity-60">
<div class="container">
<img src="https://img.pub/p/dd92c1e0a8b081befd3d.jpg" alt="LINUX DO" class="logo">
<h1 class="title">选择账户</h1>
<form method="POST" action="/accounts" class="form">
<input type="hidden" name="unique_name" value="${user.name}">
<select name="account_key" class="select">
${Object.keys(user.account).map(account =>
`<option value="${account}">${account}${user.account[account].group ? ` [${user.account[account].group}]` : ''}</option>`
).join('')}
</select>
<button type="submit" class="w-full h-12 bg-green-500 hover:bg-green-600 text-white font-bold rounded-lg transition-colors duration-300 mb-4">开始使用</button>
</form>
<form method="POST" action="/logout">
<input type="hidden" name="unique_name" value="${user.name}">
<button type="submit" class="w-full h-12 bg-red-500 hover:bg-red-600 text-white font-bold rounded-lg transition-colors duration-300">注销登录</button>
</form>
</div>
</body>
</html>
`;
return new Response(htmlContent, {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
// 合并显示页面函数
function displayMessagePage(title, message, isError = false, autoRedirect = false) {
const htmlContent = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.0/dist/tailwind.min.css" rel="stylesheet">
<link rel="icon" type="image/png" href="https://img.pub/p/8efdf03e4c4a5f057ac6.jpg">
${autoRedirect ? '<meta http-equiv="refresh" content="1; url=/">' : ''}
<style>
body { background-image: url('https://img.pub/p/2539593fdcda772068e2.png'); background-size: cover; background-position: center; background-attachment: fixed; }
.container { background: rgba(255, 255, 255, 0.85); border-radius: 15px; padding: 30px; max-width: 400px; margin: auto; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
.logo { margin: 0 auto 20px; width: 120px; height: auto; }
.title { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 15px; }
.message { font-size: 18px; color: ${isError ? '#e53e3e' : '#4a5568'}; margin-bottom: 20px; }
.button { display: inline-block; padding: 12px 24px; border-radius: 8px; font-weight: bold; transition: all 0.3s ease; text-decoration: none; width: 100%; }
.primary-button { background-color: #48bb78; color: white; }
.primary-button:hover { background-color: #38a169; }
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-icon {
display: ${autoRedirect ? 'inline-block' : 'none'};
width: 40px;
height: 40px;
border: 4px solid #48bb78;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
</style>
</head>
<body class="flex justify-center items-center min-h-screen m-0 bg-black bg-opacity-60">
<div class="container">
<img src="https://img.pub/p/dd92c1e0a8b081befd3d.jpg" alt="LINUX DO" class="logo">
<h1 class="title">${title}</h1>
<div class="loading-icon"></div>
<p class="message">${message}</p>
${!autoRedirect ? `
<a href="/" class="button primary-button">返回重试</a>
` : ''}
</div>
</body>
</html>
`;
return new Response(htmlContent, {
headers: { "Content-Type": "text/html; charset=utf-8" },
status: isError ? 400 : 200
});
}
// 更新错误页面显示函数
function displayErrorPage(errorMessage) {
return displayMessagePage("发生错误", errorMessage, true, false);
}
// 更新过渡页面显示函数
function displayTransitionPage(title, message) {
return displayMessagePage(title, message, false, true);
}
KV 配置
-
文本键
键名 示例值 描述 COOKIE_SECRET 3f9a7b3c6d84e5f1a2b9c7d8e0f3a4b1 安全加密密钥 TURNSTILE_SITE_KEY 0x4AAAAAAA**-** Cloudflare Turnstile 站点密钥 YOUR_DOMAIN chat.example.com 镜像的自定义域 -
json键
-
组变量
键名为组名,如
group1
{ "1@example.com": { "refresh_token": "", "access_token": "" }, "2@example.com": { "refresh_token": "", "access_token": "" } }
-
用户变量
键名为用户名,如
name
{ "name": "admin", "password": "test_password", "group": [ "group1", "group2" ], "account": { "1@example.com": { "refresh_token": "", "access_token": "" }, "2@example.com": { "refresh_token": "", "access_token": "" } } }
-