Skip to content

Commit

Permalink
Merge pull request #33 from BinaryStudioAcademy/task/OV-8-protect-rou…
Browse files Browse the repository at this point in the history
…ting

OV-8: protect routing
  • Loading branch information
nikita-remeslov authored Aug 28, 2024
2 parents 471c32b + 9e20e1c commit f826db6
Show file tree
Hide file tree
Showing 21 changed files with 141 additions and 8 deletions.
5 changes: 3 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@
"convict": "6.2.4",
"dotenv": "16.4.5",
"fastify": "4.28.1",
"fastify-plugin": "4.5.1",
"jose": "5.7.0",
"knex": "3.1.0",
"objection": "3.1.4",
"pg": "8.12.0",
"pino": "9.3.2",
"pino-pretty": "10.3.1",
"shared": "*",
"swagger-jsdoc": "6.2.8",
"jose": "5.7.0"
"swagger-jsdoc": "6.2.8"
}
}
6 changes: 4 additions & 2 deletions backend/src/bundles/users/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ class UserRepository implements Repository {
this.userModel = userModel;
}

public find(): ReturnType<Repository['find']> {
return Promise.resolve(null);
public async findById(userId: number): Promise<UserEntity | null> {
const user = await this.userModel.query().findById(userId).execute();

return user ? UserEntity.initialize(user) : null;
}

public async findByEmail(email: string): Promise<UserEntity | null> {
Expand Down
4 changes: 2 additions & 2 deletions backend/src/bundles/users/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class UserService implements Service {
this.userRepository = userRepository;
}

public find(): ReturnType<Service['find']> {
return Promise.resolve(null);
public async findById(userId: number): Promise<UserEntity | null> {
return await this.userRepository.findById(userId);
}

public async findByEmail(email: string): Promise<UserEntity | null> {
Expand Down
1 change: 1 addition & 0 deletions backend/src/bundles/users/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export {
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from './types/types.js';
export { type UserEntity } from './user.entity.js';
export { UserModel } from './user.model.js';
export {
userSignInValidationSchema,
Expand Down
1 change: 1 addition & 0 deletions backend/src/common/constants/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { USER_PASSWORD_SALT_ROUNDS } from './user.constants.js';
export { WHITE_ROUTES } from './white-routes.constants.js';
14 changes: 14 additions & 0 deletions backend/src/common/constants/white-routes.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApiPath, AuthApiPath } from 'shared';

const WHITE_ROUTES = [
{
path: `/api/v1${ApiPath.AUTH}${AuthApiPath.SIGN_IN}`,
method: 'POST',
},
{
path: `/api/v1${ApiPath.AUTH}${AuthApiPath.SIGN_UP}`,
method: 'POST',
},
];

export { WHITE_ROUTES };
58 changes: 58 additions & 0 deletions backend/src/common/plugins/auth/auth-jwt.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import fp from 'fastify-plugin';
import { HttpCode, HttpError, HttpHeader } from 'shared';

import { userService } from '~/bundles/users/users.js';
import { tokenService } from '~/common/services/services.js';

import { ErrorMessage, Hook } from './enums/enums.js';
import { type Route } from './types/types.js';
import { isRouteInWhiteList } from './utils/utils.js';

type Options = {
routesWhiteList: Route[];
};

const authenticateJWT = fp<Options>((fastify, { routesWhiteList }, done) => {
fastify.decorateRequest('user', null);

fastify.addHook(Hook.PRE_HANDLER, async (request) => {
if (isRouteInWhiteList(routesWhiteList, request)) {
return;
}

const authHeader = request.headers[HttpHeader.AUTHORIZATION];

if (!authHeader) {
throw new HttpError({
message: ErrorMessage.MISSING_TOKEN,
status: HttpCode.UNAUTHORIZED,
});
}

const [, token] = authHeader.split(' ');

const userId = await tokenService.getUserIdFromToken(token as string);

if (!userId) {
throw new HttpError({
message: ErrorMessage.INVALID_TOKEN,
status: HttpCode.UNAUTHORIZED,
});
}

const user = await userService.findById(userId);

if (!user) {
throw new HttpError({
message: ErrorMessage.MISSING_USER,
status: HttpCode.BAD_REQUEST,
});
}

request.user = user;
});

done();
});

export { authenticateJWT };
2 changes: 2 additions & 0 deletions backend/src/common/plugins/auth/enums/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ErrorMessage } from './error-message.enum.js';
export { Hook } from './hook.enum.js';
7 changes: 7 additions & 0 deletions backend/src/common/plugins/auth/enums/error-message.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const ErrorMessage = {
MISSING_TOKEN: 'You are not logged in',
INVALID_TOKEN: 'Token is no longer valid. Please log in again.',
MISSING_USER: 'User with this id does not exist.',
} as const;

export { ErrorMessage };
5 changes: 5 additions & 0 deletions backend/src/common/plugins/auth/enums/hook.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Hook = {
PRE_HANDLER: 'preHandler',
} as const;

export { Hook };
6 changes: 6 additions & 0 deletions backend/src/common/plugins/auth/types/route.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type Route = {
path: string;
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
};

export { type Route };
1 change: 1 addition & 0 deletions backend/src/common/plugins/auth/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { type Route } from './route.type.js';
15 changes: 15 additions & 0 deletions backend/src/common/plugins/auth/utils/check-white-routes.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type FastifyRequest } from 'fastify';

import { type Route } from '../types/types.js';

const isRouteInWhiteList = (
routesWhiteList: Route[],
request: FastifyRequest,
): boolean => {
return routesWhiteList.some(
(route) =>
route.path === request.url && route.method === request.method,
);
};

export { isRouteInWhiteList };
1 change: 1 addition & 0 deletions backend/src/common/plugins/auth/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { isRouteInWhiteList } from './check-white-routes.util.js';
1 change: 1 addition & 0 deletions backend/src/common/plugins/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { authenticateJWT } from './auth/auth-jwt.plugin.js';
6 changes: 6 additions & 0 deletions backend/src/common/server-application/base-server-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
type ValidationSchema,
} from '~/common/types/types.js';

import { WHITE_ROUTES } from '../constants/constants.js';
import { authenticateJWT } from '../plugins/plugins.js';
import {
type ServerApp,
type ServerAppApi,
Expand Down Expand Up @@ -124,6 +126,10 @@ class BaseServerApp implements ServerApp {
}

private registerPlugins(): void {
this.app.register(authenticateJWT, {
routesWhiteList: WHITE_ROUTES,
});

this.app.register(fastifyMultipart, {
limits: {
fileSize: Number.POSITIVE_INFINITY,
Expand Down
9 changes: 9 additions & 0 deletions backend/src/common/types/fastify.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'fastify';

import { type UserEntity } from '~/bundles/users/users.js';

declare module 'fastify' {
interface FastifyRequest {
user: UserEntity;
}
}
2 changes: 1 addition & 1 deletion backend/src/common/types/repository.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type Repository<T = unknown> = {
find(): Promise<T>;
findById(id: number): Promise<T | null>;
findAll(): Promise<T[]>;
create(payload: unknown): Promise<T>;
update(): Promise<T>;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/common/types/service.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type Service<T = unknown> = {
find(): Promise<T>;
findById(id: number): Promise<T | null>;
findAll(): Promise<{
items: T[];
}>;
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions shared/src/framework/http/enums/http-code.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const HttpCode = {
BAD_REQUEST: 400,
UNPROCESSED_ENTITY: 422,
INTERNAL_SERVER_ERROR: 500,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
} as const;

export { HttpCode };

0 comments on commit f826db6

Please sign in to comment.