mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
103 lines
3.6 KiB
JavaScript
103 lines
3.6 KiB
JavaScript
import { NextResponse } from "next/server";
|
|
import { createProviderConnection } from "@/models";
|
|
import { extractCodexAccountInfo } from "@/lib/oauth/providers";
|
|
import { getProviderConnectionAccess } from "@/lib/providers/connectionAccess";
|
|
|
|
/**
|
|
* POST /api/oauth/codex/import-token
|
|
* Import a ChatGPT access token (created from chatgpt.com settings)
|
|
* as a provider connection, bypassing OAuth refresh flow.
|
|
*
|
|
* Body: { accessToken: string, name?: string }
|
|
*/
|
|
export async function POST(request) {
|
|
try {
|
|
const { user } = await getProviderConnectionAccess(request);
|
|
const { accessToken, name } = await request.json();
|
|
|
|
if (!accessToken || typeof accessToken !== "string") {
|
|
return NextResponse.json(
|
|
{ error: "Access token is required" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const token = accessToken.trim();
|
|
|
|
// Extract account info from the JWT (email, workspace, plan)
|
|
let email = null;
|
|
let providerSpecificData = { authMethod: "access_token" };
|
|
|
|
// Try decoding as JWT to extract email + workspace
|
|
try {
|
|
const parts = token.split(".");
|
|
if (parts.length === 3) {
|
|
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
const missingPadding = (4 - (base64.length % 4)) % 4;
|
|
const padded = base64 + "=".repeat(missingPadding);
|
|
const payload = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
|
|
// Extract from OpenAI JWT structure
|
|
const auth = payload["https://api.openai.com/auth"] || {};
|
|
const profile = payload["https://api.openai.com/profile"] || {};
|
|
email = profile.email || payload.email || payload.preferred_username || null;
|
|
|
|
if (auth.chatgpt_account_id) {
|
|
providerSpecificData.chatgptAccountId = auth.chatgpt_account_id;
|
|
}
|
|
if (auth.chatgpt_plan_type) {
|
|
providerSpecificData.chatgptPlanType = auth.chatgpt_plan_type;
|
|
}
|
|
|
|
// Store expiry info from JWT if available
|
|
if (payload.exp) {
|
|
providerSpecificData.jwtExp = payload.exp;
|
|
}
|
|
}
|
|
} catch {
|
|
// Not a JWT or malformed — still allow import as raw token
|
|
}
|
|
|
|
// Also try extractCodexAccountInfo via id_token-style extraction
|
|
// (the access token itself may contain the same claims)
|
|
if (!email) {
|
|
const info = extractCodexAccountInfo(token);
|
|
if (info.email) email = info.email;
|
|
if (info.chatgptAccountId) providerSpecificData.chatgptAccountId = info.chatgptAccountId;
|
|
if (info.chatgptPlanType) providerSpecificData.chatgptPlanType = info.chatgptPlanType;
|
|
}
|
|
|
|
const connectionName = name || email || "ChatGPT Access Token";
|
|
|
|
// Save to database as access_token authType (no refresh token)
|
|
const connection = await createProviderConnection({
|
|
provider: "codex",
|
|
authType: "access_token",
|
|
ownerId: user.id,
|
|
accessToken: token,
|
|
name: connectionName,
|
|
email: email,
|
|
providerSpecificData,
|
|
testStatus: "active",
|
|
});
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
connection: {
|
|
id: connection.id,
|
|
provider: connection.provider,
|
|
email: connection.email,
|
|
name: connection.name,
|
|
workspace: providerSpecificData.chatgptAccountId || null,
|
|
plan: providerSpecificData.chatgptPlanType || null,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (error.message === "Unauthorized") {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
console.log("Codex access token import error:", error);
|
|
return NextResponse.json({ error: error.message }, { status: error.status || 500 });
|
|
}
|
|
}
|