add 2fa and env var(not in conf files)

This commit is contained in:
kinou-p 2023-06-13 21:12:57 +02:00
parent 47863325ea
commit 2b16af1785
30 changed files with 668 additions and 204 deletions

37
.env
View File

@ -1,3 +1,8 @@
#nessecarr var
#API_SECRET
# POSTGRES_USER=kinou
# POSTGRES_PASSWORD=pass
# POSTGRES_DB=postgreDB
@ -5,11 +10,35 @@
# POSTGRES_HOST=localhost
# POSTGRES_HOST_AUTH_METHOD=trust
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432
#URL
NGINX_ENVSUBST_TEMPLATE_SUFFIX=".conf"
BASE_URL=http://localhost
#postgres var
# POSTGRES_HOST=127.0.0.1
# DB_TYPE=postgres
POSTGRES_HOST=postgresql
POSTGRES_USER=postgres
POSTGRES_PASSWORD=pass
POSTGRES_DATABASE=postgres
PORT=3000
MODE=DEV
RUN_MIGRATIONS=true
#port
API_PORT=3000
# REACT_PORT=3000 (current = 8080)
NGINX_PORT=80
PONG_PORT=4000
CHAT_PORT=4001
POSTGRES_PORT=5432
#????
RUN_MIGRATIONS=true
REACT_HOST=0.0.0.0
#auth var
JWT_SECRET=secrethere
REDIRECT_URI=http://localhost:80/api/auth/login
API_SECRET=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
CLIENT_UID=u-s4t2ud-6d29dfa49ba7146577ffd8bf595ae8d9e5aaa3e0a9615df18777171ebf836a41

View File

@ -2,15 +2,14 @@ server {
# listen 443 ssl;
# listen 80 ssl;
# listen 443 ssl;
listen 80;
# listen ${NGINX_PORT};
listen 80;
location /{
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://react_app:8080;
}
@ -19,7 +18,6 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://api:3000/api;
}
}

View File

