-
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);
+}