1. 说明
首先当然还是按传统感谢几位大佬的前期工作!
更新:以及始皇的OAuth登陆
本帖主要是在大佬们的开发基础上,根据自己的GPT4拼车需求进行的一次精简和修改
1.1 主要工作有:
-
删去了交互界面,仅通过url中的un参数与后台数据配合进行功能实现
-
将
share_token
生成的请求参数隐藏至代码中 -
增加了用户名白名单制度
-
改为使用OAuth登陆,避免了在中转链接中泄漏
share_token
1.2 实现的效果
-
一个链接直达聊天界面,无需在意各种token
-
通过配置用户名白名单,可实现对话隔离、用户管理
-
管理者若有rt可完全自动化
-
无rt需要每十天手动更新一次at
-
使用全程不泄露at、rt和st,安全性max
例:
浏览器打开链接 https://xxxxx.xxxx.workers.dev/?un=lbwnb
无需任何操作自动跳转聊天页面,用户友好
2 配置方法
2.1 资源准备
-
一个 cloudflare 账号
-
一个有GPT4权限的账号
2.2 KV配置
在cf中新建命名空间,随意起名,随后创建以下5个变量
-
proxied_domain
:反代new.oaifree.com的地址,本帖用的是二号worker的地址,格式样例填写chat.myname.workers.dev
new.oaifree.com
即可 -
token_prefix
: 随便起即可,用于加在用户名前面避免撞unique_name,格式样例suibianqide
-
users
: 用户白名单,格式样例username1,lbwnb2,666666
-
at
: access token, 获取方式可以查看这个帖 整理自己的代码发现一个古早物件 -
rt
: refresh token, 没有可以不填(比如我就没有 )
\color{red}{更新:现在可以直接通过share\_token直连new页面了,无需二号反代worker}
\color{red}{只要把 `proxied\_domain` 改为 `new.oaifree.com` 即可}
2.3 一号worker配置
在cf中创建worker,起名可以短一些方便访问(如 tk
)
注:之前的佬友来更新代码请务必同步更新users变量的格式
将原有代码替换为如下代码:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
function parseJwt(token) {
const base64Url = token.split('.')[1]; // 获取载荷部分
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); // 将 Base64Url 转为 Base64
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload); // 返回载荷解析后的 JSON 对象
}
function isTokenExpired(token) {
const payload = parseJwt(token);
const currentTime = Math.floor(Date.now() / 1000); // 获取当前时间戳(秒)
return payload.exp < currentTime; // 检查 token 是否过期
}
async function getOAuthLink(shareToken, proxiedDomain) {
// const url = `https://${proxiedDomain}/api/auth/oauth_token`;
// 不知道为什么,好像没法直接通过反代的服务器获取oauth link
const url = `https://new.oaifree.com/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 getShareToken(userName, at) {
const url = 'https://chat.oaifree.com/token/register';
const body = new URLSearchParams({
// 此处为获取Share Token时的请求参数,可自行配置
access_token: at,
unique_name: userName,
site_limit: '', // 限制的网站
expires_in: '0', // token有效期(单位为秒),填 0 则永久有效
gpt35_limit: '-1', // gpt3.5 对话限制
gpt4_limit: '-1', // gpt4 对话限制
show_conversations: 'false', // 是否显示所有人的会话
show_userinfo: 'false', // 是否显示用户信息
reset_limit: 'false' // 是否重置对话限制
}).toString();
const apiResponse = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: body
});
const responseText = await apiResponse.text();
const tokenKeyMatch = /"token_key":"([^"]+)"/.exec(responseText);
const tokenKey = tokenKeyMatch ? tokenKeyMatch[1] : 'share token 获取失败';
return tokenKey;
}
async function handleRequest(request) {
const url = new URL(request.url);
const params = new URLSearchParams(url.search);
const userName = params.get('un');
// @ts-ignore
// 验证用户名是否存在
const users = await oai_global_variables.get("users");
if (!users.split(",").includes(userName)) {
return new Response('用户不存在', { status: 404 });
}
// @ts-ignore
const accessToken = await oai_global_variables.get('at');
if (isTokenExpired(accessToken)) {
// 给没有refresh token的萌新用(比如我),取消下面这行注释即可享用
// return new Response('当前access token未更新,请联系管理员更新', { status: 401 });
// 如果 Token 过期,执行获取新 Token 的逻辑
const url = 'https://token.oaifree.com/api/auth/refresh';
// @ts-ignore
const refreshToken = await oai_global_variables.get('rt'); // 实际情况下你可能会从某处获取这个值
// 发送 POST 请求
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 data = await response.json(); // 假设服务器返回的是 JSON 格式数据
const token = data.access_token; // 直接从 JSON 中获取 access_token
// @ts-ignore
await oai_global_variables.put('at', token);
} else {
return new Response('Error fetching access token', { status: response.status });
}
}
// @ts-ignore
const tokenPrefix = await oai_global_variables.get('token_prefix');
const shareToken = await getShareToken(tokenPrefix + userName, accessToken);
if (shareToken === 'share token 获取失败') {
return new Response('token获取失败,请刷新重试', { status: 500 });
}
// @ts-ignore
const proxiedDomain = await oai_global_variables.get('proxied_domain');
return Response.redirect(await getOAuthLink(shareToken, proxiedDomain), 302);
}
这个worker主要用于处理token相关内容
2.4 二号worker配置(已不推荐)
已不推荐
创建第二个worker,起名随意(如 chat
)
修改代码为如下内容:
马克思大佬的代码由于网站更新已失效,以下为本人重写后测试可用的代码
【优雅+安全】GPT4私人拼车神器——轻松实现聊天隔离+链接直达+全自动刷新token - #31,来自 Maple
二号worker主要用于反代
2.5 给一号worker绑定KV命名空间
在cf面板给一号worker绑定之前创建的命名空间,变量名必须填 oai_global_variables
2.6 使用方法
根据配置的用户白名单内容即可直接访问
格式为
https://<一号worker名字>.<cf账号名字/子域名>.workers.dev/?un=<白名单中的某一个用户名>
例:
假设当前配置条件如下
一号worker名字为 tk
二号worker名字为 chat
cf的账号名/子域名为 pandoranb
白名单为 lbw6666, wowowow
token_prefix 为 thetokenprefix
那么可以访问的链接就是:(聊天隔离)
https://tk.pandoranb.workers.dev/?un=lbw6666
https://tk.pandoranb.workers.dev/?un=wowowow
2.7 更新说明
本次更新更改了 users
变量的格式与一号worker的代码,其中 users
的格式修改借鉴于此贴:
采用OAuth的方式登陆后前端将不会获得share_token,内容也不会被窃听,因此建议目前在用此帖方法的都更新到最新代码和变量格式以防安全性问题
等始皇更新new.oaifree.com后,应该就可以不需要二号worker了
已经可以不用二号worker了
不知道算不算一次正式接力(毕竟开发的方向不一样,帖子主要是为方便私人拼车修改的),但我认为还是挺有实用价值的,所以分享出来供大家参考
感谢始皇的接口 感谢各位佬友的思路和代码 感谢cf大善人的服务器