From 5138b4dfc20481418899c363db722faa28a23193 Mon Sep 17 00:00:00 2001 From: Daniel Soares Date: Wed, 15 Dec 2021 15:38:39 -0300 Subject: [PATCH] feat: add auth middleware --- package.json | 1 + src/pages/api/data.ts | 3 ++- src/pages/api/users/[id].ts | 17 ++++++++++++--- src/pages/api/users/index.ts | 14 +++++++++--- src/utils/authMiddleware.ts | 41 ++++++++++++++++++++++++++++++++++++ src/utils/createSession.ts | 2 +- yarn.lock | 18 ++++++++++++++++ 7 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/utils/authMiddleware.ts diff --git a/package.json b/package.json index f1e471b..0e89934 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "lodash": "^4.17.21", "next": "12.0.7", "next-connect": "^0.11.0", + "nookies": "^2.5.2", "react": "17.0.2", "react-dom": "17.0.2", "react-icons": "^4.3.1", diff --git a/src/pages/api/data.ts b/src/pages/api/data.ts index 118ca69..ad520d5 100644 --- a/src/pages/api/data.ts +++ b/src/pages/api/data.ts @@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from 'next' import nc from 'next-connect' import { prisma } from '../../services/prisma' +import { authMiddleware } from '../../utils/authMiddleware' import { ExceptionError } from '../../utils/error' const handler = nc({ @@ -28,6 +29,7 @@ const handler = nc({ } } }) + .use(authMiddleware) .get(async (_req, res) => { const response = await prisma.data.findUnique({ where: { id: 1 } @@ -39,7 +41,6 @@ const handler = nc({ res.status(200).json(response?.data) }) - .put(async (req, res) => { const { data } = req.body diff --git a/src/pages/api/users/[id].ts b/src/pages/api/users/[id].ts index 42928e0..3a88c43 100644 --- a/src/pages/api/users/[id].ts +++ b/src/pages/api/users/[id].ts @@ -6,7 +6,12 @@ import nc from 'next-connect' import { prisma } from '../../../services/prisma' import { ExceptionError } from '../../../utils/error' -const handler = nc({ +interface Request extends NextApiRequest { + userId: string + userRole: string +} + +const handler = nc({ onNoMatch: (_req, res) => { res.status(404).json({ error: 'Not found' }) }, @@ -51,7 +56,6 @@ const handler = nc({ res.status(200).json(response) }) - .patch(async (req, res) => { const { id } = req.query const { username, password, newPassword, role } = req.body @@ -69,6 +73,10 @@ const handler = nc({ throw new ExceptionError('No id provided') } + if (req.userRole !== 'ADMIN' && req.userId !== id) { + throw new ExceptionError('Only admins can update other users') + } + if (username) { const usernameExists = await prisma.user.findUnique({ where: { username } @@ -118,7 +126,6 @@ const handler = nc({ throw new ExceptionError(err) } }) - .delete(async (req, res) => { const { id } = req.query @@ -130,6 +137,10 @@ const handler = nc({ throw new ExceptionError('Cannot delete admin', 403) } + if (req.userRole !== 'ADMIN' && req.userId !== id) { + throw new ExceptionError('Only admins can delete other users') + } + try { await prisma.data.delete({ where: { id: +id } diff --git a/src/pages/api/users/index.ts b/src/pages/api/users/index.ts index 3eeb639..33c6b08 100644 --- a/src/pages/api/users/index.ts +++ b/src/pages/api/users/index.ts @@ -4,9 +4,15 @@ import type { NextApiRequest, NextApiResponse } from 'next' import nc from 'next-connect' import { prisma } from '../../../services/prisma' +import { authMiddleware } from '../../../utils/authMiddleware' import { ExceptionError } from '../../../utils/error' -const handler = nc({ +interface Request extends NextApiRequest { + userId: string + userRole: string +} + +const handler = nc({ onNoMatch: (_req, res) => { res.status(404).json({ error: 'Not found' }) }, @@ -29,6 +35,7 @@ const handler = nc({ } } }) + .use(authMiddleware) .get(async (_req, res) => { const response = await prisma.user.findMany({ select: { @@ -44,7 +51,6 @@ const handler = nc({ res.status(200).json(response) }) - .post(async (req, res) => { const { username, password, role } = req.body @@ -56,7 +62,9 @@ const handler = nc({ throw new ExceptionError('Role must be either ADMIN or EDITOR') } - // TODO implement role validation + if (req.userRole !== 'ADMIN') { + throw new ExceptionError('Only admins can create users') + } const usernameExists = await prisma.user.findUnique({ where: { username } diff --git a/src/utils/authMiddleware.ts b/src/utils/authMiddleware.ts new file mode 100644 index 0000000..87d6608 --- /dev/null +++ b/src/utils/authMiddleware.ts @@ -0,0 +1,41 @@ +import jwt from 'jsonwebtoken' +import type { NextApiRequest, NextApiResponse } from 'next' + +import { ExceptionError } from './error' + +interface Request extends NextApiRequest { + userId: string + userRole: string +} + +type Token = { + sub: string + role: string +} + +export async function authMiddleware(req: Request, _res: NextApiResponse, next: () => void) { + const token = req.headers['authorization'] + + if (!token) { + throw new ExceptionError('No token provided', 401) + } + + const [, tokenValue] = token.split(' ') + + const { JWT_SECRET } = process.env + + if (!JWT_SECRET) { + throw new ExceptionError('JWT_SECRET is not defined') + } + + const isValidToken = jwt.verify(tokenValue, JWT_SECRET) as Token + + if (!isValidToken) { + throw new ExceptionError('Invalid token', 401) + } + + req.userId = isValidToken.sub + req.userRole = isValidToken.role + + next() +} diff --git a/src/utils/createSession.ts b/src/utils/createSession.ts index 26c049c..d7f19df 100644 --- a/src/utils/createSession.ts +++ b/src/utils/createSession.ts @@ -1,4 +1,4 @@ -import { User } from '@prisma/client' +import type { User } from '@prisma/client' import jwt from 'jsonwebtoken' import { prisma } from '../services/prisma' diff --git a/yarn.lock b/yarn.lock index 57c91d9..84d80b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1238,6 +1238,11 @@ convert-source-map@1.7.0: dependencies: safe-buffer "~5.1.1" +cookie@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + core-js-pure@^3.19.0: version "3.19.3" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.19.3.tgz#c69b2b36b58927317824994b532ec3f0f7e49607" @@ -3126,6 +3131,14 @@ node-releases@^1.1.71: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e" integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== +nookies@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/nookies/-/nookies-2.5.2.tgz#cc55547efa982d013a21475bd0db0c02c1b35b27" + integrity sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA== + dependencies: + cookie "^0.4.1" + set-cookie-parser "^2.4.6" + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -3860,6 +3873,11 @@ set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-cookie-parser@^2.4.6: + version "2.4.8" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" + integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== + setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"