159 lines
4 KiB
TypeScript
159 lines
4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server"
|
|
import {
|
|
computeEbayChallengeResponse,
|
|
getEbayNotificationEndpoint,
|
|
getEbayNotificationVerificationToken,
|
|
verifyEbayNotificationSignature,
|
|
} from "@/lib/ebay-notifications"
|
|
|
|
export const runtime = "nodejs"
|
|
|
|
type EbayNotificationPayload = {
|
|
metadata?: {
|
|
topic?: string
|
|
schemaVersion?: string
|
|
deprecated?: boolean
|
|
}
|
|
notification?: {
|
|
notificationId?: string
|
|
eventDate?: string
|
|
publishDate?: string
|
|
publishAttemptCount?: number
|
|
data?: {
|
|
username?: string
|
|
userId?: string
|
|
eiasToken?: string
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseNotificationBody(rawBody: string) {
|
|
if (!rawBody.trim()) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(rawBody) as EbayNotificationPayload
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function getChallengeCode(request: NextRequest) {
|
|
return (
|
|
request.nextUrl.searchParams.get("challenge_code") ||
|
|
request.nextUrl.searchParams.get("challengeCode") ||
|
|
""
|
|
).trim()
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const challengeCode = getChallengeCode(request)
|
|
if (!challengeCode) {
|
|
return NextResponse.json(
|
|
{ error: "Missing challenge_code query parameter." },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
const verificationToken = getEbayNotificationVerificationToken()
|
|
if (!verificationToken) {
|
|
return NextResponse.json(
|
|
{ error: "EBAY_NOTIFICATION_VERIFICATION_TOKEN is not configured." },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
|
|
const endpoint = getEbayNotificationEndpoint(request.url)
|
|
if (!endpoint) {
|
|
return NextResponse.json(
|
|
{ error: "EBAY_NOTIFICATION_ENDPOINT is not configured." },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
|
|
const challengeResponse = computeEbayChallengeResponse({
|
|
challengeCode,
|
|
endpoint,
|
|
verificationToken,
|
|
})
|
|
|
|
return NextResponse.json(
|
|
{ challengeResponse },
|
|
{
|
|
headers: {
|
|
"Cache-Control": "no-store",
|
|
},
|
|
}
|
|
)
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const body = await request.text()
|
|
const signatureHeader = request.headers.get("x-ebay-signature")
|
|
|
|
try {
|
|
const verification = await verifyEbayNotificationSignature({
|
|
body,
|
|
signatureHeader,
|
|
})
|
|
|
|
if (!verification.verified) {
|
|
if (
|
|
verification.reason ===
|
|
"Notification verification credentials are not configured."
|
|
) {
|
|
console.warn(
|
|
"[ebay/notifications] accepted notification without signature verification",
|
|
{
|
|
reason: verification.reason,
|
|
}
|
|
)
|
|
const payload = parseNotificationBody(body)
|
|
const notification = payload?.notification
|
|
|
|
console.info(
|
|
"[ebay/notifications] accepted notification without verification",
|
|
{
|
|
topic: payload?.metadata?.topic || "unknown",
|
|
notificationId: notification?.notificationId || "unknown",
|
|
publishAttemptCount: notification?.publishAttemptCount ?? null,
|
|
}
|
|
)
|
|
|
|
return new NextResponse(null, { status: 204 })
|
|
}
|
|
|
|
console.warn("[ebay/notifications] signature rejected", {
|
|
reason: verification.reason,
|
|
})
|
|
return NextResponse.json({ error: verification.reason }, { status: 412 })
|
|
}
|
|
|
|
const payload = parseNotificationBody(body)
|
|
const notification = payload?.notification
|
|
|
|
console.info("[ebay/notifications] accepted notification", {
|
|
keyId: verification.keyId,
|
|
topic: payload?.metadata?.topic || "unknown",
|
|
notificationId: notification?.notificationId || "unknown",
|
|
publishAttemptCount: notification?.publishAttemptCount ?? null,
|
|
})
|
|
|
|
return new NextResponse(null, { status: 204 })
|
|
} catch (error) {
|
|
console.error("[ebay/notifications] failed to process notification", {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
})
|
|
|
|
return NextResponse.json(
|
|
{
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Failed to verify eBay notification.",
|
|
},
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|