Skip to content

Commit

Permalink
chore(back): multiple changes (#65)
Browse files Browse the repository at this point in the history
* docs(backend): add some backend docs (#45)

* Clean some files

* Clean README

* Cleaning docks

* Updating docs format

* Updating docs format

* Updating docs format

* Update Summary

* docs(backend): add backend structure to doc

* docs(structure): add structure db file

* docs(structure): rename structure file

* feat(user): add some user field, also display avatar correctly

* chore(middleware): apply middleware on module instead of calling it for each handler in controller

* chore(guard): wip, guard cant work because of httponly on cookies, need to add https access to enable guards working with this security

* chore(security): nginx added, https worked. Also changed all call to the app by the new port

* WIP: websocket with proxy

* Solve vite websockets HMR

* fix our websockets

* Change phoneNumber to phone

* Dont expose ports on the hosts for app and back

* Prettify the Makefile

* Stop listening on HTTP port for nginx

* Implement env vars in frontend

* Implement env vars in backend

* UsersMiddleware now get token from auth header, and not from cookie

* Backend: fix routes for User controller

---------

Co-authored-by: Tanguy Rossel <tanguy.rossel@tyrossel.ch>
  • Loading branch information
Bima42 and Tanguy Rossel committed Mar 19, 2023
1 parent 1a6f3ef commit 0fd5cf4
Show file tree
Hide file tree
Showing 35 changed files with 355 additions and 172 deletions.
16 changes: 8 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ RESET = \033[0m
all: dev

dev:
@echo "${GREEN}Building containers in dev mode...${RESET}"
@echo -e "${GREEN}Building containers in dev mode...${RESET}"
@docker compose build
@make up

Expand All @@ -15,32 +15,32 @@ up:
@docker compose up

detached:
@echo "${GREEN}Starting containers in detached mode...${RESET}"
@echo -e "${GREEN}Starting containers in detached mode...${RESET}"
@docker compose up -d

build:
@echo "${GREEN}Building containers images...${RESET}"
@echo -e "${GREEN}Building containers images...${RESET}"
@docker compose build --no-cache

down:
@echo "${GREEN}Stopping containers...${RESET}"
@echo -e "${GREEN}Stopping containers...${RESET}"
@docker compose down

clean:
@echo "${GREEN}Cleaning containers...${RESET}"
@echo -e "${GREEN}Cleaning containers...${RESET}"
@docker compose down -v --remove-orphans
rm -rf frontend/node_modules backend/node_modules

stop:
@echo "${GREEN}Stopping containers...${RESET}"
@echo -e "${GREEN}Stopping containers...${RESET}"
@docker compose stop

rebuild: clean
@echo "${GREEN}Rebuilding containers...${RESET}"
@echo -e "${GREEN}Rebuilding containers...${RESET}"
@docker compose up --build --force-recreate

prune: clean
@echo "${RED}Cleaning all docker environment...${RESET}"
@echo -e "${RED}Cleaning all docker environment...${RESET}"
@docker system prune -a -f --volumes
rm -rf frontend/node_modules backend/node_modules

Expand Down
2 changes: 2 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ RUN dos2unix /usr/local/bin/entrypoint.sh

RUN chmod +x /usr/local/bin/entrypoint.sh

EXPOSE 3080

ENTRYPOINT ["entrypoint.sh"]

# Prod target
10 changes: 10 additions & 0 deletions backend/prisma/migrations/20230319130910_phone_edit/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
Warnings:
- You are about to drop the column `phoneNumber` on the `User` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "User" DROP COLUMN "phoneNumber",
ADD COLUMN "phone" TEXT,
ADD COLUMN "twoFASecret" TEXT;
3 changes: 2 additions & 1 deletion backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ datasource db {
model User {
id Int @id @unique @default(autoincrement())
twoFA Boolean @default(false)
twoFASecret String?
fortyTwoId Int?
username String @unique @db.VarChar(255)
email String @unique
avatar String?
firstName String?
lastName String?
phoneNumber String?
phone String?
status UserStatus @default(OFFLINE)
chats UserChat[]
games UserGame[]
Expand Down
26 changes: 13 additions & 13 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import {Controller, ForbiddenException, Get, Param, Req, Res, UseGuards} from '@nestjs/common';
import {
Controller,
ForbiddenException,
Get,
Param,
Req,
Res,
UseGuards
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersService } from '../users/users.service';
import { AuthGuard } from '@nestjs/passport';
import { Response, Request } from 'express';
import { UserStatus } from '@prisma/client';

Expand All @@ -13,12 +20,6 @@ export class AuthController {
) {
}

@Get('42')
@UseGuards(AuthGuard('42'))
// eslint-disable-next-line @typescript-eslint/no-empty-function
async loginFortyTwo(@Res() res: Response) {
}

/**
* Callback for 42 API authentication
* Full Documentation here : https://api.intra.42.fr/apidoc/guides/web_application_flow
Expand Down Expand Up @@ -67,7 +68,7 @@ export class AuthController {
email,
firstName: first_name,
lastName: last_name,
phoneNumber: phone,
phone: phone,
fortyTwoId: id,
avatar: image.versions.medium,
});
Expand All @@ -78,15 +79,14 @@ export class AuthController {
}

// Create token and set cookie
if (!req.cookies['access_token']) {
if (!req.cookies[process.env.JWT_COOKIE]) {
const token = this.authService._createToken(user);

if (!token) {
throw new ForbiddenException('Empty token');
}

res.cookie('access_token', token.access_token, {
httpOnly: true,
res.cookie(process.env.JWT_COOKIE, token.access_token, {
maxAge: 1000 * 60 * 60 * 24, // 1 day
secure: true,
sameSite: 'none',
Expand All @@ -109,7 +109,7 @@ export class AuthController {
@Res({ passthrough: true }) res,
@Param() params: { id: number })
{
res.clearCookie('access_token');
res.clearCookie(process.env.JWT_COOKIE);
await this.usersService.updateStatus(params.id, UserStatus.OFFLINE);
await this.authService.logout(res, params.id);
}
Expand Down
14 changes: 5 additions & 9 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ export class AuthService {
) {
}

async findOrCreate({username, email, firstName, lastName, phoneNumber, fortyTwoId, avatar}: {
async findOrCreate({username, email, firstName, lastName, phone, fortyTwoId, avatar}: {
username: string,
email: string,
firstName: string,
lastName: string,
phoneNumber: string,
phone: string,
fortyTwoId: number,
avatar: string
}) {
Expand All @@ -33,7 +33,7 @@ export class AuthService {
email,
firstName,
lastName,
phoneNumber,
phone,
fortyTwoId,
avatar
}
Expand All @@ -46,13 +46,9 @@ export class AuthService {
id: user.id
},
data: {
username,
email,
firstName,
lastName,
phoneNumber,
fortyTwoId,
avatar
fortyTwoId
}
});
}
Expand Down Expand Up @@ -91,7 +87,7 @@ export class AuthService {
throw new BadRequestException('User not found');
}

res.cookie('access_token', '', {
res.cookie(process.env.JWT_COOKIE, '', {
httpOnly: true,
secure: true,
sameSite: 'none',
Expand Down
6 changes: 6 additions & 0 deletions backend/src/auth/guards/jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

/**
* @JwtStrategy : Guard typically uses a Strategy to validate the JWT
*
* @AuthGuard: Have a canActivate() method from the CanActivate interface
* This method is called by NestJS before a route is activated : it returns a boolean value that determines whether the route should be activated or not
*/
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
5 changes: 5 additions & 0 deletions backend/src/auth/strategies/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
}

/**
* This function is called by NestJS when a user attempts to access a route that requires authentication.
* If the token is valid, validate() returns a user object, which is then stored in the request object.
* If the token is not valid, validate() throws an error, which is handled by NestJS and results in a 401 Unauthorized response to the user.
*/
async validate(payload: { id: number; email: string }) {
return payload;
}
Expand Down
10 changes: 8 additions & 2 deletions backend/src/chat/channel.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import { OnGatewayConnection, OnGatewayDisconnect, ConnectedSocket, SubscribeMes
import { Socket, Server } from 'socket.io'

@WebSocketGateway({
cors: true,
namespace: "chat"
path: "/api/socket.io",
namespace: "chat",
cors: {
origin: process.env.FRONTEND_URL,
credentials: true,
allowedHeaders: 'Content-Type, Authorization, Cookie',
methods: ["GET", "POST"],
}
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {

Expand Down
4 changes: 3 additions & 1 deletion backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ async function bootstrap() {
// Use cookie parser middleware
app.use(cookieParser());

app.setGlobalPrefix('api');

app.enableCors({
origin: process.env.FRONTEND_URL,
credentials: true,
allowedHeaders: 'Content-Type, Authorization, Cookie',
methods: 'GET,POST,DELETE',
methods: 'GET,PUT,PATCH,POST,DELETE',
});

const config = new DocumentBuilder()
Expand Down
5 changes: 3 additions & 2 deletions backend/src/users/middlewares/users.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export class UsersMiddleware implements NestMiddleware {
) {}

async use(req: Request, res: Response, next: NextFunction) {
const token = req.cookies['access_token'];
const authHeader = req.headers.authorization
const token = authHeader && authHeader.split(' ')[1]
if (!token) {
res.status(401).send('Unauthorized');
}
Expand All @@ -31,4 +32,4 @@ export class UsersMiddleware implements NestMiddleware {
res.status(401).send('Unauthorized, token is invalid');
}
}
}
}
66 changes: 37 additions & 29 deletions backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,67 @@
import { Controller, Get, Post, Body, Next, Patch, Param, Delete, Req, Res } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Req,
Res,
UseGuards,
ParseIntPipe
} from '@nestjs/common';
import { Request, Response } from 'express';
import { UsersService } from './users.service';
import { User } from '@prisma/client';
import { ApiTags } from '@nestjs/swagger';
import { UsersMiddleware } from './middlewares/users.middleware';
import { JwtAuthGuard } from '../auth/guards/jwt.guard';

@Controller('users')
@UseGuards(JwtAuthGuard)
@ApiTags('users')
export class UsersController {
constructor(
private readonly authMiddleware: UsersMiddleware,
private readonly usersService: UsersService) {}
private readonly usersService: UsersService
) {}

@Get('login')
async login(@Req() req: Request, @Res() res: Response, @Next() next: NextFunction) {
await new Promise(resolve => this.authMiddleware.use(req, res, resolve));
const user = req.user;
if (!user) {
res.status(401).send('Unauthorized');
} else {
res.status(200).send(user);
}
async login(@Req() req: Request, @Res() res: Response) {
res.status(200).send(req.user);
}

@Post('create')
async createUser(@Body() data: User): Promise<User> {
return this.usersService.create(data);
}

@Get(':id')
async getUserById(@Param('id') userId: number) {
return this.usersService.findById(userId);
@Get('all')
async getAllUsers(@Req() req: Request, @Res() res: Response) {
const users = await this.usersService.findAll();
res.status(200).send({ users });
}

@Get()
async getAllUsers(@Req() req: Request, @Res() res: Response) {
if (!req.user) {
res.status(401).send('Unauthorized');
} else {
const users = await this.usersService.getAllUsers();
res.send({ users });
}
return this.usersService.findAll();
/**
* ParseIntPipe : protection to ensures that a method handler parameter is converted to a JavaScript integer
* (or throws an exception if the conversion fails).
*
* @param userId
*/
@Get('id/:id')
async getUserById(@Param('id', ParseIntPipe) userId: number) {
return this.usersService.findById(userId);
}

@Patch(':id')
@Patch('id/:id')
async updateUser(
@Param('id') userId: number,
@Param('id', ParseIntPipe) userId: number,
@Body() data: User
): Promise<User> {
return this.usersService.update(userId, data);
}

@Delete(':id')
async deleteUser(@Param('id') userId: number) {
@Delete('id/:id')
async deleteUser(@Param('id', ParseIntPipe) userId: number) {
return this.usersService.delete(userId);
}
}
Loading

0 comments on commit 0fd5cf4

Please sign in to comment.