diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx index 6f16b5092..f17a7931a 100644 --- a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx +++ b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx @@ -1,6 +1,8 @@ import { ImageResponse } from 'next/og'; import { NextResponse } from 'next/server'; +import { verify } from '@documenso/lib/server-only/crypto/verify'; + export const runtime = 'edge'; const IMAGE_SIZE = { @@ -8,16 +10,18 @@ const IMAGE_SIZE = { height: 630, }; -type BlogPostOpenGraphImageProps = { - params: { post: string }; -}; +export async function GET(_request: Request) { + const url = new URL(_request.url); -export async function GET(_request: Request, { params }: BlogPostOpenGraphImageProps) { - const { allBlogPosts } = await import('contentlayer/generated'); + const signature = url.searchParams.get('sig'); + const title = url.searchParams.get('title'); + const author = url.searchParams.get('author'); - const blogPost = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`); + if (!title || !author || !signature) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }); + } - if (!blogPost) { + if (!verify({ title, author }, signature)) { return NextResponse.json({ error: 'Not found' }, { status: 404 }); } @@ -48,10 +52,10 @@ export async function GET(_request: Request, { params }: BlogPostOpenGraphImageP logo

- {blogPost.title} + {title}

-

Written by {blogPost.authorName}

+

Written by {author}

), { diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx index fc65d9772..d8ef587c4 100644 --- a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx +++ b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx @@ -7,6 +7,8 @@ import { ChevronLeft } from 'lucide-react'; import type { MDXComponents } from 'mdx/types'; import { useMDXComponent } from 'next-contentlayer/hooks'; +import { sign } from '@documenso/lib/server-only/crypto/sign'; + import { CallToAction } from '~/components/(marketing)/call-to-action'; export const dynamic = 'force-dynamic'; @@ -20,16 +22,28 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => { }; } + const signature = sign({ + title: blogPost.title, + author: blogPost.authorName, + }); + + // Use the url constructor to ensure that things are escaped as they should be + const openGraphImageUrl = new URL(`${blogPost.href}/opengraph`); + + openGraphImageUrl.searchParams.set('title', blogPost.title); + openGraphImageUrl.searchParams.set('author', blogPost.authorName); + openGraphImageUrl.searchParams.set('sig', signature); + return { title: { absolute: `${blogPost.title} - Documenso Blog`, }, description: blogPost.description, openGraph: { - images: [`${blogPost.href}/opengraph`], + images: [openGraphImageUrl.toString()], }, twitter: { - images: [`${blogPost.href}/opengraph`], + images: [openGraphImageUrl.toString()], }, }; };