From 728c84d0f990cc129dc63ff136df8a24b0965aa8 Mon Sep 17 00:00:00 2001 From: AntonyKLINGER Date: Mon, 27 May 2024 11:23:27 +0200 Subject: [PATCH] maj comptes admin + dr --- components/AllSessions.jsx | 46 +++++----- components/Comptes.jsx | 155 +++++++++++++++++++++++++++++++++ components/Header.jsx | 2 +- components/Modules.jsx | 9 +- components/ModulesBack.jsx | 28 +++--- components/SessionsBack.jsx | 50 ++++++----- components/SessionsModule.jsx | 47 +++++----- pages/admin.js | 31 ++++--- pages/api/accounts/create.js | 63 ++++++++++++++ pages/api/accounts/delete.js | 23 +++++ pages/api/accounts/index.js | 10 +++ pages/api/accounts/lib/auth.js | 28 ++++++ pages/api/accounts/lib/jwt.js | 27 ++++++ pages/api/emails/newAccount.js | 53 +++++++++++ pages/connexion.js | 58 ++++++++---- prisma/schema.prisma | 7 ++ styles/Admin.module.css | 31 +++++++ views/emails/newaccount.hbs | 73 ++++++++++++++++ 18 files changed, 631 insertions(+), 110 deletions(-) create mode 100644 components/Comptes.jsx create mode 100644 pages/api/accounts/create.js create mode 100644 pages/api/accounts/delete.js create mode 100644 pages/api/accounts/index.js create mode 100644 pages/api/accounts/lib/auth.js create mode 100644 pages/api/accounts/lib/jwt.js create mode 100644 pages/api/emails/newAccount.js create mode 100644 views/emails/newaccount.hbs diff --git a/components/AllSessions.jsx b/components/AllSessions.jsx index 7a8adf1..912fb5a 100644 --- a/components/AllSessions.jsx +++ b/components/AllSessions.jsx @@ -11,7 +11,7 @@ import AddSession from '@/components/AddSession' import SessionsModule from '@/components/SessionsModule' import styles from '@/styles/Admin.module.css' -export default function Modules({setPage, page}){ +export default function Modules({setPage, page, user}){ const [open, setOpen] = useState(null) const [alert, setAlert] = useState(null) @@ -107,7 +107,6 @@ export default function Modules({setPage, page}){ getSessions(tri, status, region, codes); } - console.log(sessions) return ( <> @@ -169,25 +168,28 @@ export default function Modules({setPage, page}){
{sessions.length > 0 ? ( sessions.map((session, index) => { - return ( -
- deleteSession(session.id)} - status={session.status} - setActions={setActions} - session={session} - /> -
- ) + if(session.status == 'publish' || user.type == 'Administrateur' || user.id == 10){ + return ( +
+ deleteSession(session.id)} + status={session.status} + setActions={setActions} + session={session} + user={user} + /> +
+ ) + } }) ) : ( <> @@ -229,7 +231,7 @@ export default function Modules({setPage, page}){
setOpen(null)} className={styles.Back}>Retour aux modules
- + )} diff --git a/components/Comptes.jsx b/components/Comptes.jsx new file mode 100644 index 0000000..a721771 --- /dev/null +++ b/components/Comptes.jsx @@ -0,0 +1,155 @@ +import { useState, useEffect } from 'react' +import Alert from '@/components/Alert' +import { Notif } from '@/components/Notif' +import styles from '@/styles/Admin.module.css' + +export default function Comptes(){ + + const [alert, setAlert] = useState(null) + const [notif, setNotif] = useState(null) + const [create, setCreate] = useState(false) + const [actions, setActions] = useState(1) + const [account, setAccount] = useState({}) + const [listAccount, setListAccount] = useState([]) + + useEffect(() => { + const getAccounts = async () => { + const geter = await fetch(`/api/accounts/`) + const json = await geter.json() + setListAccount(json) + } + getAccounts() + }, [alert, actions]) + + const handleChange = (e) => { + const { name, type, value } = e.target + setAccount(prev => { + return { + ...prev, + [name]: value + } + }) + } + + const createAccount = async () => { + const { email, type } = account + if(email && type){ + const add = await fetch(`/api/accounts/create`, { + method: 'POST', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: email, + type: type + }) + }) + const json = await add.json() + if(json.status == "success"){ + + //reset + setCreate(false) + setAccount({}) + setActions(prev => prev+1) + + setNotif({ + text: 'Le compte a bien été créé !', + icon: 'done' + }); + } + else{ + setNotif({ + text: 'Cette adresse e-mail admin ou DR existe déjà !', + icon: 'close' + }); + } + } + else{ + setNotif({ + text: 'Une information est manquante.', + icon: 'close' + }); + } + } + + const preDeleteAccount = (id) => { + setAlert({ + icon: 'warning', + text: 'Êtes-vous sûr de vouloir supprimer ce compte ?', + action: () => deleteAccount(id) + }); + } + + const deleteAccount = async (id) => { + const response = await fetch(`/api/accounts/delete/?id=${id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + if(response.ok){ + setNotif({ + text: 'Le compte a bien été supprimé.', + icon: 'done' + }); + } + setAlert(null) + } + + return ( + <> +
+ Comptes administrateurs et DR + +
+ {create && ( +
+
+
+ Adresse e-mail du compte + +
+
+ Type de compte +
+ + expand_more +
+
+
+ +
+ )} + {listAccount.length > 0 ? ( + <> + {listAccount.map((acc, i) => { + return ( +
+

{acc.email}

+
+ {acc.type} + +
+
+ ) + })} + + ) : ( + <> +

Aucun compte n'a été créé pour le moment.

+ + )} + + {alert != null && ( + + )} + {notif != null && ( + + )} + + ) +} \ No newline at end of file diff --git a/components/Header.jsx b/components/Header.jsx index 7ccafab..99fb35f 100644 --- a/components/Header.jsx +++ b/components/Header.jsx @@ -45,7 +45,7 @@ export default function Header(){
  • setActiveMenu(prev => !prev)}>Les Rencontres
  • {user?.id ? ( <> - {user.id == 10 ? ( + {(user.id == 10 || user.type == 'Administrateur' || user.type == 'DR') ? (
  • setActiveMenu(prev => !prev)} className={styles.pf}>Administration
  • ) : (
  • setActiveMenu(prev => !prev)} className={styles.pf}>Espace personnel
  • diff --git a/components/Modules.jsx b/components/Modules.jsx index 53262da..ff9aba9 100644 --- a/components/Modules.jsx +++ b/components/Modules.jsx @@ -8,7 +8,7 @@ import AddSession from '@/components/AddSession' import SessionsModule from '@/components/SessionsModule' import styles from '@/styles/Admin.module.css' -export default function Modules(setPage){ +export default function Modules({setPage, user}){ const [open, setOpen] = useState(null) const [alert, setAlert] = useState(null) @@ -115,7 +115,9 @@ export default function Modules(setPage){ <>
    Tous les modules - + {user.type != 'DR' && ( + + )}
    @@ -159,6 +161,7 @@ export default function Modules(setPage){ setAlert={setAlert} sessions={module.sessions} action={() => deleteModule(module.id)} + user={user} />
    ) @@ -197,7 +200,7 @@ export default function Modules(setPage){
    setOpen(null)} className={styles.Back}>Retour aux modules
    - + )} diff --git a/components/ModulesBack.jsx b/components/ModulesBack.jsx index a388448..6034920 100644 --- a/components/ModulesBack.jsx +++ b/components/ModulesBack.jsx @@ -2,7 +2,7 @@ import Link from 'next/link' import { useState, useEffect } from 'react' import styles from '@/styles/ModuleBack.module.css' -export default function ModulesBack({date, code, lastUpdate, category, title, id, setOpen, setAlert, action, sessions}){ +export default function ModulesBack({date, code, lastUpdate, category, title, id, setOpen, setAlert, action, sessions, user}){ function formatDate(dateString) { const date = new Date(dateString); @@ -37,17 +37,21 @@ export default function ModulesBack({date, code, lastUpdate, category, title, id Module ({nbSession} session{nbSession > 1 ? 's' : ''} à venir) :{title}
    - - + {user.type != 'DR' && ( + <> + + + + )}
    diff --git a/components/SessionsBack.jsx b/components/SessionsBack.jsx index c6b6aaa..b3113ba 100644 --- a/components/SessionsBack.jsx +++ b/components/SessionsBack.jsx @@ -2,7 +2,7 @@ import Link from 'next/link' import { useState, useEffect } from 'react' import styles from '@/styles/SessionsBack.module.css' -export default function SessionsBack({isModule, date, session, code, region, title, id, setOpen, setAlert, setActions, action, status, moduleId, dept}){ +export default function SessionsBack({isModule, date, session, code, region, title, id, setOpen, setAlert, setActions, action, status, moduleId, dept, user}){ function formatDate(dateString) { const date = new Date(dateString); @@ -70,28 +70,32 @@ export default function SessionsBack({isModule, date, session, code, region, tit
    - - - {status == 'brouillon' && ( - + {user.type != 'DR' && ( + <> + + + {status == 'brouillon' && ( + + )} + )}
    {!isModule && ( diff --git a/components/SessionsModule.jsx b/components/SessionsModule.jsx index 822c782..cdbd149 100644 --- a/components/SessionsModule.jsx +++ b/components/SessionsModule.jsx @@ -5,7 +5,7 @@ import EditModule from '@/components/EditModule' import AddModule from '@/components/AddModule' import styles from '@/styles/Admin.module.css' -export default function SessionsModule({ id, setOpen, open, nom }){ +export default function SessionsModule({ id, setOpen, open, nom, user }){ const [nav, setNav] = useState(0) const [alert, setAlert] = useState(null) @@ -60,7 +60,9 @@ export default function SessionsModule({ id, setOpen, open, nom }){ <>
    Toutes les sessions pour le module :
    {nom}
    - + {user.type != 'DR' && ( + + )}
    @@ -69,25 +71,28 @@ export default function SessionsModule({ id, setOpen, open, nom }){
    {sessions.length > 0 ? ( sessions.map((session, index) => { - return ( -
    - deleteSession(session.id)} - status={session.status} - setActions={setActions} - session={session} - isModule="yes" - /> -
    - ) + if(session.status == "publish" || user.type == 'Administrateur' || user.id == 10){ + return ( +
    + deleteSession(session.id)} + status={session.status} + setActions={setActions} + session={session} + isModule="yes" + user={user} + /> +
    + ) + } }) ) : ( <> diff --git a/pages/admin.js b/pages/admin.js index 696a51c..8dc73f7 100644 --- a/pages/admin.js +++ b/pages/admin.js @@ -3,30 +3,34 @@ import { useState, uesEffect } from 'react' import nextCookies from 'next-cookies'; import { verifyToken } from '@/utils/auth'; import Modules from '@/components/Modules' -import Inscriptions from '@/components/Inscriptions' +import Comptes from '@/components/Comptes' import Sessions from '@/components/Sessions' import AllSessions from '@/components/AllSessions' import styles from '@/styles/Admin.module.css' export async function getServerSideProps(context) { const { auth: token } = nextCookies(context); + console.log('Token:', token); const user = verifyToken(token); - - if (!user || user.id != 10) { - return { - redirect: { - destination: '/connexion', - permanent: false, - }, - }; + console.log('User:', user); + + if (!user || (user.id != 10 && user.type != 'Administrateur' && user.type != 'DR')) { + return { + redirect: { + destination: '/connexion', + permanent: false, + }, + }; } - + return { props: { user } }; } export default function Admin({ user }){ + console.log('User in Admin component:', user); const [page, setPage] = useState(0) + const [userInfo, setUserInfo] = useState(user) const logout = async () => { const unlog = await fetch('/api/logout') @@ -48,6 +52,7 @@ export default function Admin({ user }){
    • {setPage(0)}}>Voir les modules
    • {setPage(2)}}>Voir les sessions
    • +
    • {setPage(1)}}>Comptes
    • {/*
    • {setPage(1)}}>Inscriptions
    • */}
    • Déconnexion
    @@ -55,13 +60,13 @@ export default function Admin({ user }){
    {page == 0 && ( - + )} {page == 1 && ( - + )} {page == 2 && ( - + )}
    diff --git a/pages/api/accounts/create.js b/pages/api/accounts/create.js new file mode 100644 index 0000000..fbb69fe --- /dev/null +++ b/pages/api/accounts/create.js @@ -0,0 +1,63 @@ +import bcrypt from 'bcrypt'; +import prisma from '@/prisma'; +import fetch from 'node-fetch'; + +const SALT_ROUNDS = 10; + +function generatePassword(length) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let password = ''; + for (let i = 0; i < length; i++) { + password += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return password; +} + +export default async function handle(req, res) { + if (req.method === 'POST') { + const { email, type } = req.body; + + try { + // Vérifier si un compte avec cet email existe déjà + const existingAccount = await prisma.account.findUnique({ + where: { email }, + }); + + if (existingAccount) { + res.status(200).json({ status: 'already exist' }); + return; + } + + // Générer un mot de passe de 12 caractères + let password = generatePassword(12); + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); + + const newAccount = await prisma.account.create({ + data: { + email, + type, + password: hashedPassword, + }, + }); + + await fetch(`${process.env.WEBSITE_URL}/api/emails/newAccount`, { + method: 'POST', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email, + type, + password, + }), + }); + + res.status(201).json({ status: 'success' }); + } catch (error) { + res.status(500).json({ error: `Impossible de créer le compte: ${error.message}` }); + } + } else { + res.setHeader('Allow', ['POST']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/accounts/delete.js b/pages/api/accounts/delete.js new file mode 100644 index 0000000..4315d70 --- /dev/null +++ b/pages/api/accounts/delete.js @@ -0,0 +1,23 @@ +import prisma from '@/prisma' + +export default async function handle(req, res) { + if (req.method === 'DELETE') { + const { id } = req.query; + const accountId = parseInt(id); + + try { + + await prisma.account.delete({ + where: { id: accountId }, + }); + + res.json({ message: 'Compte supprimé avec succès' }); + } catch (error) { + console.error(error); + res.status(500).json({ error: `Impossible de supprimer le compte : ${error.message}` }); + } + } else { + res.setHeader('Allow', ['DELETE']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/accounts/index.js b/pages/api/accounts/index.js new file mode 100644 index 0000000..691ecf8 --- /dev/null +++ b/pages/api/accounts/index.js @@ -0,0 +1,10 @@ +import prisma from '@/prisma' + +export default async function handle(req, res) { + + let queryOptions = {} + + const accounts = await prisma.account.findMany(queryOptions) || []; + + res.json(accounts); +} diff --git a/pages/api/accounts/lib/auth.js b/pages/api/accounts/lib/auth.js new file mode 100644 index 0000000..fcc0e75 --- /dev/null +++ b/pages/api/accounts/lib/auth.js @@ -0,0 +1,28 @@ +import bcrypt from 'bcrypt'; +import prisma from '@/prisma'; + +async function verifyUser(email, motDePasse) { + const user = await prisma.account.findUnique({ + where: { email: email }, + }); + + if (!user) { + return { success: false, message: "Utilisateur non trouvé." }; + } + + const match = await bcrypt.compare(motDePasse, user.password); + + if (match) { + return { success: true, account: user }; + } else { + return { success: false, message: "Mot de passe incorrect." }; + } +} + +export default async function handle(req, res) { + const { mail, motDePasse } = req.query; + + const verify = await verifyUser(mail, motDePasse); + + res.json(verify); +} diff --git a/pages/api/accounts/lib/jwt.js b/pages/api/accounts/lib/jwt.js new file mode 100644 index 0000000..39b6520 --- /dev/null +++ b/pages/api/accounts/lib/jwt.js @@ -0,0 +1,27 @@ +import cookie from 'cookie'; +import jwt from 'jsonwebtoken'; + +export default async function handle(req, res) { + if(req.method === 'POST') { + const { mail, id, type } = req.body + + const token = jwt.sign( + { mail: mail, id: id, type: type }, + process.env.JWT_SECRET, + { expiresIn: '5h' } + ); + + res.setHeader('Set-Cookie', cookie.serialize('auth', token, { + httpOnly: true, + secure: process.env.NODE_ENV !== 'development', + maxAge: 60 * 60 * 5, + path: '/', + sameSite: 'strict', + })); + + res.status(200).json({ success: true, message: 'Connexion réussie et cookie créé.' }); + } else { + res.setHeader('Allow', ['POST']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/emails/newAccount.js b/pages/api/emails/newAccount.js new file mode 100644 index 0000000..a6953ea --- /dev/null +++ b/pages/api/emails/newAccount.js @@ -0,0 +1,53 @@ +const nodemailer = require('nodemailer'); +const hbs = require('nodemailer-express-handlebars'); +const path = require('path'); + + +export default async function handler(req, res) { + + const { email, password, type } = req.body + + const transporter = nodemailer.createTransport({ + host: 'smtp-relay.brevo.com', + port: 587, + secure: false, + auth: { + user: 'contact@territoiresentransitions.fr', + pass: process.env.BREVO_KEY + }, + tls: {rejectUnauthorized: false} + }); + + transporter.use('compile', hbs({ + viewEngine: { + extName: '.hbs', + partialsDir: path.resolve('./views/emails'), + defaultLayout: false, + }, + viewPath: path.resolve('./views/emails'), + extName: '.hbs', + })); + + const mailOptions = { + from: '"ADEME" ', + to: email, + subject: "Votre nouveau compte sur les Rencontres Territoire Engagé Transition écologique !", + template: 'newaccount', + context: { + password: password, + type: type, + email: email, + siteUrl: process.env.WEBSITE_URL, + } + }; + + transporter.sendMail(mailOptions, (error, info) => { + if (error) { + res.json(error) + // return console.log(error); + } + console.log('Message sent: %s', info.messageId); + res.status(200).json({ status: 'sended' }) + }); + +} \ No newline at end of file diff --git a/pages/connexion.js b/pages/connexion.js index 0c8c39e..7908d29 100644 --- a/pages/connexion.js +++ b/pages/connexion.js @@ -14,38 +14,56 @@ export default function Login(){ const router = useRouter(); const handleChange = async (event) => { - const { name, type, value } = event.target + const { name, type, checked, value } = event.target setUserLogin(prev => { return { ...prev, - [name]: value + [name]: type == 'checkbox' ? checked : value } }) } const login = async () => { - const { mail, motDePasse } = userLogin; + const { mail, motDePasse, isAdmin } = userLogin; if (mail && motDePasse) { - const test = await fetch(`/api/users/lib/auth/?mail=${mail}&motDePasse=${motDePasse}`); - const json = await test.json(); - if (json.success == true) { - + if(isAdmin){ + const test = await fetch(`/api/accounts/lib/auth/?mail=${mail}&motDePasse=${motDePasse}`); + const json = await test.json(); + if (json.success == true) { + const jwtResponse = await fetch(`/api/accounts/lib/jwt`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id: json.account.id, mail: json.account.mail, type: json.account.type }), + }); + + const jwtJson = await jwtResponse.json(); + if(jwtJson.success){ + router.push('/admin'); + } + } else { + setNotif({ + text: 'Identifiant ou mot de passe invalide(s)', + icon: 'close' + }); + } + } + else{ + const test = await fetch(`/api/users/lib/auth/?mail=${mail}&motDePasse=${motDePasse}`); + const json = await test.json(); + if (json.success == true) { const jwtResponse = await fetch(`/api/users/lib/jwt`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ id: json.user.id, mail: json.user.mail }), + body: JSON.stringify({ id: json.user.id, mail: json.user.mail, type: 'user' }), }); const jwtJson = await jwtResponse.json(); if(jwtJson){ - if(json.user.id == 10){ - router.push('/admin'); - } - else{ - router.push('/espace-personnel'); - } + router.push('/espace-personnel'); } } else { @@ -54,8 +72,8 @@ export default function Login(){ icon: 'close' }); } - } + } else { setNotif({ text: 'Veuillez remplir tous les champs !', @@ -118,6 +136,16 @@ export default function Login(){
    +
    + +    + Je suis administrateur ou DR +
    Créer un compte diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3172b18..4f17334 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -119,3 +119,10 @@ model Review { session Session @relation(fields: [sessionId], references: [id]) user User @relation(fields: [userId], references: [id]) } + +model Account { + id Int @id @default(autoincrement()) @db.SmallInt + email String @unique + password String? + type String? +} diff --git a/styles/Admin.module.css b/styles/Admin.module.css index 7b5398b..af363cf 100644 --- a/styles/Admin.module.css +++ b/styles/Admin.module.css @@ -282,4 +282,35 @@ .visubox button span{ color:#FFF; font-size: 1.15rem; +} + +.Subtitle{ + font-size: 1.1rem; + margin-bottom: 5px; + font-weight: 500; +} + +.acc{ + border-bottom:1px solid rgba(0,0,0,.05) +} + +.acc p{ + width: 50%; +} + +.acc span{ + background-color: var(--primaryBack); + color: var(--primary); + display: inline-block; + padding:10px 15px; + border-radius: 8px; +} + +.acc button{ + color:#FFF; + background-color: var(--primary); + border:0px; + border-radius: 8px; + padding:10px 15px; + cursor:pointer; } \ No newline at end of file diff --git a/views/emails/newaccount.hbs b/views/emails/newaccount.hbs new file mode 100644 index 0000000..995b60a --- /dev/null +++ b/views/emails/newaccount.hbs @@ -0,0 +1,73 @@ + + + + + + + + + + +
    +
    + top email +
    +

    Votre compte {{type}} vient d'être créé !

    +

    Pour vous connecter, utilisez vos accès : votre adresse e-mail : {{email}} et votre nouveau mot de passe.

    +

    Voici votre nouveau mot de passe :

    + {{password}} +

    Attention à le garder secrètement de votre côté.

    +

    Vous pouvez vous connecter à partir de ce lien : Connexion.

    +
    + +
    +
    + +