diff --git a/package.json b/package.json index 4e81a298..86939297 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "jest-environment-jsdom": "^29.7.0", "nanoid": "^5.0.6", "next": "14.1.3", - "next-auth": "5.0.0-beta.13", + "next-auth": "5.0.0-beta.15", "next-themes": "^0.3.0", "postcss": "8.4.35", "react": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f56bdd4..5f59b382 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,8 +99,8 @@ dependencies: specifier: 14.1.3 version: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) next-auth: - specifier: 5.0.0-beta.13 - version: 5.0.0-beta.13(next@14.1.3)(react@18.2.0) + specifier: 5.0.0-beta.15 + version: 5.0.0-beta.15(next@14.1.3)(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0)(react@18.2.0) @@ -156,8 +156,8 @@ packages: '@jridgewell/trace-mapping': 0.3.22 dev: false - /@auth/core@0.27.0: - resolution: {integrity: sha512-3bydnRJIM/Al6mkYmb53MsC+6G8ojw3lLPzwgVnX4dCo6N2lrib6Wq6r0vxZIhuHGjLObqqtUfpeaEj5aeTHFg==} + /@auth/core@0.28.0: + resolution: {integrity: sha512-/fh/tb/L4NMSYcyPoo4Imn8vN6MskcVfgESF8/ndgtI4fhD/7u7i5fTVzWgNRZ4ebIEGHNDbWFRxaTu1NtQgvA==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 @@ -6052,8 +6052,8 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: false - /next-auth@5.0.0-beta.13(next@14.1.3)(react@18.2.0): - resolution: {integrity: sha512-2m2Gq69WQ0YXcHCCpHn2y5z1bxSlqD/XOuAgrdtz49/VIAdTFFeYZz97RYqf6xMF8VGmoG32VUnJ6LzaHk6Fwg==} + /next-auth@5.0.0-beta.15(next@14.1.3)(react@18.2.0): + resolution: {integrity: sha512-UQggNq8CDu3/w8CYkihKLLnRPNXel98K0j7mtjj9a6XTNYo4Hni8xg/2h1YhElW6vXE8mgtvmH11rU8NKw86jQ==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 @@ -6068,7 +6068,7 @@ packages: nodemailer: optional: true dependencies: - '@auth/core': 0.27.0 + '@auth/core': 0.28.0 next: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 dev: false diff --git a/src/auth/index.ts b/src/auth/index.ts index 53337314..4ea60774 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -44,6 +44,17 @@ export const { }, }); +export const safelyRunServerAdminAction = async ( + callback: () => T, +): Promise => { + const session = await auth(); + if (session?.user) { + return callback(); + } else { + throw new Error('Unauthorized server action request'); + } +}; + export const generateAuthSecret = () => fetch( 'https://generate-secret.vercel.app/32', { cache: 'no-cache' }, diff --git a/src/photo/actions.ts b/src/photo/actions.ts index fe2bb913..7b2033e7 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -33,47 +33,54 @@ import { import { extractExifDataFromBlobPath } from './server'; import { TAG_FAVS, isTagFavs } from '@/tag'; import { convertPhotoToPhotoDbInsert } from '.'; +import { safelyRunServerAdminAction } from '@/auth'; export async function createPhotoAction(formData: FormData) { - const photo = convertFormDataToPhotoDbInsert(formData, true); + return safelyRunServerAdminAction(async () => { + const photo = convertFormDataToPhotoDbInsert(formData, true); - const updatedUrl = await convertUploadToPhoto(photo.url, photo.id); - - if (updatedUrl) { photo.url = updatedUrl; } - - await sqlInsertPhoto(photo); - - revalidateAllKeysAndPaths(); - - redirect(PATH_ADMIN_PHOTOS); + const updatedUrl = await convertUploadToPhoto(photo.url, photo.id); + + if (updatedUrl) { photo.url = updatedUrl; } + + await sqlInsertPhoto(photo); + + revalidateAllKeysAndPaths(); + + redirect(PATH_ADMIN_PHOTOS); + }); } export async function updatePhotoAction(formData: FormData) { - const photo = convertFormDataToPhotoDbInsert(formData); + return safelyRunServerAdminAction(async () => { + const photo = convertFormDataToPhotoDbInsert(formData); - await sqlUpdatePhoto(photo); + await sqlUpdatePhoto(photo); - revalidateAllKeysAndPaths(); + revalidateAllKeysAndPaths(); - redirect(PATH_ADMIN_PHOTOS); + redirect(PATH_ADMIN_PHOTOS); + }); } export async function toggleFavoritePhotoAction( photoId: string, shouldRedirect?: boolean, ) { - const photo = await getPhoto(photoId); - if (photo) { - const { tags } = photo; - photo.tags = tags.some(tag => tag === TAG_FAVS) - ? tags.filter(tag => !isTagFavs(tag)) - : [...tags, TAG_FAVS]; - await sqlUpdatePhoto(convertPhotoToPhotoDbInsert(photo)); - revalidateAllKeysAndPaths(); - if (shouldRedirect) { - redirect(pathForPhoto(photoId)); + return safelyRunServerAdminAction(async () => { + const photo = await getPhoto(photoId); + if (photo) { + const { tags } = photo; + photo.tags = tags.some(tag => tag === TAG_FAVS) + ? tags.filter(tag => !isTagFavs(tag)) + : [...tags, TAG_FAVS]; + await sqlUpdatePhoto(convertPhotoToPhotoDbInsert(photo)); + revalidateAllKeysAndPaths(); + if (shouldRedirect) { + redirect(pathForPhoto(photoId)); + } } - } + }); } export async function deletePhotoAction( @@ -81,82 +88,96 @@ export async function deletePhotoAction( photoUrl: string, shouldRedirect?: boolean, ) { - await sqlDeletePhoto(photoId).then(() => deleteStorageUrl(photoUrl)); - revalidateAllKeysAndPaths(); - if (shouldRedirect) { - redirect(PATH_ROOT); - } + return safelyRunServerAdminAction(async () => { + await sqlDeletePhoto(photoId).then(() => deleteStorageUrl(photoUrl)); + revalidateAllKeysAndPaths(); + if (shouldRedirect) { + redirect(PATH_ROOT); + } + }); }; export async function deletePhotoFormAction(formData: FormData) { - return deletePhotoAction( - formData.get('id') as string, - formData.get('url') as string, + return safelyRunServerAdminAction(async () => + deletePhotoAction( + formData.get('id') as string, + formData.get('url') as string, + ) ); }; export async function deletePhotoTagGloballyAction(formData: FormData) { - const tag = formData.get('tag') as string; + return safelyRunServerAdminAction(async () => { + const tag = formData.get('tag') as string; - await sqlDeletePhotoTagGlobally(tag); + await sqlDeletePhotoTagGlobally(tag); - revalidatePhotosKey(); - revalidateAdminPaths(); + revalidatePhotosKey(); + revalidateAdminPaths(); + }); } export async function renamePhotoTagGloballyAction(formData: FormData) { - const tag = formData.get('tag') as string; - const updatedTag = formData.get('updatedTag') as string; + return safelyRunServerAdminAction(async () => { + const tag = formData.get('tag') as string; + const updatedTag = formData.get('updatedTag') as string; - if (tag && updatedTag && tag !== updatedTag) { - await sqlRenamePhotoTagGlobally(tag, updatedTag); - revalidatePhotosKey(); - revalidateTagsKey(); - redirect(PATH_ADMIN_TAGS); - } + if (tag && updatedTag && tag !== updatedTag) { + await sqlRenamePhotoTagGlobally(tag, updatedTag); + revalidatePhotosKey(); + revalidateTagsKey(); + redirect(PATH_ADMIN_TAGS); + } + }); } export async function deleteBlobPhotoAction(formData: FormData) { - await deleteStorageUrl(formData.get('url') as string); + return safelyRunServerAdminAction(async () => { + await deleteStorageUrl(formData.get('url') as string); - revalidateAdminPaths(); + revalidateAdminPaths(); - if (formData.get('redirectToPhotos') === 'true') { - redirect(PATH_ADMIN_PHOTOS); - } + if (formData.get('redirectToPhotos') === 'true') { + redirect(PATH_ADMIN_PHOTOS); + } + }); } export async function getExifDataAction( photoFormPrevious: Partial, ): Promise> { - const { url } = photoFormPrevious; - if (url) { - const { photoFormExif } = await extractExifDataFromBlobPath(url); - if (photoFormExif) { - return photoFormExif; + return safelyRunServerAdminAction(async () => { + const { url } = photoFormPrevious; + if (url) { + const { photoFormExif } = await extractExifDataFromBlobPath(url); + if (photoFormExif) { + return photoFormExif; + } } - } - return {}; + return {}; + }); } export async function syncPhotoExifDataAction(formData: FormData) { - const photoId = formData.get('id') as string; - if (photoId) { - const photo = await getPhoto(photoId); - if (photo) { - const { photoFormExif } = await extractExifDataFromBlobPath(photo.url); - if (photoFormExif) { - const photoFormDbInsert = convertFormDataToPhotoDbInsert({ - ...convertPhotoToFormData(photo), - ...photoFormExif, - }); - await sqlUpdatePhoto(photoFormDbInsert); - revalidatePhotosKey(); + return safelyRunServerAdminAction(async () => { + const photoId = formData.get('id') as string; + if (photoId) { + const photo = await getPhoto(photoId); + if (photo) { + const { photoFormExif } = await extractExifDataFromBlobPath(photo.url); + if (photoFormExif) { + const photoFormDbInsert = convertFormDataToPhotoDbInsert({ + ...convertPhotoToFormData(photo), + ...photoFormExif, + }); + await sqlUpdatePhoto(photoFormDbInsert); + revalidatePhotosKey(); + } } } - } + }); } export async function syncCacheAction() { - revalidateAllKeysAndPaths(); + return safelyRunServerAdminAction(revalidateAllKeysAndPaths); }