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);
}