]> git.nothing2do.fr Git - diary-web.git/commitdiff
test of htaccess CORS master
authorgaby <gaby@nothing2do.fr>
Sat, 25 Apr 2026 06:37:16 +0000 (08:37 +0200)
committergaby <gaby@nothing2do.fr>
Sat, 25 Apr 2026 06:37:16 +0000 (08:37 +0200)
config/config.php
include/WebAuthnManager.php
public/index.php
public/login.php
public/register.php

index e6e7be72cedcbd64167f6362b3b0fbcfe9109e9b..e1c61d6bcce51f702edd2cff0a63aaf9c0be9b3a 100644 (file)
@@ -2,15 +2,15 @@
 // config/config.php
 
 // Configuration de la base de données PostgreSQL
-define('DB_HOST', 'localhost');
-define('DB_NAME', 'diary');
-define('DB_USER', 'user');
-define('DB_PASS', 'password');
+define('DB_HOST', 'db_host');
+define('DB_NAME', 'db_name');
+define('DB_USER', 'db_user');
+define('DB_PASS', 'db_pass');
 
 // Configuration WebAuthn
 define('WEBAUTHN_RP_NAME', 'Diary-web');
-$rpId = isset($_SERVER['HTTP_HOST']) ? parse_url((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST) : 'nothing2do.fr';
-$origin = isset($_SERVER['HTTP_HOST']) ? (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] : 'https://www.nothing2do.fr';
+$rpId = 'dw.nothing2do.fr';
+$origin = 'https://dw.nothing2do.fr';
 define('WEBAUTHN_RP_ID', $rpId);
 define('WEBAUTHN_ORIGIN', $origin);
 
@@ -60,4 +60,4 @@ try {
 } catch (PDOException $e) {
     die("Erreur de connexion ou d'initialisation de la base de données : " . $e->getMessage());
 }
-?>
\ No newline at end of file
+?>
index 128c1d15faafee999cce19b6d0d49e6b530115f8..fe447f557553153482fde13854d512853491488d 100644 (file)
@@ -41,16 +41,22 @@ class WebAuthnManager {
 
     public function generateRegistrationOptions($username) {
         $rpEntity = new PublicKeyCredentialRpEntity($this->rpName, $this->rpId);
+        
+        // Convert username to a valid user ID (must be ArrayBuffer or ArrayBufferView)
+        $userId = hash('sha256', $username, true); // Binary SHA-256 hash
+        
         $userEntity = new PublicKeyCredentialUserEntity(
-            $username,
-            $username,
-            $username
+            $username,  // name
+            $userId,    // id (must be binary)
+            $username   // displayName
         );
 
         $challenge = random_bytes(32);
 
         $_SESSION['challenge'] = base64_encode($challenge);
         $_SESSION['username'] = $username;
+        $_SESSION['user_id_hash'] = bin2hex($userId); // Store for later verification
+        // Note: We use standard base64 for JSON, not base64url
 
         $creationOptions = PublicKeyCredentialCreationOptions::create(
             $rpEntity,
@@ -78,8 +84,10 @@ class WebAuthnManager {
 
         $challenge = base64_decode($_SESSION['challenge']);
         $username = $_SESSION['username'];
+        $userIdHash = isset($_SESSION['user_id_hash']) ? hex2bin($_SESSION['user_id_hash']) : hash('sha256', $username, true);
         unset($_SESSION['challenge']);
         unset($_SESSION['username']);
+        unset($_SESSION['user_id_hash']);
 
         try {
             error_log("WebAuthn register: decoding JSON response");
@@ -88,9 +96,28 @@ class WebAuthnManager {
             
             if ($attestationData === null) {
                 error_log("WebAuthn register: JSON decode failed. Error: " . json_last_error_msg());
+                error_log("WebAuthn register: Raw response: " . substr($attestationResponse, 0, 500));
                 return false;
             }
             
+            // Validate required fields
+            $requiredFields = ['id', 'rawId', 'response', 'type'];
+            foreach ($requiredFields as $field) {
+                if (!isset($attestationData[$field])) {
+                    error_log("WebAuthn register: Missing required field: $field");
+                    return false;
+                }
+            }
+            
+            // Validate response fields
+            $responseFields = ['attestationObject', 'clientDataJSON'];
+            foreach ($responseFields as $field) {
+                if (!isset($attestationData['response'][$field])) {
+                    error_log("WebAuthn register: Missing required response field: $field");
+                    return false;
+                }
+            }
+            
             error_log("WebAuthn register: JSON decoded successfully");
             
             // Convert base64 back to binary
@@ -126,14 +153,21 @@ class WebAuthnManager {
         }
     }
 
-    public function generateAuthenticationOptions() {
+    public function generateAuthenticationOptions($userId) {
         $challenge = random_bytes(32);
 
         $_SESSION['challenge'] = base64_encode($challenge);
 
         $rpEntity = new PublicKeyCredentialRpEntity($this->rpName, $this->rpId);
         
-        return PublicKeyCredentialRequestOptions::create($rpEntity, $challenge);
+        // Convert userId to binary format if it's not already
+        if (is_numeric($userId)) {
+            $userIdBinary = hash('sha256', strval($userId), true);
+        } else {
+            $userIdBinary = hash('sha256', $userId, true);
+        }
+        
+        return PublicKeyCredentialRequestOptions::create($rpEntity, $challenge, null, 'public-key', null, null, $userIdBinary);
     }
 
     public function authenticate($assertionResponse, $storedPublicKey) {
index 75f41f8c13b18688dfc11e0c7ea7dca33ed53c94..eaebfffa2d3048a11462e3f7bac552558f53b3c5 100644 (file)
@@ -1,5 +1,9 @@
 <?php
+error_reporting(E_ALL & ~E_DEPRECATED);
+header("Access-Control-Allow-Origin: https://dw.nothing2do.fr");
+header("Access-Control-Allow-Credentials: true");
 // index.php
+error_reporting(E_ALL & ~E_DEPRECATED);
 session_start();
 require_once __DIR__ . '/../include/Database.php';
 require_once __DIR__ . '/../include/TripletManager.php';
index b9a7fa0ec577ab0ba1cec3ca0eacbcaf0470fb1f..c585b828d1bfa5535676bfcf258c9ab6ac005b14 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+error_reporting(E_ALL & ~E_DEPRECATED);
+header("Access-Control-Allow-Origin: https://dw.nothing2do.fr");
+header("Access-Control-Allow-Credentials: true");
 // login.php (version modernisée)
 session_start();
 require_once __DIR__ . '/../include/Database.php';
@@ -33,7 +36,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 
                 if ($yubikey) {
                     // User has a YubiKey, proceed with WebAuthn authentication
-                    $authenticationOptions = $webAuthnManager->generateAuthenticationOptions();
+                    $authenticationOptions = $webAuthnManager->generateAuthenticationOptions($user['id']);
                     $_SESSION['authentication_username'] = $username;
                     $_SESSION['authentication_user_id'] = $user['id'];
                     $_SESSION['authentication_public_key'] = $yubikey['public_key'];
index d5e2792a4eb321c7fc646b09f59d4e3b5f471fd4..6a91ad3a12fab7d48f263e75ab85615749ec9e4e 100644 (file)
@@ -1,9 +1,22 @@
 <?php
+error_reporting(E_ALL & ~E_DEPRECATED);
 // register.php
 session_start();
 require_once __DIR__ . '/../include/Database.php';
 require_once __DIR__ . '/../include/WebAuthnManager.php';
 
+// Set CORS headers for WebAuthn
+header("Access-Control-Allow-Origin: https://dw.nothing2do.fr");
+header("Access-Control-Allow-Credentials: true");
+header("Access-Control-Allow-Headers: Content-Type");
+header("Access-Control-Allow-Methods: POST, OPTIONS");
+
+// Handle preflight requests
+if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
+    http_response_code(200);
+    exit();
+}
+
 $db = new Database();
 $pdo = $db->connect();
 $webAuthnManager = new WebAuthnManager();
@@ -182,21 +195,190 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
         // Helper function to convert ArrayBuffer to Base64
         function arrayBufferToBase64(buffer) {
             try {
-                const bytes = new Uint8Array(buffer);
-                let binary = '';
-                for (let i = 0; i < bytes.byteLength; i++) {
-                    binary += String.fromCharCode(bytes[i]);
+                // Handle ArrayBuffer directly
+                if (buffer instanceof ArrayBuffer) {
+                    buffer = new Uint8Array(buffer);
+                }
+                
+                // Check if it's already a Uint8Array
+                if (!(buffer instanceof Uint8Array)) {
+                    throw new Error('Input is not an ArrayBuffer or Uint8Array');
                 }
-                return btoa(binary);
+                
+                // Chunked conversion to avoid stack overflow
+                const chunkSize = 0x8000; // 32KB chunks
+                let result = '';
+                
+                for (let i = 0; i < buffer.length; i += chunkSize) {
+                    const chunk = buffer.subarray(i, i + chunkSize);
+                    const binaryString = String.fromCharCode.apply(null, chunk);
+                    result += btoa(binaryString);
+                }
+                
+                return result;
             } catch (error) {
                 console.error('Error converting ArrayBuffer to Base64:', error);
-                console.error('Buffer:', buffer);
-                console.error('Buffer type:', typeof buffer);
-                console.error('Buffer length:', buffer ? buffer.byteLength : 'N/A');
-                throw error;
+                console.error('Buffer type:', buffer ? buffer.constructor.name : 'null');
+                console.error('Buffer length:', buffer ? buffer.length || buffer.byteLength : 'N/A');
+                
+                // Try alternative method
+                try {
+                    let binary = '';
+                    const bytes = new Uint8Array(buffer);
+                    const len = bytes.byteLength;
+                    
+                    for (let i = 0; i < len; i++) {
+                        binary += String.fromCharCode(bytes[i]);
+                    }
+                    
+                    return btoa(binary);
+                } catch (fallbackError) {
+                    console.error('Fallback method also failed:', fallbackError);
+                    throw new Error('Failed to convert ArrayBuffer to Base64: ' + error.message);
+                }
+            }
+        }
+        
+        // Helper function to convert base64 to Uint8Array
+        function base64ToUint8Array(base64) {
+            try {
+                // Handle both standard base64 and base64url
+                let processedBase64 = base64.replace(/-/g, '+').replace(/_/g, '/');
+                
+                // Add padding if needed
+                const padLength = processedBase64.length % 4;
+                const paddedBase64 = padLength ? processedBase64 + '==='.slice(padLength) : processedBase64;
+                
+                // Convert to binary string
+                const binaryString = atob(paddedBase64);
+                
+                // Convert to Uint8Array
+                const bytes = new Uint8Array(binaryString.length);
+                for (let i = 0; i < binaryString.length; i++) {
+                    bytes[i] = binaryString.charCodeAt(i);
+                }
+                
+                return bytes;
+            } catch (error) {
+                console.error('Error in base64ToUint8Array:', error);
+                console.error('Input:', base64);
+                console.error('Input length:', base64 ? base64.length : 'N/A');
+                throw new Error('Failed to convert base64 to Uint8Array: ' + error.message);
+            }
+        }
+        
+        // Helper function to convert string to Uint8Array
+        function stringToUint8Array(str) {
+            try {
+                // If it's already in the right format, return as-is
+                if (str instanceof ArrayBuffer) {
+                    return new Uint8Array(str);
+                }
+                
+                // If it's a hex string (from PHP hash)
+                if (/^[0-9a-f]+$/i.test(str)) {
+                    const matches = str.match(/.{1,2}/g);
+                    const bytes = new Uint8Array(matches.length);
+                    for (let i = 0; i < matches.length; i++) {
+                        bytes[i] = parseInt(matches[i], 16);
+                    }
+                    return bytes;
+                }
+                
+                // If it's base64
+                try {
+                    const binaryString = atob(str);
+                    const bytes = new Uint8Array(binaryString.length);
+                    for (let i = 0; i < binaryString.length; i++) {
+                        bytes[i] = binaryString.charCodeAt(i);
+                    }
+                    return bytes;
+                } catch (e) {
+                    // Fallback: treat as regular string
+                    const encoder = new TextEncoder();
+                    return encoder.encode(str);
+                }
+            } catch (error) {
+                console.error('Error converting string to Uint8Array:', error);
+                throw new Error('Failed to convert user ID: ' + error.message);
             }
         }
         
+        // Helper function to validate Base64 string
+        function isValidBase64(str) {
+            try {
+                // Check if string is valid Base64
+                if (!/^[A-Za-z0-9+/]+={0,2}$/.test(str)) {
+                    return false;
+                }
+                
+                // Try to decode it
+                const decoded = atob(str);
+                return decoded.length > 0;
+            } catch (e) {
+                return false;
+            }
+        }
+        
+        // Helper function to convert credential to a format suitable for server
+        function prepareCredentialForServer(credential) {
+            try {
+                console.log('Preparing credential for server...');
+                
+                // Use the browser's native conversion where possible
+                const response = credential.response;
+                
+                // For rawId, use credential.id which is already base64url
+                const rawIdBase64 = credential.id;
+                
+                // Convert ArrayBuffer data using a more reliable method
+                const arrayBufferToBase64Safe = (buffer) => {
+                    try {
+                        // Method 1: Using FileReader (most reliable)
+                        return new Promise((resolve, reject) => {
+                            const reader = new FileReader();
+                            reader.onload = () => {
+                                const result = reader.result;
+                                // Remove data URL prefix if present
+                                const base64 = result.split(',')[1] || result;
+                                resolve(base64);
+                            };
+                            reader.onerror = reject;
+                            reader.readAsDataURL(new Blob([buffer]));
+                        });
+                    } catch (e) {
+                        // Fallback to manual conversion
+                        const bytes = new Uint8Array(buffer);
+                        let binary = '';
+                        for (let i = 0; i < bytes.byteLength; i++) {
+                            binary += String.fromCharCode(bytes[i]);
+                        }
+                        return btoa(binary);
+                    }
+                };
+                
+                return Promise.all([
+                    arrayBufferToBase64Safe(response.attestationObject),
+                    arrayBufferToBase64Safe(response.clientDataJSON)
+                ])
+                .then(([attestationObjectBase64, clientDataJSONBase64]) => {
+                    return {
+                        id: credential.id,
+                        rawId: rawIdBase64,
+                        type: credential.type,
+                        response: {
+                            attestationObject: attestationObjectBase64,
+                            clientDataJSON: clientDataJSONBase64,
+                            transports: response.getTransports ? response.getTransports() : []
+                        }
+                    };
+                })
+                .catch(error => {
+                    console.error('Error in promise chain:', error);
+                    throw new Error('Failed to prepare credential data: ' + error.message);
+                });
+        }
+        
         document.addEventListener('DOMContentLoaded', function() {
             const form = document.getElementById('registrationForm');
             const usernameInput = document.getElementById('username');
@@ -272,58 +454,94 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
                     webauthnMessage.textContent = 'Veuillez toucher votre YubiKey...';
                     
                     // Convert the registration options to the format expected by the browser
+                    console.log('Original registration options:', registrationOptions);
+                    
                     const publicKey = {
                         challenge: Uint8Array.from(atob(registrationOptions.challenge), c => c.charCodeAt(0)),
                         rp: registrationOptions.rp,
-                        user: registrationOptions.user,
+                        user: {
+                            ...registrationOptions.user,
+                            id: (() => {
+                                const id = registrationOptions.user.id;
+                                console.log('User ID type:', typeof id, 'value:', id);
+                                
+                                if (id instanceof ArrayBuffer) {
+                                    return new Uint8Array(id);
+                                }
+                                
+                                // Try base64 first (this is what PHP sends)
+                                try {
+                                    return base64ToUint8Array(id);
+                                } catch (e) {
+                                    console.warn('base64 conversion failed, trying other methods:', e.message);
+                                }
+                                
+                                // Try hex string
+                                try {
+                                    if (/^[0-9a-f]+$/i.test(id)) {
+                                        const matches = id.match(/.{1,2}/g);
+                                        const bytes = new Uint8Array(matches.length);
+                                        for (let i = 0; i < matches.length; i++) {
+                                            bytes[i] = parseInt(matches[i], 16);
+                                        }
+                                        return bytes;
+                                    }
+                                } catch (e) {
+                                    console.warn('Hex conversion failed:', e.message);
+                                }
+                                
+                                // Try regular base64
+                                try {
+                                    const binaryString = atob(id);
+                                    const bytes = new Uint8Array(binaryString.length);
+                                    for (let i = 0; i < binaryString.length; i++) {
+                                        bytes[i] = binaryString.charCodeAt(i);
+                                    }
+                                    return bytes;
+                                } catch (e) {
+                                    console.warn('Base64 conversion failed:', e.message);
+                                }
+                                
+                                // Last resort: treat as UTF-8 string
+                                const encoder = new TextEncoder();
+                                return encoder.encode(id);
+                            })()
+                        },
                         pubKeyCredParams: registrationOptions.pubKeyCredParams,
                         authenticatorSelection: registrationOptions.authenticatorSelection,
                         timeout: registrationOptions.timeout,
                         attestation: registrationOptions.attestation
                     };
+                    
+                    console.log('Converted publicKey options:', publicKey);
 
                     // Call the WebAuthn API
+                    console.log('Calling WebAuthn API with options:', registrationOptions);
                     const credential = await navigator.credentials.create({ publicKey });
 
                     if (credential) {
-                        // Send the attestation response to the server
-                        // Convert credential to a format that can be sent to the server
-                        const rawIdBase64 = arrayBufferToBase64(credential.rawId);
-                        const attestationObjectBase64 = arrayBufferToBase64(credential.response.attestationObject);
-                        const clientDataJSONBase64 = arrayBufferToBase64(credential.response.clientDataJSON);
-                        
-                        console.log('Raw ID (Base64):', rawIdBase64);
-                        console.log('Attestation Object (Base64):', attestationObjectBase64);
-                        console.log('Client Data JSON (Base64):', clientDataJSONBase64);
-                        
-                        // Vérifier que les données Base64 sont valides
-                        if (!rawIdBase64 || !attestationObjectBase64 || !clientDataJSONBase64) {
-                            throw new Error('Données Base64 invalides');
-                        }
-                        
-                        const attestationResponse = {
-                            id: credential.id,
-                            rawId: rawIdBase64,
-                            response: {
-                                attestationObject: attestationObjectBase64,
-                                clientDataJSON: clientDataJSONBase64
-                            },
-                            type: credential.type
-                        };
-                        
-                        console.log('Attestation response:', attestationResponse);
+                        console.log('WebAuthn credential received:', credential);
+                        console.log('Credential ID:', credential.id);
+                        console.log('Credential type:', credential.type);
+                        console.log('Credential rawId:', credential.rawId);
+                        console.log('Credential response:', credential.response);
                         
+                        // Send the attestation response to the server
                         try {
+                            console.log('Preparing credential for server (async)...');
+                            const attestationResponse = await prepareCredentialForServer(credential);
+                            console.log('Prepared attestation response:', attestationResponse);
+                            
                             const attestationResponseJSON = JSON.stringify(attestationResponse);
                             console.log('Attestation response JSON:', attestationResponseJSON);
                             
                             const response = await fetch('register.php', {
-                            method: 'POST',
-                            headers: {
-                                'Content-Type': 'application/x-www-form-urlencoded',
-                            },
-                            body: `attestationResponse=${encodeURIComponent(attestationResponseJSON)}`
-                        });
+                                method: 'POST',
+                                headers: {
+                                    'Content-Type': 'application/x-www-form-urlencoded',
+                                },
+                                body: `attestationResponse=${encodeURIComponent(attestationResponseJSON)}`
+                            });
 
                         const responseText = await response.text();
                         console.log('Server response (step 2 - attestation):', responseText);