From addae9087a0da6ca98f400f3d3ed721927d3ea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Malak?= Date: Fri, 29 Oct 2021 13:58:55 +0200 Subject: [PATCH] Check snippet read permissions in controllers --- src/controllers/auth/getProfile.ts | 34 ++++++++++++++++++++++ src/controllers/snippets/countTags.ts | 13 ++++++++- src/controllers/snippets/deleteSnippet.ts | 4 +-- src/controllers/snippets/getAllSnippets.ts | 14 +++++++-- src/controllers/snippets/searchSnippets.ts | 8 +++-- src/environment.d.ts | 2 +- src/middleware/authenticate.ts | 14 +++++++-- src/routes/snippets.ts | 8 ++--- src/typescript/interfaces/Request.ts | 4 +-- 9 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 src/controllers/auth/getProfile.ts diff --git a/src/controllers/auth/getProfile.ts b/src/controllers/auth/getProfile.ts new file mode 100644 index 0000000..9cbbee6 --- /dev/null +++ b/src/controllers/auth/getProfile.ts @@ -0,0 +1,34 @@ +import { Response, NextFunction } from 'express'; +import { asyncWrapper } from '../../middleware'; +import { UserInstance, UserModel } from '../../models'; +import { UserInfoRequest } from '../../typescript/interfaces'; + +interface RequestBody {} + +interface ResponseBody { + data: Omit; +} + +/** + * @description Get user profile by token + * @route /api/auth/me + * @request POST + * @access Public + */ +export const getProfile = asyncWrapper( + async ( + req: UserInfoRequest, + res: Response, + next: NextFunction + ): Promise => { + const user = (await UserModel.findOne({ + where: { id: req.user.id }, + attributes: { exclude: ['password'] }, + raw: true + })) as UserInstance; + + res.status(200).json({ + data: user + }); + } +); diff --git a/src/controllers/snippets/countTags.ts b/src/controllers/snippets/countTags.ts index a06e36b..1c5105d 100644 --- a/src/controllers/snippets/countTags.ts +++ b/src/controllers/snippets/countTags.ts @@ -2,6 +2,7 @@ import { Response, NextFunction } from 'express'; import { QueryTypes } from 'sequelize'; import { sequelize } from '../../db'; import { asyncWrapper } from '../../middleware'; +import { UserInfoRequest } from '../../typescript/interfaces'; /** * @description Count tags @@ -10,13 +11,23 @@ import { asyncWrapper } from '../../middleware'; * @access Private */ export const countTags = asyncWrapper( - async (req: Request, res: Response, next: NextFunction): Promise => { + async ( + req: UserInfoRequest, + res: Response, + next: NextFunction + ): Promise => { + let where = !req.user.isAdmin + ? `WHERE snippets.createdBy = ${req.user.id}` + : ''; + const result = await sequelize.query( `SELECT COUNT(tags.name) as count, tags.name FROM snippets_tags INNER JOIN tags ON snippets_tags.tag_id = tags.id + INNER JOIN snippets ON snippets_tags.snippet_id = snippets.id + ${where} GROUP BY tags.name ORDER BY name ASC`, { diff --git a/src/controllers/snippets/deleteSnippet.ts b/src/controllers/snippets/deleteSnippet.ts index f8efb8a..74aa145 100644 --- a/src/controllers/snippets/deleteSnippet.ts +++ b/src/controllers/snippets/deleteSnippet.ts @@ -1,8 +1,8 @@ import { Response, NextFunction } from 'express'; import { asyncWrapper } from '../../middleware'; import { SnippetModel, Snippet_TagModel } from '../../models'; -import { Snippet, UserInfoRequest } from '../../typescript/interfaces'; -import { tagParser, createTags, ErrorResponse } from '../../utils'; +import { UserInfoRequest } from '../../typescript/interfaces'; +import { ErrorResponse } from '../../utils'; interface Params { id: number; diff --git a/src/controllers/snippets/getAllSnippets.ts b/src/controllers/snippets/getAllSnippets.ts index 63b850b..bff830b 100644 --- a/src/controllers/snippets/getAllSnippets.ts +++ b/src/controllers/snippets/getAllSnippets.ts @@ -1,6 +1,7 @@ -import { Request, Response, NextFunction } from 'express'; +import { Response, NextFunction } from 'express'; import { asyncWrapper } from '../../middleware'; import { SnippetModel, TagModel } from '../../models'; +import { UserInfoRequest } from '../../typescript/interfaces'; /** * @description Get all snippets @@ -8,7 +9,13 @@ import { SnippetModel, TagModel } from '../../models'; * @request GET */ export const getAllSnippets = asyncWrapper( - async (req: Request, res: Response, next: NextFunction): Promise => { + async ( + req: UserInfoRequest, + res: Response, + next: NextFunction + ): Promise => { + let where = req.user.isAdmin ? {} : { createdBy: req.user.id }; + const snippets = await SnippetModel.findAll({ include: { model: TagModel, @@ -17,7 +24,8 @@ export const getAllSnippets = asyncWrapper( through: { attributes: [] } - } + }, + where }); const populatedSnippets = snippets.map(snippet => { diff --git a/src/controllers/snippets/searchSnippets.ts b/src/controllers/snippets/searchSnippets.ts index 0658fa4..88782da 100644 --- a/src/controllers/snippets/searchSnippets.ts +++ b/src/controllers/snippets/searchSnippets.ts @@ -1,7 +1,8 @@ -import { Request, Response, NextFunction } from 'express'; +import { Response, NextFunction } from 'express'; import { asyncWrapper } from '../../middleware'; import { SnippetModel, TagModel } from '../../models'; import { Op } from 'sequelize'; +import { UserInfoRequest } from '../../typescript/interfaces'; interface Body { query: string; @@ -17,7 +18,7 @@ interface Body { */ export const searchSnippets = asyncWrapper( async ( - req: Request<{}, {}, Body>, + req: UserInfoRequest, res: Response, next: NextFunction ): Promise => { @@ -49,6 +50,9 @@ export const searchSnippets = asyncWrapper( }, { language: languageFilter + }, + { + createdBy: req.user.id } ] }, diff --git a/src/environment.d.ts b/src/environment.d.ts index 3b5fc11..11be474 100644 --- a/src/environment.d.ts +++ b/src/environment.d.ts @@ -1,7 +1,7 @@ declare global { namespace NodeJS { interface ProcessEnv { - PORT: number; + PORT: string; NODE_ENV: string; JWT_SECRET: string; } diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts index 4201d9c..20abb06 100644 --- a/src/middleware/authenticate.ts +++ b/src/middleware/authenticate.ts @@ -1,12 +1,20 @@ -import { NextFunction, Request, Response } from 'express'; +import { NextFunction, Response } from 'express'; import { asyncWrapper } from '.'; import { ErrorResponse } from '../utils'; import { verify } from 'jsonwebtoken'; import { Token, UserInfoRequest } from '../typescript/interfaces'; import { UserModel } from '../models'; +interface Query { + token?: string; +} + export const authenticate = asyncWrapper( - async (req: UserInfoRequest, res: Response, next: NextFunction) => { + async ( + req: UserInfoRequest<{}, {}, Query>, + res: Response, + next: NextFunction + ) => { let token: string | null = null; // Check if token was provided @@ -14,6 +22,8 @@ export const authenticate = asyncWrapper( if (req.headers.authorization.startsWith('Bearer ')) { token = req.headers.authorization.split(' ')[1]; } + } else if (req.query.token) { + token = req.query.token; } if (token) { diff --git a/src/routes/snippets.ts b/src/routes/snippets.ts index 9d58474..2ce5de7 100644 --- a/src/routes/snippets.ts +++ b/src/routes/snippets.ts @@ -21,7 +21,7 @@ snippetRouter requireBody('title', 'language', 'code', 'tags'), createSnippet ) - .get(getAllSnippets); + .get(authenticate, getAllSnippets); snippetRouter .route('/:id') @@ -29,6 +29,6 @@ snippetRouter .put(authenticate, updateSnippet) .delete(authenticate, deleteSnippet); -snippetRouter.route('/statistics/count').get(countTags); -snippetRouter.route('/raw/:id').get(getRawCode); -snippetRouter.route('/search').post(searchSnippets); +snippetRouter.route('/statistics/count').get(authenticate, countTags); +snippetRouter.route('/raw/:id').get(authenticate, getRawCode); +snippetRouter.route('/search').post(authenticate, searchSnippets); diff --git a/src/typescript/interfaces/Request.ts b/src/typescript/interfaces/Request.ts index c64abf9..228919b 100644 --- a/src/typescript/interfaces/Request.ts +++ b/src/typescript/interfaces/Request.ts @@ -1,7 +1,7 @@ import { Request } from 'express'; -export interface UserInfoRequest - extends Request { +export interface UserInfoRequest + extends Request { user: { id: number; email: string;