Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OV-8: protect routing #33

Merged
merged 30 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8ce8243
OV-8: + empty auth jwt plugin
Sanchousina Aug 20, 2024
44b2532
OV-8: + register plugins
Sanchousina Aug 20, 2024
da1397e
OV-8: + dependencies
Sanchousina Aug 20, 2024
5af5016
OV-8: * merging with OV-5
Sanchousina Aug 20, 2024
9721153
OV-8: + findById method to Repository type
Sanchousina Aug 20, 2024
89ec60b
OV-8: + findById method to Service type
Sanchousina Aug 20, 2024
b707dc3
OV-8: + findById method to user service and repository
Sanchousina Aug 20, 2024
5e46444
OV-8: * userId to number
Sanchousina Aug 20, 2024
50245d6
OV-8: + http codes to http code enum
Sanchousina Aug 20, 2024
ee3a4e5
OV-8: + hook and error messages enums for auth plugin
Sanchousina Aug 20, 2024
508cd31
Merge remote-tracking branch 'origin/task/OV-5-JWT-token' into task/O…
Sanchousina Aug 20, 2024
8eb1aae
OV-8: * export user entity
Sanchousina Aug 20, 2024
3561451
OV-8: * implement auth jwt plugin
Sanchousina Aug 20, 2024
f034695
OV-8: + white routes constant
Sanchousina Aug 20, 2024
36a220e
OV-8: * refactor checking for white route
Sanchousina Aug 20, 2024
9e4004b
OV-8: * use white routes constant
Sanchousina Aug 20, 2024
502aad9
OV-8: * extract fastify module augmentation into file
Sanchousina Aug 20, 2024
a9e0c53
Merge remote-tracking branch 'origin/next' into task/OV-8-protect-rou…
Sanchousina Aug 20, 2024
218665a
OV-8: * modify find user method instead of adding findById
Sanchousina Aug 21, 2024
2f9f951
OV-8: * move dependencies to backend
Sanchousina Aug 21, 2024
9f17f91
OV-8: * white routes constant to include path method
Sanchousina Aug 21, 2024
3aaf539
OV-8: + route type
Sanchousina Aug 21, 2024
dbfec2d
OV-8: * checking if route is in white list with method
Sanchousina Aug 21, 2024
5a24c65
Merge remote-tracking branch 'origin/task/OV-5-JWT-token' into task/O…
Sanchousina Aug 21, 2024
b4fbbde
OV-8: + util function for checking route in white routes
Sanchousina Aug 21, 2024
a88eca1
OV-8: * modify find method in user repository instead of findById
Sanchousina Aug 21, 2024
1ac2392
OV-8: * use id instead of userId in types for repository and service
Sanchousina Aug 21, 2024
0354932
OV-8: - remove commented code
Sanchousina Aug 23, 2024
e9b8425
OV-8: * rename find to findById in service and repository
Sanchousina Aug 27, 2024
9e20e1c
Merge remote-tracking branch origin/task/OV-5-JWT-token into task/OV-…
Sanchousina Aug 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/src/bundles/users/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ class UserRepository implements Repository {
return user ? UserEntity.initialize(user) : 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 findAll(): Promise<UserEntity[]> {
const users = await this.userModel.query().execute();

Expand Down
4 changes: 4 additions & 0 deletions backend/src/bundles/users/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class UserService implements Service {
return await this.userRepository.findByEmail(email);
}

public async findById(userId: number): Promise<UserEntity | null> {
return await this.userRepository.findById(userId);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modify method find instead of adding this

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only in user service or in user repository as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified only in user service for now

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please also modify it in the repository

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

public async findAll(): Promise<UserGetAllResponseDto> {
const items = await this.userRepository.findAll();

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';
8 changes: 8 additions & 0 deletions backend/src/common/constants/white-routes.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ApiPath, AuthApiPath } from 'shared';

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

export { WHITE_ROUTES };
60 changes: 60 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,60 @@
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';

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

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

fastify.addHook(Hook.PRE_HANDLER, async (request) => {
const isRouteInWhiteList = options.routesWhiteList.includes(
request.url,
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also check for method in whitelist because each route can have multiple methods

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


if (isRouteInWhiteList) {
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 };
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';
10 changes: 10 additions & 0 deletions backend/src/common/server-application/base-server-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 @@ -122,6 +124,12 @@ class BaseServerApp implements ServerApp {
);
}

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

private initValidationCompiler(): void {
this.app.setValidatorCompiler(
({ schema }: { schema: ValidationSchema }) => {
Expand Down Expand Up @@ -200,6 +208,8 @@ class BaseServerApp implements ServerApp {

await this.initMiddlewares();

this.registerPlugins();

this.initValidationCompiler();

this.initErrorHandler();
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;
}
}
1 change: 1 addition & 0 deletions backend/src/common/types/repository.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type Repository<T = unknown> = {
create(payload: unknown): Promise<T>;
update(): Promise<T>;
delete(): Promise<boolean>;
findById(userId: number): Promise<T | null>;
};

export { type Repository };
1 change: 1 addition & 0 deletions backend/src/common/types/service.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ type Service<T = unknown> = {
findAll(): Promise<{
items: T[];
}>;
findById(userId: number): Promise<T | null>;
create(payload: unknown): Promise<T>;
update(): Promise<T>;
delete(): Promise<boolean>;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/bundles/common/components/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type Properties = {
};

const Button: React.FC<Properties> = ({ type = 'button', label }) => (
<LibraryButton type={type} color="brand.900" width="full">
<LibraryButton type={type} width="full">
{label}
</LibraryButton>
);
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/bundles/common/components/components.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
export { Button } from './button/button.js';
export { Input } from './input/input.js';
export { Link } from './link/link.js';
export { Loader } from './loader/loader.js';
export { Overlay } from './overlay/overlay.js';
export { RouterProvider } from './router-provider/router-provider.js';
export {
Box,
Circle,
ChakraProvider as ComponentsProvider,
Flex,
Heading,
Text,
VStack,
} from '@chakra-ui/react';
export { FormikProvider as FormProvider } from 'formik';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SPIN_ANIMATION } from './spin-animation.constant.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { keyframes } from '@chakra-ui/react';

const SPIN_ANIMATION = keyframes`
0% { transform: rotate(0deg);}
100% { transform: rotate(360deg)}
`;

export { SPIN_ANIMATION };
32 changes: 32 additions & 0 deletions frontend/src/bundles/common/components/loader/loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Box, Circle, Flex, Text } from '@chakra-ui/react';

import { SPIN_ANIMATION } from './libs/constants/constants.js';

const Loader = (): JSX.Element => {
return (
<Flex flexDirection="column" alignItems="center">
<Box position="relative" width="100px" height="100px">
<Circle
size="full"
backgroundColor="white"
color="text.default"
>
LOGO
</Circle>
<Circle
position="absolute"
inset="0"
borderWidth="5px"
borderColor="shadow.200"
borderTopColor="brand.secondary.300"
animation={`${SPIN_ANIMATION} 1s linear infinite`}
/>
</Box>
<Text fontSize="lg" marginTop="10px">
Loading...
</Text>
</Flex>
);
};

export { Loader };
26 changes: 26 additions & 0 deletions frontend/src/bundles/common/components/overlay/overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Fade, Flex } from '@chakra-ui/react';

type Properties = {
isOpen: boolean;
children: React.ReactNode;
};

const Overlay = ({ isOpen, children }: Properties): JSX.Element => {
return (
<Fade in={isOpen}>
<Flex
width="full"
height="full"
position="absolute"
background="shadow.700"
color="white"
justifyContent="center"
alignItems="center"
>
{children}
</Flex>
</Fade>
);
};

export { Overlay };
1 change: 1 addition & 0 deletions frontend/src/bundles/users/components/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { UserCard } from './user-card/user-card.js';
32 changes: 32 additions & 0 deletions frontend/src/bundles/users/components/user-card/user-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
Button,
Circle,
Flex,
Text,
VStack,
} from '~/bundles/common/components/components.js';

const UserCard: React.FC = () => (
<VStack rounded="lg" bg="background.600" spacing="10px" p="15px 5px 10px">
<Flex
w="full"
align="center"
color="brand.secondary.900"
gap="15px"
pl="10px"
>
{/* TODO: replace Circle and Text content with dynamic values */}
<Circle
size="40px"
border="2px solid"
borderColor="brand.secondary.900"
>
FN
</Circle>
<Text>Firstname Lastname</Text>
</Flex>
<Button label="Create video" />
</VStack>
);

export { UserCard };
21 changes: 21 additions & 0 deletions frontend/src/framework/theme/styles/colors.styles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
const colors = {
white: '#ffffff',
background: {
900: '#0a0049',
600: '#35399a',
300: '#3c9cf5',
50: '#e2e1ec',
},
brand: {
900: '#1a365d',
200: '#b3e0ff',
secondary: {
300: '#ff6e1c',
600: '#eb5500',
900: '#e13b00',
},
},
shadow: {
200: 'rgba(0, 0, 0, 0.2)',
700: 'rgba(0, 0, 0, 0.7)',
},
typography: {
900: '#181b1a',
600: '#616271',
300: '#989898',
},
text: {
default: '#36454f',
Expand Down
Loading
Loading