From 4aa73f5f7fb838669ce844c8ef0f3ae224c6cfa8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 21 Nov 2023 22:46:42 +0100 Subject: [PATCH] feat(getAllFeeds): get all feeds route done with only access admin --- back/__tests__/controllers/articles.test.ts | 4 +- back/__tests__/controllers/feeds.test.ts | 67 +++++++++++++++++++++ back/package.json | 2 +- back/prisma/seed.ts | 1 + back/prisma/seedingOperatons.ts | 2 +- back/src/apiErrors.ts | 5 ++ back/src/app.ts | 9 ++- back/src/controllers/auth.ts | 2 +- back/src/controllers/feeds.ts | 11 ++++ back/src/database/feeds.ts | 6 ++ back/src/middlewares.ts | 20 ++++++ 11 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 back/__tests__/controllers/feeds.test.ts create mode 100644 back/src/controllers/feeds.ts create mode 100644 back/src/database/feeds.ts diff --git a/back/__tests__/controllers/articles.test.ts b/back/__tests__/controllers/articles.test.ts index 83c4930..67ed4fb 100644 --- a/back/__tests__/controllers/articles.test.ts +++ b/back/__tests__/controllers/articles.test.ts @@ -13,9 +13,10 @@ import app from '../../src/app'; let authToken = ''; -describe('Articles router tests', () => { +describe('Articles controller tests', () => { beforeAll(async () => { await deleteAllUsers(database); + await deleteAllFeeds(database); }); describe('GET all articles', () => { @@ -126,6 +127,7 @@ describe('Articles router tests', () => { afterAll(async () => { await deleteAllFeeds(database); + await deleteAllUsers(database); }); }); diff --git a/back/__tests__/controllers/feeds.test.ts b/back/__tests__/controllers/feeds.test.ts new file mode 100644 index 0000000..983ea77 --- /dev/null +++ b/back/__tests__/controllers/feeds.test.ts @@ -0,0 +1,67 @@ +import HttpStatusCode from '#types/HttpStatusCode'; +import { + deleteAllFeeds, + deleteAllUsers, + exampleFeeds, + populateFeeds, +} from 'prisma/seedingOperatons'; +import { database } from 'src/lucia'; +import request from 'supertest'; +import app from '../../src/app'; + +let authToken = ''; +let registeredUserId = ''; + +describe('Feeds controller tests', () => { + describe('GET all feeds', () => { + beforeAll(async () => { + await populateFeeds(database); + const res = await request(app).post('/api/users/register').send({ + email: 'alexis.moins@epitech.eu', + password: 'mySecretPassword', + username: 'Alexis', + }); + authToken = res.body.token; + registeredUserId = res.body.user.id; + }); + + test('GET all feeds unauthenticated', async () => { + const res = await request(app).get('/api/feeds'); + expect(res.statusCode).toStrictEqual(HttpStatusCode.UNAUTHORIZED_401); + }); + + test('GET all feeds authenticated as non admin', async () => { + const res = await request(app) + .get('/api/feeds') + .set('Authorization', `Bearer ${authToken}`); + expect(res.statusCode).toStrictEqual(HttpStatusCode.FORBIDDEN_403); + }); + + test('GET all feeds authenticated as admin', async () => { + await setRegisteredUserAdmin(registeredUserId); + const res = await request(app) + .get('/api/feeds') + .set('Authorization', `Bearer ${authToken}`); + expect(res.statusCode).toStrictEqual(HttpStatusCode.OK_200); + expect(JSON.parse(JSON.stringify(res.body))).toStrictEqual( + JSON.parse(JSON.stringify(exampleFeeds)) + ); + }); + + afterAll(async () => { + await deleteAllFeeds(database); + await deleteAllUsers(database); + }); + }); +}); + +async function setRegisteredUserAdmin(userId: string) { + await database.user.update({ + where: { + id: userId, + }, + data: { + is_admin: true, + }, + }); +} diff --git a/back/package.json b/back/package.json index 3f59bd6..7c1823f 100644 --- a/back/package.json +++ b/back/package.json @@ -4,7 +4,7 @@ "license": "MIT", "scripts": { "dev": "tsx watch src/index.ts", - "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --colors --coverage", + "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --colors --coverage --runInBand", "lint": "biome lint src/", "format": "biome format --write src/" }, diff --git a/back/prisma/seed.ts b/back/prisma/seed.ts index b4146e5..7923804 100644 --- a/back/prisma/seed.ts +++ b/back/prisma/seed.ts @@ -9,6 +9,7 @@ const prismaClient = new PrismaClient(); async function main() { console.log(`Start seeding ...`); + // TODO: Real user population working with lucia await populateUser(prismaClient); await populateFeeds(prismaClient); await populateArticles(prismaClient); diff --git a/back/prisma/seedingOperatons.ts b/back/prisma/seedingOperatons.ts index 93603c3..3d74c09 100644 --- a/back/prisma/seedingOperatons.ts +++ b/back/prisma/seedingOperatons.ts @@ -35,7 +35,7 @@ const exampleUsers: User[] = [ }, ]; -const exampleFeeds: Feed[] = [ +export const exampleFeeds: Feed[] = [ { id: 1, url: 'https://cointelegraph.com/rss/tag/bitcoin', diff --git a/back/src/apiErrors.ts b/back/src/apiErrors.ts index 9756582..f1dd4fa 100644 --- a/back/src/apiErrors.ts +++ b/back/src/apiErrors.ts @@ -39,6 +39,11 @@ enum ApiErrors { */ UNAUTHORIZED = 'UNAUTHORIZED', + /** + * Raised when the user is not admin and the endpoint reached need admin rights + */ + FORBIDDEN = 'FORBIDDEN', + /** * Raised when the resource at the given id was not found by prisma. */ diff --git a/back/src/app.ts b/back/src/app.ts index 284272a..2bacbe0 100644 --- a/back/src/app.ts +++ b/back/src/app.ts @@ -5,9 +5,15 @@ import cors from 'cors'; import articles from '@controllers/articles'; import auth from '@controllers/auth'; +import feeds from '@controllers/feeds'; import users from '@controllers/users'; -import { authenticationRequired, errorHandler, logger } from '~middlewares'; +import { + adminRoleRequired, + authenticationRequired, + errorHandler, + logger, +} from '~middlewares'; const app = express(); @@ -18,6 +24,7 @@ app.use(logger); app.use('/api/users/', auth, authenticationRequired, users); app.use('/api/articles', articles); +app.use('/api/feeds', authenticationRequired, adminRoleRequired, feeds); app.use(errorHandler); diff --git a/back/src/controllers/auth.ts b/back/src/controllers/auth.ts index b207930..7f57e76 100644 --- a/back/src/controllers/auth.ts +++ b/back/src/controllers/auth.ts @@ -42,7 +42,7 @@ controller.post('/register', async (req, res) => { }, attributes: { email: body.email, - username: body.email, + username: body.username, currency: body.currency as string, is_admin: false, }, diff --git a/back/src/controllers/feeds.ts b/back/src/controllers/feeds.ts new file mode 100644 index 0000000..9f9373c --- /dev/null +++ b/back/src/controllers/feeds.ts @@ -0,0 +1,11 @@ +import express from 'express'; +import HttpStatusCode from '#types/HttpStatusCode'; +import { findAllFeeds } from '../database/feeds'; + +const controller = express.Router(); + +controller.get('/', async (req, res) => { + return res.status(HttpStatusCode.OK_200).send(await findAllFeeds()); +}); + +export default controller; diff --git a/back/src/database/feeds.ts b/back/src/database/feeds.ts new file mode 100644 index 0000000..74f746e --- /dev/null +++ b/back/src/database/feeds.ts @@ -0,0 +1,6 @@ +import { Feed } from '@prisma/client'; +import { database } from '~lucia'; + +export async function findAllFeeds(): Promise { + return await database.feed.findMany(); +} diff --git a/back/src/middlewares.ts b/back/src/middlewares.ts index 3780ff9..f1353a6 100644 --- a/back/src/middlewares.ts +++ b/back/src/middlewares.ts @@ -16,6 +16,26 @@ export function logger(req: Request, _: Response, next: NextFunction) { next(); } +/** + * Only pass to the next middleware if the request is authenticated. + */ +export async function adminRoleRequired( + req: Request, + res: Response, + next: NextFunction, +) { + console.log('[AUTH] admin endpoint middleware'); + + if (!req.lucia || !req.lucia.user || !req.lucia.sessionId) + return res + .status(HttpStatusCode.UNAUTHORIZED_401) + .send(ApiErrors.UNAUTHORIZED); + if (!req.lucia.user.is_admin) + return res.status(HttpStatusCode.FORBIDDEN_403).send(ApiErrors.FORBIDDEN); + + next(); +} + /** * Only pass to the next middleware if the request is authenticated. */