【优雅++安全】GPT4私人拼车神器更新

由于原帖已无法编辑,故本次更新以新贴方式发布

原帖地址:

更新内容:

  • 修复 refresh token 自动更新 access token 后无法正常更新 share token 的 BUG

之前没有 rt 所以根本没试过,这两天终于也是三级了可以自己测试了

  • 将反向代理集成至单个worker,不再需要部署单独反代worker

  • share token 过期或输入错误的 username后,自动跳转至新增的基于 username 的登陆界面(仍然支持原来的链接直达,只是新增了支持的登陆方式)

注:由于自定义反代后无法实现st过期检测,故此次直接使用单个worker同时集成流量代理和token管理功能,删去了自定义反向代理地址的配置

更新界面预览:

更新方式:

在原帖配置的基础上,将worker代码替换为如下代码即可

addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request))
})

let hostname;

function formHTML(message) {
	return `<!doctype html>
<html lang="en-US">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Login - ChatGPT</title>
		<style>
			@charset "UTF-8";
			.oai-header img {
				display: flex;
				height: 32px;
				width: 32px;
				background-color: #fff;
				padding: 32px 0 0;
				fill: #202123;
			}

			a {
				font-weight: 400;
				text-decoration: inherit;
				color: #10a37f;
			}

			.main-container {
				flex: 1 0 auto;
				min-height: 0;
				display: grid;
				box-sizing: border-box;
				grid-template-rows: [left-start center-start right-start] 1fr [left-end center-end right-end];
				grid-template-columns: [left-start center-start] 1fr [left-end right-start] 1fr [center-end right-end];
				align-items: center;
				justify-content: center;
				justify-items: center;
				grid-column-gap: 160px;
				column-gap: 160px;
				padding: 80px;
				width: 100%;
			}

			.login-container {
				background-color: #fff;
				padding: 0 40px 40px;
				border-radius: 3px;
				box-shadow: none;
				width: 320px;
				box-sizing: content-box;
				flex-shrink: 0;
			}

			.title-wrapper {
				padding: 40px 40px 24px;
				box-sizing: content-box;
			}

			.title {
				font-size: 32px;
				font: 'Söhne';
				margin: 24px 0 0;
				color: #2d333a;
				width: 320px;
				text-align: center;
			}

			.input-wrapper {
				position: relative;
				margin-bottom: 25px;
				width: 320px;
				box-sizing: content-box;
			}

			.username-input {
				-webkit-appearance: none;
				-moz-appearance: none;
				appearance: none;
				background-color: #fff;
				border: 1px solid #c2c8d0;
				border-radius: 6px;
				box-sizing: border-box;
				color: #2d333a;
				font-family: inherit;
				font-size: 16px;
				height: 52px;
				line-height: 1.1;
				outline: none;
				padding-block: 1px;
				padding-inline: 2px;
				padding: 0 16px;
				transition:
					box-shadow 0.2s ease-in-out,
					border-color 0.2s ease-in-out;
				width: 100%;
				text-rendering: auto;
				letter-spacing: normal;
				word-spacing: normal;
				text-transform: none;
				text-indent: 0px;
				text-shadow: none;
				display: inline-block;
				text-align: start;
				margin: 0;
			}

			.username-input:focus,
			.username-input:valid {
				border: 1px solid #10a37f;
				outline: none;
			}

			.username-input:focus-within {
				box-shadow: 1px #10a37f;
			}

			.username-input:focus + .username-label,
			.username-input:valid + .username-label {
				font-size: 14px;
				top: 0;
				left: 10px;
				color: #10a37f;
				background-color: #fff;
			}

			.username-label {
				position: absolute;
				top: 26px;
				left: 16px;
				background-color: #fff;
				color: #6f7780;
				font-size: 16px;
				font-weight: 400;
				margin-bottom: 8px;
				max-width: 90%;
				overflow: hidden;
				pointer-events: none;
				padding: 1px 6px;
				text-overflow: ellipsis;
				transform: translateY(-50%);
				transform-origin: 0;
				transition:
					transform 0.15s ease-in-out,
					top 0.15s ease-in-out,
					padding 0.15s ease-in-out;
				white-space: nowrap;
				z-index: 1;
			}

			.continue-btn {
				position: relative;
				height: 52px;
				width: 320px;
				background-color: #10a37f;
				color: #fff;
				margin: 24px 0 0;
				align-items: center;
				justify-content: center;
				display: flex;
				border-radius: 6px;
				padding: 4px 16px;
				font: inherit;
				border-width: 0px;
				cursor: pointer;
			}

			.continue-btn:hover {
				box-shadow: inset 0 0 0 150px #0000001a;
			}

			:root {
				font-family:
					Söhne,
					-apple-system,
					BlinkMacSystemFont,
					Helvetica,
					sans-serif;
				line-height: 1.5;
				font-weight: 400;
				font-synthesis: none;
				text-rendering: optimizeLegibility;
				-webkit-font-smoothing: antialiased;
				-moz-osx-font-smoothing: grayscale;
			}

			.page-wrapper {
				display: flex;
				flex-direction: column;
				justify-content: space-between;
				min-height: 100%;
			}

			.oai-header {
				display: flex;
				justify-content: center;
				align-items: center;
				width: 100%;
				background-color: #fff;
			}

			.oai-header img {
				display: flex;
				height: 64px;
				width: 64px;
				background-color: #fff;
				padding: 64px 0 0;
				fill: #202123;
			}

			body {
				background-color: #fff;
				display: block;
				margin: 0;
			}

			.content-wrapper {
				display: flex;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				width: 100%;
				height: auto;
				white-space: normal;
				border-radius: 5px;
				position: relative;
				grid-area: center;
				box-shadow: none;
				vertical-align: baseline;
				box-sizing: content-box;
			}
		</style>
	</head>
	<body>
		<div id="root">
			<div class="page-wrapper">
				<header class="oai-header"><img src="https://img2.imgtp.com/2024/04/20/z9twuEmo.svg" alt="OpenAI's Logo" /></header>
				<main class="main-container">
					<section class="content-wrapper">
						<div class="title-wrapper"><h1 class="title">${message}</h1></div>
						<div class="login-container">
							<form method="POST">
								<div class="input-wrapper">
									<input
										class="username-input"
										inputmode="username"
										type="username"
										id="username-input"
										name="username"
										autocomplete="username"
										autocapitalize="none"
										spellcheck="false"
										required
										placeholder=" "
									/><label class="username-label" for="username-input">Enter your username here</label>
								</div>
								<button class="continue-btn">Continue</button>
							</form>
						</div>
					</section>
				</main>
			</div>
		</div>
	</body>
</html>
`;
}

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) {
    try {
        const payload = parseJwt(token);
        const currentTime = Math.floor(Date.now() / 1000); // 获取当前时间戳(秒)
        return payload.exp < currentTime; // 检查 token 是否过期
    } catch {
        return true;
    }
}

