]> git.nothing2do.fr Git - diary-web/.git/commitdiff
initial commit
authorgaby <gaby@nothing2do.fr>
Thu, 16 Apr 2026 12:25:57 +0000 (14:25 +0200)
committergaby <gaby@nothing2do.fr>
Thu, 16 Apr 2026 12:25:57 +0000 (14:25 +0200)
12 files changed:
README.md [new file with mode: 0644]
config/config.php [new file with mode: 0644]
include/Database.php [new file with mode: 0644]
include/TripletManager.php [new file with mode: 0644]
include/WebAuthnManager.php [new file with mode: 0644]
prompt [new file with mode: 0644]
public/index.php [new file with mode: 0644]
public/login.php [new file with mode: 0644]
public/register.php [new file with mode: 0644]
rapport-final-yubikey-triplet.html [new file with mode: 0644]
rapport-yubikey-triplet.html [new file with mode: 0644]
sql/init_db.sql [new file with mode: 0644]

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/config/config.php b/config/config.php
new file mode 100644 (file)
index 0000000..9d46fdd
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+// config/config.php
+
+// Configuration de la base de données PostgreSQL
+define('DB_HOST', 'postgresql-nothing2do.eu.alwaysdata.net');
+define('DB_NAME', 'nothing2do.eu_diary');
+define('DB_USER', 'nothing2do.eu');
+define('DB_PASS', 'Sr7FEaj2SK');
+
+// Configuration WebAuthn
+define('WEBAUTHN_RP_NAME', 'Diary-web');
+define('WEBAUTHN_RP_ID', parse_url($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST));
+define('WEBAUTHN_ORIGIN', $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST']);
+
+// Initialisation de la base de données
+try {
+    $db = new PDO("pgsql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
+    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+    // Vérification et création des tables si elles n'existent pas
+    $db->exec("
+        CREATE TABLE IF NOT EXISTS users (
+            user_id SERIAL PRIMARY KEY,
+            username VARCHAR(255) NOT NULL UNIQUE,
+            yubikey_id INT UNIQUE,
+            FOREIGN KEY (yubikey_id) REFERENCES yubikeys(yubikey_id) ON DELETE SET NULL
+        );
+
+        CREATE TABLE IF NOT EXISTS yubikeys (
+            yubikey_id SERIAL PRIMARY KEY,
+            user_id INT UNIQUE,
+            key_data TEXT NOT NULL,
+            public_key TEXT NOT NULL,
+            FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
+        );
+
+        CREATE TABLE IF NOT EXISTS triplets (
+            triplet_id SERIAL PRIMARY KEY,
+            user_id INT NOT NULL,
+            label VARCHAR(255) NOT NULL,
+            keyword VARCHAR(255) NOT NULL,
+            action VARCHAR(255) NOT NULL,
+            FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
+        );
+
+        CREATE INDEX IF NOT EXISTS idx_triplets_user_id ON triplets(user_id);
+        CREATE INDEX IF NOT EXISTS idx_yubikeys_user_id ON yubikeys(user_id);
+    ");
+
+} catch (PDOException $e) {
+    die("Erreur de connexion ou d'initialisation de la base de données : " . $e->getMessage());
+}
+?>
diff --git a/include/Database.php b/include/Database.php
new file mode 100644 (file)
index 0000000..e026072
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+// includes/Database.php
+
+class Database {
+    private $host;
+    private $db_name;
+    private $username;
+    private $password;
+    private $conn;
+
+    public function __construct() {
+        // Remplace ces valeurs par celles de ta configuration
+        $this->host = 'localhost';
+        $this->db_name = 'ton_nom_de_base';
+        $this->username = 'ton_utilisateur';
+        $this->password = 'ton_mot_de_passe';
+    }
+
+    public function connect() {
+        $this->conn = null;
+
+        try {
+            $dsn = "pgsql:host={$this->host};dbname={$this->db_name}";
+            $this->conn = new PDO($dsn, $this->username, $this->password);
+            $this->conn->exec("set names utf8");
+            $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+        } catch(PDOException $exception) {
+            echo "Erreur de connexion à la base de données : " . $exception->getMessage();
+            exit;
+        }
+
+        return $this->conn;
+    }
+}
+?>
diff --git a/include/TripletManager.php b/include/TripletManager.php
new file mode 100644 (file)
index 0000000..655ea49
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+// includes/TripletManager.php
+
+class TripletManager {
+    private $pdo;
+
+    public function __construct(PDO $pdo) {
+        $this->pdo = $pdo;
+    }
+
+    public function getTripletsByUser($userId) {
+        $stmt = $this->pdo->prepare("SELECT * FROM triplets WHERE user_id = ?");
+        $stmt->execute([$userId]);
+        return $stmt->fetchAll(PDO::FETCH_ASSOC);
+    }
+
+    public function createTriplet($userId, $label, $keyword, $action) {
+        $stmt = $this->pdo->prepare("INSERT INTO triplets (user_id, label, keyword, action) VALUES (?, ?, ?, ?)");
+        $stmt->execute([$userId, $label, $keyword, $action]);
+        return $this->pdo->lastInsertId();
+    }
+
+    public function updateTriplet($tripletId, $label, $keyword, $action) {
+        $stmt = $this->pdo->prepare("UPDATE triplets SET label = ?, keyword = ?, action = ? WHERE triplet_id = ?");
+        $stmt->execute([$label, $keyword, $action, $tripletId]);
+        return $stmt->rowCount();
+    }
+
+    public function deleteTriplet($tripletId) {
+        $stmt = $this->pdo->prepare("DELETE FROM triplets WHERE triplet_id = ?");
+        $stmt->execute([$tripletId]);
+        return $stmt->rowCount();
+    }
+}
+?>
diff --git a/include/WebAuthnManager.php b/include/WebAuthnManager.php
new file mode 100644 (file)
index 0000000..06ed58c
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+// includes/WebAuthnManager.php
+
+require_once __DIR__ . '/../vendor/autoload.php'; // Assure-toi d'avoir installé webauthn-lib via Composer
+
+use Webauthn\{
+    PublicKeyCredentialCreationOptions,
+    PublicKeyCredentialRequestOptions,
+    AuthenticatorSelectionCriteria,
+    PublicKeyCredentialDescriptor,
+    PublicKeyCredentialParameters,
+    PublicKeyCredentialUserEntity,
+    PublicKeyCredentialSource,
+    AuthenticatorAttestationResponse,
+    AuthenticatorAssertionResponse
+};
+
+class WebAuthnManager {
+    private $rpName;
+    private $rpId;
+    private $origin;
+
+    public function __construct() {
+        $this->rpName = "Mon Application";
+        $this->rpId = parse_url($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST);
+        $this->origin = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
+    }
+
+    public function generateRegistrationOptions($username) {
+        $userEntity = new PublicKeyCredentialUserEntity(
+            $username,
+            random_bytes(16),
+            $username
+        );
+
+        $challenge = random_bytes(32);
+
+        $_SESSION['challenge'] = base64_encode($challenge);
+
+        return PublicKeyCredentialCreationOptions::create(
+            $this->rpName,
+            $userEntity,
+            $challenge,
+            [new PublicKeyCredentialDescriptor('public-key', AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE)]
+        );
+    }
+
+    public function register($attestationResponse, $username) {
+        if (!isset($_SESSION['challenge'])) {
+            return false;
+        }
+
+        $challenge = base64_decode($_SESSION['challenge']);
+        unset($_SESSION['challenge']);
+
+        $publicKeyCredentialSource = PublicKeyCredentialSource::createFromString($attestationResponse);
+        $publicKeyCredential = $publicKeyCredentialSource->getPublicKeyCredential();
+
+        if (!$publicKeyCredential->verify($challenge, $this->origin)) {
+            return false;
+        }
+
+        return [
+            'credentialId' => base64_encode($publicKeyCredential->getId()),
+            'publicKey' => base64_encode($publicKeyCredential->getPublicKey())
+        ];
+    }
+
+    public function generateAuthenticationOptions() {
+        $challenge = random_bytes(32);
+
+        $_SESSION['challenge'] = base64_encode($challenge);
+
+        return PublicKeyCredentialRequestOptions::create($challenge);
+    }
+
+    public function authenticate($assertionResponse, $storedPublicKey) {
+        if (!isset($_SESSION['challenge'])) {
+            return false;
+        }
+
+        $challenge = base64_decode($_SESSION['challenge']);
+        unset($_SESSION['challenge']);
+
+        $publicKeyCredentialSource = PublicKeyCredentialSource::createFromString($assertionResponse);
+        $publicKeyCredential = $publicKeyCredentialSource->getPublicKeyCredential();
+
+        if (!$publicKeyCredential->verify($challenge, $this->origin, base64_decode($storedPublicKey))) {
+            return false;
+        }
+
+        return true;
+    }
+}
+?>
diff --git a/prompt b/prompt
new file mode 100644 (file)
index 0000000..ce0a96b
--- /dev/null
+++ b/prompt
@@ -0,0 +1,19 @@
+i've no memory at all, so, one step at a time (and my shell is zsh, i've only ssh access to the server, all the access code for the app is in config.php)
+i want a php application who register user with a yubikey associated with a username.
+i don't own the server but i rent a ssh access to it and some space on the filesystem to host this application.
+the application should use the webauthn capability of the yubikey
+the application should initialize correctly at his first launch and should be "mobile" friendly.
+the application store information in a postgresql datebase.
+the application's database contain a list of triplet containing 3 associated string : label, keyword and action, and an unique ID.
+once the user is logged in, do as if action("start") was called.
+if there is no triplet to show at anytime, show a triplet (action défault, start, input text action à executer).
+the triplet is shown as a clickable button showing the "label" and, on click, pass the "action" associated with this "label" to the function action(string)
+the "configuration" keyword is used to act as a configuration page keyword.
+every time action(string) is called, string should be the new text of the status bar
+action(set name) is letting the user setting a string named name to a string given by the user. 
+action(box texte) is showing a box with text "texte" and a "ok" button.
+action(new) is letting the user create a new triplet (with label, keyword and action).
+action(input text "help string") let the user input a string with the "help string" shown, to be given to action().
+action(string) select all the triplet containing the string in their keyword and show according buttons.
+action(choose keyw) should show all triplet's label containing keyw into their keyword string and offer to edit them by they ID.
+action(edit ID) should offer a way to edit a precise triplet selected by his ID.
diff --git a/public/index.php b/public/index.php
new file mode 100644 (file)
index 0000000..9d2297b
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+// index.php
+session_start();
+require_once __DIR__ . '/includes/Database.php';
+require_once __DIR__ . '/includes/TripletManager.php';
+
+// Initialisation de la base de données
+$db = new Database();
+$pdo = $db->connect();
+$tripletManager = new TripletManager($pdo);
+
+// Vérification de la session utilisateur
+if (!isset($_SESSION['user_id'])) {
+    header("Location: login.php");
+    exit();
+}
+
+// Gestion des actions
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
+    $action = $_POST['action'];
+    $_SESSION['status'] = $action;
+    // Logique pour chaque type d'action
+    switch ($action) {
+        case "new":
+            // Afficher le formulaire pour créer un nouveau triplet
+            break;
+        case "input text":
+            // Afficher une boîte de dialogue pour entrer du texte
+            break;
+        case "box texte":
+            // Afficher une boîte de dialogue avec du texte
+            break;
+        case "set name":
+            // Mettre à jour le nom du triplet
+            break;
+        case "choose keyw":
+            // Choisir un mot-clé pour le triplet
+            break;
+        case "edit ID":
+            // Ouvrir l'éditeur pour le triplet avec l'ID
+            break;
+        case "configuration":
+            // Ouvrir la page de configuration
+            break;
+        default:
+            // Gérer l'action inconnue
+            break;
+    }
+}
+
+// Récupération des triplets de l'utilisateur
+$triplets = $tripletManager->getTripletsByUser($_SESSION['user_id']);
+
+// Si aucun triplet n'existe, afficher un triplet par défaut
+if (empty($triplets)) {
+    $triplets = [
+        ['triplet_id' => 0, 'label' => 'Démarrer', 'keyword' => 'default', 'action' => 'start']
+    ];
+}
+?>
+
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Application PHP avec YubiKey WebAuthn</title>
+    <style>
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 1200px;
+            margin: 0 auto;
+            padding: 20px;
+            background-color: #f9f9f9;
+        }
+        header {
+            text-align: center;
+            padding: 20px 0;
+            border-bottom: 1px solid #ddd;
+            margin-bottom: 30px;
+        }
+        h1, h2, h3 {
+            color: #2c3e50;
+        }
+        #status-bar {
+            background-color: #e74c3c;
+            color: white;
+            padding: 10px;
+            text-align: center;
+            margin-bottom: 20px;
+            border-radius: 5px;
+        }
+        .triplet {
+            background-color: white;
+            padding: 15px;
+            margin-bottom: 10px;
+            border-radius: 5px;
+            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+        }
+        .triplet h3 {
+            margin-top: 0;
+        }
+        form {
+            margin-top: 20px;
+        }
+        input, button {
+            padding: 10px;
+            margin-right: 10px;
+        }
+        @media (max-width: 600px) {
+            .triplet {
+                padding: 10px;
+            }
+            input, button {
+                width: 100%;
+                margin-bottom: 10px;
+            }
+        }
+    </style>
+</head>
+<body>
+    <header>
+        <h1>Bienvenue, <?php echo htmlspecialchars($_SESSION['username']); ?></h1>
+    </header>
+
+    <div id="status-bar">
+        <?php echo isset($_SESSION['status']) ? htmlspecialchars($_SESSION['status']) : 'Prêt'; ?>
+    </div>
+
+    <section>
+        <h2>Vos Triplets</h2>
+        <?php foreach ($triplets as $triplet): ?>
+            <div class="triplet">
+                <h3><?php echo htmlspecialchars($triplet['label']); ?></h3>
+                <p><strong>Mot-clé :</strong> <?php echo htmlspecialchars($triplet['keyword']); ?></p>
+                <p><strong>Action :</strong> <?php echo htmlspecialchars($triplet['action']); ?></p>
+                <form method="post" style="display: inline;">
+                    <input type="hidden" name="action" value="edit ID">
+                    <input type="hidden" name="triplet_id" value="<?php echo $triplet['triplet_id']; ?>">
+                    <button type="submit">Modifier</button>
+                </form>
+            </div>
+        <?php endforeach; ?>
+    </section>
+
+    <section>
+        <h2>Actions</h2>
+        <form method="post">
+            <input type="text" name="action" placeholder="Entrez une action" required>
+            <button type="submit">Envoyer</button>
+        </form>
+        <p>Exemples d'actions : "new", "input text", "box texte", "set name", "choose keyw", "edit ID", "configuration"</p>
+    </section>
+
+    <footer>
+        <p>© 2026 - Application PHP avec YubiKey WebAuthn et PostgreSQL</p>
+    </footer>
+</body>
+</html>
diff --git a/public/login.php b/public/login.php
new file mode 100644 (file)
index 0000000..307bf44
--- /dev/null
@@ -0,0 +1,212 @@
+<?php
+// login.php (version modernisée)
+session_start();
+require_once __DIR__ . '/../includes/Database.php';
+require_once __DIR__ . '/../includes/WebAuthnManager.php';
+
+$db = new Database();
+$pdo = $db->connect();
+$webAuthnManager = new WebAuthnManager();
+
+// Rediriger si déjà connecté
+if (isset($_SESSION['user_id'])) {
+    header("Location: index.php");
+    exit();
+}
+
+// Traitement du formulaire de connexion
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    $username = trim($_POST['username'] ?? '');
+    $yubikeyResponse = trim($_POST['yubikey'] ?? '');
+
+    if (!empty($username) && !empty($yubikeyResponse)) {
+        $stmt = $pdo->prepare("SELECT user_id, yubikey_id FROM users WHERE username = ?");
+        $stmt->execute([$username]);
+        $user = $stmt->fetch(PDO::FETCH_ASSOC);
+
+        if ($user) {
+            $stmt = $pdo->prepare("SELECT key_data, public_key FROM yubikeys WHERE yubikey_id = ?");
+            $stmt->execute([$user['yubikey_id']]);
+            $yubikey = $stmt->fetch(PDO::FETCH_ASSOC);
+
+            if ($yubikey) {
+                $authenticated = $webAuthnManager->authenticate($yubikeyResponse, $yubikey['public_key']);
+                if ($authenticated) {
+                    $_SESSION['user_id'] = $user['user_id'];
+                    $_SESSION['username'] = $username;
+                    $_SESSION['status'] = "Connexion réussie !";
+                    header("Location: index.php");
+                    exit();
+                }
+            }
+        }
+        $error = "Nom d'utilisateur ou réponse YubiKey invalide.";
+    }
+}
+?>
+
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Connexion - Application Sécurisée</title>
+    <style>
+        :root {
+            --primary: #4a6bff;
+            --primary-hover: #3a5bef;
+            --error: #ff4a6b;
+            --light: #f9f9f9;
+            --dark: #2c3e50;
+            --border-radius: 12px;
+            --box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+            --transition: all 0.3s ease;
+        }
+
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%);
+            min-height: 100vh;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            padding: 20px;
+        }
+
+        .login-container {
+            background: white;
+            width: 100%;
+            max-width: 450px;
+            padding: 40px;
+            border-radius: var(--border-radius);
+            box-shadow: var(--box-shadow);
+            transition: var(--transition);
+        }
+
+        .login-container:hover {
+            box-shadow: 0 12px 25px rgba(0, 0, 0, 0.15);
+        }
+
+        h1 {
+            text-align: center;
+            color: var(--dark);
+            margin-bottom: 30px;
+            font-weight: 600;
+        }
+
+        .error-message {
+            background: rgba(255, 74, 107, 0.1);
+            color: var(--error);
+            padding: 12px;
+            border-radius: var(--border-radius);
+            margin-bottom: 20px;
+            text-align: center;
+            font-size: 14px;
+        }
+
+        .form-group {
+            margin-bottom: 20px;
+        }
+
+        label {
+            display: block;
+            margin-bottom: 8px;
+            color: var(--dark);
+            font-weight: 500;
+        }
+
+        input[type="text"] {
+            width: 100%;
+            padding: 12px 16px;
+            border: 1px solid #ddd;
+            border-radius: var(--border-radius);
+            font-size: 16px;
+            transition: var(--transition);
+        }
+
+        input[type="text"]:focus {
+            border-color: var(--primary);
+            box-shadow: 0 0 0 3px rgba(74, 107, 255, 0.1);
+            outline: none;
+        }
+
+        .login-button {
+            width: 100%;
+            padding: 14px;
+            background: var(--primary);
+            color: white;
+            border: none;
+            border-radius: var(--border-radius);
+            font-size: 16px;
+            font-weight: 600;
+            cursor: pointer;
+            transition: var(--transition);
+            margin-top: 10px;
+        }
+
+        .login-button:hover {
+            background: var(--primary-hover);
+            transform: translateY(-2px);
+        }
+
+        .login-button:active {
+            transform: translateY(0);
+        }
+
+        .link-container {
+            text-align: center;
+            margin-top: 20px;
+        }
+
+        .link-container a {
+            color: var(--primary);
+            text-decoration: none;
+            font-weight: 500;
+            transition: var(--transition);
+        }
+
+        .link-container a:hover {
+            text-decoration: underline;
+        }
+
+        @media (max-width: 480px) {
+            .login-container {
+                padding: 30px 20px;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="login-container">
+        <h1>Connexion</h1>
+
+        <?php if (isset($error)): ?>
+            <div class="error-message"><?php echo htmlspecialchars($error); ?></div>
+        <?php endif; ?>
+
+        <form method="post">
+            <div class="form-group">
+                <label for="username">Nom d'utilisateur</label>
+                <input type="text" id="username" name="username" placeholder="Entrez votre nom d'utilisateur" required>
+            </div>
+
+            <div class="form-group">
+                <label for="yubikey">Réponse YubiKey</label>
+                <input type="text" id="yubikey" name="yubikey" placeholder="Touchez votre YubiKey" required>
+            </div>
+
+            <button type="submit" class="login-button">Se connecter</button>
+        </form>
+
+        <div class="link-container">
+            <p>Pas encore de compte ? <a href="register.php">S'inscrire</a></p>
+        </div>
+    </div>
+</body>
+</html>
diff --git a/public/register.php b/public/register.php
new file mode 100644 (file)
index 0000000..b67ba95
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+// register.php
+session_start();
+require_once __DIR__ . '/../includes/Database.php';
+require_once __DIR__ . '/../includes/WebAuthnManager.php';
+
+$db = new Database();
+$pdo = $db->connect();
+$webAuthnManager = new WebAuthnManager();
+
+// Rediriger si déjà connecté
+if (isset($_SESSION['user_id'])) {
+    header("Location: index.php");
+    exit();
+}
+
+// Traitement du formulaire d'inscription
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    $username = trim($_POST['username'] ?? '');
+    $yubikeyResponse = trim($_POST['yubikey'] ?? '');
+
+    if (!empty($username) && !empty($yubikeyResponse)) {
+        // Vérifier si le nom d'utilisateur existe déjà
+        $stmt = $pdo->prepare("SELECT user_id FROM users WHERE username = ?");
+        $stmt->execute([$username]);
+        if ($stmt->fetch()) {
+            $error = "Ce nom d'utilisateur est déjà pris.";
+        } else {
+            // Générer un défi WebAuthn et enregistrer la nouvelle YubiKey
+            $registrationData = $webAuthnManager->register($yubikeyResponse);
+
+            if ($registrationData) {
+                // Enregistrer l'utilisateur et la YubiKey dans la base de données
+                $pdo->beginTransaction();
+                try {
+                    // Insérer l'utilisateur
+                    $stmt = $pdo->prepare("INSERT INTO users (username) VALUES (?)");
+                    $stmt->execute([$username]);
+                    $userId = $pdo->lastInsertId();
+
+                    // Insérer la YubiKey
+                    $stmt = $pdo->prepare("INSERT INTO yubikeys (user_id, key_data) VALUES (?, ?)");
+                    $stmt->execute([$userId, $registrationData['credentialId']]);
+
+                    // Mettre à jour l'utilisateur avec l'ID de la YubiKey
+                    $stmt = $pdo->prepare("UPDATE users SET yubikey_id = ? WHERE user_id = ?");
+                    $stmt->execute([$pdo->lastInsertId(), $userId]);
+
+                    $pdo->commit();
+
+                    $_SESSION['status'] = "Inscription réussie ! Vous pouvez maintenant vous connecter.";
+                    header("Location: login.php");
+                    exit();
+                } catch (Exception $e) {
+                    $pdo->rollBack();
+                    $error = "Une erreur est survenue lors de l'inscription : " . $e->getMessage();
+                }
+            } else {
+                $error = "La réponse YubiKey est invalide.";
+            }
+        }
+    }
+}
+?>
+
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Inscription - Application PHP avec YubiKey WebAuthn</title>
+    <style>
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 600px;
+            margin: 0 auto;
+            padding: 20px;
+            background-color: #f9f9f9;
+        }
+        h1 {
+            text-align: center;
+            color: #2c3e50;
+        }
+        .register-form {
+            background-color: white;
+            padding: 20px;
+            border-radius: 5px;
+            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+        }
+        .register-form div {
+            margin-bottom: 15px;
+        }
+        label {
+            display: block;
+            margin-bottom: 5px;
+            font-weight: bold;
+        }
+        input[type="text"], button {
+            width: 100%;
+            padding: 10px;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+        }
+        button {
+            background-color: #2ecc71;
+            color: white;
+            border: none;
+            cursor: pointer;
+        }
+        button:hover {
+            background-color: #27ae60;
+        }
+        .error {
+            color: #e74c3c;
+            margin-bottom: 15px;
+        }
+        .link {
+            text-align: center;
+            margin-top: 15px;
+        }
+    </style>
+</head>
+<body>
+    <h1>Inscription</h1>
+
+    <?php if (isset($error)): ?>
+        <p class="error"><?php echo htmlspecialchars($error); ?></p>
+    <?php endif; ?>
+
+    <div class="register-form">
+        <form method="post">
+            <div>
+                <label for="username">Nom d'utilisateur :</label>
+                <input type="text" id="username" name="username" required>
+            </div>
+            <div>
+                <label for="yubikey">Réponse YubiKey :</label>
+                <input type="text" id="yubikey" name="yubikey" required>
+            </div>
+            <button type="submit">S'inscrire</button>
+        </form>
+        <div class="link">
+            <p>Déjà un compte ? <a href="login.php">Se connecter</a></p>
+        </div>
+    </div>
+</body>
+</html>
diff --git a/rapport-final-yubikey-triplet.html b/rapport-final-yubikey-triplet.html
new file mode 100644 (file)
index 0000000..7ae082b
--- /dev/null
@@ -0,0 +1,273 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Rapport: Application PHP avec YubiKey WebAuthn et PostgreSQL</title>
+    <style>
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 1200px;
+            margin: 0 auto;
+            padding: 20px;
+            background-color: #f9f9f9;
+        }
+        header {
+            text-align: center;
+            padding: 20px 0;
+            border-bottom: 1px solid #ddd;
+            margin-bottom: 30px;
+        }
+        h1, h2, h3 {
+            color: #2c3e50;
+        }
+        h1 {
+            font-size: 2.2em;
+            margin-bottom: 10px;
+        }
+        h2 {
+            font-size: 1.6em;
+            margin-top: 30px;
+            margin-bottom: 15px;
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+        }
+        h3 {
+            font-size: 1.3em;
+            margin-top: 20px;
+            margin-bottom: 10px;
+        }
+        section {
+            background-color: white;
+            padding: 20px;
+            margin-bottom: 20px;
+            border-radius: 5px;
+            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+        }
+        pre {
+            background-color: #f4f4f4;
+            padding: 15px;
+            border-radius: 5px;
+            overflow-x: auto;
+            font-size: 0.9em;
+        }
+        code {
+            font-family: 'Courier New', Courier, monospace;
+            background-color: #f4f4f4;
+            padding: 2px 5px;
+            border-radius: 3px;
+        }
+        table {
+            width: 100%;
+            border-collapse: collapse;
+            margin: 20px 0;
+        }
+        th, td {
+            padding: 12px;
+            text-align: left;
+            border-bottom: 1px solid #ddd;
+        }
+        th {
+            background-color: #f2f2f2;
+        }
+        tr:hover {
+            background-color: #f5f5f5;
+        }
+        .highlight {
+            background-color: #fffde7;
+            padding: 15px;
+            border-left: 4px solid #ffd600;
+            margin: 20px 0;
+        }
+        footer {
+            text-align: center;
+            margin-top: 40px;
+            padding: 20px;
+            border-top: 1px solid #ddd;
+            color: #7f8c8d;
+        }
+        @media print {
+            body {
+                background-color: white;
+                padding: 0;
+                max-width: 100%;
+            }
+            section {
+                box-shadow: none;
+                border: 1px solid #ddd;
+                page-break-inside: avoid;
+            }
+        }
+    </style>
+</head>
+<body>
+    <header>
+        <h1>Rapport: Application PHP avec YubiKey WebAuthn et PostgreSQL</h1>
+        <p>Guide complet pour le développement d'une application sécurisée</p>
+    </header>
+    <section>
+        <h2>Introduction</h2>
+        <p>
+            Ce rapport fournit un guide détaillé, étape par étape, pour développer une application web PHP sécurisée hébergée sur un serveur loué accessible via SSH (avec le shell zsh), avec un backend de base de données PostgreSQL. L'application permet l'inscription et la connexion des utilisateurs à l'aide de clés YubiKey via le protocole WebAuthn (FIDO2), la gestion des triplets (ID unique, libellé, mot-clé, action), et une interface mobile-friendly.
+        </p>
+        <p>
+            Le guide couvre la configuration de l'environnement, la conception de la base de données, l'intégration de WebAuthn, et la mise en œuvre de la logique de l'application, en garantissant la sécurité et l'utilisabilité.
+        </p>
+    </section>
+    <section>
+        <h2>Configuration du Serveur et de l'Environnement</h2>
+        <h3>Configuration Initiale du Serveur</h3>
+        <p>
+            Assurez-vous que votre serveur dispose des logiciels suivants :
+        </p>
+        <ul>
+            <li>PHP 8.2+</li>
+            <li>PostgreSQL 14+</li>
+            <li>Extensions PHP : <code>pdo_pgsql</code>, <code>openssl</code>, <code>mbstring</code></li>
+            <li>Bibliothèque WebAuthn (ex: <a href="https://github.com/webauthn/webauthn-lib">webauthn-lib</a>)</li>
+        </ul>
+        <h3>Structure du Projet</h3>
+        <p>
+            Voici une structure de base pour votre application :
+        </p>
+        <pre><code>/var/www/ton_app/
+├── config/
+│   └── config.php          # Configuration de la base de données et WebAuthn
+├── includes/
+│   ├── Database.php         # Classe pour gérer la connexion PostgreSQL
+│   ├── WebAuthnManager.php  # Gestion de l’authentification YubiKey
+│   └── TripletManager.php   # Gestion des triplets
+├── public/
+│   ├── index.php            # Point d’entrée principal
+│   ├── login.php            # Page de login
+│   ├── register.php         # Page d’inscription
+│   └── assets/              # CSS/JS
+├── sql/
+│   └── init_db.sql          # Script d’initialisation de la base de données
+└── README.md                # Documentation</code></pre>
+    </section>
+    <section>
+        <h2>Tableau Récapitulatif : Schéma de la Base de Données</h2>
+        <table>
+            <thead>
+                <tr>
+                    <th>Table</th>
+                    <th>Colonnes (Type)</th>
+                    <th>Description</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr>
+                    <td>users</td>
+                    <td>user_id (SERIAL PK), username (VARCHAR), password (VARCHAR), yubikey_id (INT FK)</td>
+                    <td>Stocke les informations d'identification des utilisateurs et l'association YubiKey</td>
+                </tr>
+                <tr>
+                    <td>yubikeys</td>
+                    <td>yubikey_id (SERIAL PK), user_id (INT FK), key_data (TEXT)</td>
+                    <td>Stocke les informations d'identification YubiKey et l'association utilisateur</td>
+                </tr>
+                <tr>
+                    <td>triplets</td>
+                    <td>triplet_id (SERIAL PK), label (VARCHAR), keyword (VARCHAR), action (VARCHAR)</td>
+                    <td>Stocke les triplets pour les actions utilisateur</td>
+                </tr>
+            </tbody>
+        </table>
+    </section>
+    <section>
+        <h2>Authentification WebAuthn/YubiKey</h2>
+        <p>
+            Pour intégrer WebAuthn avec YubiKey, utilisez une bibliothèque comme <a href="https://github.com/webauthn/webauthn-lib">webauthn-lib</a> :
+        </p>
+        <pre><code>&lt;?php
+// Dans register.php
+$webauthn = new WebAuthn\WebAuthn($config);
+$registrationData = $webauthn->register(...);
+// Enregistrer $registrationData->credentialId dans la table yubikeys
+?&gt;</code></pre>
+        <div class="highlight">
+            <p>
+                <strong>Bonnes pratiques WebAuthn :</strong>
+            </p>
+            <ul>
+                <li>Vérifiez l'origine des requêtes WebAuthn.</li>
+                <li>Utilisez une génération et une vérification sécurisées des défis.</li>
+                <li>Suivez la spécification WebAuthn pour des flux d'authentification sécurisés.</li>
+            </ul>
+        </div>
+    </section>
+    <section>
+        <h2>Gestion des Triplets</h2>
+        <p>
+            Utilisez des requêtes préparées pour manipuler les triplets :
+        </p>
+        <pre><code>&lt;?php
+class TripletManager {
+    public function createTriplet($label, $keyword, $action) {
+        $stmt = $this->pdo->prepare("INSERT INTO triplets (label, keyword, action) VALUES (?, ?, ?)");
+        $stmt->execute([$label, $keyword, $action]);
+    }
+}
+?&gt;</code></pre>
+    </section>
+    <section>
+        <h2>Barre d'État et Interface Mobile-Friendly</h2>
+        <h3>Mises à Jour de la Barre d'État</h3>
+        <p>
+            Mettez à jour la barre d'état à chaque appel d'action :
+        </p>
+        <pre><code>function action(actionString) {
+    document.getElementById('status-bar').innerText = actionString;
+    // Logique supplémentaire
+}</code></pre>
+        <h3>Design Mobile-Friendly</h3>
+        <p>
+            Utilisez du CSS responsive :
+        </p>
+        <pre><code>.login-form {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+.login-form input, .login-form button {
+    width: 100%;
+    padding: 10px;
+    margin-bottom: 10px;
+}</code></pre>
+    </section>
+    <section>
+        <h2>Sécurité et Gestion des Erreurs</h2>
+        <h3>Prévention des Injections SQL</h3>
+        <p>
+            Utilisez toujours des requêtes préparées :
+        </p>
+        <pre><code>&lt;?php
+$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
+$stmt->execute([$username]);
+?&gt;</code></pre>
+        <h3>Gestion des Erreurs</h3>
+        <p>
+            Gérez les erreurs de manière élégante :
+        </p>
+        <pre><code>&lt;?php
+try {
+    // Opération de base de données
+} catch (PDOException $e) {
+    echo "Erreur : " . $e->getMessage();
+}
+?&gt;</code></pre>
+    </section>
+    <section>
+        <h2>Conclusion</h2>
+        <p>
+            Ce rapport fournit un guide détaillé, étape par étape, pour développer une application web PHP sécurisée hébergée sur un serveur loué avec accès SSH, utilisant PostgreSQL et l'authentification YubiKey WebAuthn. Le guide couvre la configuration de l'environnement, la conception de la base de données, l'inscription et la connexion des utilisateurs avec WebAuthn, la gestion des triplets, et la mise en œuvre d'une interface mobile-friendly. En suivant ces étapes, vous pouvez garantir une application sécurisée, fonctionnelle et conviviale qui répond à toutes les exigences spécifiées.
+        </p>
+    </section>
+    <footer>
+        <p>© 2026 - Rapport Technique - Application PHP avec YubiKey WebAuthn et PostgreSQL</p>
+    </footer>
+</body>
+</html>
diff --git a/rapport-yubikey-triplet.html b/rapport-yubikey-triplet.html
new file mode 100644 (file)
index 0000000..5f78017
--- /dev/null
@@ -0,0 +1,477 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Rapport: Application PHP avec YubiKey WebAuthn et PostgreSQL</title>
+    <style>
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 1200px;
+            margin: 0 auto;
+            padding: 20px;
+            background-color: #f9f9f9;
+        }
+        header {
+            text-align: center;
+            padding: 20px 0;
+            border-bottom: 1px solid #ddd;
+            margin-bottom: 30px;
+        }
+        h1, h2, h3 {
+            color: #2c3e50;
+        }
+        h1 {
+            font-size: 2.2em;
+            margin-bottom: 10px;
+        }
+        h2 {
+            font-size: 1.6em;
+            margin-top: 30px;
+            margin-bottom: 15px;
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+        }
+        h3 {
+            font-size: 1.3em;
+            margin-top: 20px;
+            margin-bottom: 10px;
+        }
+        section {
+            background-color: white;
+            padding: 20px;
+            margin-bottom: 20px;
+            border-radius: 5px;
+            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+        }
+        pre {
+            background-color: #f4f4f4;
+            padding: 15px;
+            border-radius: 5px;
+            overflow-x: auto;
+            font-size: 0.9em;
+        }
+        code {
+            font-family: 'Courier New', Courier, monospace;
+            background-color: #f4f4f4;
+            padding: 2px 5px;
+            border-radius: 3px;
+        }
+        table {
+            width: 100%;
+            border-collapse: collapse;
+            margin: 20px 0;
+        }
+        th, td {
+            padding: 12px;
+            text-align: left;
+            border-bottom: 1px solid #ddd;
+        }
+        th {
+            background-color: #f2f2f2;
+        }
+        tr:hover {
+            background-color: #f5f5f5;
+        }
+        .highlight {
+            background-color: #fffde7;
+            padding: 15px;
+            border-left: 4px solid #ffd600;
+            margin: 20px 0;
+        }
+        footer {
+            text-align: center;
+            margin-top: 40px;
+            padding: 20px;
+            border-top: 1px solid #ddd;
+            color: #7f8c8d;
+        }
+        @media print {
+            body {
+                background-color: white;
+                padding: 0;
+                max-width: 100%;
+            }
+            section {
+                box-shadow: none;
+                border: 1px solid #ddd;
+                page-break-inside: avoid;
+            }
+        }
+    </style>
+</head>
+<body>
+    <header>
+        <h1>Rapport: Application PHP avec YubiKey WebAuthn et PostgreSQL</h1>
+        <p>Guide complet pour le développement d'une application sécurisée</p>
+    </header>
+
+    <section>
+        <h2>Introduction</h2>
+        <p>
+            Ce rapport fournit un guide détaillé, étape par étape, pour développer une application web PHP sécurisée hébergée sur un serveur loué accessible via SSH (avec le shell zsh), avec un backend de base de données PostgreSQL. L'application permet l'inscription et la connexion des utilisateurs à l'aide de clés YubiKey via le protocole WebAuthn (FIDO2), la gestion des triplets (ID unique, libellé, mot-clé, action), et une interface mobile-friendly.
+        </p>
+        <p>
+            Le guide couvre la configuration de l'environnement, la conception de la base de données, l'intégration de WebAuthn, et la mise en œuvre de la logique de l'application, en garantissant la sécurité et l'utilisabilité.
+        </p>
+    </section>
+
+    <section>
+        <h2>Configuration du Serveur et de l'Environnement</h2>
+
+        <h3>Configuration Initiale du Serveur</h3>
+        <p>
+            Avec un accès SSH à un serveur loué utilisant le shell zsh, la première étape consiste à s'assurer que PHP et PostgreSQL sont installés et correctement configurés. Le serveur doit avoir PHP 8.x et PostgreSQL 13+ installés. Si ce n'est pas le cas, voici les commandes d'installation pour les systèmes basés sur Ubuntu :
+        </p>
+        <pre><code>sudo apt update
+sudo apt install php postgresql php-pgsql</code></pre>
+
+        <h3>Configuration de PostgreSQL</h3>
+        <p>
+            PostgreSQL doit être configuré pour accepter les connexions entrantes. Modifiez le fichier <code>pg_hba.conf</code> :
+        </p>
+        <pre><code>sudo nano /etc/postgresql/13/main/pg_hba.conf</code></pre>
+        <p>
+            Ajoutez la ligne suivante pour permettre les connexions depuis n'importe quelle IP (ajustez selon les exigences de sécurité) :
+        </p>
+        <pre><code>host all all 0.0.0.0/0 md5</code></pre>
+        <p>
+            Redémarrez PostgreSQL :
+        </p>
+        <pre><code>sudo service postgresql restart</code></pre>
+
+        <h3>Connexion PHP à PostgreSQL</h3>
+        <p>
+            PHP interagit avec PostgreSQL en utilisant la fonction <code>pg_connect</code> ou PDO (PHP Data Objects). Voici un exemple de code de connexion :
+        </p>
+        <pre><code>&lt;?php
+$host = "localhost";
+$dbname = "votre_base_de_données";
+$user = "postgres";
+$password = "votre_mot_de_passe";
+
+$dbconn = pg_connect("host=$host dbname=$dbname user=$user password=$password")
+    or die('Échec de la connexion : ' . pg_last_error());
+
+if (pg_connection_status($dbconn) === PGSQL_CONNECTION_OK) {
+    echo "Connexion OK";
+} else {
+    echo "Échec de la connexion";
+}
+?&gt;</code></pre>
+        <p>
+            Alternativement, en utilisant PDO :
+        </p>
+        <pre><code>&lt;?php
+$dsn = "pgsql:host=$host;dbname=$dbname";
+$pdo = new PDO($dsn, $user, $password);
+$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+?&gt;</code></pre>
+    </section>
+
+    <section>
+        <h2>Conception du Schéma de la Base de Données</h2>
+
+        <h3>Table des Utilisateurs</h3>
+        <p>
+            Stocke les informations d'identification des utilisateurs et les associations YubiKey :
+        </p>
+        <pre><code>CREATE TABLE users (
+    user_id SERIAL PRIMARY KEY,
+    username VARCHAR(255) UNIQUE NOT NULL,
+    password VARCHAR(255),
+    yubikey_id INT UNIQUE,
+    FOREIGN KEY (yubikey_id) REFERENCES yubikeys(yubikey_id)
+);</code></pre>
+
+        <h3>Table des YubiKeys</h3>
+        <p>
+            Stocke les informations d'identification YubiKey et leur association avec les utilisateurs :
+        </p>
+        <pre><code>CREATE TABLE yubikeys (
+    yubikey_id SERIAL PRIMARY KEY,
+    user_id INT,
+    key_data TEXT NOT NULL,
+    FOREIGN KEY (user_id) REFERENCES users(user_id)
+);</code></pre>
+
+        <h3>Table des Triplets</h3>
+        <p>
+            Stocke les triplets (ID unique, libellé, mot-clé, action) :
+        </p>
+        <pre><code>CREATE TABLE triplets (
+    triplet_id SERIAL PRIMARY KEY,
+    label VARCHAR(255) NOT NULL,
+    keyword VARCHAR(255) NOT NULL,
+    action VARCHAR(255) NOT NULL
+);</code></pre>
+
+        <h3>Initialisation au Premier Lancement</h3>
+        <p>
+            L'application doit vérifier l'existence de ces tables au premier lancement et les créer si elles sont manquantes. Cela peut être réalisé avec un script PHP qui exécute les commandes SQL ci-dessus.
+        </p>
+    </section>
+
+    <section>
+        <h2>Inscription et Authentification des Utilisateurs avec WebAuthn</h2>
+
+        <h3>Intégration de la Bibliothèque WebAuthn</h3>
+        <p>
+            Utilisez une bibliothèque PHP WebAuthn telle que <code>davidearl/webauthn</code> ou <code>lbuchs/WebAuthn</code>. Installez via Composer :
+        </p>
+        <pre><code>composer require davidearl/webauthn phpseclib/phpseclib</code></pre>
+
+        <h3>Processus d'Inscription</h3>
+        <p>
+            1. **Préparation à l'inscription** : Générez un défi et préparez la YubiKey pour l'inscription.
+        </p>
+        <pre><code>&lt;?php
+use Davidearl\WebAuthn\WebAuthn;
+
+$webauthn = new WebAuthn();
+$registrationData = $webauthn->prepareForRegistration($yubikeyData);
+?&gt;</code></pre>
+        <p>
+            2. **Stockage des données utilisateur et YubiKey** : Enregistrez les détails de l'utilisateur et les informations d'identification YubiKey dans la base de données.
+        </p>
+        <pre><code>&lt;?php
+function saveUser($username, $yubikeyData) {
+    $db = new Database();
+    $pdo = $db->connect();
+
+    $stmt = $pdo->prepare("INSERT INTO users (username, yubikey_id) VALUES (?, ?)");
+    $stmt->execute([$username, $yubikeyData]);
+}
+?&gt;</code></pre>
+
+        <h3>Processus de Connexion</h3>
+        <p>
+            1. **Authentification de l'utilisateur** : Vérifiez la réponse YubiKey lors de la connexion.
+        </p>
+        <pre><code>&lt;?php
+$webauthn = new WebAuthn();
+$authenticated = $webauthn->authenticate($loginData, $storedCredential);
+
+if ($authenticated) {
+    // Accorder l'accès
+} else {
+    // Refuser l'accès
+}
+?&gt;</code></pre>
+        <p>
+            2. **Vérification de l'utilisateur dans la base de données** : Vérifiez si l'utilisateur existe et si la YubiKey est correctement associée.
+        </p>
+        <pre><code>&lt;?php
+function verifyUser($username, $yubikeyData) {
+    $db = new Database();
+    $pdo = $db->connect();
+
+    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND yubikey_id = ?");
+    $stmt->execute([$username, $yubikeyData]);
+    return $stmt->fetch();
+}
+?&gt;</code></pre>
+    </section>
+
+    <section>
+        <h2>Implémentation de la Fonctionnalité des Triplets</h2>
+
+        <h3>Opérations de Base de Données pour les Triplets</h3>
+        <p>
+            Utilisez des requêtes préparées pour interagir de manière sécurisée avec la base de données :
+        </p>
+        <pre><code>&lt;?php
+class TripletManager {
+    private $pdo;
+
+    public function __construct(PDO $pdo) {
+        $this->pdo = $pdo;
+    }
+
+    public function createTriplet($label, $keyword, $action) {
+        $stmt = $this->pdo->prepare("INSERT INTO triplets (label, keyword, action) VALUES (?, ?, ?)");
+        $stmt->execute([$label, $keyword, $action]);
+    }
+
+    public function getTriplets() {
+        $stmt = $this->pdo->query("SELECT * FROM triplets");
+        return $stmt->fetchAll(PDO::FETCH_ASSOC);
+    }
+
+    public function getTripletById($id) {
+        $stmt = $this->pdo->prepare("SELECT * FROM triplets WHERE triplet_id = ?");
+        $stmt->execute([$id]);
+        return $stmt->fetch();
+    }
+
+    public function updateTriplet($id, $label, $keyword, $action) {
+        $stmt = $this->pdo->prepare("UPDATE triplets SET label = ?, keyword = ?, action = ? WHERE triplet_id = ?");
+        $stmt->execute([$label, $keyword, $action, $id]);
+    }
+
+    public function deleteTriplet($id) {
+        $stmt = $this->pdo->prepare("DELETE FROM triplets WHERE triplet_id = ?");
+        $stmt->execute([$id]);
+    }
+}
+?&gt;</code></pre>
+
+        <h3>Affichage des Triplets</h3>
+        <p>
+            Récupérez et affichez les triplets sous forme de boutons :
+        </p>
+        <pre><code>&lt;?php
+$triplets = $tripletManager->getTriplets();
+foreach ($triplets as $triplet) {
+    echo "&lt;button onclick='action(\\\"{$triplet['action']}\\\")'&gt;{$triplet['label']}&lt;/button&gt;";
+}
+?&gt;</code></pre>
+    </section>
+
+    <section>
+        <h2>Implémentation de la Fonction action(string)</h2>
+        <p>
+            La fonction <code>action(string)</code> gère diverses commandes déclenchées par les triplets :
+        </p>
+        <pre><code>&lt;?php
+function action($actionString) {
+    switch ($actionString) {
+        case "start":
+            // Exécuter la logique de démarrage
+            break;
+        case "set name":
+            // Inviter l'utilisateur à définir un nom
+            break;
+        case "box texte":
+            // Afficher une modale avec le texte
+            break;
+        case "new":
+            // Créer un nouveau triplet
+            break;
+        case "input text 'help string'":
+            // Afficher un champ de saisie avec un texte d'aide
+            break;
+        case "string":
+            // Filtrer les triplets par mot-clé
+            break;
+        case "choose keyw":
+            // Afficher les triplets contenant le mot-clé
+            break;
+        case "edit ID":
+            // Ouvrir l'éditeur pour le triplet avec l'ID
+            break;
+        case "configuration":
+            // Ouvrir la page de configuration
+            break;
+        default:
+            // Gérer l'action inconnue
+            break;
+    }
+}
+?&gt;</code></pre>
+    </section>
+
+    <section>
+        <h2>Barre d'État et Interface Mobile-Friendly</h2>
+
+        <h3>Mises à Jour de la Barre d'État</h3>
+        <p>
+            Mettez à jour la barre d'état à chaque appel d'action :
+        </p>
+        <pre><code>function action(actionString) {
+    document.getElementById('status-bar').innerText = actionString;
+    // Logique supplémentaire
+}</code></pre>
+
+        <h3>Design Mobile-Friendly</h3>
+        <p>
+            Utilisez du CSS responsive :
+        </p>
+        <pre><code>.login-form {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+.login-form input, .login-form button {
+    width: 100%;
+    padding: 10px;
+    margin-bottom: 10px;
+}</code></pre>
+    </section>
+
+    <section>
+        <h2>Sécurité et Gestion des Erreurs</h2>
+
+        <h3>Prévention des Injections SQL</h3>
+        <p>
+            Utilisez toujours des requêtes préparées :
+        </p>
+        <pre><code>&lt;?php
+$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
+$stmt->execute([$username]);
+?&gt;</code></pre>
+
+        <h3>Bonnes Pratiques WebAuthn</h3>
+        <ul>
+            <li>Vérifiez l'origine des requêtes WebAuthn.</li>
+            <li>Utilisez une génération et une vérification sécurisées des défis.</li>
+            <li>Suivez la spécification WebAuthn pour des flux d'authentification sécurisés.</li>
+        </ul>
+
+        <h3>Gestion des Erreurs</h3>
+        <p>
+            Gérez les erreurs de manière élégante :
+        </p>
+        <pre><code>&lt;?php
+try {
+    // Opération de base de données
+} catch (PDOException $e) {
+    echo "Erreur : " . $e->getMessage();
+}
+?&gt;</code></pre>
+    </section>
+
+    <section>
+        <h2>Tableau Récapitulatif : Schéma de la Base de Données</h2>
+        <table>
+            <thead>
+                <tr>
+                    <th>Table</th>
+                    <th>Colonnes (Type)</th>
+                    <th>Description</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr>
+                    <td>users</td>
+                    <td>user_id (SERIAL PK), username (VARCHAR), password (VARCHAR), yubikey_id (INT FK)</td>
+                    <td>Stocke les informations d'identification des utilisateurs et l'association YubiKey</td>
+                </tr>
+                <tr>
+                    <td>yubikeys</td>
+                    <td>yubikey_id (SERIAL PK), user_id (INT FK), key_data (TEXT)</td>
+                    <td>Stocke les informations d'identification YubiKey et l'association utilisateur</td>
+                </tr>
+                <tr>
+                    <td>triplets</td>
+                    <td>triplet_id (SERIAL PK), label (VARCHAR), keyword (VARCHAR), action (VARCHAR)</td>
+                    <td>Stocke les triplets pour les actions utilisateur</td>
+                </tr>
+            </tbody>
+        </table>
+    </section>
+
+    <section>
+        <h2>Conclusion</h2>
+        <p>
+            Ce rapport fournit un guide détaillé, étape par étape, pour développer une application web PHP sécurisée hébergée sur un serveur loué avec accès SSH, utilisant PostgreSQL et l'authentification YubiKey WebAuthn. Le guide couvre la configuration de l'environnement, la conception de la base de données, l'inscription et la connexion des utilisateurs avec WebAuthn, la gestion des triplets, et la mise en œuvre d'une interface mobile-friendly. En suivant ces étapes, vous pouvez garantir une application sécurisée, fonctionnelle et conviviale qui répond à toutes les exigences spécifiées.
+        </p>
+    </section>
+
+    <footer>
+        <p>© 2026 - Rapport Technique - Application PHP avec YubiKey WebAuthn et PostgreSQL</p>
+    </footer>
+</body>
+</html>
diff --git a/sql/init_db.sql b/sql/init_db.sql
new file mode 100644 (file)
index 0000000..d4d963c
--- /dev/null
@@ -0,0 +1,38 @@
+-- sql/init_db.sql
+-- Script d'initialisation de la base de données pour l'application PHP avec YubiKey WebAuthn
+
+-- Table des utilisateurs
+CREATE TABLE IF NOT EXISTS users (
+    user_id SERIAL PRIMARY KEY,
+    username VARCHAR(255) NOT NULL UNIQUE,
+    yubikey_id INT UNIQUE,
+    FOREIGN KEY (yubikey_id) REFERENCES yubikeys(yubikey_id) ON DELETE SET NULL
+);
+
+-- Table des YubiKeys
+CREATE TABLE IF NOT EXISTS yubikeys (
+    yubikey_id SERIAL PRIMARY KEY,
+    user_id INT UNIQUE,
+    key_data TEXT NOT NULL,
+    public_key TEXT NOT NULL,
+    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
+);
+
+-- Table des triplets
+CREATE TABLE IF NOT EXISTS triplets (
+    triplet_id SERIAL PRIMARY KEY,
+    user_id INT NOT NULL,
+    label VARCHAR(255) NOT NULL,
+    keyword VARCHAR(255) NOT NULL,
+    action VARCHAR(255) NOT NULL,
+    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
+);
+
+-- Index pour optimiser les requêtes
+CREATE INDEX IF NOT EXISTS idx_triplets_user_id ON triplets(user_id);
+CREATE INDEX IF NOT EXISTS idx_yubikeys_user_id ON yubikeys(user_id);
+
+-- Commentaire pour documenter la base de données
+COMMENT ON TABLE users IS 'Stocke les informations d\'identification des utilisateurs et l\'association YubiKey';
+COMMENT ON TABLE yubikeys IS 'Stocke les informations d\'identification YubiKey et l\'association utilisateur';
+COMMENT ON TABLE triplets IS 'Stocke les triplets (ID unique, libellé, mot-clé, action) pour chaque utilisateur';