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

Features/ban unban #27

Merged
merged 7 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
168 changes: 152 additions & 16 deletions backend/controllers/Account.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import type express from "express";
import type SocketIO from "socket.io";
import express from "express";
import nodemailer from "nodemailer";
import {
generateAuthenticationToken,
generateAuthorizationToken,
verifyAuthorizationToken,
} from "../auth/tokenUtils";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

class AccountController {
/**
Expand All @@ -8,12 +17,16 @@ class AccountController {
* @param req The Express request object
* @param res The Express response object
*/
public static async sendMagicLink(req: express.Request, res: express.Response) {
public static async sendMagicLink(
req: express.Request,
res: express.Response
) {
// TODO: Send a magic link containing the AUTHORIZATION token to the user's email
/**
* VALIDATION
* * Validate the user email (must be a Devinci email)
*

* PROCESS
* * Generate an AUTHORIZATION token
* * Send the magic link to the user's email
Expand All @@ -22,6 +35,44 @@ class AccountController {
* * Send a success message
* * Send an error message if the email is invalid
*/
const { email } = req.body;

const isDevinciEmail = (email: string): boolean => {
Copy link
Member

Choose a reason for hiding this comment

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

pas besoin de faire une fonction si elle n'est utilisé qu'une fois

const expression: RegExp = /^[a-zA-Z0-9._-]+@edu\.devinci.fr$/;
Copy link
Member

Choose a reason for hiding this comment

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

Il manque un \ sur le point

// Regex actuel
 const expression: RegExp = /^[a-zA-Z0-9._-]+@edu\.devinci.fr$/;
// Regex juste
 const expression: RegExp = /^[a-zA-Z0-9._-]+@edu\.devinci\.fr$/;


return expression.test(email);
};

if (isDevinciEmail(email) == true) {
Copy link
Member

Choose a reason for hiding this comment

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

pas besoin de sur-vérifié la boolean if (isDevinciEmail(email)) { suffit

const token: string = generateAuthorizationToken(email);
const link: string = `url/login?token=${token}`;

const transporter = nodemailer.createTransport({
host: "your_host",
Copy link
Member

Choose a reason for hiding this comment

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

plus judicieux de mettre une variable d'environnement (exemple. process.env.MAIL_HOST) ce qui simplifie aussi le test du code en local

port: 587,
secure: true,
auth: {
user: "your_email_address",
Copy link
Member

Choose a reason for hiding this comment

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

same

pass: "your_email_password",
Copy link
Member

Choose a reason for hiding this comment

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

same

},
});

const message = {
from: "your_email",
Copy link
Member

Choose a reason for hiding this comment

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

same

to: email,
subject: "Lien pour se connecter",
html: `Clique pour te connecter: <a href="${link}">${link}</a>`,
};

try {
await transporter.sendMail(message);
res.status(200).send("Lien envoyé. Regarder vos mails.");
} catch (error) {
res.status(500).send("Une erreur s'est produite.");
}
} else {
res.send("Email non valide");
}
}

/**
Expand All @@ -32,19 +83,30 @@ class AccountController {
* @param res The Express response object
*/
public static async login(req: express.Request, res: express.Response) {
// TODO: Log the user
/**
* VALIDATION
* * Validate AUTHORIZATION token
*
* PROCESS
* * Generate an AUTHENTICATION token
*
* RESPONSE
* * Send the AUTHENTICATION token
* * Send an error message if the AUTHORIZATION token is invalid
* * Send an error message if the AUTHORIZATION token is expired
*/
const { token, email } = req.body;
if (!verifyAuthorizationToken(token, email)) {
return res.status(401).send("Invalid token");
}

try {
const user = await prisma.account.findFirst({
where: {
devinciEmail: email,
},
});

if (!user) {
await prisma.account.create({
data: {
devinciEmail: email,
},
});
}
} catch (error) {
return res.status(500).send("Unable to connect to the database");
}

return res.status(200).send(generateAuthenticationToken(email));
}

// Admin routes
Expand Down Expand Up @@ -92,7 +154,81 @@ class AccountController {
* * Send a success message
* * Send an error message if the user ID is invalid
*/
const { user, target } = req.body;

const isUserAdmin = (id: number): boolean => {
try {
const user = prisma.account.findUnique({
where: { id: id },
select: { isAdmin: true }
});

return user?.isAdmin ?? false;
} catch (error) {
return false;
}
}

const isUserBanned = (id: number): boolean => {
try {
const user = prisma.account.findUnique({
where: { id: id },
select: { isBanned: true }
});

return user?.isAdmin ?? false;
} catch (error) {
console.log(error);
return false;
}
}

if (isUserAdmin(user.id) == false) {
res.send("User is not an admin");
return;
}


try {
if (isUserBanned(target.id) == true) {
await prisma.account.update({
where: { id: target.id },
data: { isBanned: false }
});

res.send("User unbanned successfully");
} else if (isUserBanned(target.id) == false) {
await prisma.account.update({
where: { id: target.id },
data: { isBanned: true }
});

res.send("User banned successfully");
}
} catch (error) {
console.error(error);
res.status(500).send("Error banning/unbanning user");
return;
}
}

/**
* Auth a websocket client
* @server WebSocket
*
* @param socket The client socket
* @param data The payload
*/
public static async authSocket(socket: SocketIO.Socket, [token, email]: [string, string]) {
if (verifyAuthenticationToken(token, email)) {
socket.data.token = token;
socket.data.email = email;

socket.emit("auth-callback", true);
} else {
socket.emit("auth-callback", false);
}
}
}

export default AccountController;
export default AccountController;
1 change: 0 additions & 1 deletion backend/package-lock.json

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

28 changes: 11 additions & 17 deletions frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
root: true,
env: { browser: true, es2020: true },
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
},
};
52 changes: 26 additions & 26 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
}