<?php
-// includes/Database.php
+// include/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';
+ $this->conn = null;
}
public function connect() {
- $this->conn = null;
-
+ // Include configuration
+ require_once __DIR__ . '/../config/config.php';
+
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) {
+ // Use the global $db connection from config.php
+ global $db;
+ return $db;
+ } catch(Exception $exception) {
echo "Erreur de connexion à la base de données : " . $exception->getMessage();
exit;
}
-
- return $this->conn;
}
}
?>
<?php
-// includes/WebAuthnManager.php
+// include/WebAuthnManager.php
require_once __DIR__ . '/../vendor/autoload.php';
PublicKeyCredentialUserEntity,
PublicKeyCredentialSource,
AuthenticatorAttestationResponse,
- AuthenticatorAssertionResponse
+ AuthenticatorAssertionResponse,
+ PublicKeyCredentialRpEntity
};
class WebAuthnManager {
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'];
+ $this->rpName = WEBAUTHN_RP_NAME;
+ $this->rpId = WEBAUTHN_RP_ID;
+ $this->origin = WEBAUTHN_ORIGIN;
+ }
+
+ public function getRpName() {
+ return $this->rpName;
+ }
+
+ public function getRpId() {
+ return $this->rpId;
+ }
+
+ public function getOrigin() {
+ return $this->origin;
}
public function generateRegistrationOptions($username) {
+ $rpEntity = new PublicKeyCredentialRpEntity($this->rpName, $this->rpId);
$userEntity = new PublicKeyCredentialUserEntity(
$username,
- random_bytes(16),
+ $username,
$username
);
$challenge = random_bytes(32);
$_SESSION['challenge'] = base64_encode($challenge);
+ $_SESSION['username'] = $username;
- return PublicKeyCredentialCreationOptions::create(
- $this->rpName,
+ $creationOptions = PublicKeyCredentialCreationOptions::create(
+ $rpEntity,
$userEntity,
$challenge,
- [new PublicKeyCredentialDescriptor('public-key', AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE)]
+ []
+ );
+
+ $creationOptions->authenticatorSelection = new AuthenticatorSelectionCriteria(
+ AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE,
+ false,
+ AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED
);
+
+ return $creationOptions;
}
- public function register($attestationResponse, $username) {
- if (!isset($_SESSION['challenge'])) {
+ public function register($attestationResponse) {
+ if (!isset($_SESSION['challenge']) || !isset($_SESSION['username'])) {
return false;
}
$challenge = base64_decode($_SESSION['challenge']);
+ $username = $_SESSION['username'];
unset($_SESSION['challenge']);
-
- $publicKeyCredentialSource = PublicKeyCredentialSource::createFromString($attestationResponse);
- $publicKeyCredential = $publicKeyCredentialSource->getPublicKeyCredential();
-
- if (!$publicKeyCredential->verify($challenge, $this->origin)) {
+ unset($_SESSION['username']);
+
+ try {
+ $publicKeyCredentialSource = PublicKeyCredentialSource::createFromString($attestationResponse);
+ $publicKeyCredential = $publicKeyCredentialSource->getPublicKeyCredential();
+
+ if (!$publicKeyCredential->verify($challenge, $this->origin)) {
+ return false;
+ }
+
+ return [
+ 'credentialId' => base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId()),
+ 'publicKey' => base64_encode($publicKeyCredentialSource->getPublicKey()),
+ 'counter' => $publicKeyCredentialSource->getCounter()
+ ];
+ } catch (Exception $e) {
+ error_log("WebAuthn registration error: " . $e->getMessage());
return false;
}
-
- return [
- 'credentialId' => base64_encode($publicKeyCredential->getId()),
- 'publicKey' => base64_encode($publicKeyCredential->getPublicKey())
- ];
}
public function generateAuthenticationOptions() {
$_SESSION['challenge'] = base64_encode($challenge);
- return PublicKeyCredentialRequestOptions::create($challenge);
+ $rpEntity = new PublicKeyCredentialRpEntity($this->rpName, $this->rpId);
+
+ return PublicKeyCredentialRequestOptions::create($rpEntity, $challenge);
}
public function authenticate($assertionResponse, $storedPublicKey) {
$challenge = base64_decode($_SESSION['challenge']);
unset($_SESSION['challenge']);
- $publicKeyCredentialSource = PublicKeyCredentialSource::createFromString($assertionResponse);
- $publicKeyCredential = $publicKeyCredentialSource->getPublicKeyCredential();
+ try {
+ $publicKeyCredentialSource = PublicKeyCredentialSource::createFromString($assertionResponse);
+ $publicKeyCredential = $publicKeyCredentialSource->getPublicKeyCredential();
+
+ $storedPublicKeyDecoded = base64_decode($storedPublicKey);
- if (!$publicKeyCredential->verify($challenge, $this->origin, base64_decode($storedPublicKey))) {
+ if (!$publicKeyCredential->verify($challenge, $this->origin, $storedPublicKeyDecoded)) {
+ return false;
+ }
+
+ return true;
+ } catch (Exception $e) {
+ error_log("WebAuthn authentication error: " . $e->getMessage());
return false;
}
-
- return true;
}
-}
-?>
+}
\ No newline at end of file
<?php
// index.php
session_start();
-require_once __DIR__ . '/includes/Database.php';
-require_once __DIR__ . '/includes/TripletManager.php';
+require_once __DIR__ . '/../include/Database.php';
+require_once __DIR__ . '/../include/TripletManager.php';
// Initialisation de la base de données
$db = new Database();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
$_SESSION['status'] = $action;
+
+ // Parse action with possible parameters
+ $actionParts = explode(' ', $action, 2);
+ $baseAction = $actionParts[0];
+ $actionParam = $actionParts[1] ?? '';
+
// Logique pour chaque type d'action
- switch ($action) {
+ switch ($baseAction) {
case "new":
// Afficher le formulaire pour créer un nouveau triplet
+ $_SESSION['action_mode'] = 'new';
break;
- case "input text":
+ case "input":
// Afficher une boîte de dialogue pour entrer du texte
+ $_SESSION['action_mode'] = 'input';
+ $_SESSION['input_help'] = str_replace('"', '', $actionParam); // Remove quotes
break;
- case "box texte":
+ case "box":
// Afficher une boîte de dialogue avec du texte
+ $_SESSION['action_mode'] = 'box';
+ $_SESSION['box_text'] = $actionParam;
break;
- case "set name":
+ case "set":
// Mettre à jour le nom du triplet
+ $_SESSION['action_mode'] = 'set';
+ $_SESSION['set_name'] = $actionParam;
break;
- case "choose keyw":
+ case "choose":
// Choisir un mot-clé pour le triplet
+ $_SESSION['action_mode'] = 'choose';
+ $_SESSION['choose_keyword'] = $actionParam;
break;
- case "edit ID":
+ case "edit":
// Ouvrir l'éditeur pour le triplet avec l'ID
+ $_SESSION['action_mode'] = 'edit';
+ $_SESSION['edit_id'] = $actionParam;
break;
case "configuration":
// Ouvrir la page de configuration
+ $_SESSION['action_mode'] = 'configuration';
break;
default:
- // Gérer l'action inconnue
+ // Rechercher les triplets contenant le mot-clé
+ if (!empty($action)) {
+ $_SESSION['search_keyword'] = $action;
+ // Filtrer les triplets
+ $filteredTriplets = array_filter($triplets, function($triplet) use ($action) {
+ return strpos($triplet['keyword'], $action) !== false;
+ });
+
+ if (!empty($filteredTriplets)) {
+ $triplets = $filteredTriplets;
+ }
+ }
break;
}
+
+ // Redirection pour éviter le re-post
+ header("Location: index.php");
+ exit();
+}
+
+// Gestion de la création de triplet
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_triplet'])) {
+ $label = trim($_POST['label']);
+ $keyword = trim($_POST['keyword']);
+ $action = trim($_POST['action']);
+
+ if (!empty($label) && !empty($keyword) && !empty($action)) {
+ $tripletManager->createTriplet($_SESSION['user_id'], $label, $keyword, $action);
+ $_SESSION['status'] = "Triplet créé avec succès!";
+ } else {
+ $_SESSION['status'] = "Tous les champs sont requis!";
+ }
+
+ header("Location: index.php");
+ exit();
+}
+
+// Gestion de la mise à jour de triplet
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_triplet'])) {
+ $tripletId = intval($_POST['triplet_id']);
+ $label = trim($_POST['label']);
+ $keyword = trim($_POST['keyword']);
+ $action = trim($_POST['action']);
+
+ if (!empty($label) && !empty($keyword) && !empty($action)) {
+ $tripletManager->updateTriplet($tripletId, $label, $keyword, $action);
+ $_SESSION['status'] = "Triplet mis à jour avec succès!";
+ } else {
+ $_SESSION['status'] = "Tous les champs sont requis!";
+ }
+
+ header("Location: index.php");
+ exit();
+}
+
+// Gestion de la suppression de triplet
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_triplet'])) {
+ $tripletId = intval($_POST['triplet_id']);
+ $tripletManager->deleteTriplet($tripletId);
+ $_SESSION['status'] = "Triplet supprimé avec succès!";
+
+ header("Location: index.php");
+ exit();
}
// Récupération des triplets de l'utilisateur
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
+ .triplet-actions {
+ margin-top: 10px;
+ padding-top: 10px;
+ border-top: 1px solid #eee;
+ }
+ .triplet-actions button {
+ padding: 5px 10px;
+ margin-left: 5px;
+ }
+ form div {
+ margin-bottom: 10px;
+ }
+ form label {
+ display: inline-block;
+ width: 80px;
+ }
+ form input[type="text"] {
+ padding: 5px;
+ width: 200px;
+ }
.triplet h3 {
margin-top: 0;
}
</header>
<div id="status-bar">
- <?php echo isset($_SESSION['status']) ? htmlspecialchars($_SESSION['status']) : 'Prêt'; ?>
+ <?php
+ $status = isset($_SESSION['status']) ? $_SESSION['status'] : 'Prêt';
+ unset($_SESSION['status']); // Clear status after showing
+ echo htmlspecialchars($status);
+ ?>
</div>
+ <!-- Affichage des modes d'action -->
+ <?php if (isset($_SESSION['action_mode'])): ?>
+ <?php
+ $actionMode = $_SESSION['action_mode'];
+ unset($_SESSION['action_mode']); // Clear after use
+
+ switch ($actionMode) {
+ case 'new':
+ echo '<section id="newTripletSection">';
+ echo '<h2>Créer un nouveau triplet</h2>';
+ echo '<form method="post">';
+ echo '<div><label>Label:</label><input type="text" name="label" required></div>';
+ echo '<div><label>Mot-clé:</label><input type="text" name="keyword" required></div>';
+ echo '<div><label>Action:</label><input type="text" name="action" required></div>';
+ echo '<button type="submit" name="create_triplet">Créer</button>';
+ echo '<button type="button" onclick="window.location.href=\"index.php\"">Annuler</button>';
+ echo '</form>';
+ echo '</section>';
+ break;
+
+ case 'input':
+ $helpText = $_SESSION['input_help'] ?? '';
+ unset($_SESSION['input_help']);
+ echo '<section id="inputSection">';
+ echo '<h2>Entrée de texte</h2>';
+ if ($helpText) {
+ echo '<p>' . htmlspecialchars($helpText) . '</p>';
+ }
+ echo '<form method="post">';
+ echo '<div><input type="text" name="action" required></div>';
+ echo '<button type="submit">Envoyer</button>';
+ echo '<button type="button" onclick="window.location.href=\"index.php\"">Annuler</button>';
+ echo '</form>';
+ echo '</section>';
+ break;
+
+ case 'box':
+ $boxText = $_SESSION['box_text'] ?? '';
+ unset($_SESSION['box_text']);
+ echo '<section id="boxSection">';
+ echo '<h2>Boîte de dialogue</h2>';
+ echo '<p>' . htmlspecialchars($boxText) . '</p>';
+ echo '<button onclick="window.location.href=\"index.php\"">OK</button>';
+ echo '</section>';
+ break;
+
+ case 'edit':
+ $tripletId = $_SESSION['edit_id'] ?? 0;
+ unset($_SESSION['edit_id']);
+ $tripletToEdit = null;
+ foreach ($triplets as $triplet) {
+ if ($triplet['triplet_id'] == $tripletId) {
+ $tripletToEdit = $triplet;
+ break;
+ }
+ }
+
+ if ($tripletToEdit):
+ echo '<section id="editTripletSection">';
+ echo '<h2>Modifier le triplet</h2>';
+ echo '<form method="post">';
+ echo '<input type="hidden" name="triplet_id" value="' . $tripletId . '">';
+ echo '<div><label>Label:</label><input type="text" name="label" value="' . htmlspecialchars($tripletToEdit['label']) . '" required></div>';
+ echo '<div><label>Mot-clé:</label><input type="text" name="keyword" value="' . htmlspecialchars($tripletToEdit['keyword']) . '" required></div>';
+ echo '<div><label>Action:</label><input type="text" name="action" value="' . htmlspecialchars($tripletToEdit['action']) . '" required></div>';
+ echo '<button type="submit" name="update_triplet">Mettre à jour</button>';
+ echo '<button type="button" onclick="window.location.href=\"index.php\"">Annuler</button>';
+ echo '</form>';
+ echo '</section>';
+ else:
+ echo '<section><p>Triplet non trouvé.</p></section>';
+ endif;
+ break;
+
+ case 'configuration':
+ echo '<section id="configurationSection">';
+ echo '<h2>Configuration</h2>';
+ echo '<p>Page de configuration (à implémenter)</p>';
+ echo '<button onclick="window.location.href=\"index.php\"">Retour</button>';
+ echo '</section>';
+ break;
+ }
+ ?>
+ <?php else: ?>
+
<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; ?>
+ <?php
+ // Afficher le message de recherche si applicable
+ if (isset($_SESSION['search_keyword'])):
+ echo '<p>Résultats de recherche pour: <strong>' . htmlspecialchars($_SESSION['search_keyword']) . '</strong></p>';
+ unset($_SESSION['search_keyword']);
+ endif;
+ ?>
+
+ <?php if (empty($triplets)): ?>
+ <p>Aucun triplet trouvé. Créez un nouveau triplet avec l'action "new".</p>
+ <?php else: ?>
+ <?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>
+ <div class="triplet-actions">
+ <form method="post" style="display: inline;">
+ <input type="hidden" name="action" value="edit <?php echo $triplet['triplet_id']; ?>">
+ <button type="submit">Modifier</button>
+ </form>
+ <form method="post" style="display: inline; margin-left: 10px;">
+ <input type="hidden" name="triplet_id" value="<?php echo $triplet['triplet_id']; ?>">
+ <button type="submit" name="delete_triplet" onclick="return confirm('Êtes-vous sûr de vouloir supprimer ce triplet ?')">Supprimer</button>
+ </form>
+ </div>
+ </div>
+ <?php endforeach; ?>
+ <?php endif; ?>
</section>
<section>
<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>
+ <p>Exemples d'actions : "new", "input texte", "box texte", "set name", "choose motcle", "edit ID", "configuration", ou un mot-clé pour rechercher</p>
</section>
+ <?php endif; // End of action mode check ?>
<footer>
<p>© 2026 - Application PHP avec YubiKey WebAuthn et PostgreSQL</p>
<?php
// login.php (version modernisée)
session_start();
-require_once __DIR__ . '/../includes/Database.php';
-require_once __DIR__ . '/../includes/WebAuthnManager.php';
+require_once __DIR__ . '/../include/Database.php';
+require_once __DIR__ . '/../include/WebAuthnManager.php';
$db = new Database();
$pdo = $db->connect();
// 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");
+ if (isset($_POST['username'])) {
+ // Step 1: Generate authentication options
+ $username = trim($_POST['username']);
+
+ if (!empty($username)) {
+ $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) {
+ // Generate authentication options
+ $authenticationOptions = $webAuthnManager->generateAuthenticationOptions();
+ $_SESSION['authentication_username'] = $username;
+ $_SESSION['authentication_user_id'] = $user['user_id'];
+ $_SESSION['authentication_public_key'] = $yubikey['public_key'];
+
+ header('Content-Type: application/json');
+ echo json_encode([
+ 'success' => true,
+ 'options' => $authenticationOptions->jsonSerialize()
+ ]);
exit();
}
}
+
+ header('Content-Type: application/json');
+ echo json_encode(['success' => false, 'error' => 'Nom d\'utilisateur ou YubiKey invalide.']);
+ exit();
}
- $error = "Nom d'utilisateur ou réponse YubiKey invalide.";
+ } elseif (isset($_POST['assertionResponse'])) {
+ // Step 2: Process the assertion response
+ $assertionResponse = trim($_POST['assertionResponse']);
+
+ if (!empty($assertionResponse) && isset($_SESSION['authentication_public_key'])) {
+ $publicKey = $_SESSION['authentication_public_key'];
+ $username = $_SESSION['authentication_username'];
+ $userId = $_SESSION['authentication_user_id'];
+
+ unset($_SESSION['authentication_public_key']);
+ unset($_SESSION['authentication_username']);
+ unset($_SESSION['authentication_user_id']);
+
+ // Authenticate the WebAuthn credential
+ $authenticated = $webAuthnManager->authenticate($assertionResponse, $publicKey);
+
+ if ($authenticated) {
+ $_SESSION['user_id'] = $userId;
+ $_SESSION['username'] = $username;
+ $_SESSION['status'] = "Connexion réussie !";
+
+ header('Content-Type: application/json');
+ echo json_encode(['success' => true, 'redirect' => 'index.php']);
+ exit();
+ }
+ }
+
+ header('Content-Type: application/json');
+ echo json_encode(['success' => false, 'error' => 'Échec de l\'authentification YubiKey.']);
+ exit();
}
}
?>
<div class="error-message"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
- <form method="post">
+ <form method="post" id="loginForm">
<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 class="form-group" id="yubikeySection" style="display: none;">
+ <label>Authentification YubiKey</label>
+ <p>Veuillez toucher votre YubiKey pour vous connecter.</p>
+ <div id="webauthnMessage"></div>
</div>
- <button type="submit" class="login-button">Se connecter</button>
+ <button type="submit" class="login-button" id="loginButton">Se connecter</button>
</form>
+
+ <script>
+ document.addEventListener('DOMContentLoaded', function() {
+ const form = document.getElementById('loginForm');
+ const usernameInput = document.getElementById('username');
+ const yubikeySection = document.getElementById('yubikeySection');
+ const loginButton = document.getElementById('loginButton');
+ const webauthnMessage = document.getElementById('webauthnMessage');
+
+ let authenticationOptions = null;
+
+ form.addEventListener('submit', async function(e) {
+ e.preventDefault();
+
+ if (!usernameInput.value.trim()) {
+ alert('Veuillez entrer un nom d\'utilisateur');
+ return;
+ }
+
+ if (authenticationOptions === null) {
+ // Step 1: Get authentication options
+ try {
+ const response = await fetch('login.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `username=${encodeURIComponent(usernameInput.value)}`
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ authenticationOptions = data.options;
+ usernameInput.disabled = true;
+ loginButton.disabled = true;
+ yubikeySection.style.display = 'block';
+ webauthnMessage.textContent = 'Prêt pour l\'authentification YubiKey...';
+
+ // Step 2: Call WebAuthn API
+ await startWebAuthnAuthentication();
+ } else {
+ alert(data.error || 'Erreur lors de la préparation de la connexion');
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ alert('Erreur lors de la communication avec le serveur');
+ }
+ }
+ });
+
+ async function startWebAuthnAuthentication() {
+ try {
+ webauthnMessage.textContent = 'Veuillez toucher votre YubiKey...';
+
+ // Convert the authentication options to the format expected by the browser
+ const publicKey = {
+ challenge: Uint8Array.from(atob(authenticationOptions.challenge), c => c.charCodeAt(0)),
+ rpId: authenticationOptions.rpId,
+ timeout: authenticationOptions.timeout,
+ userVerification: authenticationOptions.userVerification
+ };
+
+ // Call the WebAuthn API
+ const credential = await navigator.credentials.get({ publicKey });
+
+ if (credential) {
+ // Send the assertion response to the server
+ const response = await fetch('login.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `assertionResponse=${encodeURIComponent(JSON.stringify(credential))}`
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ webauthnMessage.textContent = 'Connexion réussie ! Redirection...';
+ window.location.href = data.redirect;
+ } else {
+ webauthnMessage.textContent = data.error || 'Erreur lors de la connexion';
+ loginButton.disabled = false;
+ }
+ } else {
+ webauthnMessage.textContent = 'Aucune credential reçue';
+ loginButton.disabled = false;
+ }
+ } catch (error) {
+ console.error('WebAuthn error:', error);
+ webauthnMessage.textContent = 'Erreur YubiKey: ' + error.message;
+ loginButton.disabled = false;
+ }
+ }
+ });
+ </script>
<div class="link-container">
<p>Pas encore de compte ? <a href="register.php">S'inscrire</a></p>
<?php
// register.php
session_start();
-require_once __DIR__ . '/../includes/Database.php';
-require_once __DIR__ . '/../includes/WebAuthnManager.php';
+require_once __DIR__ . '/../include/Database.php';
+require_once __DIR__ . '/../include/WebAuthnManager.php';
$db = new Database();
$pdo = $db->connect();
// 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 (isset($_POST['username'])) {
+ // Step 1: Generate registration options
+ $username = trim($_POST['username']);
+
+ if (!empty($username)) {
+ // 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 {
+ // Generate WebAuthn registration options
+ $registrationOptions = $webAuthnManager->generateRegistrationOptions($username);
+ $_SESSION['registration_options'] = $registrationOptions;
+ $_SESSION['registration_username'] = $username;
+
+ // Return JSON for JavaScript WebAuthn API
+ header('Content-Type: application/json');
+ echo json_encode([
+ 'success' => true,
+ 'options' => $registrationOptions->jsonSerialize()
+ ]);
+ exit();
+ }
+ }
+ } elseif (isset($_POST['attestationResponse'])) {
+ // Step 2: Process the attestation response
+ $attestationResponse = trim($_POST['attestationResponse']);
+
+ if (!empty($attestationResponse) && isset($_SESSION['registration_username'])) {
+ $username = $_SESSION['registration_username'];
+ unset($_SESSION['registration_username']);
+
+ // Register the WebAuthn credential
+ $registrationData = $webAuthnManager->register($attestationResponse);
if ($registrationData) {
// Enregistrer l'utilisateur et la YubiKey dans la base de données
$userId = $pdo->lastInsertId();
// Insérer la YubiKey
- $stmt = $pdo->prepare("INSERT INTO yubikeys (user_id, key_data) VALUES (?, ?)");
- $stmt->execute([$userId, $registrationData['credentialId']]);
+ $stmt = $pdo->prepare("INSERT INTO yubikeys (user_id, key_data, public_key) VALUES (?, ?, ?)");
+ $stmt->execute([$userId, $registrationData['credentialId'], $registrationData['publicKey']]);
// Mettre à jour l'utilisateur avec l'ID de la YubiKey
$stmt = $pdo->prepare("UPDATE users SET yubikey_id = ? WHERE user_id = ?");
$pdo->commit();
$_SESSION['status'] = "Inscription réussie ! Vous pouvez maintenant vous connecter.";
- header("Location: login.php");
+ header('Content-Type: application/json');
+ echo json_encode(['success' => true, 'redirect' => 'login.php']);
exit();
} catch (Exception $e) {
$pdo->rollBack();
- $error = "Une erreur est survenue lors de l'inscription : " . $e->getMessage();
+ header('Content-Type: application/json');
+ echo json_encode(['success' => false, 'error' => "Une erreur est survenue lors de l'inscription : " . $e->getMessage()]);
+ exit();
}
} else {
- $error = "La réponse YubiKey est invalide.";
+ header('Content-Type: application/json');
+ echo json_encode(['success' => false, 'error' => "La réponse YubiKey est invalide."]);
+ exit();
}
}
}
<?php endif; ?>
<div class="register-form">
- <form method="post">
+ <form method="post" id="registrationForm">
<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 id="yubikeySection" style="display: none;">
+ <label>Enregistrement YubiKey :</label>
+ <p>Veuillez toucher votre YubiKey pour compléter l'inscription.</p>
+ <div id="webauthnMessage"></div>
</div>
- <button type="submit">S'inscrire</button>
+ <button type="submit" id="registerButton">S'inscrire</button>
</form>
+
+ <script>
+ document.addEventListener('DOMContentLoaded', function() {
+ const form = document.getElementById('registrationForm');
+ const usernameInput = document.getElementById('username');
+ const yubikeySection = document.getElementById('yubikeySection');
+ const registerButton = document.getElementById('registerButton');
+ const webauthnMessage = document.getElementById('webauthnMessage');
+
+ let registrationOptions = null;
+
+ form.addEventListener('submit', async function(e) {
+ e.preventDefault();
+
+ if (!usernameInput.value.trim()) {
+ alert('Veuillez entrer un nom d\'utilisateur');
+ return;
+ }
+
+ if (registrationOptions === null) {
+ // Step 1: Get registration options
+ try {
+ const response = await fetch('register.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `username=${encodeURIComponent(usernameInput.value)}`
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ registrationOptions = data.options;
+ usernameInput.disabled = true;
+ registerButton.disabled = true;
+ yubikeySection.style.display = 'block';
+ webauthnMessage.textContent = 'Prêt pour l\'authentification YubiKey...';
+
+ // Step 2: Call WebAuthn API
+ await startWebAuthnRegistration();
+ } else {
+ alert(data.error || 'Erreur lors de la préparation de l\'inscription');
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ alert('Erreur lors de la communication avec le serveur');
+ }
+ }
+ });
+
+ async function startWebAuthnRegistration() {
+ try {
+ webauthnMessage.textContent = 'Veuillez toucher votre YubiKey...';
+
+ // Convert the registration options to the format expected by the browser
+ const publicKey = {
+ challenge: Uint8Array.from(atob(registrationOptions.challenge), c => c.charCodeAt(0)),
+ rp: registrationOptions.rp,
+ user: registrationOptions.user,
+ pubKeyCredParams: registrationOptions.pubKeyCredParams,
+ authenticatorSelection: registrationOptions.authenticatorSelection,
+ timeout: registrationOptions.timeout,
+ attestation: registrationOptions.attestation
+ };
+
+ // Call the WebAuthn API
+ const credential = await navigator.credentials.create({ publicKey });
+
+ if (credential) {
+ // Send the attestation response to the server
+ const response = await fetch('register.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `attestationResponse=${encodeURIComponent(JSON.stringify(credential))}`
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ webauthnMessage.textContent = 'Inscription réussie ! Redirection...';
+ window.location.href = data.redirect;
+ } else {
+ webauthnMessage.textContent = data.error || 'Erreur lors de l\'inscription';
+ registerButton.disabled = false;
+ }
+ } else {
+ webauthnMessage.textContent = 'Aucune credential reçue';
+ registerButton.disabled = false;
+ }
+ } catch (error) {
+ console.error('WebAuthn error:', error);
+ webauthnMessage.textContent = 'Erreur YubiKey: ' + error.message;
+ registerButton.disabled = false;
+ }
+ }
+ });
+ </script>
<div class="link">
<p>Déjà un compte ? <a href="login.php">Se connecter</a></p>
</div>