false, 'message' => 'Méthode non autorisée']); exit; } class FacialVerificationAPI { private $assure_api; private $maxAttempts = 3; public function __construct() { $this->assure_api = $_SESSION['assure']; $this->maxAttempts = $this->assure_api ? $this->assure_api->get_nbTentativeBiometrie($_SESSION['codeEntite']) : 3; } /** * Valide un token de vérification */ public function validateToken($token) { try { $request = $this->assure_api->valider_token(); if (!$request) { return [ 'success' => false, 'message' => 'Lien expiré ou invalide' ]; } return [ 'success' => true, 'message' => 'Token valide', 'assure' => [ 'nom' => $request['nom'], 'prenoms' => $request['prenoms'] ] ]; } catch (Exception $e) { error_log("Erreur validateToken: " . $e->getMessage()); return [ 'success' => false, 'message' => 'Erreur serveur' ]; } } /** * Compare deux visages avec l'API de reconnaissance faciale * Utilisez Azure Face API, AWS Rekognition, ou une solution locale */ private function compareFaces($referenceImagePath, $capturedImageBase64) { // Option 1: Azure Face API (Recommandé) return $this->compareWithAzureFaceAPI($referenceImagePath, $capturedImageBase64); // Option 2: AWS Rekognition // return $this->compareWithAWSRekognition($referenceImagePath, $capturedImageBase64); // Option 3: Solution locale avec OpenCV/dlib (avancé) // return $this->compareWithLocalFaceRecognition($referenceImagePath, $capturedImageBase64); } /** * Comparaison avec Azure Face API */ private function compareWithAzureFaceAPI($referenceImagePath, $capturedImageBase64) { $endpoint = AZURE_FACE_ENDPOINT; $apiKey = AZURE_FACE_API_KEY; try { // 1. Détecter le visage dans l'image de référence $referenceImageData = base64_encode(file_get_contents($referenceImagePath)); $referenceFaceId = $this->detectFaceAzure($referenceImageData, $endpoint, $apiKey); if (!$referenceFaceId) { return [ 'match' => false, 'confidence' => 0, 'error' => 'Aucun visage détecté dans la photo de référence' ]; } // 2. Détecter le visage dans l'image capturée $capturedImageData = explode(',', $capturedImageBase64)[1]; $capturedFaceId = $this->detectFaceAzure($capturedImageData, $endpoint, $apiKey); if (!$capturedFaceId) { return [ 'match' => false, 'confidence' => 0, 'error' => 'Aucun visage détecté dans votre photo' ]; } // 3. Comparer les deux visages $verifyUrl = $endpoint . '/face/v1.0/verify'; $data = [ 'faceId1' => $referenceFaceId, 'faceId2' => $capturedFaceId ]; $ch = curl_init($verifyUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Ocp-Apim-Subscription-Key: ' . $apiKey ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { throw new Exception("Azure API error: " . $response); } $result = json_decode($response, true); return [ 'match' => $result['isIdentical'], 'confidence' => round($result['confidence'] * 100, 2), 'error' => null ]; } catch (Exception $e) { error_log("Erreur Azure Face API: " . $e->getMessage()); return [ 'match' => false, 'confidence' => 0, 'error' => 'Erreur lors de la vérification faciale' ]; } } /** * Détecte un visage avec Azure Face API et retourne le faceId */ private function detectFaceAzure($imageBase64, $endpoint, $apiKey) { $detectUrl = $endpoint . '/face/v1.0/detect?returnFaceId=true'; $ch = curl_init($detectUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, base64_decode($imageBase64)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/octet-stream', 'Ocp-Apim-Subscription-Key: ' . $apiKey ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { return null; } $faces = json_decode($response, true); if (empty($faces)) { return null; } return $faces[0]['faceId']; } /** * Comparaison avec AWS Rekognition (Alternative) */ private function compareWithAWSRekognition($referenceImagePath, $capturedImageBase64) { require_once 'vendor/autoload.php'; try { $rekognitionClient = new Aws\Rekognition\RekognitionClient([ 'version' => 'latest', 'region' => AWS_REGION, 'credentials' => [ 'key' => AWS_ACCESS_KEY_ID, 'secret' => AWS_SECRET_ACCESS_KEY ] ]); $referenceImageData = file_get_contents($referenceImagePath); $capturedImageData = base64_decode(explode(',', $capturedImageBase64)[1]); $result = $rekognitionClient->compareFaces([ 'SourceImage' => ['Bytes' => $referenceImageData], 'TargetImage' => ['Bytes' => $capturedImageData], 'SimilarityThreshold' => 80 ]); if (empty($result['FaceMatches'])) { return [ 'match' => false, 'confidence' => 0, 'error' => 'Les visages ne correspondent pas' ]; } $similarity = $result['FaceMatches'][0]['Similarity']; return [ 'match' => $similarity >= 80, 'confidence' => round($similarity, 2), 'error' => null ]; } catch (Exception $e) { error_log("Erreur AWS Rekognition: " . $e->getMessage()); return [ 'match' => false, 'confidence' => 0, 'error' => 'Erreur lors de la vérification faciale' ]; } } /** * Enregistre la photo capturée */ private function saveCapturedImage($assureId, $imageBase64) { $uploadDir = 'uploads/facial_verification/'; if (!file_exists($uploadDir)) { mkdir($uploadDir, 0755, true); } $imageData = explode(',', $imageBase64)[1]; $imageData = base64_decode($imageData); $filename = $uploadDir . $assureId . '_' . time() . '.jpg'; file_put_contents($filename, $imageData); return $filename; } /** * Met à jour le statut de la vérification */ private function updateVerificationStatus($token, $status, $matchResult = null, $capturedPhotoPath = null) { $sql = "UPDATE facial_verification_requests SET status = ?, verified_at = NOW(), match_confidence = ?, captured_photo_path = ?, attempts = attempts + 1 WHERE verification_token = ?"; $stmt = $this->db->prepare($sql); $stmt->execute([ $status, $matchResult ? $matchResult['confidence'] : null, $capturedPhotoPath, $token ]); } /** * Crée une session d'autorisation pour l'accès aux prestations */ private function createAuthorizationSession($assureId, $verificationRequestId) { $sessionToken = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', time() + 3600); $sql = "INSERT INTO prestation_authorization_sessions (assure_id, verification_request_id, session_token, expires_at, status) VALUES (?, ?, ?, ?, 'active')"; $stmt = $this->db->prepare($sql); $stmt->execute([$assureId, $verificationRequestId, $sessionToken, $expiresAt]); return $sessionToken; } /** * Vérifie le visage capturé */ public function verifyFace($token, $capturedImageBase64) { $request = $this->assure_api->valider_token(); var_dump($request); exit; try { // 1. Récupérer les infos de la demande $sql = "SELECT vr.*, a.photo_reference_path, a.id as assure_id FROM facial_verification_requests vr JOIN assures a ON vr.assure_id = a.id WHERE vr.verification_token = ? AND vr.status = 'pending' AND vr.expires_at > NOW()"; $stmt = $this->db->prepare($sql); $stmt->execute([$token]); $request = $stmt->fetch(PDO::FETCH_ASSOC); if (!$request) { return [ 'success' => false, 'match' => false, 'message' => 'Demande expirée ou invalide' ]; } // 2. Vérifier le nombre de tentatives if ($request['attempts'] >= $this->maxAttempts) { $this->updateVerificationStatus($token, 'failed'); return [ 'success' => false, 'match' => false, 'message' => 'Nombre maximum de tentatives atteint' ]; } // 3. Enregistrer la photo capturée $capturedPhotoPath = $this->saveCapturedImage($request['assure_id'], $capturedImageBase64); // 4. Comparer les visages $comparisonResult = $this->compareFaces( $request['photo_reference_path'], $capturedImageBase64 ); if ($comparisonResult['error']) { $this->updateVerificationStatus($token, 'error', $comparisonResult, $capturedPhotoPath); return [ 'success' => false, 'match' => false, 'message' => $comparisonResult['error'], 'attempts_remaining'=> $this->maxAttempts - ($request['attempts'] + 1) ]; } // 5. Seuil de confiance minimum (80%) $confidenceThreshold = 80; $isMatch = $comparisonResult['match'] && $comparisonResult['confidence'] >= $confidenceThreshold; if ($isMatch) { $this->updateVerificationStatus($token, 'verified', $comparisonResult, $capturedPhotoPath); $sessionToken = $this->createAuthorizationSession($request['assure_id'], $request['id']); return [ 'success' => true, 'match' => true, 'confidence' => $comparisonResult['confidence'], 'message' => 'Identité vérifiée avec succès', 'session_token' => $sessionToken, 'redirect_url' => 'saisie_prestations.php?token=' . $sessionToken ]; } else { $attemptsRemaining = $this->maxAttempts - ($request['attempts'] + 1); if ($attemptsRemaining > 0) { $this->updateVerificationStatus($token, 'pending', $comparisonResult, $capturedPhotoPath); return [ 'success' => false, 'match' => false, 'confidence' => $comparisonResult['confidence'], 'message' => 'Votre visage ne correspond pas', 'attempts_remaining' => $attemptsRemaining ]; } else { $this->updateVerificationStatus($token, 'failed', $comparisonResult, $capturedPhotoPath); return [ 'success' => false, 'match' => false, 'confidence' => $comparisonResult['confidence'], 'message' => 'Vérification échouée. Nombre maximum de tentatives atteint.', 'attempts_remaining' => 0 ]; } } } catch (Exception $e) { error_log("Erreur verifyFace: " . $e->getMessage()); return [ 'success' => false, 'match' => false, 'message' => 'Erreur lors de la vérification: ' . $e->getMessage() ]; } } } // Traiter la requête if ($_SERVER['REQUEST_METHOD'] === 'POST') { $input = json_decode(file_get_contents('php://input'), true); $action = $input['action'] ?? null; $api = new FacialVerificationAPI(); switch ($action) { case 'validate_token': $token = $input['token'] ?? null; if (!$token) { echo json_encode(['success' => false, 'message' => 'Token requis']); exit; } echo json_encode($api->validateToken($token)); break; case 'verify_face': $token = $input['token'] ?? null; $image = $input['image'] ?? null; if (!$token || !$image) { echo json_encode(['success' => false, 'message' => 'Token et image requis']); exit; } echo json_encode($api->verifyFace($token, $image)); break; default: echo json_encode(['success' => false, 'message' => 'Action invalide']); } } else { // echo json_encode(['success' => false, 'message' => 'Méthode non autorisée']); ob_end_clean(); // ✅ vide le buffer avant d'envoyer le JSON echo json_encode(['success' => false, 'message' => 'Méthode non autorisée']); }