ai_api / src /app /authorize /route.ts
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
3.89 kB
import { NextResponse } from "next/server";
import { createProviderConnection } from "@/models";
import { parseTraeCallbackQuery } from "./parseCallback";
/**
* GET /authorize
*
* Loopback callback for the Trae SOLO desktop OAuth flow (auth_from=solo).
* Trae's authorize server validates that auth_callback_url ends with the
* literal `/authorize` path β€” any other path makes the page short-circuit
* to "Login Failed". So this handler lives at the app root, not under
* /api/oauth/trae/. The provider tag is implicit (only Trae uses /authorize).
*
* Receives the redirect from https://www.trae.ai/authorization after the user
* confirms login. Trae's auth server packs the entire credential set into
* query parameters (no separate token-exchange HTTP call exists):
*
* userJwt β€” JSON string with { ClientID, Token, RefreshToken, TokenExpireAt,
* RefreshExpireAt, TokenExpireDuration }
* userInfo β€” JSON string with { UserID, TenantID, Region, AIRegion, ... }
* refreshToken, loginTraceID, host, refreshExpireAt, userRegion, scope β€” flat fields
*
* We parse the bundle, persist a connection via `createProviderConnection`
* (which encrypts the token), and return an HTML page that postMessages the
* opening window before closing itself β€” that's how TraeAuthModal knows
* the import succeeded.
*
* State validation: the caller passes its UUID as `login_trace_id` in the
* authorize URL; Trae echoes it back as `loginTraceID`. The modal verifies
* the echoed state before trusting the postMessage.
*/
function htmlClose(message: Record<string, unknown>): NextResponse {
// Embedding values: only emit the small/sanitized status payload β€” never the
// raw token. We post to the loopback origin pair (localhost + 127.0.0.1) on
// this same port rather than "*": Trae forces the callback onto 127.0.0.1,
// but the dashboard opener is usually on localhost, so a single
// window.location.origin target would silently drop the message. Restricting
// to the two known loopback hosts keeps it secure (CWE-359) and working.
const safe = JSON.stringify({
type: "trae-oauth-callback",
...message,
}).replace(/</g, "\\u003c");
return new NextResponse(
`<!doctype html><html><body style="font:16px sans-serif;padding:40px">
<h2 style="margin:0 0 8px">Trae authorization ${message.success ? "βœ“" : "failed"}</h2>
<p>${message.success ? "You can close this window." : "Return to the dashboard."}</p>
<script>
(function () {
try {
if (!window.opener) return;
var msg = ${safe};
var loc = window.location;
var targets = [loc.origin];
var alt = loc.hostname === "127.0.0.1" ? "localhost" : loc.hostname === "localhost" ? "127.0.0.1" : null;
if (alt) targets.push(loc.protocol + "//" + alt + (loc.port ? ":" + loc.port : ""));
targets.forEach(function (t) { try { window.opener.postMessage(msg, t); } catch (e) {} });
} catch (e) {}
})();
setTimeout(function () { window.close(); }, ${message.success ? 800 : 4000});
</script>
</body></html>`,
{ status: 200, headers: { "Content-Type": "text/html; charset=utf-8" } }
);
}
export async function GET(request: Request) {
const url = new URL(request.url);
const q = url.searchParams;
const parsed = parseTraeCallbackQuery(q);
if (!parsed.ok) {
return htmlClose({ success: false, error: parsed.error });
}
try {
const connection: any = await createProviderConnection(parsed.record);
return htmlClose({
success: true,
connectionId: connection.id,
loginTraceId: q.get("loginTraceID") || null,
});
} catch (err: any) {
console.error("[trae callback] error:", err);
return htmlClose({ success: false, error: "Internal error during callback" });
}
}