import { NextResponse } from "next/server"; import { getAuthSettings } from "@/lib/db/queries"; const normalizeCallbackUrl = (value: string | null) => { if (!value) return "/"; try { const parsed = new URL(value, "https://local.invalid"); if (parsed.origin !== "https://local.invalid") return "/"; return `${parsed.pathname}${parsed.search}${parsed.hash}` || "/"; } catch { return "/"; } }; const encodeState = (payload: { org: string; callbackUrl: string }) => { return `v1.${Buffer.from(JSON.stringify(payload), "utf8").toString( "base64url", )}`; }; export async function GET(request: Request) { const settings = await getAuthSettings(); if (!settings.isDingTalkEnabled) { return NextResponse.json( { error: "DingTalk login is not enabled" }, { status: 400 }, ); } const urlObj = new URL(request.url); const orgName = urlObj.searchParams.get("org"); const organizations = settings.dingTalkOrganizations ?? []; const selectedOrg = orgName ? organizations.find((org) => org.name === orgName) : organizations[0]; const appKey = selectedOrg?.appKey ?? settings.dingTalkAppKey; if (!appKey) { return NextResponse.json( { error: "DingTalk appKey is not configured" }, { status: 400 }, ); } // 动态构建回调地址 // 优先使用 AUTH_URL 环境变量,确保使用正确的公网域名 const requestUrl = new URL(request.url); const baseUrl = process.env.AUTH_URL || requestUrl.origin; const redirectUri = `${baseUrl}/dingtalk-callback.html`; // state 中保存组织和登录后的回跳地址,回调接口仍兼容旧的纯组织名 state。 const state = encodeState({ org: orgName || selectedOrg?.name || "default", callbackUrl: normalizeCallbackUrl(urlObj.searchParams.get("callbackUrl")), }); // 钉钉 OAuth 2.0 授权地址 const url = `https://login.dingtalk.com/oauth2/auth?` + `redirect_uri=${encodeURIComponent(redirectUri)}&` + `response_type=code&` + `client_id=${encodeURIComponent(appKey)}&` + `scope=openid&` + `state=${encodeURIComponent(state)}&` + `prompt=consent`; return NextResponse.redirect(url); }