From 566ddf06b1cfefa95d04ae44c08d8f456e3e040f Mon Sep 17 00:00:00 2001 From: jfgiraud Date: Thu, 20 Aug 2026 16:01:58 +0000 Subject: [PATCH] Front + gestion des utilisateurs --- html/admin.html | 419 +++++++++++++++++ html/securite.html | 340 ++++++++++++++ js/indices.js | 456 ++++++++++++++++--- js/securite.js | 916 ++++++++++++++++++++++++++++++++++++++ php/api_login.php | 328 ++++++++++++++ php/database.php | 46 ++ php/securite/auth.php | 46 ++ php/securite/create.php | 122 +++++ php/securite/password.php | 97 ++++ php/securite/status.php | 46 ++ php/securite/users.php | 36 ++ 11 files changed, 2799 insertions(+), 53 deletions(-) create mode 100644 html/admin.html create mode 100644 html/securite.html create mode 100644 js/securite.js create mode 100644 php/api_login.php create mode 100644 php/database.php create mode 100644 php/securite/auth.php create mode 100644 php/securite/create.php create mode 100644 php/securite/password.php create mode 100644 php/securite/status.php create mode 100644 php/securite/users.php diff --git a/html/admin.html b/html/admin.html new file mode 100644 index 0000000..5404127 --- /dev/null +++ b/html/admin.html @@ -0,0 +1,419 @@ + + + + + + + + Administration - Giraud Finance + + + + + + + + + + + +
+
+ Logo Giraud Finance +
+ +
+

+ Espace d'Administration +

+
+ +
+ + ← Retour au site + +
+
+ + + +
+ + +
+
+

Connexion requise

+ +

+ Veuillez vous identifier pour accéder au panneau de configuration. +

+
+ +
+ + +
+ + + +
+ + + +
+ + + +
+ + + + + + + + +
+ +
+ + Accès réservé aux administrateurs + +
+
+ + + + +
+ + + + + + + + + + diff --git a/html/securite.html b/html/securite.html new file mode 100644 index 0000000..7e26e82 --- /dev/null +++ b/html/securite.html @@ -0,0 +1,340 @@ + + + + + + + Sécurité - Giraud Finance + + + + + + + + + + + +
+ + +
+
+ + +
+

Sécurité

+ +

Gestion des utilisateurs

+
+ + + +
+ + + + + + + + ← Retour au menu + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +
+ Utilisateur + + Créé le + + Dernière connexion + + Statut + + Actions +
+
+
+
+ + + + + + + + + + + + + + diff --git a/js/indices.js b/js/indices.js index 1c6c5db..f12da04 100644 --- a/js/indices.js +++ b/js/indices.js @@ -1,16 +1,29 @@ +// ========================================================= // Tableaux globaux +// ========================================================= + let configIndices = []; // Données issues de la table "indice" let coursActionsData = []; // Données issues de la table "cours_actions" +// ========================================================= +// CHARGEMENT DES DONNÉES +// ========================================================= + function chargerDonnees() { // Chargement en parallèle des deux API Promise.all([ fetch("./php/api_lecture_tableindice.php").then((res) => res.json()), fetch("./php/api_indices.php").then((res) => res.json()), ]) + .then(([configResult, coursResult]) => { + // ----------------------------------------------------- + // Configuration des indices + // ----------------------------------------------------- + if (configResult.status === "success") { configIndices = configResult.data; + console.log( "Configuration indices chargée :", configIndices.length, @@ -20,8 +33,13 @@ function chargerDonnees() { console.error("Erreur API config indices :", configResult.message); } + // ----------------------------------------------------- + // Cours des actions + // ----------------------------------------------------- + if (coursResult.status === "success") { coursActionsData = coursResult.data; + console.log( "Cours actions chargés :", coursActionsData.length, @@ -31,184 +49,516 @@ function chargerDonnees() { console.error("Erreur API cours_actions :", coursResult.message); } + // ----------------------------------------------------- // Mise à jour des vues + // ----------------------------------------------------- + afficherSyntheseCartes(); + alimenterBandeauxTicker(); + // ----------------------------------------------------- // Statut de rafraîchissement + // ----------------------------------------------------- + const statut = document.getElementById("statut"); + if (statut) { statut.innerText = `Dernière mise à jour : ${new Date().toLocaleTimeString()}`; } }) - .catch((error) => - console.error("Erreur réseau lors du chargement des API :", error), - ); + + .catch((error) => { + console.error("Erreur réseau lors du chargement des API :", error); + }); } +// ========================================================= +// AFFICHAGE DES CARTES +// ========================================================= + function afficherSyntheseCartes() { const container = document.getElementById("cards-container"); + if (!container) return; container.innerHTML = ""; - // CARTES : On se base STRICTEMENT sur le champ afficher == 1 + // ------------------------------------------------------- + // On se base STRICTEMENT sur afficher == 1 + // ------------------------------------------------------- + const actionsAffichees = configIndices.filter( (item) => String(item.afficher) === "1" || item.afficher == 1, ); if (actionsAffichees.length > 0) { actionsAffichees.forEach((conf) => { + // --------------------------------------------------- // Croisement avec les cours en direct + // --------------------------------------------------- + const d = coursActionsData.find( (item) => item.symbole === conf.symbole || item.isin === conf.isin, ) || {}; const ecartValeur = parseFloat(d.variation || 0); + const isPositive = ecartValeur >= 0; + const badgeColor = isPositive ? "text-emerald-400" : "text-rose-400"; + const sign = isPositive ? "+" : ""; + // --------------------------------------------------- + // Volume + // --------------------------------------------------- + const volumeFormate = d.volume ? parseInt(d.volume).toLocaleString("fr-FR") : "--"; + // --------------------------------------------------- + // Carte + // --------------------------------------------------- + container.innerHTML += ` -
- -
-
-

