import CryptoJS from "crypto-js"; import { NextResponse } from "next/server"; const CODE_CRYPTO_KEY = "k#8Mp$2Qr&9Tz@4W"; const SHARED_API = ( process.env.NEXT_PUBLIC_SHARED_API_PATH || "http://172.16.115.31:6610" ).replace(/\/+$/, ""); // SSO 接口的租户标识(account / phone-smscode / access_token 共用,由环境变量驱动) const SSO_TENANT = process.env.SSO_TENANT || "cowarobot"; function decrypt(encrypted: string): string { try { const bytes = CryptoJS.AES.decrypt(encrypted, CODE_CRYPTO_KEY); return bytes.toString(CryptoJS.enc.Utf8); } catch { return encrypted; } } /** * 调用真实后端 SSO 端点。 * 透传响应(含后端签发的一次性授权 code),不再自造 code。 */ async function callSsoApi(path: string, body: object) { const url = `${SHARED_API}${path}`; console.log("[sso-portal] →", url, body); const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const text = await res.text(); let data: any; try { data = JSON.parse(text); } catch { throw new Error(`后端返回非 JSON [${res.status}]: ${text.slice(0, 200)}`); } return { data, status: res.status }; } // ─── POST /api/login ─── // 与 uirefbase 保持一致:客户端发 JSON.stringify(encrypt(...)) // req.json() 去掉外层引号得到加密字符串,decrypt 解密得到原始 JSON export async function POST(req: Request) { try { const decryptData = await req.json(); // JSON 字符串 → 去掉引号 → 加密原文 const body = JSON.parse(decrypt(decryptData)); const type = body?.type; console.log( "[sso-portal] login | type:", type, "| body keys:", Object.keys(body || {}), ); if (!type) { return NextResponse.json({ error: "缺少 type 字段", debug_type: typeof type, }); } const client_id = body.client_id; const client_redirect_uri = body.client_redirect_uri; // OAuth2 授权码流程:client_id / client_redirect_uri 为必填(授权 code 必须绑定应用) if (!client_id || !client_redirect_uri) { return NextResponse.json({ error: "缺少 client_id 或 client_redirect_uri,无法签发授权 code", }); } let path: string; let ssoBody: Record; if (type === "account") { path = "/api/v1/basis/sso/account"; ssoBody = { account: body.account, password: body.password, tenant: SSO_TENANT, client_id, client_redirect_uri, }; } else if (type === "phone") { path = "/api/v1/basis/sso/phone/smscode"; ssoBody = { phone: body.phone, smscode: body.verify_code, tenant: SSO_TENANT, client_id, client_redirect_uri, }; } else { console.error("[sso-portal] 未知 type:", type); return NextResponse.json({ error: `不支持的登录方式: ${type}` }); } const { data: ssoRes, status } = await callSsoApi(path, ssoBody); console.log( "[sso-portal] backend status:", status, "| res:", JSON.stringify(ssoRes).slice(0, 300), ); // ⚠️ code 在新流程里是「授权码字符串」,不再是业务状态码。 // 兼容平铺 { code } 与外壳 { code:200, data:{ code } } 两种响应。 const authCode = ssoRes?.data?.code ?? ssoRes?.code; if (!authCode || typeof authCode !== "string") { const message = ssoRes?.message || ssoRes?.msg || "登录失败,后端未返回授权 code"; console.log("[sso-portal] no auth code:", message); return NextResponse.json({ error: message }); } console.log("[sso-portal] auth code →", authCode); return NextResponse.json({ code: authCode }); } catch (err: any) { console.error("[sso-portal] login error:", err.message); return NextResponse.json({ error: err?.message || "登录失败" }); } }