mirror of
https://github.com/dsoaress/link-free.git
synced 2026-08-19 16:06:04 +01:00
feat: add auth middleware
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"next": "12.0.7",
|
"next": "12.0.7",
|
||||||
"next-connect": "^0.11.0",
|
"next-connect": "^0.11.0",
|
||||||
|
"nookies": "^2.5.2",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'
|
|||||||
import nc from 'next-connect'
|
import nc from 'next-connect'
|
||||||
|
|
||||||
import { prisma } from '../../services/prisma'
|
import { prisma } from '../../services/prisma'
|
||||||
|
import { authMiddleware } from '../../utils/authMiddleware'
|
||||||
import { ExceptionError } from '../../utils/error'
|
import { ExceptionError } from '../../utils/error'
|
||||||
|
|
||||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
const handler = nc<NextApiRequest, NextApiResponse>({
|
||||||
@@ -28,6 +29,7 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.use(authMiddleware)
|
||||||
.get(async (_req, res) => {
|
.get(async (_req, res) => {
|
||||||
const response = await prisma.data.findUnique({
|
const response = await prisma.data.findUnique({
|
||||||
where: { id: 1 }
|
where: { id: 1 }
|
||||||
@@ -39,7 +41,6 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
|
|
||||||
res.status(200).json(response?.data)
|
res.status(200).json(response?.data)
|
||||||
})
|
})
|
||||||
|
|
||||||
.put(async (req, res) => {
|
.put(async (req, res) => {
|
||||||
const { data } = req.body
|
const { data } = req.body
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import nc from 'next-connect'
|
|||||||
import { prisma } from '../../../services/prisma'
|
import { prisma } from '../../../services/prisma'
|
||||||
import { ExceptionError } from '../../../utils/error'
|
import { ExceptionError } from '../../../utils/error'
|
||||||
|
|
||||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
interface Request extends NextApiRequest {
|
||||||
|
userId: string
|
||||||
|
userRole: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = nc<Request, NextApiResponse>({
|
||||||
onNoMatch: (_req, res) => {
|
onNoMatch: (_req, res) => {
|
||||||
res.status(404).json({ error: 'Not found' })
|
res.status(404).json({ error: 'Not found' })
|
||||||
},
|
},
|
||||||
@@ -51,7 +56,6 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
|
|
||||||
res.status(200).json(response)
|
res.status(200).json(response)
|
||||||
})
|
})
|
||||||
|
|
||||||
.patch(async (req, res) => {
|
.patch(async (req, res) => {
|
||||||
const { id } = req.query
|
const { id } = req.query
|
||||||
const { username, password, newPassword, role } = req.body
|
const { username, password, newPassword, role } = req.body
|
||||||
@@ -69,6 +73,10 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
throw new ExceptionError('No id provided')
|
throw new ExceptionError('No id provided')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.userRole !== 'ADMIN' && req.userId !== id) {
|
||||||
|
throw new ExceptionError('Only admins can update other users')
|
||||||
|
}
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
const usernameExists = await prisma.user.findUnique({
|
const usernameExists = await prisma.user.findUnique({
|
||||||
where: { username }
|
where: { username }
|
||||||
@@ -118,7 +126,6 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
throw new ExceptionError(err)
|
throw new ExceptionError(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
.delete(async (req, res) => {
|
.delete(async (req, res) => {
|
||||||
const { id } = req.query
|
const { id } = req.query
|
||||||
|
|
||||||
@@ -130,6 +137,10 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
throw new ExceptionError('Cannot delete admin', 403)
|
throw new ExceptionError('Cannot delete admin', 403)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.userRole !== 'ADMIN' && req.userId !== id) {
|
||||||
|
throw new ExceptionError('Only admins can delete other users')
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.data.delete({
|
await prisma.data.delete({
|
||||||
where: { id: +id }
|
where: { id: +id }
|
||||||
|
|||||||
@@ -4,9 +4,15 @@ import type { NextApiRequest, NextApiResponse } from 'next'
|
|||||||
import nc from 'next-connect'
|
import nc from 'next-connect'
|
||||||
|
|
||||||
import { prisma } from '../../../services/prisma'
|
import { prisma } from '../../../services/prisma'
|
||||||
|
import { authMiddleware } from '../../../utils/authMiddleware'
|
||||||
import { ExceptionError } from '../../../utils/error'
|
import { ExceptionError } from '../../../utils/error'
|
||||||
|
|
||||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
interface Request extends NextApiRequest {
|
||||||
|
userId: string
|
||||||
|
userRole: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = nc<Request, NextApiResponse>({
|
||||||
onNoMatch: (_req, res) => {
|
onNoMatch: (_req, res) => {
|
||||||
res.status(404).json({ error: 'Not found' })
|
res.status(404).json({ error: 'Not found' })
|
||||||
},
|
},
|
||||||
@@ -29,6 +35,7 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.use(authMiddleware)
|
||||||
.get(async (_req, res) => {
|
.get(async (_req, res) => {
|
||||||
const response = await prisma.user.findMany({
|
const response = await prisma.user.findMany({
|
||||||
select: {
|
select: {
|
||||||
@@ -44,7 +51,6 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
|
|
||||||
res.status(200).json(response)
|
res.status(200).json(response)
|
||||||
})
|
})
|
||||||
|
|
||||||
.post(async (req, res) => {
|
.post(async (req, res) => {
|
||||||
const { username, password, role } = req.body
|
const { username, password, role } = req.body
|
||||||
|
|
||||||
@@ -56,7 +62,9 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
|||||||
throw new ExceptionError('Role must be either ADMIN or EDITOR')
|
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({
|
const usernameExists = await prisma.user.findUnique({
|
||||||
where: { username }
|
where: { username }
|
||||||
|
|||||||
41
src/utils/authMiddleware.ts
Normal file
41
src/utils/authMiddleware.ts
Normal file
@@ -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()
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { User } from '@prisma/client'
|
import type { User } from '@prisma/client'
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
|
|
||||||
import { prisma } from '../services/prisma'
|
import { prisma } from '../services/prisma'
|
||||||
|
|||||||
18
yarn.lock
18
yarn.lock
@@ -1238,6 +1238,11 @@ convert-source-map@1.7.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.1"
|
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:
|
core-js-pure@^3.19.0:
|
||||||
version "3.19.3"
|
version "3.19.3"
|
||||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.19.3.tgz#c69b2b36b58927317824994b532ec3f0f7e49607"
|
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"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e"
|
||||||
integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==
|
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:
|
normalize-package-data@^2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
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"
|
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
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:
|
setimmediate@^1.0.4:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||||
|
|||||||
Reference in New Issue
Block a user