@ -22,6 +22,7 @@
"express-session": "^1.17.3",
"hi-base32": "^0.5.1",
"nanoid": "^3.3.4",
"otpauth": "^9.1.2",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
@ -6099,6 +6100,14 @@
"npm": ">=6"
}
},
"node_modules/jssha": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.0.tgz",
"integrity": "sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==",
"engines": {
"node": "*"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
@ -6685,6 +6694,17 @@
"node": ">=0.10.0"
}
},
"node_modules/otpauth": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.1.2.tgz",
"integrity": "sha512-iI5nlVvMFP3aTPdjG/fnC4mhVJ/KZOSnBrvo/VnYHUwlTp9jVLjAe2B3i3pyCH+3/E5jYQRSvuHk/8oas3870g==",
"dependencies": {
"jssha": "~3.3.0"
},
"funding": {
"url": "https://github.com/hectorm/otpauth?sponsor=1"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",

View File

@ -33,6 +33,7 @@
"express-session": "^1.17.3",
"hi-base32": "^0.5.1",
"nanoid": "^3.3.4",
"otpauth": "^9.1.2",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",

View File

@ -9,7 +9,15 @@ import { UsersService } from './users/users.service';
import { MatchLog } from './model/user.entity'
import { generate } from 'rxjs';
import { generateQRcode } from './users/2fa';
// import { generateQRcode } from './users/2fa';
import { generateOTP } from './users/2fa';
import { VerifyOTP } from './users/2fa';
import { ValidateOTP } from './users/2fa';
//2fa
// import { initStorage, getUser, setUser } from './storage';
@ -216,20 +224,30 @@ export class AppController {
//========================================================================================================
//========================================================================================================
// import { Prisma } from "@prisma/client";
// import { Request, Response, NextFunction } from "express";
// import { prisma } from "../server";
@Redirect('http://localhost/token', 302)
@Get('auth/login')
async login2(@Req() request: Request) {
const url = request.url;
const user = await this.loginClass.Login42(url);
console.log(`user in auth/login= ${user}`);
const data = this.authService.login(user);
console.log(`user in auth/login= ${user.username}`);
const data = await this.authService.login(user);
console.log(`all data in api = ${data}`)
const myJSON = JSON.stringify(data);
console.log(`all data json version= ${myJSON}`)
console.log(`data in api = ${(await data).access_token}`)
// console.log(`data i = ${(await data).access_token}`)
const token = (await data).access_token;
// console
await this.userService.save(user);
return { url: `http://localhost/token?data=${encodeURIComponent(JSON.stringify(token))}` };
}
@ -238,17 +256,49 @@ export class AppController {
async get2fa(@Request() req)
{
const user = await this.userService.findOne(req.user.username);
return user.doubleAuth;
return user.otp_enabled;
}
@UseGuards(JwtAuthGuard)
@Get('/QRcode')
async createQrCode(@Request() req)
@Post('/otp')
async createOTP(@Request() req)
{
return (await generateQRcode(req));
const user = await this.userService.findOne(req.user.username);
// const user2 = await this.userService.findOne(req.user.username);
const res = await generateOTP(user);
await this.userService.save(user);
// console.log(user);
return res;
}
@UseGuards(JwtAuthGuard)
@Post('/verifyOtp')
async verifyOTP(@Request() req, @Body() data: any)
{
const user = await this.userService.findOne(req.user.username);
const res = await VerifyOTP(user, data.token)
await this.userService.save(user);
return res
}
@UseGuards(JwtAuthGuard)
@Post('/validateOtp')
async validateOTP(@Request() req, @Body() data: any)
{
const user = await this.userService.findOne(req.user.username);
const res = await ValidateOTP(user, data.token)
// await this.userService.save(user);
return res
}
// @UseGuards(JwtAuthGuard)
// @Get('/QRcode')
// async createQrCode(@Request() req)
// {
// return (await generateQRcode(req));
// }
@UseGuards(JwtAuthGuard)
@Post('/quit')
async setOffline(@Request() req) {
@ -271,6 +321,8 @@ export class AppController {
async createConv(@Request() req, @Body() data: any) {
///create conv and return it ? id?
console.log(`data post /conv= ${data}`);
console.log(`data post /conv= ${data.members}`);
console.log(`data post /conv= ${data.name}`);
// let test = {id: 2, members: "cc"};
return await this.chatService.createConv(data);
// res.json(messages);
@ -278,20 +330,10 @@ export class AppController {
// @UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard)
@Get('/conv')
async getConv(@Request() req, @Body() data: any) {
///create conv and return it ? id?
// console.log(`data get /conv= ${data}`);
// let test = {id: 2, members: "cc"};
// let tab = [data.member, "test"];
// console.log(`tab= ${tab}`);
return await this.chatService.getConv(data.member);
// return await this.chatService.getConv(req.user.username);
// res.json(messages);
async getConv(@Request() req) {
return await this.chatService.getConv(req.user.username);
}
// @UseGuards(JwtAuthGuard)

View File

@ -28,7 +28,7 @@ import { JwtStrategy } from './jwt.strategy';
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60000s' },
// signOptions: { expiresIn: '60000s' },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],

View File

@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '@nestjs/jwt';
import { User } from 'src/model/user.entity';
@Injectable()
export class AuthService {
@ -19,13 +20,13 @@ export class AuthService {
return null;
}
async login(user) {
const myJSON = JSON.stringify(user);
async login(user: User) {
// const myJSON = JSON.stringify(user);
// console.log(`in login all user= ${myJSON}`)
// console.log(`in login user= ${user.username}`)
console.log(`in login user= ${user.username}`)
const payload = { username: user.username, sub: user.userId };
// console.log(`in login payload name= ${payload.username}`)
// console.log(`in login payload sub= ${payload.sub}`)
console.log(`in login payload name= ${payload.username}`)
console.log(`in login payload sub= ${payload.sub}`)
return {
access_token: this.jwtService.sign(payload),
};

View File

@ -1,3 +1,4 @@
export const jwtConstants = {
secret: 'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.',
// secret: 'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.',
secret: process.env.JWT_SECRET,
};

View File

@ -24,10 +24,11 @@ export class loginClass {
const data = {
grant_type: 'authorization_code',
client_id: 'u-s4t2ud-6d29dfa49ba7146577ffd8bf595ae8d9e5aaa3e0a9615df18777171ebf836a41',
client_secret: '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',
client_id: process.env.CLIENT_UID || 'u-s4t2ud-6d29dfa49ba7146577ffd8bf595ae8d9e5aaa3e0a9615df18777171ebf836a41',
// client_secret: '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',
client_secret: process.env.API_SECRET,
code: code,
redirect_uri: 'http://localhost:80/api/auth/login',
redirect_uri: process.env.REDIRECT_URI || 'http://localhost:80/api/auth/login',
};
try {
@ -64,9 +65,12 @@ export class loginClass {
loss: 0,
rank: 1200,
userId: userId,
otp_base32: null,
children: null,
status: 1,
doubleAuth: 0,
// doubleAuth: 0,
otp_enabled: false,
otp_verified: false,
friendRequest: null,
friends: null,
photo: null,

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/04/09 14:53:49 by apommier #+# #+# */
/* Updated: 2023/06/01 13:07:12 by apommier ### ########.fr */
/* Updated: 2023/06/12 14:51:44 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -14,11 +14,11 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const getTypeOrmConfig = (): TypeOrmModuleOptions => ({
type: 'postgres',
host: 'postgresql',
port: 5432,
username: 'postgres',
password: 'pass',
database: 'postgres',
host: process.env.POSTGRES_HOST || 'postgresql',
port: parseInt(process.env.POSTGRES_PORT, 10) || 5432,
username: process.env.POSTGRES_USER || 'postgres',
password: process.env.POSTGRES_PASSWORD || 'pass',
database: process.env.POSTGRES_DATABASE || 'postgres',
entities: ["dist/**/*.entity.js"],
// entities: [join(__dirname, '**', '*.entity.{ts,js}')]
// entities: ['**/*.entity{.ts,.js}'], //basic

View File

@ -1,6 +1,10 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';
import * as dotenv from 'dotenv';
dotenv.config();
console.log(process.env);
// async function bootstrap() {
// const app = await NestFactory.create(AppModule);
@ -27,6 +31,6 @@ async function bootstrap() {
saveUninitialized: false,
}),
);
await app.listen(3000);
await app.listen(parseInt(process.env.API_PORT) || 3000);
}
bootstrap();

View File

@ -20,6 +20,18 @@ export class User {
@PrimaryGeneratedColumn()
id: number;
// otp_enabled Boolean @default(false)
// otp_verified Boolean @default(false)
@Column({ default: false })
otp_enabled: boolean;
@Column({ default: false })
otp_verified: boolean;
@Column({ nullable: true })
otp_base32: string;
@Column({ nullable: true })
nickname: string;
@ -47,8 +59,8 @@ export class User {
@Column({ default: 0 })
userId: number;
@Column({ default: 0 })
doubleAuth: number;
// @Column({ default: 0 })
// doubleAuth: number;
@Column('text', { array: true, nullable: true })
friendRequest: string[];

View File

@ -1,6 +1,109 @@
import crypto from 'crypto';
// import crypto from 'crypto';
import base32Decode from 'base32-decode';
import crypto from "crypto";
import * as OTPAuth from "otpauth";
import { encode } from "hi-base32";
// [...] Register user
// [...] Login user
// [...] Generate OTP
const generateRandomBase32 = async () => {
const {randomBytes} = await import('crypto');
const buffer = randomBytes(15);
const base32 = encode(buffer).replace(/=/g, "").substring(0, 24);
return base32;
};
export const generateOTP = async (user) => {
try {
const base32_secret = await generateRandomBase32();
let totp = new OTPAuth.TOTP({
issuer: "Localhost",
label: "OnlinePong",
algorithm: "SHA1",
digits: 6,
period: 15,
secret: base32_secret,
});
let otpauth_url = totp.toString();
const res = {
otpauth_url: otpauth_url,
base32_secret: base32_secret
}
console.log("res= ", res)
//update db with otp var
user.otp_enabled = true;
user.otp_base32 = base32_secret;
return (res)
} catch (error) {
console.log(error)
}
};
export const VerifyOTP = async (user, token: string) => {
try {
let totp = new OTPAuth.TOTP({
issuer: "Localhost",
label: "OnlinePong",
algorithm: "SHA1",
digits: 6,
period: 15,
secret: user.otp_base32,
});
let delta = totp.validate({ token });
if (delta === null) {
console.log("error verify token")
return ("error verify token")
}
else
{
user.otp_verified = true;
console.log("token verified")
}
} catch (error) {
console.log(error)
}
};
export const ValidateOTP = async (user, token: string) => {
try {
let totp = new OTPAuth.TOTP({
issuer: "Localhost",
label: "OnlinePong",
algorithm: "SHA1",
digits: 6,
period: 15,
secret: user.otp_base32,
});
let delta = totp.validate({ token });
if (delta === null) {
console.log("error validate token")
return ("error validate token")
}
else
{
// user.otp_verified = true;
console.log("token validated")
}
} catch (error) {
console.log(error)
}
};
// import { randomBytes} from 'crypto';
// import { promisify } from 'util';
@ -33,48 +136,48 @@ import base32Decode from 'base32-decode';
// type QRcode = any;
export function generateHOTP(secret, counter) {
const decodedSecret = base32Decode(secret, 'RFC4648');
// export function generateHOTP(secret, counter) {
// const decodedSecret = base32Decode(secret, 'RFC4648');
const buffer = Buffer.alloc(8);
for (let i = 0; i < 8; i++) {
buffer[7 - i] = counter & 0xff;
counter = counter >> 8;
}
// Step 1: Generate an HMAC-SHA-1 value
const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
hmac.update(buffer);
const hmacResult = hmac.digest();
// Step 2: Generate a 4-byte string (Dynamic Truncation)
const offset = hmacResult[hmacResult.length - 1] & 0xf;
const code =
((hmacResult[offset] & 0x7f) << 24) |
((hmacResult[offset + 1] & 0xff) << 16) |
((hmacResult[offset + 2] & 0xff) << 8) |
(hmacResult[offset + 3] & 0xff);
// Step 3: Compute an HOTP value
return code % 10 ** 6;
}
// const buffer = Buffer.alloc(8);
// for (let i = 0; i < 8; i++) {
// buffer[7 - i] = counter & 0xff;
// counter = counter >> 8;
// }
export function generateTOTP(secret, window = 0)
{
const counter = Math.floor(Date.now() / 30000);
return generateHOTP(secret, counter + window);
}
// // Step 1: Generate an HMAC-SHA-1 value
// const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
// hmac.update(buffer);
// const hmacResult = hmac.digest();
export function verifyTOTP(token, secret, window = 1)
{
for (let errorWindow = -window; errorWindow <= +window; errorWindow++)
{
const totp = generateTOTP(secret, errorWindow);
if (token === totp)
return true;
}
return false;
}
// // Step 2: Generate a 4-byte string (Dynamic Truncation)
// const offset = hmacResult[hmacResult.length - 1] & 0xf;
// const code =
// ((hmacResult[offset] & 0x7f) << 24) |
// ((hmacResult[offset + 1] & 0xff) << 16) |
// ((hmacResult[offset + 2] & 0xff) << 8) |
// (hmacResult[offset + 3] & 0xff);
// // Step 3: Compute an HOTP value
// return code % 10 ** 6;
// }
// export function generateTOTP(secret, window = 0)
// {
// const counter = Math.floor(Date.now() / 30000);
// return generateHOTP(secret, counter + window);
// }
// export function verifyTOTP(token, secret, window = 1)
// {
// for (let errorWindow = -window; errorWindow <= +window; errorWindow++)
// {
// const totp = generateTOTP(secret, errorWindow);
// if (token === totp)
// return true;
// }
// return false;
// }
@ -94,86 +197,86 @@ export function verifyTOTP(token, secret, window = 1)
// import * as base32Encode from 'base32-encode';
// import { base32Encode } from 'base32-encode';
// import base32Encode from 'base32-encode';
import { encode } from 'thirty-two';
// import { encode } from 'thirty-two';
// ...
// // ...
import * as qrcode from 'qrcode';
import * as fs from 'fs';
// import * as qrcode from 'qrcode';
// import * as fs from 'fs';
import { nanoid } from "nanoid";
// import * as nanoid from 'nanoid'
// import { nanoid } from "nanoid";
// // import * as nanoid from 'nanoid'
export async function generateQRcode(req)
{
// const base32Encode = (await import('base32-encode'));
// const nanoid = (await import('nanoid'));
// export async function generateQRcode(req)
// {
// // const base32Encode = (await import('base32-encode'));
// // const nanoid = (await import('nanoid'));
// const util = (await import('util'));
// const qrcode = (await import('qrcode'));
// // const util = (await import('util'));
// // const qrcode = (await import('qrcode'));
const user = req.user;
let res;
// For security, we no longer show the QR code after is verified
// if (user.mfaEnabled) return res.status(404).end();
// const user = req.user;
// let res;
// // For security, we no longer show the QR code after is verified
// // if (user.mfaEnabled) return res.status(404).end();
// if (!user.mfaSecret) { //to do
const buffer = nanoid(14);
// generate unique secret for user
// this secret will be used to check the verification code sent by user
// const buffer = await util.promisify(crypto.randomBytes)(14);
// const buffer = crypto.lib.WordArray.random(32)
user.mfaSecret = encode(buffer).toString('utf8');
// user.mfaSecret = base32Encoded(buffer, 'RFC4648', { padding: false });
// // if (!user.mfaSecret) { //to do
// const buffer = nanoid(14);
// // generate unique secret for user
// // this secret will be used to check the verification code sent by user
// // const buffer = await util.promisify(crypto.randomBytes)(14);
// // const buffer = crypto.lib.WordArray.random(32)
// user.mfaSecret = encode(buffer).toString('utf8');
// // user.mfaSecret = base32Encoded(buffer, 'RFC4648', { padding: false });
// setUser(user); // to do !!
// // setUser(user); // to do !!
// }
// // }
const issuer = 'Google';
const algorithm = 'SHA1';
const digits = '6';
const period = '30';
const otpType = 'totp';
const configUri = `otpauth://${otpType}/${issuer}:${user.username}?algorithm=${algorithm}&digits=${digits}&period=${period}&issuer=${issuer}&secret=${user.mfaSecret}`;
// const issuer = 'Google';
// const algorithm = 'SHA1';
// const digits = '6';
// const period = '30';
// const otpType = 'totp';
// const configUri = `otpauth://${otpType}/${issuer}:${user.username}?algorithm=${algorithm}&digits=${digits}&period=${period}&issuer=${issuer}&secret=${user.mfaSecret}`;
// res.setHeader('Content-Type', 'image/png');
const QRCode = require('qrcode');
console.log(`before done`);
// QRCode.toFileStream(res, configUri);
// const filePath = 'qrcode.png'; // Specify the file path where the QR code should be saved
// // res.setHeader('Content-Type', 'image/png');
// const QRCode = require('qrcode');
// console.log(`before done`);
// // QRCode.toFileStream(res, configUri);
// // const filePath = 'qrcode.png'; // Specify the file path where the QR code should be saved
const qrCodeData = buffer; // Replace with your actual QR code data
const filePath = 'qrcode.png'; // Specify the file path where the QR code should be saved
// const qrCodeData = buffer; // Replace with your actual QR code data
// const filePath = 'qrcode.png'; // Specify the file path where the QR code should be saved
qrcode.toFile(filePath, qrCodeData, (error) => {
if (error) {
console.error(error);
// Handle the error appropriately
return;
}
// QR code image has been generated and saved to the file
// Or, you can create a buffer of the image data directly
})
// qrcode.toFile(filePath, qrCodeData, (error) => {
// if (error) {
// console.error(error);
// // Handle the error appropriately
// return;
// }
// // QR code image has been generated and saved to the file
// // Or, you can create a buffer of the image data directly
// })
// qrcode.toFile(filePath, configUri, (error) => {
// if (error) {
// console.error(error);
// // Handle the error appropriately
// return;
// }
// const readableStream = fs.createReadStream(filePath);
// res.data = readableStream;
// Use the readable stream as needed
// });
// // qrcode.toFile(filePath, configUri, (error) => {
// // if (error) {
// // console.error(error);
// // // Handle the error appropriately
// // return;
// // }
// // const readableStream = fs.createReadStream(filePath);
// // res.data = readableStream;
// // Use the readable stream as needed
// // });
// qrcode.toFileStream(res, configUri);
console.log(`QRcode done`);
return res;
// return
}
// // qrcode.toFileStream(res, configUri);
// console.log(`QRcode done`);
// return res;
// // return
// }

View File

@ -15,6 +15,7 @@
"@nestjs/platform-socket.io": "^9.4.0",
"@nestjs/websockets": "^9.4.0",
"cors": "^2.8.5",
"dotenv": "^16.1.4",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"socket.io-client": "^4.6.1",
@ -3398,6 +3399,17 @@
"node": ">=6.0.0"
}
},
"node_modules/dotenv": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz",
"integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",

View File

@ -26,6 +26,7 @@
"@nestjs/platform-socket.io": "^9.4.0",
"@nestjs/websockets": "^9.4.0",
"cors": "^2.8.5",
"dotenv": "^16.1.4",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"socket.io-client": "^4.6.1",

View File

@ -3,6 +3,10 @@ import { AppModule } from './app.module';
import * as cors from 'cors';
import { Server } from 'socket.io';
import * as socketio from 'socket.io';
import * as dotenv from 'dotenv';
dotenv.config();
console.log(process.env);
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
@ -36,7 +40,7 @@ async function bootstrap() {
});
});
await app.listen(4001);
await app.listen(parseInt(process.env.CHAT_PORT) || 4001);
}
bootstrap();

View File

@ -15,6 +15,7 @@
"@nestjs/platform-socket.io": "^9.4.0",
"@nestjs/websockets": "^9.4.0",
"cors": "^2.8.5",
"dotenv": "^16.1.4",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"socket.io-client": "^4.6.1",
@ -3398,6 +3399,17 @@
"node": ">=6.0.0"
}
},
"node_modules/dotenv": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz",
"integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",

View File

@ -26,6 +26,7 @@
"@nestjs/platform-socket.io": "^9.4.0",
"@nestjs/websockets": "^9.4.0",
"cors": "^2.8.5",
"dotenv": "^16.1.4",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"socket.io-client": "^4.6.1",

View File

@ -21,6 +21,10 @@ import { AppModule } from './app.module';
import * as cors from 'cors';
import { Server } from 'socket.io';
import * as socketio from 'socket.io';
import * as dotenv from 'dotenv';
dotenv.config();
console.log(process.env);
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
@ -54,7 +58,7 @@ async function bootstrap() {
});
});
await app.listen(4000);
await app.listen(process.env.PONG_PORT || 4000);
}
bootstrap();

View File

@ -68,11 +68,19 @@ export class PongGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
// }
// // console.log(`from: ${client.id}`);
// }
@SubscribeMessage('pong:invite')
createPrivateGame(client: Socket, payload: any): void {
//after invite accepted ?
//set the two user in a game ?
}
@SubscribeMessage('pong:matchmaking')
addMatchmaking(client: Socket, payload: any): void {
console.log("matchmaking");
console.log(payload);
console.log(`option= ${payload.option}`);
// Add the client to the waitingClients set along with their chosen option
this.waitingClients.add({ client, option: payload.option });
console.log("Adding client to waiting list...");
@ -147,6 +155,21 @@ addMatchmaking(client: Socket, payload: any): void {
// console.log("END OF HANDLE");
// }
@SubscribeMessage('pong:power')
sendPower(client: Socket, payload: any): void
{
console.log(`from: ${client.id}`);
console.log(payload);
const game = this.games.get(payload.gameId);
const playersIds = game.map(socket => socket.id);
if (playersIds[0] === payload.id)
this.clients[playersIds[1]].emit('pong:power', payload);
else if (playersIds[1] === payload.id)
this.clients[playersIds[0]].emit('pong:power', payload);
console.log("END OF HANDLE");
}
@SubscribeMessage('pong:message')
handleMessage(client: Socket, payload: any): void
{

View File

@ -0,0 +1,21 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 404.jsx :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/09 23:07:12 by apommier #+# #+# */
/* Updated: 2023/06/12 17:09:11 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
function PageNotFound() {
return (
<div>
<p>404 Page not found</p>
</div>
);
}
export default PageNotFound

View File

@ -1,5 +1,5 @@
import React from "react";
import {Routes, Route} from 'react-router-dom';
import {Routes, Route, Navigate} from 'react-router-dom';
import HomeLogin from "../pages/Home.js";
import Home from "../pages/Home.jsx";
@ -17,15 +17,32 @@ import SuccessToken from '../script/tokenSuccess'
import DoubleAuth from "../pages/2fa.js";
import Game from "../pages/Game.jsx";
import Social from "../components/Social/Social.jsx";
import PageNotFound from "../components/404.jsx";
import Logout from "../components/Profile/Logout.jsx";
function AnimatedRoute () {
const location = useLocation();
if (!localStorage.getItem('token'))
{
return (
<AnimatePresence>
<Routes location={location} key={location.pathname}>
<Route exact path="/" element={<HomeLogin/>}/>
<Route exact path="/token" element={<SuccessToken />}/>
<Route path="/404" element={<HomeLogin/>} />
<Route path="*" element={<Navigate to="/404" />} />
</Routes>
</AnimatePresence>
)
}
return (
<AnimatePresence>
<Routes location={location} key={location.pathname}>
<Route exact path="/" element={<HomeLogin/>}/>
{/* <Route exact path="/login" element={<HomeLogin/>}/> */}
<Route exact path="/" element={<Home/>}/>
<Route exact path="/profile" element={<Home/>}/>
<Route exact path="/profile/:username" element={<Home/>}/>
@ -41,6 +58,9 @@ function AnimatedRoute () {
<Route exact path="/login42" element={<Login42 />}/>
<Route exact path="/logout" element={<Logout />}/>
<Route exact path="/messages" element={<Messages />}/>
<Route path="/404" element={<PageNotFound />} />
<Route path="*" element={<Navigate to="/404" />} />
</Routes>
</AnimatePresence>
)

View File

@ -6,10 +6,33 @@ function PlayButton() {
const history = useNavigate();
// const handleButtonClick = () => {
// let path = `play`;
// history(path);
// };
const handleButtonClick = () => {
let path = `play`;
let path = `play?`;
const superpowerCheckbox = document.querySelector('input[value="superpower"]');
if (superpowerCheckbox.checked) {
path += 'superpower=true&';
}
const obstacleCheckbox = document.querySelector('input[value="obstacle"]');
if (obstacleCheckbox.checked) {
path += 'obstacle=true&';
}
const speedCheckbox = document.querySelector('input[value="speed"]');
if (speedCheckbox.checked) {
path += 'speed=true&';
}
// Remove the trailing '&' character
path = path.slice(0, -1);
console.log(path)
history(path);
};
};
return (
<div className="notClicked" id="canvas_container">

View File

@ -1,13 +1,9 @@
import React, {useState, useEffect} from 'react';
import {AiOutlineMenuUnfold} from 'react-icons/ai';
// import * as AiIcons from 'react-icons/ai';
import {Link} from 'react-router-dom';
// import { SidebarData } from './Sidebar/SidebarData';
import DefaultPicture from '../assets/profile.jpg'
import { motion, AnimatePresence } from 'framer-motion'
import Modal from './Sidebar/Modal';
// import {BiLogOutCircle} from 'react-icons/bi';
// import AnimatePresence from
import '../styles/Header.css';
import api from '../script/axiosApi';
@ -32,8 +28,8 @@ function Header() {
console.error('Error fetching profile picture:', error);
}
};
fetchProfilePicture();
if (localStorage.getItem('token'))
fetchProfilePicture();
}, []);
// console.log(`profile pic= ${profilePicture}`)

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/01 18:24:46 by apommier #+# #+# */
/* Updated: 2023/06/09 09:00:06 by apommier ### ########.fr */
/* Updated: 2023/06/12 17:05:08 by apommier ### ########.fr */
/* */
/* ************************************************************************** */

View File

@ -46,28 +46,7 @@ export default function Friend({currentUser})
};
fetchProfilePicture();
})
function getStatus(friend)
{
let status = friend.status
console.log(`status= ${status}`)
let statusColor;
if (status === 0)
statusColor = 'grey';
else if (status === 1)
statusColor = 'green';
else if (status === 2)
statusColor = 'blue';
return statusColor;
}
const handleSpectate = (user) => {
//socket connection and add to party with one with username
console.log(`spectate hehe`)
console.log(`user= ${user}`)
};
}, [])
const handleButtonClick = (user) => {
let path = `http://localhost/profile/${user.username}`;
@ -79,10 +58,12 @@ export default function Friend({currentUser})
const Accept = (user) => {
console.log("accept")
console.log(`request = ${request}`)
}
const Refuse = (user) => {
console.log("refuse")
console.log(`request = ${request}`)
}
return (

View File

@ -1,13 +1,48 @@
import { useEffect } from 'react';
import { useEffect, useLocation } from 'react';
// import { useState, useRef } from 'react';
import { drawCanvas } from './canvas.js';
import DrawCanvas from './canvas.js';
import queryString from 'query-string';
import '../styles/field.css';
import { useParams } from "react-router-dom";
// import { withRouter } from 'react-router-dom';
function Field()
{
useEffect(() => {
// const location = useLocation();
const queryParams = queryString.parse(window.location.search);
console.log("launch canva hehe")
drawCanvas();
let Modifiers = 0;
if (queryParams.superpower === 'true') {
Modifiers += 1;
}
if (queryParams.obstacle === 'true') {
Modifiers += 2;
}
if (queryParams.speed === 'true') {
Modifiers += 4;
}
// console.log(`modifiers= ${Modifiers}`)
// DrawCanvas(Modifiers);
// return () => {
// console.log("000000000000000000000000000000000")
// // socketRef.current.disconnect();
// };
// console.log(`modifiers= ${Modifiers}`)
const cleanup = DrawCanvas(Modifiers);
return () => {
console.log("Cleanup");
cleanup(); // Call the cleanup function to stop the ongoing process or perform necessary cleanup tasks
};
}, []);
// const [buttonClicked, setButtonClicked] = useState(false);
@ -28,6 +63,7 @@ function Field()
}
export default Field;
// export default withRouter(Field);
// function Field() {

View File

@ -2,19 +2,44 @@
import api from '../script/axiosApi';
// import { useEffect } from 'react';
// import { useEffect, useRef } from 'react';
import io from 'socket.io-client';
// const socket = io('http://192.168.1.14:4000');
// const socket = io('http://86.209.110.20:4000');
// const socket = io('http://172.29.113.91:4000');
export function drawCanvas() {
function DrawCanvas(option) {
console.log(`option= ${option}`);
const superpowerModifier = option & 1; // Retrieves the superpower modifier
const obstacleModifier = (option >> 1) & 1; // Retrieves the obstacle modifier
const speedModifier = (option >> 2) & 1; // Retrieves the speed modifier
console.log(`superpowerModifier = ${superpowerModifier}`);
console.log(`obstacleModifier = ${obstacleModifier}`);
console.log(`speedModifier = ${speedModifier}`);
// const socketRef = useRef(null);
// socketRef.current = io('http://localhost:4000');
const socket = io('http://localhost:4000');
// const socket = io()
// const socket = socketRef.current
console.log("start function");
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// useEffect(() => {
// console.log("useeffect?????????????????")
// return () => {
// console.log("000000000000000000000000000000000")
// socketRef.current.disconnect();
// };
// }, []);
//========================================================================================================
//========================================================================================================
// Var Declaration
@ -28,6 +53,7 @@ export function drawCanvas() {
let opRank;
//general canvas
let running = true;
const scale = window.devicePixelRatio;
canvas.width = canvas.offsetWidth;
// canvas.height = canvas.width * 0.7
@ -39,8 +65,9 @@ export function drawCanvas() {
let paddleY = canvas.height / 2 - (paddleHeight / 2);
let paddleX = canvas.width / 40;
let paddleSpeed = canvas.height / 40;
//opponent var
let opPaddleHeight = canvas.height * 0.25;
let oPaddleY = paddleY;
//mouse and touch
@ -81,6 +108,7 @@ export function drawCanvas() {
console.log(`id ion matcj= ${myId}`)
const info = {
id: myId,
option: option,
};
socket.emit('pong:matchmaking', info);
}
@ -165,6 +193,27 @@ export function drawCanvas() {
vY = data.vY;
});
socket.on('pong:paddle', (data) => {
console.log("paddle info receive")
oPaddleY = (data.paddleY / data.height) * canvas.height
});
socket.on('pong:power', (data) => {
console.log("paddle info receive")
oPaddleY = 0;
opPaddleHeight = canvas.height;
setTimeout(() => {
// code à exécuter après 5 secondes
opPaddleHeight = canvas.height * 0.25;
oPaddleY = canvas.height / 2 - paddleHeight / 2;
console.log('Cinq secondes se sont écoulées.');
}, 5000);
// oPaddleY = (data.paddleY / data.height) * canvas.height
});
function send_info()
{
if (gameId === 0)
@ -183,11 +232,6 @@ export function drawCanvas() {
socket.emit('pong:message', info);
}
socket.on('pong:paddle', (data) => {
console.log("paddle info receive")
oPaddleY = (data.paddleY / data.height) * canvas.height
});
function send_point()
{
if (gameId === 0)
@ -229,6 +273,17 @@ export function drawCanvas() {
socket.emit('pong:paddle', info);
}
function use_power()
{
const info = {
gameId: gameId,
width: canvas.width,
height: canvas.height,
id: myId,
}
socket.emit('pong:power', info);
}
function send_forced_info()
{
if (gameId === 0)
@ -275,7 +330,7 @@ export function drawCanvas() {
function drawPaddle() {
ctx.fillStyle = 'white';
ctx.fillRect(paddleX, paddleY, paddleWidth, paddleHeight);
ctx.fillRect(canvas.width - paddleX - paddleWidth, oPaddleY, paddleWidth, paddleHeight);
ctx.fillRect(canvas.width - paddleX - paddleWidth, oPaddleY, paddleWidth, opPaddleHeight);
}
function drawball()
@ -287,7 +342,7 @@ export function drawCanvas() {
ctx.fill();
}
//========================================================================================================
//========================================================================================================
// Loop
@ -298,9 +353,18 @@ matchmaking();
// while (!gameId)
// ;
// Define a function to stop the drawing process
const stopDrawCanvas = () => {
running = false;
// Perform any necessary cleanup tasks
// ...
};
function draw(timestamp)
{
console.log("send loose");
if (!running)
return ;
if (gameId === 0 )
{
requestAnimationFrame(draw);
@ -333,7 +397,7 @@ function draw(timestamp)
const deltaTime = timestamp - lastUpdateTime;
lastUpdateTime = timestamp;
ballX += vX * deltaTime * canvas.width;
ballY += vY * deltaTime * canvas.width;
ballY += vY * deltaTime * canvas.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawPaddle();
@ -343,7 +407,8 @@ function draw(timestamp)
is_out();
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
//========================================================================================================
//========================================================================================================
@ -360,6 +425,13 @@ requestAnimationFrame(draw);
vY = vX * Math.sin(-bounceAngle);
if (vX < 0)
vX = -vX;
if (speedModifier)
{
if (vX > 0)
vX += 0.0001;
else
vX -= 0.0001;
}
}
@ -387,7 +459,12 @@ requestAnimationFrame(draw);
}
if (ballY - ballRadius - 2 <= 0 || ballY + ballRadius + 2 >= canvas.height) //touch up or down wall
{
// if ()
vY = -vY;
if (ballY > (canvas.height / 2))//down wall
ballY = canvas.height - ballRadius - 2
else
ballY = ballRadius + 2
// send_info();
}
// else if (ballX + ballRadius + 2 >= canvas.width) //touch right wall
@ -416,6 +493,24 @@ requestAnimationFrame(draw);
send_point();
// send_forced_info();
}
if (ballX > canvas.width)
{
console.log("win point")
// if (ballY <= paddleY + paddleHeight + ballRadius && ballY >= paddleY - ballRadius)
// {
// console.log('true hehe');
// ballX = paddleX + paddleWidth + ballRadius;
// updateVector();
// return ;
// }
// ballX = canvas.width / 2;
// ballY = canvas.height / 2;
// vX = 0;
// vY = 0;
// hisScore += 1;
// send_point();
// // send_forced_info();
}
}
@ -510,8 +605,11 @@ requestAnimationFrame(draw);
}
else if (event.code === "KeyR")
{
if (!superpowerModifier)
return ;
paddleY = 0;
paddleHeight = canvas.height;
use_power();
setTimeout(() => {
// code à exécuter après 5 secondes
paddleHeight = canvas.height * 0.25;
@ -521,4 +619,9 @@ requestAnimationFrame(draw);
}
});
}
requestAnimationFrame(draw);
console.log("retuuuuuuuuuuurn")
return (stopDrawCanvas);
}
export default DrawCanvas

View File

@ -13,7 +13,7 @@ console.log(`getToken = ${getToken()}`)
console.log(`Bearer ${localStorage.getItem("token")}`)
let api = axios.create({
baseURL: 'http://localhost/api',
baseURL: 'http://localhost/api',
headers: {
// Authorization: `Bearer ${getToken()}`,
Authorization : `Bearer ${localStorage.getItem("token")}`

View File

@ -8,14 +8,25 @@ services:
env_file: .env
depends_on:
- api
# command: sh -c "envsubst < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
ports:
- 80:80
volumes:
- ./conf/nginx.conf:/etc/nginx/conf.d/default.conf
- ./conf/nginx.conf:/etc/nginx/conf.d/default.conf
# volumes:
# - "./conf:/etc/nginx/templates/"
# ports:
# - 80:80
# volumes:
# - ./conf/nginx.conf:/etc/nginx/conf.d/default.conf
# command: sh -c "envsubst < /etc/nginx/conf.d/default.conf > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
# - ./containers/frontend:/var/www/html
networks:
- pongNetwork
react_app:
image: node:latest
container_name: react_app
@ -48,6 +59,7 @@ services:
entrypoint: ["sh", "-c" , "npm install && npm run start:dev"]
postgresql:
env_file: .env
image: postgres:14.1-alpine
restart: unless-stopped
container_name: postgresql