From 5ee98aaeab327aac659feaddad4e4dbdb3084188 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Wed, 1 Jan 2025 21:00:01 -0500 Subject: [PATCH 1/4] Use bypass secret for next/image server-side fetches --- src/photo/index.ts | 5 ++++- src/photo/server.ts | 5 +++-- src/site/config.ts | 4 ++++ src/utility/vercel.ts | 12 ++++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 src/utility/vercel.ts diff --git a/src/photo/index.ts b/src/photo/index.ts index 97c96b71..8c250dd8 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -13,6 +13,7 @@ import { formatExposureTime, } from '@/utility/exif'; import { parameterize } from '@/utility/string'; +import { fetchBypass } from '@/utility/vercel'; import camelcaseKeys from 'camelcase-keys'; import { isBefore } from 'date-fns'; import type { Metadata } from 'next'; @@ -308,7 +309,9 @@ export const getKeywordsForPhoto = (photo: Photo) => .map(keyword => keyword.toLocaleLowerCase()); export const isNextImageReadyBasedOnPhotos = async (photos: Photo[]) => - photos.length > 0 && fetch(getNextImageUrlForRequest(photos[0].url, 640)) + photos.length > 0 && fetchBypass( + getNextImageUrlForRequest(photos[0].url, 640) + ) .then(response => response.ok) .catch(() => false); diff --git a/src/photo/server.ts b/src/photo/server.ts index 5078b13c..0373522b 100644 --- a/src/photo/server.ts +++ b/src/photo/server.ts @@ -12,6 +12,7 @@ import { PhotoFormData } from './form'; import { FilmSimulation } from '@/simulation'; import sharp, { Sharp } from 'sharp'; import { GEO_PRIVACY_ENABLED, PRO_MODE_ENABLED } from '@/site/config'; +import { fetchBypass } from '@/utility/vercel'; const IMAGE_WIDTH_RESIZE = 200; const IMAGE_WIDTH_BLUR = 200; @@ -128,7 +129,7 @@ const blurImage = async (image: ArrayBuffer) => ); export const resizeImageFromUrl = async (url: string) => - fetch(decodeURIComponent(url)) + fetchBypass(decodeURIComponent(url)) .then(res => res.arrayBuffer()) .then(buffer => resizeImage(buffer)) .catch(e => { @@ -137,7 +138,7 @@ export const resizeImageFromUrl = async (url: string) => }); export const blurImageFromUrl = async (url: string) => - fetch(decodeURIComponent(url)) + fetchBypass(decodeURIComponent(url)) .then(res => res.arrayBuffer()) .then(buffer => blurImage(buffer)) .catch(e => { diff --git a/src/site/config.ts b/src/site/config.ts index 55961890..0f1ecce6 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -35,6 +35,10 @@ export const IS_PRODUCTION = process.env.NODE_ENV === 'production' && ( !VERCEL_ENV ); +export const IS_PREVIEW = VERCEL_ENV === 'preview'; + +export const VERCEL_BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET; + // User-facing domain, potential site title const SITE_DOMAIN = process.env.NEXT_PUBLIC_SITE_DOMAIN || diff --git a/src/utility/vercel.ts b/src/utility/vercel.ts new file mode 100644 index 00000000..7ab10dcd --- /dev/null +++ b/src/utility/vercel.ts @@ -0,0 +1,12 @@ +import { IS_PREVIEW, VERCEL_BYPASS_SECRET } from "@/site/config"; + +export const fetchBypass: typeof fetch = (url, options) => + IS_PREVIEW && VERCEL_BYPASS_SECRET + ? fetch(url, { + ...options, + headers: { + ...options?.headers, + 'x-vercel-protection-bypass': VERCEL_BYPASS_SECRET + }, + }) + : fetch(url, options); From a3a620d04d9e06b094ec8ef27ca2f31070ad0805 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 2 Jan 2025 14:39:56 -0500 Subject: [PATCH 2/4] Append bypass secret to internal OG image requests --- .../components/ImagePhotoGrid.tsx | 8 ++++++- src/services/next-image.ts | 11 +++++++++- src/site/config.ts | 1 + src/utility/useOnVisible.ts | 2 +- src/utility/vercel.ts | 22 +++++++++++-------- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/image-response/components/ImagePhotoGrid.tsx b/src/image-response/components/ImagePhotoGrid.tsx index 3722d562..c855efd8 100644 --- a/src/image-response/components/ImagePhotoGrid.tsx +++ b/src/image-response/components/ImagePhotoGrid.tsx @@ -5,6 +5,7 @@ import { NextImageSize, getNextImageUrlForRequest, } from '@/services/next-image'; +import { IS_PREVIEW } from '@/site/config'; export default function ImagePhotoGrid({ photos, @@ -64,7 +65,12 @@ export default function ImagePhotoGrid({ }} > { const url = new URL(`${baseUrl}/_next/image`); @@ -21,6 +26,10 @@ export const getNextImageUrlForRequest = ( url.searchParams.append('w', size.toString()); url.searchParams.append('q', quality.toString()); + if (addBypassSecret && VERCEL_BYPASS_SECRET) { + url.searchParams.append(VERCEL_BYPASS_KEY, VERCEL_BYPASS_SECRET); + } + return url.toString(); }; diff --git a/src/site/config.ts b/src/site/config.ts index 0f1ecce6..7a632de7 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -37,6 +37,7 @@ export const IS_PRODUCTION = process.env.NODE_ENV === 'production' && ( export const IS_PREVIEW = VERCEL_ENV === 'preview'; +export const VERCEL_BYPASS_KEY = 'x-vercel-protection-bypass'; export const VERCEL_BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET; // User-facing domain, potential site title diff --git a/src/utility/useOnVisible.ts b/src/utility/useOnVisible.ts index 97a35406..b3948420 100644 --- a/src/utility/useOnVisible.ts +++ b/src/utility/useOnVisible.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; export default function useOnVisible( - ref: React.RefObject, + ref: React.RefObject, onVisible?: () => void ) { useEffect(() => { diff --git a/src/utility/vercel.ts b/src/utility/vercel.ts index 7ab10dcd..76fabb82 100644 --- a/src/utility/vercel.ts +++ b/src/utility/vercel.ts @@ -1,12 +1,16 @@ -import { IS_PREVIEW, VERCEL_BYPASS_SECRET } from "@/site/config"; +import { + IS_PREVIEW, + VERCEL_BYPASS_KEY, + VERCEL_BYPASS_SECRET, +} from '@/site/config'; export const fetchBypass: typeof fetch = (url, options) => IS_PREVIEW && VERCEL_BYPASS_SECRET - ? fetch(url, { - ...options, - headers: { - ...options?.headers, - 'x-vercel-protection-bypass': VERCEL_BYPASS_SECRET - }, - }) - : fetch(url, options); + ? fetch(url, { + ...options, + headers: { + ...options?.headers, + [VERCEL_BYPASS_KEY]: VERCEL_BYPASS_SECRET, + }, + }) + : fetch(url, options); From 5936c71c7dae25fd9da510aea9f0674ece26a057 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 2 Jan 2025 21:56:53 -0500 Subject: [PATCH 3/4] Standardize on query params for next/image bypass --- src/app/admin/photos/[photoId]/edit/page.tsx | 12 +++++++++--- src/photo/form/PhotoForm.tsx | 4 ++-- src/photo/index.ts | 17 +++++++++++------ src/photo/server.ts | 5 ++--- src/services/next-image.ts | 7 +++++-- src/utility/vercel.ts | 2 +- 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/app/admin/photos/[photoId]/edit/page.tsx b/src/app/admin/photos/[photoId]/edit/page.tsx index df5ffccf..1e2e9fa1 100644 --- a/src/app/admin/photos/[photoId]/edit/page.tsx +++ b/src/app/admin/photos/[photoId]/edit/page.tsx @@ -2,7 +2,11 @@ import { redirect } from 'next/navigation'; import { getPhotoNoStore, getUniqueTagsCached } from '@/photo/cache'; import { PATH_ADMIN } from '@/site/paths'; import PhotoEditPageClient from '@/photo/PhotoEditPageClient'; -import { AI_TEXT_GENERATION_ENABLED, BLUR_ENABLED } from '@/site/config'; +import { + AI_TEXT_GENERATION_ENABLED, + BLUR_ENABLED, + IS_PREVIEW, +} from '@/site/config'; import { blurImageFromUrl, resizeImageFromUrl } from '@/photo/server'; import { getNextImageUrlForManipulation } from '@/services/next-image'; @@ -21,12 +25,14 @@ export default async function PhotoEditPage({ // Only generate image thumbnails when AI generation is enabled const imageThumbnailBase64 = AI_TEXT_GENERATION_ENABLED - ? await resizeImageFromUrl(getNextImageUrlForManipulation(photo.url)) + ? await resizeImageFromUrl( + getNextImageUrlForManipulation(photo.url, IS_PREVIEW) + ) : ''; const blurData = BLUR_ENABLED ? await blurImageFromUrl( - getNextImageUrlForManipulation(photo.url) + getNextImageUrlForManipulation(photo.url, IS_PREVIEW) ) : ''; diff --git a/src/photo/form/PhotoForm.tsx b/src/photo/form/PhotoForm.tsx index 139fb388..5e1750c1 100644 --- a/src/photo/form/PhotoForm.tsx +++ b/src/photo/form/PhotoForm.tsx @@ -27,7 +27,7 @@ import usePreventNavigation from '@/utility/usePreventNavigation'; import { useAppState } from '@/state/AppState'; import UpdateBlurDataButton from '../UpdateBlurDataButton'; import { getNextImageUrlForManipulation } from '@/services/next-image'; -import { BLUR_ENABLED } from '@/site/config'; +import { BLUR_ENABLED, IS_PREVIEW } from '@/site/config'; import { PhotoDbInsert } from '..'; import ErrorNote from '@/components/ErrorNote'; @@ -204,7 +204,7 @@ export default function PhotoForm({ case 'blurData': return shouldDebugImageFallbacks && type === 'edit' && formData.url ? setFormData(data => ({ ...data, blurData }))} /> diff --git a/src/photo/index.ts b/src/photo/index.ts index 8c250dd8..17f8c381 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -3,7 +3,7 @@ import { formatFocalLength } from '@/focal'; import { Lens } from '@/lens'; import { getNextImageUrlForRequest } from '@/services/next-image'; import { FilmSimulation } from '@/simulation'; -import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config'; +import { HIGH_DENSITY_GRID, IS_PREVIEW, SHOW_EXIF_DATA } from '@/site/config'; import { ABSOLUTE_PATH_FOR_HOME_IMAGE } from '@/site/paths'; import { formatDate, formatDateFromPostgresString } from '@/utility/date'; import { @@ -13,7 +13,6 @@ import { formatExposureTime, } from '@/utility/exif'; import { parameterize } from '@/utility/string'; -import { fetchBypass } from '@/utility/vercel'; import camelcaseKeys from 'camelcase-keys'; import { isBefore } from 'date-fns'; import type { Metadata } from 'next'; @@ -308,10 +307,16 @@ export const getKeywordsForPhoto = (photo: Photo) => .filter(Boolean) .map(keyword => keyword.toLocaleLowerCase()); -export const isNextImageReadyBasedOnPhotos = async (photos: Photo[]) => - photos.length > 0 && fetchBypass( - getNextImageUrlForRequest(photos[0].url, 640) - ) +export const isNextImageReadyBasedOnPhotos = async ( + photos: Photo[], +): Promise => + photos.length > 0 && fetch(getNextImageUrlForRequest( + photos[0].url, + 640, + undefined, + undefined, + IS_PREVIEW, + )) .then(response => response.ok) .catch(() => false); diff --git a/src/photo/server.ts b/src/photo/server.ts index 0373522b..5078b13c 100644 --- a/src/photo/server.ts +++ b/src/photo/server.ts @@ -12,7 +12,6 @@ import { PhotoFormData } from './form'; import { FilmSimulation } from '@/simulation'; import sharp, { Sharp } from 'sharp'; import { GEO_PRIVACY_ENABLED, PRO_MODE_ENABLED } from '@/site/config'; -import { fetchBypass } from '@/utility/vercel'; const IMAGE_WIDTH_RESIZE = 200; const IMAGE_WIDTH_BLUR = 200; @@ -129,7 +128,7 @@ const blurImage = async (image: ArrayBuffer) => ); export const resizeImageFromUrl = async (url: string) => - fetchBypass(decodeURIComponent(url)) + fetch(decodeURIComponent(url)) .then(res => res.arrayBuffer()) .then(buffer => resizeImage(buffer)) .catch(e => { @@ -138,7 +137,7 @@ export const resizeImageFromUrl = async (url: string) => }); export const blurImageFromUrl = async (url: string) => - fetchBypass(decodeURIComponent(url)) + fetch(decodeURIComponent(url)) .then(res => res.arrayBuffer()) .then(buffer => blurImage(buffer)) .catch(e => { diff --git a/src/services/next-image.ts b/src/services/next-image.ts index 8c82a69d..682130d1 100644 --- a/src/services/next-image.ts +++ b/src/services/next-image.ts @@ -35,5 +35,8 @@ export const getNextImageUrlForRequest = ( // Generate small, low-bandwidth images for quick manipulations such as // generating blur data or image thumbnails for AI text generation -export const getNextImageUrlForManipulation = (imageUrl: string) => - getNextImageUrlForRequest(imageUrl, 640, 90); +export const getNextImageUrlForManipulation = ( + imageUrl: string, + addBypassSecret = false, +) => + getNextImageUrlForRequest(imageUrl, 640, 90, undefined, addBypassSecret); diff --git a/src/utility/vercel.ts b/src/utility/vercel.ts index 76fabb82..fd788f11 100644 --- a/src/utility/vercel.ts +++ b/src/utility/vercel.ts @@ -4,7 +4,7 @@ import { VERCEL_BYPASS_SECRET, } from '@/site/config'; -export const fetchBypass: typeof fetch = (url, options) => +export const fetchWithBypass: typeof fetch = (url, options) => IS_PREVIEW && VERCEL_BYPASS_SECRET ? fetch(url, { ...options, From 604292b9f10adcc1f1c70952914ad93c5564f8c7 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 2 Jan 2025 22:00:20 -0500 Subject: [PATCH 4/4] Fix code linting --- src/image-response/components/ImagePhotoGrid.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/image-response/components/ImagePhotoGrid.tsx b/src/image-response/components/ImagePhotoGrid.tsx index c855efd8..cdec9da2 100644 --- a/src/image-response/components/ImagePhotoGrid.tsx +++ b/src/image-response/components/ImagePhotoGrid.tsx @@ -66,7 +66,8 @@ export default function ImagePhotoGrid({ >