diff --git a/main.ts b/main.ts index 5089d93..d89c7f6 100644 --- a/main.ts +++ b/main.ts @@ -114,10 +114,11 @@ async function verifyDiscourseSSO(request: Request): Promise { return session.value.username; } -// 修改 generateSSO 函数为异步函数 +// 修改 generateSSO 函数 async function generateSSO(returnUrl: string): Promise { const encoder = new TextEncoder(); - const rawPayload = `return_sso_url=${encodeURIComponent(returnUrl)}`; + const nonce = crypto.randomUUID(); + const rawPayload = `nonce=${nonce}&return_sso_url=${encodeURIComponent(returnUrl)}`; const base64Payload = btoa(rawPayload); const key = await crypto.subtle.importKey( "raw", @@ -134,6 +135,13 @@ async function generateSSO(returnUrl: string): Promise { const sig = Array.from(new Uint8Array(signature)) .map(b => b.toString(16).padStart(2, '0')) .join(''); + + // 存储 nonce 用于验证回调 + await kv.set(['sso_nonce', nonce], { + return_url: returnUrl, + created_at: new Date().toISOString() + }, { expireIn: 5 * 60 * 1000 }); // 5分钟过期 + return `${DISCOURSE_URL}/session/sso_provider?sso=${encodeURIComponent(base64Payload)}&sig=${sig}`; } @@ -392,17 +400,17 @@ const html = ` // 更新登录UI function updateLoginUI() { const loginStatus = document.getElementById('loginStatus'); - const submitForm = document.getElementById('submitForm'); + const submitTab = document.querySelector('a[href="#submit"]'); if (currentUser) { loginStatus.innerHTML = \` 欢迎, \${currentUser} \`; - submitForm.style.display = 'block'; + submitTab.style.display = 'block'; } else { loginStatus.innerHTML = ''; - submitForm.style.display = 'none'; + submitTab.style.display = 'none'; } } @@ -435,20 +443,22 @@ const html = ` tbody.innerHTML = ''; prices.forEach(price => { - const vendor = vendors[price.channel_type]; + const vendor = vendors?.[price.channel_type]; const tr = document.createElement('tr'); + const inputRatio = price.input_ratio ?? 0; + const outputRatio = price.output_ratio ?? 0; tr.innerHTML = \` \${price.model} \${price.billing_type === 'tokens' ? '按量计费' : '按次计费'} - \${vendor?.name} - \${vendor?.name || '未知供应商'} + \${vendor?.name ?? '未知供应商'} + \${vendor?.name ?? '未知供应商'} \${price.currency} \${price.input_price} \${price.output_price} - \${price.input_ratio.toFixed(4)} - \${price.output_ratio.toFixed(4)} + \${inputRatio.toFixed(4)} + \${outputRatio.toFixed(4)} 查看来源 \${price.status} @@ -462,6 +472,8 @@ const html = ` }); } catch (error) { console.error('加载价格数据失败:', error); + const tbody = document.getElementById('priceTable'); + tbody.innerHTML = '加载数据失败'; } } @@ -558,14 +570,24 @@ const html = ` const kv = await Deno.openKv(); // 读取价格数据 -async function readPrices(): Promise { - const prices = await kv.get(["prices"]); - return prices.value || []; +async function readPrices(): Promise { + try { + const prices = await kv.get(["prices"]); + return prices.value || []; + } catch (error) { + console.error('读取价格数据失败:', error); + return []; + } } // 写入价格数据 -async function writePrices(prices: any[]): Promise { - await kv.set(["prices"], prices); +async function writePrices(prices: Price[]): Promise { + try { + await kv.set(["prices"], prices); + } catch (error) { + console.error('写入价格数据失败:', error); + throw new Error('写入价格数据失败'); + } } // 修改验证函数 @@ -671,8 +693,22 @@ async function handler(req: Request): Promise { // 解码 payload const payload = atob(sso); const payloadParams = new URLSearchParams(payload); - const username = payloadParams.get('username'); + + // 验证 nonce + const nonce = payloadParams.get('nonce'); + if (!nonce) { + throw new Error('Missing nonce'); + } + const nonceData = await kv.get(['sso_nonce', nonce]); + if (!nonceData.value) { + throw new Error('Invalid or expired nonce'); + } + + // 删除已使用的 nonce + await kv.delete(['sso_nonce', nonce]); + + const username = payloadParams.get('username'); if (!username) { throw new Error('Missing username'); } @@ -694,7 +730,7 @@ async function handler(req: Request): Promise { }); } catch (error) { console.error('SSO 回调处理失败:', error); - return new Response("SSO verification failed", { + return new Response("SSO verification failed: " + error.message, { status: 400, headers: { "Content-Type": "text/plain", @@ -889,13 +925,27 @@ async function handler(req: Request): Promise { // 获取价格列表 if (url.pathname === "/api/prices" && req.method === "GET") { - const prices = await readPrices(); - return new Response(JSON.stringify(prices), { - headers: { - "Content-Type": "application/json", - ...headers - } - }); + try { + const prices = await readPrices(); + return new Response(JSON.stringify(prices), { + headers: { + "Content-Type": "application/json", + ...headers + } + }); + } catch (error) { + console.error('获取价格列表失败:', error); + return new Response(JSON.stringify({ + error: "获取价格列表失败", + details: error.message + }), { + status: 500, + headers: { + "Content-Type": "application/json", + ...headers + } + }); + } } // 提供静态页面