import { NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; import { requireAdmin } from "@/lib/auth/auth"; const TIKTOK_OAUTH_STATE_COOKIE = "tiktok-oauth-state"; function escapeHtml(value: unknown) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } /** * GET /api/auth/tiktok/callback * Handles the OAuth callback from TikTok. * Exchanges the authorization code for access + refresh tokens. * Displays the tokens so you can copy them to your .env file. */ export async function GET(request: NextRequest) { await requireAdmin(); const { searchParams } = new URL(request.url); const code = searchParams.get("code"); const error = searchParams.get("error"); const cookieStore = await cookies(); const expectedState = cookieStore.get(TIKTOK_OAUTH_STATE_COOKIE)?.value; const state = searchParams.get("state"); cookieStore.delete(TIKTOK_OAUTH_STATE_COOKIE); if (!expectedState || !state || state !== expectedState) { return NextResponse.json({ error: "Invalid OAuth state" }, { status: 400 }); } if (error) { return NextResponse.json( { error, description: searchParams.get("error_description") }, { status: 400 } ); } if (!code) { return NextResponse.json( { error: "No authorization code received" }, { status: 400 } ); } const clientKey = process.env.TIKTOK_CLIENT_KEY; const clientSecret = process.env.TIKTOK_CLIENT_SECRET; const redirectUri = `${process.env.BASE_URL}/api/auth/tiktok/callback`; if (!clientKey || !clientSecret) { return NextResponse.json( { error: "TIKTOK_CLIENT_KEY or TIKTOK_CLIENT_SECRET not set" }, { status: 500 } ); } // Exchange the code for tokens const tokenRes = await fetch( "https://open.tiktokapis.com/v2/oauth/token/", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_key: clientKey, client_secret: clientSecret, code, grant_type: "authorization_code", redirect_uri: redirectUri, }), } ); const tokenData = (await tokenRes.json()) as Record; if (tokenData.error) { return NextResponse.json( { error: tokenData.error, description: tokenData.error_description }, { status: 400 } ); } // Return an HTML page showing the tokens to copy const html = ` TikTok OAuth Success

✅ TikTok OAuth Success!

Copy these values to your .env file:

TIKTOK_ACCESS_TOKEN=
${escapeHtml(tokenData.access_token)}

TIKTOK_REFRESH_TOKEN=
${escapeHtml(tokenData.refresh_token)}

TIKTOK_OPEN_ID=
${escapeHtml(tokenData.open_id)}

Access token expires in ${Math.floor(Number(tokenData.expires_in ?? 0) / 3600)} hours.
Refresh token expires in ${Math.floor(Number(tokenData.refresh_expires_in ?? 0) / 86400)} days.
The server will auto-refresh the access token using the refresh token.

Full response
${escapeHtml(JSON.stringify(tokenData, null, 2))}
`; return new NextResponse(html, { headers: { "Content-Type": "text/html" }, }); }