import { ensureSchema, getRawDb } from "../../../lib/mvp-db"; import { getRuntimeEnv } from "../../../lib/runtime-env"; import { buildWecomOAuthUrl, exchangeWecomOAuthCode, resolveWecomConfig, WecomClientError, type WecomBindings, } from "../../../lib/wecom-client"; import { getRequestPrincipal } from "../../../lib/user-auth"; import { signWecomBindState, verifyWecomBindState, } from "../../../lib/wecom-oauth-service"; const env = getRuntimeEnv(); function bindResultPage( title: string, message: string, options: { ok?: boolean; status?: number; redirectHome?: boolean } = {}, ) { const ok = options.ok ?? false; const html = ` ${title}
${ok ? "✓" : "!"}

${title}

${message}

回到工作台
${options.redirectHome ? "" : ""} `; return new Response(html, { status: options.status ?? 200, headers: { "content-type": "text/html; charset=utf-8", "cache-control": "private, no-store", }, }); } function appOrigin(requestUrl: URL) { const configured = String(env.APP_ORIGIN ?? "").trim().replace(/\/$/, ""); return configured || requestUrl.origin; } async function handleStart(request: Request, requestUrl: URL) { const principal = await getRequestPrincipal(request); if (!principal || principal.kind !== "user") { return Response.redirect(new URL("/", requestUrl).toString(), 302); } const config = resolveWecomConfig(env as unknown as WecomBindings); if (!config.corpId || !config.agentId || !config.secret) { return bindResultPage( "暂不可用", "服务器未配置企业微信自建应用(WECOM_CORP_ID / WECOM_AGENT_ID / WECOM_SECRET),请联系管理员。", { status: 503 }, ); } const state = await signWecomBindState(principal.user.id); const redirectUri = `${appOrigin(requestUrl)}/api/wecom-bind`; return new Response(null, { status: 302, headers: { location: buildWecomOAuthUrl({ corpId: config.corpId, agentId: config.agentId, redirectUri, state, }), "cache-control": "private, no-store", }, }); } async function handleCallback(request: Request, requestUrl: URL) { const code = String(requestUrl.searchParams.get("code") ?? "").trim(); const state = String(requestUrl.searchParams.get("state") ?? "").trim(); if (!code || !state) { return bindResultPage("绑定失败", "缺少企业微信授权参数,请回到工作台重新发起绑定。", { status: 400, }); } const verified = await verifyWecomBindState(state); if (!verified) { return bindResultPage( "绑定链接已过期", "本次授权会话已失效(10 分钟内有效),请回到工作台重新点击「企业微信授权绑定」。", { status: 400 }, ); } const principal = await getRequestPrincipal(request); if ( !principal || principal.kind !== "user" || principal.user.id !== verified.userId ) { return bindResultPage( "登录态校验失败", "请先在本浏览器登录发起绑定的那个后台账号,再重新完成企业微信授权。", { status: 403 }, ); } const config = resolveWecomConfig(env as unknown as WecomBindings); let identity: { userId?: string; externalUserId?: string }; try { identity = await exchangeWecomOAuthCode(config, code); } catch (error) { const message = error instanceof WecomClientError ? error.message : "企业微信授权身份换取失败,请稍后重试。"; return bindResultPage("绑定失败", message, { status: 502 }); } if (!identity.userId) { return bindResultPage( "无法绑定", "当前企微身份不是企业内部成员(外部联系人不能绑定后台账号),请用企业内部成员身份授权。", { status: 400 }, ); } await ensureSchema(); const db = getRawDb(); const conflict = await db .prepare( "SELECT id, username FROM users WHERE wecom_userid = ? AND id <> ? LIMIT 1", ) .bind(identity.userId, principal.user.id) .first<{ id: string; username: string }>(); if (conflict) { return bindResultPage( "该企微账号已被绑定", `企微账号 ${identity.userId} 已绑定到后台账号「${conflict.username}」,如需换绑请联系管理员在「用户管理」中处理。`, { status: 409 }, ); } await db .prepare( "UPDATE users SET wecom_userid = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", ) .bind(identity.userId, principal.user.id) .run(); return bindResultPage( "绑定成功", `已将企微账号 ${identity.userId} 绑定到当前后台账号,正在返回工作台…`, { ok: true, redirectHome: true }, ); } export async function GET(request: Request) { const requestUrl = new URL(request.url); if (requestUrl.searchParams.has("code")) { return handleCallback(request, requestUrl); } return handleStart(request, requestUrl); }