- ${conf.nom || conf.indice} (${conf.symbole || "N/A"}) -

- ${conf.isin || "Aucun ISIN"} - ${conf.indice || ""} -
-
-
- ${d.cours ? parseFloat(d.cours).toLocaleString("fr-FR", { minimumFractionDigits: 2 }) : "--"} -
-
- ${sign}${ecartValeur.toLocaleString("fr-FR", { minimumFractionDigits: 2, maximumFractionDigits: 4 })} - (${sign}${parseFloat(d.variation_pct || 0).toFixed(2)}%) -
-
- ${d.date_cours || "--/--"} -
-
+
+ + + +
+ +
+ +

+ + ${conf.nom || conf.indice} + + + (${conf.symbole || "N/A"}) + + +

+ + + + ${conf.isin || "Aucun ISIN"} + + + + + ${conf.indice || ""} + +
- -
-
- Haut - ${d.plus_haut ? parseFloat(d.plus_haut).toLocaleString("fr-FR", { minimumFractionDigits: 2 }) : "--"} -
-
- Bas - ${d.plus_bas ? parseFloat(d.plus_bas).toLocaleString("fr-FR", { minimumFractionDigits: 2 }) : "--"} -
-
- Volume - ${volumeFormate} -
+ + + +
+ +
+ + ${ + d.cours + ? parseFloat(d.cours).toLocaleString("fr-FR", { + minimumFractionDigits: 2, + }) + : "--" + } + +
+ + + + +
+ + ${sign} + + ${ecartValeur.toLocaleString("fr-FR", { + minimumFractionDigits: 2, + maximumFractionDigits: 4, + })} + + + + ( + ${sign}${parseFloat(d.variation_pct || 0).toFixed(2)}% + ) + + + +
+ + + + +
+ + ${d.date_cours || "--/--"} + +
+
+ +
+ + + + +
+ +
+ + + Haut + + + + + ${ + d.plus_haut + ? parseFloat(d.plus_haut).toLocaleString("fr-FR", { + minimumFractionDigits: 2, + }) + : "--" + } + + + +
+ + +
+ + + Bas + + + + + ${ + d.plus_bas + ? parseFloat(d.plus_bas).toLocaleString("fr-FR", { + minimumFractionDigits: 2, + }) + : "--" + } + + + +
+ + +
+ + + Volume + + + + ${volumeFormate} + + +
+ +
+
+ `; }); } else { container.innerHTML = ` -
- Aucune action configurée à 'afficher = 1'. + +
+ + + Aucune action configurée à 'afficher = 1'. + +
+ `; } } +// ========================================================= +// PRÉPARATION DES BANDEAUX +// ========================================================= + function alimenterBandeauxTicker() { const preparerDonneesTicker = (nomIndiceRecherche) => { const cleanSearch = nomIndiceRecherche .replace(/[^a-z0-9]/gi, "") .toLowerCase(); - // BANDEAUX : On prend l'intégralité des actions disponibles pour l'indice (via coursActionsData ou configIndices) + // --------------------------------------------------- + // Recherche dans coursActionsData + // --------------------------------------------------- + let liste = coursActionsData.filter((item) => { - if (!item.indice) return false; + if (!item.indice) { + return false; + } + const cleanItemIndice = item.indice .replace(/[^a-z0-9]/gi, "") .toLowerCase(); + return cleanItemIndice === cleanSearch; }); + // --------------------------------------------------- + // Si aucune donnée, utiliser configIndices + // --------------------------------------------------- + if (liste.length === 0) { liste = configIndices.filter((item) => { - if (!item.indice) return false; + if (!item.indice) { + return false; + } + const cleanItemIndice = item.indice .replace(/[^a-z0-9]/gi, "") .toLowerCase(); + return cleanItemIndice === cleanSearch; }); } + // --------------------------------------------------- // Fusion et formatage + // --------------------------------------------------- + let listeFinale = liste.map((conf) => { - let coursMatch = + const coursMatch = coursActionsData.find( (item) => item.symbole === conf.symbole || item.isin === conf.isin, ) || conf; + return { nom: conf.nom || coursMatch.nom || conf.symbole, + cours: coursMatch.cours || 0, + variation_pct: coursMatch.variation_pct || 0, }; }); + // --------------------------------------------------- // Tri alphabétique + // --------------------------------------------------- + listeFinale.sort((a, b) => a.nom.localeCompare(b.nom)); return listeFinale; }; + // ------------------------------------------------------- + // Remplissage des trois bandeaux + // ------------------------------------------------------- + remplirUnBandeau("ticker-cac40", preparerDonneesTicker("CAC40")); + remplirUnBandeau("ticker-ibex35", preparerDonneesTicker("IBEX35")); + remplirUnBandeau("ticker-dowjones", preparerDonneesTicker("DOWJONES")); } +// ========================================================= +// REMPLIR UN BANDEAU +// ========================================================= + function remplirUnBandeau(elementId, donneesTriees) { const container = document.getElementById(elementId); + if (!container) return; if (donneesTriees.length === 0) { - container.innerHTML = `Aucune donnée disponible`; + container.innerHTML = ` + Aucune donnée disponible + `; + return; } let blocs = ""; + donneesTriees.forEach((item) => { const isPositive = parseFloat(item.variation_pct || 0) >= 0; + const colorClass = isPositive ? "text-emerald-400" : "text-rose-400"; + const sign = isPositive ? "+" : ""; + const coursFormate = parseFloat(item.cours || 0).toLocaleString("fr-FR", { minimumFractionDigits: 2, }); + const variationFormatee = parseFloat(item.variation_pct || 0).toFixed(2); blocs += ` -
- ${item.nom} - ${coursFormate} - ${sign}${variationFormatee}% -
+ +
+ + + ${item.nom} + + + + ${coursFormate} + + + + ${sign}${variationFormatee}% + + +
+ `; }); + // Triple répétition pour assurer + // le défilement continu + container.innerHTML = blocs + blocs + blocs; + const dureeTotale = Math.max(20, donneesTriees.length * 4); + container.style.animationDuration = `${dureeTotale}s`; } -// Démarrage +// ========================================================= +// DÉMARRAGE +// ========================================================= + +// Chargement initial chargerDonnees(); + +// Rafraîchissement toutes les 60 secondes setInterval(chargerDonnees, 60000); + +// ========================================================= +// RACCOURCIS CLAVIER +// ========================================================= + +document.addEventListener("keydown", (event) => { + // F1 → Administration + + if (event.key === "F1") { + event.preventDefault(); + + window.location.href = "https://www.giraud-finance.com/html/admin.html"; + } + + // F2 → Site principal + + if (event.key === "F2") { + event.preventDefault(); + + window.location.href = "https://giraud-finance.com"; + } +}); diff --git a/js/securite.js b/js/securite.js new file mode 100644 index 0000000..313c5e8 --- /dev/null +++ b/js/securite.js @@ -0,0 +1,916 @@ +"use strict"; + +/* + * ========================================================= + * CONFIGURATION + * ========================================================= + */ + +const API_LOGIN = "../php/api_login.php"; +const API = "../php/securite/"; + +/* + * ========================================================= + * ELEMENTS HTML + * ========================================================= + */ + +const listeUtilisateurs = document.getElementById("listeUtilisateurs"); + +const message = document.getElementById("message"); + +const btnNouvelUtilisateur = document.getElementById("btnNouvelUtilisateur"); + +/* Modal création */ + +const modalCreation = document.getElementById("modalCreation"); + +const formCreation = document.getElementById("formCreation"); + +const btnAnnulerCreation = document.getElementById("btnAnnulerCreation"); + +const creationNom = document.getElementById("creationNom"); + +const creationPass = document.getElementById("creationPass"); + +const creationPass2 = document.getElementById("creationPass2"); + +/* Modal mot de passe */ + +const modalPassword = document.getElementById("modalPassword"); + +const formPassword = document.getElementById("formPassword"); + +const btnAnnulerPassword = document.getElementById("btnAnnulerPassword"); + +const passwordId = document.getElementById("passwordId"); + +const passwordUtilisateur = document.getElementById("passwordUtilisateur"); + +const nouveauPass = document.getElementById("nouveauPass"); + +const nouveauPass2 = document.getElementById("nouveauPass2"); + +/* + * ========================================================= + * MESSAGE + * ========================================================= + */ + +function afficherMessage(texte, type = "success") { + if (!message) { + return; + } + + message.textContent = texte; + + message.className = "mb-4 px-4 py-3 rounded-lg"; + + if (type === "success") { + message.classList.add( + "bg-emerald-900", + "text-emerald-300", + "border", + "border-emerald-700", + ); + } else if (type === "error") { + message.classList.add( + "bg-rose-900", + "text-rose-300", + "border", + "border-rose-700", + ); + } else { + message.classList.add( + "bg-yellow-900", + "text-yellow-300", + "border", + "border-yellow-700", + ); + } + + setTimeout(() => { + message.classList.add("hidden"); + }, 5000); +} + +/* + * ========================================================= + * VERIFICATION SESSION + * ========================================================= + */ + +async function verifierSession() { + try { + const response = await fetch(API_LOGIN, { + method: "POST", + + headers: { + "Content-Type": "application/json", + }, + + credentials: "same-origin", + + cache: "no-store", + + body: JSON.stringify({ + action: "check", + }), + }); + + if (!response.ok) { + window.location.href = "/html/admin.html"; + + return false; + } + + const data = await response.json(); + + console.log("Vérification session :", data); + + if (data.status !== "success" || data.authenticated !== true) { + window.location.href = "/html/admin.html"; + + return false; + } + + return true; + } catch (error) { + console.error("Erreur vérification session :", error); + + window.location.href = "/html/admin.html"; + + return false; + } +} + +/* + * ========================================================= + * ECHAPPER HTML + * ========================================================= + */ + +function escapeHtml(value) { + if (value === null || value === undefined) { + return ""; + } + + return String(value) + .replace(/&/g, "&") + + .replace(//g, ">") + + .replace(/"/g, """) + + .replace(/'/g, "'"); +} + +/* + * ========================================================= + * FORMAT DATE + * ========================================================= + */ + +function formaterDate(date) { + if (!date || date === "0000-00-00 00:00:00") { + return "Jamais"; + } + + const d = new Date(String(date).replace(" ", "T")); + + if (Number.isNaN(d.getTime())) { + return date; + } + + return d.toLocaleString("fr-FR", { + dateStyle: "short", + timeStyle: "short", + }); +} + +/* + * ========================================================= + * CHARGER UTILISATEURS + * ========================================================= + */ + +async function chargerUtilisateurs() { + if (!listeUtilisateurs) { + console.error("ERREUR : #listeUtilisateurs introuvable dans securite.html"); + + return; + } + + listeUtilisateurs.innerHTML = ` + + + + + + Chargement... + + + + + + `; + + try { + const response = await fetch(API + "users.php", { + method: "GET", + + credentials: "same-origin", + + cache: "no-store", + }); + + console.log("users.php HTTP :", response.status); + + if (response.status === 401) { + window.location.href = "/html/admin.html"; + + return; + } + + const data = await response.json(); + + console.log("Réponse users.php :", data); + + if (data.success !== true) { + throw new Error( + data.message || "Impossible de charger les utilisateurs.", + ); + } + + const utilisateurs = Array.isArray(data.users) ? data.users : []; + + console.log("Utilisateurs reçus :", utilisateurs.length); + + console.table(utilisateurs); + + afficherUtilisateurs(utilisateurs); + } catch (error) { + console.error("Erreur chargement utilisateurs :", error); + + listeUtilisateurs.innerHTML = ` + + + + + + Erreur lors du chargement des utilisateurs. + + + + + + `; + + afficherMessage(error.message || "Erreur lors du chargement.", "error"); + } +} + +/* + * ========================================================= + * AFFICHER UTILISATEURS + * ========================================================= + */ + +function afficherUtilisateurs(utilisateurs) { + if (!utilisateurs || utilisateurs.length === 0) { + listeUtilisateurs.innerHTML = ` + + + + + + Aucun utilisateur. + + + + + + `; + + return; + } + + listeUtilisateurs.innerHTML = utilisateurs + .map((utilisateur) => { + const id = Number(utilisateur.id); + + const nom = escapeHtml(utilisateur.nom); + + const actif = Number(utilisateur.actif) === 1; + + const created = formaterDate(utilisateur.created_at); + + const dernierLogin = formaterDate(utilisateur.dernier_login); + + return ` + + + + + + + +
+ + ${nom} + +
+ +
+ + ID : ${id} + +
+ + + + + + + + + ${created} + + + + + + + + + ${dernierLogin} + + + + + + + + + ${ + actif + ? ` + + + + Actif + + + + ` + : ` + + + + Inactif + + + + ` + } + + + + + + + + +
+ + + + + + +
+ + + + + + `; + }) + .join(""); + + /* + * Boutons mot de passe + */ + + document.querySelectorAll(".btn-password").forEach((button) => { + button.addEventListener("click", () => { + ouvrirModalPassword(button.dataset.id, button.dataset.nom); + }); + }); + + /* + * Boutons statut + */ + + document.querySelectorAll(".btn-status").forEach((button) => { + button.addEventListener("click", () => { + changerStatut(button.dataset.id, Number(button.dataset.actif)); + }); + }); +} + +/* + * ========================================================= + * MODALE CREATION + * ========================================================= + */ + +function ouvrirModalCreation() { + if (!modalCreation) { + return; + } + + modalCreation.classList.remove("hidden"); + + modalCreation.classList.add("flex"); + + setTimeout(() => creationNom?.focus(), 100); +} + +function fermerModalCreation() { + if (!modalCreation) { + return; + } + + modalCreation.classList.add("hidden"); + + modalCreation.classList.remove("flex"); + + formCreation?.reset(); +} + +/* + * ========================================================= + * CREER UTILISATEUR + * ========================================================= + */ + +async function creerUtilisateur() { + const nom = creationNom.value.trim(); + + const pass = creationPass.value; + + const pass2 = creationPass2.value; + + if (!nom) { + afficherMessage("Veuillez saisir un nom utilisateur.", "error"); + + creationNom.focus(); + + return; + } + + if (pass.length < 4) { + afficherMessage( + "Le mot de passe doit contenir au moins 4 caractères.", + "error", + ); + + creationPass.focus(); + + return; + } + + if (pass !== pass2) { + afficherMessage("Les deux mots de passe ne correspondent pas.", "error"); + + creationPass2.focus(); + + return; + } + + const bouton = formCreation.querySelector('button[type="submit"]'); + + bouton.disabled = true; + + bouton.textContent = "Création..."; + + try { + const response = await fetch(API + "create.php", { + method: "POST", + + headers: { + "Content-Type": "application/json", + }, + + credentials: "same-origin", + + body: JSON.stringify({ + nom: nom, + pass: pass, + }), + }); + + if (response.status === 401) { + window.location.href = "/html/admin.html"; + + return; + } + + const data = await response.json(); + + if (data.success !== true) { + throw new Error(data.message || "Impossible de créer l'utilisateur."); + } + + fermerModalCreation(); + + afficherMessage("Utilisateur créé avec succès.", "success"); + + await chargerUtilisateurs(); + } catch (error) { + console.error("Erreur création utilisateur :", error); + + afficherMessage(error.message || "Erreur lors de la création.", "error"); + } finally { + bouton.disabled = false; + + bouton.textContent = "Créer"; + } +} + +/* + * ========================================================= + * MODALE PASSWORD + * ========================================================= + */ + +function ouvrirModalPassword(id, nom) { + passwordId.value = id; + + passwordUtilisateur.textContent = "Utilisateur : " + nom; + + nouveauPass.value = ""; + + nouveauPass2.value = ""; + + modalPassword.classList.remove("hidden"); + + modalPassword.classList.add("flex"); + + setTimeout(() => nouveauPass?.focus(), 100); +} + +function fermerModalPassword() { + modalPassword.classList.add("hidden"); + + modalPassword.classList.remove("flex"); + + formPassword?.reset(); + + passwordId.value = ""; + + passwordUtilisateur.textContent = ""; +} + +/* + * ========================================================= + * MODIFIER PASSWORD + * ========================================================= + */ + +async function modifierPassword() { + const id = Number(passwordId.value); + + const pass = nouveauPass.value; + + const pass2 = nouveauPass2.value; + + if (!id) { + afficherMessage("Utilisateur invalide.", "error"); + + return; + } + + if (pass.length < 4) { + afficherMessage( + "Le mot de passe doit contenir au moins 4 caractères.", + "error", + ); + + nouveauPass.focus(); + + return; + } + + if (pass !== pass2) { + afficherMessage("Les deux mots de passe ne correspondent pas.", "error"); + + nouveauPass2.focus(); + + return; + } + + const bouton = formPassword.querySelector('button[type="submit"]'); + + bouton.disabled = true; + + bouton.textContent = "Modification..."; + + try { + const response = await fetch(API + "password.php", { + method: "POST", + + headers: { + "Content-Type": "application/json", + }, + + credentials: "same-origin", + + body: JSON.stringify({ + id: id, + pass: pass, + }), + }); + + if (response.status === 401) { + window.location.href = "/html/admin.html"; + + return; + } + + const data = await response.json(); + + if (data.success !== true) { + throw new Error( + data.message || "Impossible de modifier le mot de passe.", + ); + } + + fermerModalPassword(); + + afficherMessage("Mot de passe modifié avec succès.", "success"); + } catch (error) { + console.error("Erreur modification mot de passe :", error); + + afficherMessage( + error.message || "Erreur lors de la modification.", + "error", + ); + } finally { + bouton.disabled = false; + + bouton.textContent = "Modifier"; + } +} + +/* + * ========================================================= + * CHANGER STATUT + * ========================================================= + */ + +async function changerStatut(id, actifActuel) { + const nouveauStatut = actifActuel === 1 ? 0 : 1; + + const confirmation = + nouveauStatut === 1 + ? "Voulez-vous activer cet utilisateur ?" + : "Voulez-vous désactiver cet utilisateur ?"; + + if (!window.confirm(confirmation)) { + return; + } + + try { + const response = await fetch(API + "status.php", { + method: "POST", + + headers: { + "Content-Type": "application/json", + }, + + credentials: "same-origin", + + body: JSON.stringify({ + id: Number(id), + actif: nouveauStatut, + }), + }); + + if (response.status === 401) { + window.location.href = "/html/admin.html"; + + return; + } + + const data = await response.json(); + + if (data.success !== true) { + throw new Error(data.message || "Impossible de modifier le statut."); + } + + afficherMessage( + nouveauStatut === 1 ? "Utilisateur activé." : "Utilisateur désactivé.", + "success", + ); + + await chargerUtilisateurs(); + } catch (error) { + console.error("Erreur changement statut :", error); + + afficherMessage( + error.message || "Erreur lors de la modification.", + "error", + ); + } +} + +/* + * ========================================================= + * EVENEMENTS + * ========================================================= + */ + +btnNouvelUtilisateur?.addEventListener("click", ouvrirModalCreation); + +btnAnnulerCreation?.addEventListener("click", fermerModalCreation); + +btnAnnulerPassword?.addEventListener("click", fermerModalPassword); + +formCreation?.addEventListener("submit", (event) => { + event.preventDefault(); + + creerUtilisateur(); +}); + +formPassword?.addEventListener("submit", (event) => { + event.preventDefault(); + + modifierPassword(); +}); + +/* + * Fermeture modales + */ + +modalCreation?.addEventListener("click", (event) => { + if (event.target === modalCreation) { + fermerModalCreation(); + } +}); + +modalPassword?.addEventListener("click", (event) => { + if (event.target === modalPassword) { + fermerModalPassword(); + } +}); + +/* + * ESC + */ + +document.addEventListener("keydown", (event) => { + if (event.key !== "Escape") { + return; + } + + if (modalCreation && !modalCreation.classList.contains("hidden")) { + fermerModalCreation(); + } + + if (modalPassword && !modalPassword.classList.contains("hidden")) { + fermerModalPassword(); + } +}); + +/* + * ========================================================= + * INITIALISATION + * ========================================================= + */ + +document.addEventListener("DOMContentLoaded", async () => { + console.log("securite.js chargé"); + + console.log("listeUtilisateurs =", listeUtilisateurs); + + const authentifie = await verifierSession(); + + if (!authentifie) { + return; + } + + await chargerUtilisateurs(); +}); diff --git a/php/api_login.php b/php/api_login.php new file mode 100644 index 0000000..3911ec1 --- /dev/null +++ b/php/api_login.php @@ -0,0 +1,328 @@ + 0, + 'path' => '/', + 'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', + 'httponly' => true, + 'samesite' => 'Strict' +]); + +session_start(); + + +/* + * ========================================================= + * CONFIGURATION + * ========================================================= + */ + +require_once __DIR__ . '/database.php'; + + +header('Content-Type: application/json; charset=utf-8'); + + +/* + * ========================================================= + * LECTURE JSON + * ========================================================= + */ + +$data = json_decode( + file_get_contents('php://input'), + true +); + +$action = $data['action'] ?? ''; + + +/* + * ========================================================= + * REPONSE JSON + * ========================================================= + */ + +function jsonResponse( + string $status, + string $message = '', + array $extra = [] +): never { + + echo json_encode( + array_merge( + [ + 'status' => $status, + 'message' => $message + ], + $extra + ), + JSON_UNESCAPED_UNICODE + ); + + exit; +} + + +/* + * ========================================================= + * VERIFICATION SESSION + * ========================================================= + */ + +if ($action === 'check') { + + if ( + isset($_SESSION['user_id']) && + isset($_SESSION['username']) + ) { + + jsonResponse( + 'success', + '', + [ + 'authenticated' => true, + 'user_id' => $_SESSION['user_id'], + 'nom' => $_SESSION['username'] + ] + ); + } + + + jsonResponse( + 'error', + 'Session inactive.', + [ + 'authenticated' => false + ] + ); +} + + +/* + * ========================================================= + * DECONNEXION + * ========================================================= + */ + +if ($action === 'logout') { + + /* + * Détruire complètement la session + */ + + $_SESSION = []; + + + /* + * Supprimer le cookie de session + */ + + if ( + ini_get('session.use_cookies') + ) { + + $params = session_get_cookie_params(); + + setcookie( + session_name(), + '', + time() - 42000, + $params['path'], + $params['domain'] ?? '', + $params['secure'], + $params['httponly'] + ); + } + + + session_destroy(); + + + jsonResponse( + 'success', + 'Déconnexion effectuée.' + ); +} + + +/* + * ========================================================= + * CONNEXION + * ========================================================= + */ + +if ($action === 'login') { + + $nom = + trim( + (string)($data['nom'] ?? '') + ); + + $pass = + (string)($data['pass'] ?? ''); + + + if ( + $nom === '' || + $pass === '' + ) { + + jsonResponse( + 'error', + 'Identifiant et mot de passe obligatoires.' + ); + } + + + /* + * Recherche utilisateur + */ + + $sql = " + SELECT + id, + nom, + pass, + actif + FROM login + WHERE nom = :nom + LIMIT 1 + "; + + + $stmt = $pdo->prepare($sql); + + $stmt->execute([ + ':nom' => $nom + ]); + + + $user = + $stmt->fetch(PDO::FETCH_ASSOC); + + + /* + * Utilisateur inexistant + */ + + if (!$user) { + + /* + * Même traitement que mauvais mot de passe + * afin de ne pas révéler si le compte existe. + */ + + jsonResponse( + 'error', + 'Identifiant ou mot de passe incorrect.' + ); + } + + + /* + * Compte désactivé + */ + + if ((int)$user['actif'] !== 1) { + + jsonResponse( + 'error', + 'Ce compte est désactivé.' + ); + } + + + /* + * Vérification du mot de passe + */ + + if ( + !password_verify( + $pass, + $user['pass'] + ) + ) { + + jsonResponse( + 'error', + 'Identifiant ou mot de passe incorrect.' + ); + } + + + /* + * Nouveau numéro de session + * après authentification + */ + + session_regenerate_id(true); + + + /* + * Variables de session + */ + + $_SESSION['user_id'] = + (int)$user['id']; + + $_SESSION['username'] = + $user['nom']; + + $_SESSION['authenticated'] = + true; + + + /* + * Dernière connexion + */ + + $sql = " + UPDATE login + SET dernier_login = NOW() + WHERE id = :id + "; + + + $stmt = $pdo->prepare($sql); + + $stmt->execute([ + ':id' => $user['id'] + ]); + + + /* + * Réponse + */ + + jsonResponse( + 'success', + 'Connexion réussie.', + [ + 'authenticated' => true, + 'user_id' => (int)$user['id'], + 'nom' => $user['nom'] + ] + ); +} + + +/* + * ========================================================= + * ACTION INCONNUE + * ========================================================= + */ + +jsonResponse( + 'error', + 'Action inconnue.' +); diff --git a/php/database.php b/php/database.php new file mode 100644 index 0000000..86f1818 --- /dev/null +++ b/php/database.php @@ -0,0 +1,46 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false + ] + ); +} catch (PDOException $e) { + + http_response_code(500); + + header( + 'Content-Type: application/json; charset=utf-8' + ); + + echo json_encode( + [ + 'status' => 'error', + 'message' => 'Erreur de connexion à la base de données.' + ], + JSON_UNESCAPED_UNICODE + ); + + exit; +} diff --git a/php/securite/auth.php b/php/securite/auth.php new file mode 100644 index 0000000..d784cd3 --- /dev/null +++ b/php/securite/auth.php @@ -0,0 +1,46 @@ + 0, + 'path' => '/', + 'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', + 'httponly' => true, + 'samesite' => 'Strict' +]); + +session_start(); + + +/* + * ========================================================= + * VERIFICATION AUTHENTIFICATION + * ========================================================= + */ + +if ( + empty($_SESSION['user_id']) || + empty($_SESSION['authenticated']) +) { + + http_response_code(401); + + header( + 'Content-Type: application/json; charset=utf-8' + ); + + echo json_encode([ + 'success' => false, + 'message' => 'Authentification requise.' + ], JSON_UNESCAPED_UNICODE); + + exit; +} diff --git a/php/securite/create.php b/php/securite/create.php new file mode 100644 index 0000000..c3b555a --- /dev/null +++ b/php/securite/create.php @@ -0,0 +1,122 @@ +prepare($sql); + + $stmt->execute([ + ':nom' => $nom, + ':pass' => $hash + ]); + + + /* + * ===================================================== + * REPONSE + * ===================================================== + */ + + echo json_encode([ + 'success' => true, + 'message' => 'Utilisateur créé.' + ], JSON_UNESCAPED_UNICODE); +} catch (PDOException $e) { + + http_response_code(400); + + + /* + * Nom utilisateur déjà existant + */ + + if (($e->errorInfo[1] ?? null) == 1062) { + + $message = + 'Ce nom utilisateur existe déjà.'; + } else { + + $message = + 'Erreur lors de la création.'; + } + + + echo json_encode([ + 'success' => false, + 'message' => $message + ], JSON_UNESCAPED_UNICODE); +} catch (Exception $e) { + + http_response_code(400); + + echo json_encode([ + 'success' => false, + 'message' => $e->getMessage() + ], JSON_UNESCAPED_UNICODE); +} diff --git a/php/securite/password.php b/php/securite/password.php new file mode 100644 index 0000000..4fc1a61 --- /dev/null +++ b/php/securite/password.php @@ -0,0 +1,97 @@ +prepare($sql); + + $stmt->execute([ + ':pass' => $hash, + ':id' => $id + ]); + + + /* + * ===================================================== + * REPONSE + * ===================================================== + */ + + echo json_encode([ + 'success' => true, + 'message' => 'Mot de passe modifié.' + ], JSON_UNESCAPED_UNICODE); +} catch (Exception $e) { + + http_response_code(400); + + echo json_encode([ + 'success' => false, + 'message' => $e->getMessage() + ], JSON_UNESCAPED_UNICODE); +} diff --git a/php/securite/status.php b/php/securite/status.php new file mode 100644 index 0000000..af4f2e3 --- /dev/null +++ b/php/securite/status.php @@ -0,0 +1,46 @@ +prepare($sql); + + $stmt->execute([ + ':id' => $id + ]); + + echo json_encode([ + 'success' => true + ], JSON_UNESCAPED_UNICODE); +} catch (Exception $e) { + + http_response_code(400); + + echo json_encode([ + 'success' => false, + 'message' => $e->getMessage() + ], JSON_UNESCAPED_UNICODE); +} diff --git a/php/securite/users.php b/php/securite/users.php new file mode 100644 index 0000000..85fae28 --- /dev/null +++ b/php/securite/users.php @@ -0,0 +1,36 @@ +query($sql); + + echo json_encode([ + 'success' => true, + 'users' => $stmt->fetchAll(PDO::FETCH_ASSOC) + ], JSON_UNESCAPED_UNICODE); +} catch (PDOException $e) { + + http_response_code(500); + + echo json_encode([ + 'success' => false, + 'message' => 'Erreur base de données.' + ], JSON_UNESCAPED_UNICODE); +}