Initial open-source self-hosted Songs2YT edition
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { findUserByApiKey } from "./api-keys";
|
||||
import { checkApiRateLimit } from "./api-rate-limit";
|
||||
import { hasProFeatures } from "./edition";
|
||||
import { getSessionUser } from "./session";
|
||||
|
||||
function extractBearerToken(req: NextRequest) {
|
||||
const auth = req.headers.get("authorization");
|
||||
if (!auth?.startsWith("Bearer ")) return null;
|
||||
return auth.slice(7).trim();
|
||||
}
|
||||
|
||||
export async function requirePaidApiUser(req: NextRequest) {
|
||||
const token = extractBearerToken(req);
|
||||
if (!token) {
|
||||
return {
|
||||
error: NextResponse.json(
|
||||
{ error: "Missing API key. Use Authorization: Bearer <your_api_key>" },
|
||||
{ status: 401 },
|
||||
),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
const user = await findUserByApiKey(token);
|
||||
if (!user) {
|
||||
return {
|
||||
error: NextResponse.json({ error: "Invalid API key" }, { status: 401 }),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!hasProFeatures(user.plan)) {
|
||||
return {
|
||||
error: NextResponse.json(
|
||||
{ error: "API access requires an active Pro subscription" },
|
||||
{ status: 403 },
|
||||
),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!user.youtubeConnection) {
|
||||
return {
|
||||
error: NextResponse.json(
|
||||
{ error: "YouTube account not connected. Sign in via the dashboard first." },
|
||||
{ status: 403 },
|
||||
),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
const rateLimit = await checkApiRateLimit(user.id);
|
||||
if (!rateLimit.ok) {
|
||||
return {
|
||||
error: NextResponse.json(
|
||||
{
|
||||
error: "API rate limit exceeded. Try again shortly.",
|
||||
retryAfterSeconds: rateLimit.retryAfterSeconds,
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: { "Retry-After": String(rateLimit.retryAfterSeconds) },
|
||||
},
|
||||
),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
return { error: null, user };
|
||||
}
|
||||
|
||||
export async function requirePaidUserFromSessionOrApi(req: NextRequest) {
|
||||
const apiResult = await requirePaidApiUser(req);
|
||||
if (apiResult.user) return apiResult;
|
||||
|
||||
const sessionUser = await getSessionUser();
|
||||
if (!sessionUser) {
|
||||
return apiResult.error
|
||||
? apiResult
|
||||
: {
|
||||
error: NextResponse.json({ error: "Unauthorized" }, { status: 401 }),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!hasProFeatures(sessionUser.plan)) {
|
||||
return {
|
||||
error: NextResponse.json(
|
||||
{ error: "API access requires an active Pro subscription" },
|
||||
{ status: 403 },
|
||||
),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!sessionUser.youtubeConnection) {
|
||||
return {
|
||||
error: NextResponse.json({ error: "YouTube account not connected" }, { status: 403 }),
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
return { error: null, user: sessionUser };
|
||||
}
|
||||
Reference in New Issue
Block a user