async function getOAuthLink(shareToken) {
    const url = `https://new.oaifree.com/api/auth/oauth_token`;
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Origin': `https://${hostname}`,
            '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 redirectOAuth(un) {
    // @ts-ignore
    let 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 格式数据
            accessToken = data.access_token; // 直接从 JSON 中获取 access_token

            // @ts-ignore
            await oai_global_variables.put('at', accessToken);
        } 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 + un, accessToken);
    if (shareToken === 'share token 获取失败') {
        return new Response('token获取失败,请刷新重试', { status: 500 });
    }

    return Response.redirect(await getOAuthLink(shareToken), 302);
}

async function handleLogin(request) {
    const url = new URL(request.url);
    const params = new URLSearchParams(url.search);
    let userName = params.get('un');

	if (request.method === 'POST') {
		const formData = await request.formData();
		const submittedUserName = formData.get('username')?.toString().trim();
		if (submittedUserName) {
			userName = submittedUserName;
		}
	}

    if (userName) {
        // @ts-ignore
        // 验证用户名是否存在
        const users = await oai_global_variables.get("users");
        if (users.split(",").includes(userName)) {
            return redirectOAuth(userName);
        } else {
            return new Response(formHTML(`Invalid user name, please retry`), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
        }
    } else if (request.method === 'POST') {
		const formData = await request.formData();
		const userName = formData.get('username')?.toString().trim();
        return Response.redirect(`https://${hostname}/?un=${userName}`, 302);
	} else {
        return new Response(formHTML("Welcome back!"), { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
	}
}

async function handleRequest(request) {

    const url = new URL(request.url);
	hostname = url.hostname;
    const params = new URLSearchParams(url.search);
    const userName = params.get('un');
    if (userName || url.pathname === '/auth/login_auth0' || url.pathname === '/auth/login') {
        return handleLogin(request);
    } else {
        url.host = 'new.oaifree.com';
        return fetch(new Request(url, request));
    }
}

更新参考:

32 Likes

优雅

3 Likes

前排

2 Likes

前排

2 Likes

前排

mark

mark

优雅,顺便蹲个便宜的PLUS车

感谢大佬分享

1 Like

支持一下

太优雅了

哥哥报错invalid access token

应该是你的at没配置对

1 Like

手机端可以吗?我记得手机端已用就跳转到始皇的网站了
更新了然后就记录都全锁了、重新使用 :joy: :joy: :joy:


这样吗

大佬,上次的 share token 的 BUG,问题在哪呢?

上次share token的问题,改 一行 两行就行

改成

1 Like

对的

手机网页端和pc网页端的跳转方式都是一样的,你是更新后遇到什么问题了吗?

不跳始皇站是为了检测share token过期,所以这个worker已经集成了反向代理

太优雅了