/** * Haha OpenAI OAuth REST API * * POST /api/haha-openai-oauth/start — 生成 PKCE+state,返回 authorize URL * GET /callback/openai — 用户浏览器 redirect 到此,完成 token 交换 * GET /api/haha-openai-oauth — 查询当前登录状态(不回传 token 本体) * DELETE /api/haha-openai-oauth — 登出,删除 token 文件 */ import { z } from 'zod' import { hahaOpenAIOAuthService } from '../services/hahaOpenAIOAuthService.js' import { ApiError, errorResponse } from '../middleware/errorHandler.js' const StartRequestSchema = z.object({ serverPort: z.number().int().positive(), }) function html(body: string): Response { return new Response(body, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }, }) } export async function handleHahaOpenAIOAuthApi( req: Request, url: URL, segments: string[], ): Promise { try { const action = segments[2] // segments: ['api', 'haha-openai-oauth', ] if (action === 'start' && req.method === 'POST') { let body: unknown try { body = await req.json() } catch { throw ApiError.badRequest('Invalid JSON body') } const parsed = StartRequestSchema.safeParse(body) if (!parsed.success) { throw ApiError.badRequest('serverPort (positive integer) required') } const session = hahaOpenAIOAuthService.startSession({ serverPort: parsed.data.serverPort, }) return Response.json({ authorizeUrl: session.authorizeUrl, state: session.state, }) } if (action === undefined && req.method === 'GET') { const tokens = await hahaOpenAIOAuthService.ensureFreshTokens() if (!tokens) { return Response.json({ loggedIn: false }) } return Response.json({ loggedIn: true, expiresAt: tokens.expiresAt, email: tokens.email, accountId: tokens.accountId, }) } if (action === undefined && req.method === 'DELETE') { await hahaOpenAIOAuthService.deleteTokens() return Response.json({ ok: true }) } return Response.json({ error: 'Not Found' }, { status: 404 }) } catch (error) { return errorResponse(error) } } export async function handleHahaOpenAIOAuthCallback(url: URL): Promise { const code = url.searchParams.get('code') const state = url.searchParams.get('state') const error = url.searchParams.get('error') if (error) { return html(renderCallbackPage(false, `OAuth provider returned: ${error}`)) } if (!code || !state) { return html(renderCallbackPage(false, 'Missing code or state parameter')) } try { await hahaOpenAIOAuthService.completeSession(code, state) return html(renderCallbackPage(true, null)) } catch (err) { const msg = err instanceof Error ? err.message : String(err) return html(renderCallbackPage(false, msg)) } } function renderCallbackPage(success: boolean, errorMsg: string | null): string { if (success) { return ` OpenAI Login Success

✓ OpenAI Login Successful

You can close this window and return to Claude Code Haha.

` } return ` OpenAI Login Failed

✗ OpenAI Login Failed

${escapeHtml(errorMsg ?? 'Unknown error')}
` } function escapeHtml(s: string): string { return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') }