feat: implement admin video upload system with platform integration for YouTube and TikTok
This commit is contained in:
@@ -20,6 +20,7 @@ function escapeHtml(value: unknown) {
|
||||
* 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");
|
||||
|
||||
@@ -11,7 +11,7 @@ const TIKTOK_OAUTH_STATE_COOKIE = "tiktok-oauth-state";
|
||||
* Visit this URL once to start the OAuth flow and get your refresh token.
|
||||
*/
|
||||
export async function GET() {
|
||||
|
||||
await requireAdmin();
|
||||
|
||||
const clientKey = process.env.TIKTOK_CLIENT_KEY;
|
||||
const redirectUri = `${process.env.BASE_URL}/api/auth/tiktok/callback`;
|
||||
@@ -27,7 +27,7 @@ export async function GET() {
|
||||
|
||||
const params = new URLSearchParams({
|
||||
client_key: clientKey,
|
||||
scope: "user.info.basic,user.info.profile,user.info.stats,video.list",
|
||||
scope: "user.info.basic,user.info.profile,user.info.stats,video.list,video.publish",
|
||||
response_type: "code",
|
||||
redirect_uri: redirectUri,
|
||||
state: csrfState,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdmin } from "@/lib/auth";
|
||||
|
||||
function escapeHtml(value: unknown) {
|
||||
return String(value ?? "")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
await requireAdmin();
|
||||
const params = new URL(request.url).searchParams;
|
||||
const cookieStore = await cookies();
|
||||
const expected = cookieStore.get("youtube-oauth-state")?.value;
|
||||
cookieStore.delete("youtube-oauth-state");
|
||||
if (!expected || expected !== params.get("state"))
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid OAuth state." },
|
||||
{ status: 400 },
|
||||
);
|
||||
const code = params.get("code");
|
||||
if (!code)
|
||||
return NextResponse.json(
|
||||
{ error: "No authorization code received." },
|
||||
{ status: 400 },
|
||||
);
|
||||
const response = await fetch("https://oauth2.googleapis.com/token", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
code,
|
||||
client_id: process.env.YOUTUBE_CLIENT_ID ?? "",
|
||||
client_secret: process.env.YOUTUBE_CLIENT_SECRET ?? "",
|
||||
redirect_uri: `${process.env.BASE_URL}/api/auth/youtube/callback`,
|
||||
grant_type: "authorization_code",
|
||||
}),
|
||||
});
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
if (!response.ok || !data.refresh_token)
|
||||
return NextResponse.json(
|
||||
{ error: "YouTube token exchange failed." },
|
||||
{ status: 400 },
|
||||
);
|
||||
const html = `<!doctype html><html><body style="font-family:system-ui;max-width:720px;margin:40px auto;padding:20px"><h1>YouTube OAuth success</h1><p>Copy these values into your server environment:</p><pre>YOUTUBE_REFRESH_TOKEN=${escapeHtml(data.refresh_token)}\nYOUTUBE_ACCESS_TOKEN=${escapeHtml(data.access_token)}</pre><p>Restart the app after saving them.</p></body></html>`;
|
||||
return new NextResponse(html, { headers: { "Content-Type": "text/html" } });
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireAdmin } from "@/lib/auth";
|
||||
|
||||
const COOKIE = "youtube-oauth-state";
|
||||
|
||||
export async function GET() {
|
||||
await requireAdmin();
|
||||
const clientId = process.env.YOUTUBE_CLIENT_ID;
|
||||
const redirectUri = `${process.env.BASE_URL}/api/auth/youtube/callback`;
|
||||
if (!clientId)
|
||||
return NextResponse.json(
|
||||
{ error: "YOUTUBE_CLIENT_ID is not configured." },
|
||||
{ status: 500 },
|
||||
);
|
||||
const state = randomBytes(32).toString("hex");
|
||||
const url = new URL("https://accounts.google.com/o/oauth2/v2/auth");
|
||||
url.search = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: "code",
|
||||
access_type: "offline",
|
||||
prompt: "consent",
|
||||
scope: "https://www.googleapis.com/auth/youtube.upload",
|
||||
state,
|
||||
}).toString();
|
||||
const response = NextResponse.redirect(url);
|
||||
(await cookies()).set(COOKIE, state, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
maxAge: 600,
|
||||
path: "/api/auth/youtube",
|
||||
});
|
||||
return response;
|
||||
}
|
||||
Reference in New Issue
Block a user