diff --git a/src/_middleware.ts b/src/_middleware.ts deleted file mode 100644 index 59120f4..0000000 --- a/src/_middleware.ts +++ /dev/null @@ -1,29 +0,0 @@ -import PATHS from "constants/paths"; - -export { default } from "next-auth/middleware"; - -// WAIT: for fix - Next.js@13.4.4 seems to be broken -// cf: https://github.com/nextauthjs/next-auth/issues/7650 -// (this file must renamed "middleware.ts") - -export const config = { - matcher: [ - PATHS.HOME, - - PATHS.LINK.CREATE, - PATHS.LINK.EDIT, - PATHS.LINK.REMOVE, - - PATHS.CATEGORY.CREATE, - PATHS.CATEGORY.EDIT, - PATHS.CATEGORY.REMOVE, - - PATHS.API.CATEGORY.CREATE, - PATHS.API.CATEGORY.EDIT, - PATHS.API.CATEGORY.REMOVE, - - PATHS.API.LINK.CREATE, - PATHS.API.LINK.EDIT, - PATHS.API.LINK.REMOVE, - ], -}; diff --git a/src/lib/api/handler.ts b/src/lib/api/handler.ts new file mode 100644 index 0000000..18d60ae --- /dev/null +++ b/src/lib/api/handler.ts @@ -0,0 +1,93 @@ +import { User } from "@prisma/client"; +import { PrismaClientKnownRequestError } from "@prisma/client/runtime"; +import { NextApiRequest, NextApiResponse } from "next"; +import { Session } from "next-auth"; + +import getUserOrThrow from "lib/user/getUserOrThrow"; +import { getSessionOrThrow } from "utils/session"; + +type ApiHandlerMethod = ({ + req, + res, + session, + user, +}: { + req: NextApiRequest; + res: NextApiResponse; + session: Session; + user: User; +}) => Promise; + +// This API Handler strongly inspired by +// Source: https://jasonwatmore.com/next-js-13-middleware-for-authentication-and-error-handling-on-api-routes + +export function apiHandler(handler: { + get?: ApiHandlerMethod; + post?: ApiHandlerMethod; + put?: ApiHandlerMethod; + patch?: ApiHandlerMethod; + delete?: ApiHandlerMethod; +}) { + return async (req: NextApiRequest, res: NextApiResponse) => { + const method = req.method.toLowerCase(); + if (!handler[method]) + return res + .status(405) + .json({ error: `Method ${req.method} Not Allowed` }); + + try { + const session = await getSessionOrThrow(req, res); + const user = await getUserOrThrow(session); + + await handler[method]({ req, res, session, user }); + } catch (err) { + errorHandler(err, res); + } + }; +} + +function errorHandler(error: any, response: NextApiResponse) { + if (typeof error === "string") { + const is404 = error.toLowerCase().endsWith("not found"); + const statusCode = is404 ? 404 : 400; + + return response.status(statusCode).json({ message: error }); + } + + // does not fit with current error throwed + // TODO: fix errors returned + // by getSessionOrThrow or getUserOrThrow + if (error.name === "UnauthorizedError") { + // authentication error + return response.status(401).json({ message: "You must be connected" }); + } + + const errorMessage = + error.constructor.name === "PrismaClientKnownRequestError" + ? handlePrismaError(error) // Handle Prisma specific errors + : error.message; + + console.error(error); + return response.status(500).json({ message: errorMessage }); +} + +function handlePrismaError({ + meta, + code, + message, +}: PrismaClientKnownRequestError) { + switch (code) { + case "P2002": + return `Duplicate field value: ${meta.target}`; + case "P2003": + return `Foreign key constraint failed on the field: ${meta.field_name}`; + case "P2014": + return `Invalid ID: ${meta.target}`; + case "P2003": + return `Invalid input data: ${meta.target}`; + + // Details should not leak to client, be carreful with this + default: + return `Something went wrong: ${message}`; + } +} diff --git a/src/lib/user/getUserOrThrow.ts b/src/lib/user/getUserOrThrow.ts index 5ba5b31..0f9626a 100644 --- a/src/lib/user/getUserOrThrow.ts +++ b/src/lib/user/getUserOrThrow.ts @@ -2,6 +2,9 @@ import { Session } from "next-auth"; import prisma from "utils/prisma"; export default async function getUserOrThrow(session: Session) { + if (!session || session === null) { + throw new Error("You must be connected"); + } return await prisma.user.findFirstOrThrow({ where: { email: session?.user?.email, diff --git a/src/utils/session.ts b/src/utils/session.ts index f1a4905..ba428d7 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -3,11 +3,15 @@ import { getServerSession } from "next-auth/next"; import { authOptions } from "pages/api/auth/[...nextauth]"; +export async function getSession(req: NextApiRequest, res: NextApiResponse) { + return await getServerSession(req, res, authOptions); +} + export async function getSessionOrThrow( req: NextApiRequest, res: NextApiResponse ) { - const session = await getServerSession(req, res, authOptions); + const session = await getSession(req, res); if (!session) { throw new Error("You must be connected");