Téléverser les fichiers vers "/"
This commit is contained in:
commit
850a40526b
243
index.php
Normal file
243
index.php
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
<?php
|
||||||
|
// chatbot.php — Version sécurisée production + anti prompt injection
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
CONFIG & ENV
|
||||||
|
======================= */
|
||||||
|
$env = parse_ini_file(__DIR__ . '/.env');
|
||||||
|
define('API_KEY', $env['DEESEEK_API_KEY']);
|
||||||
|
define('SYSTEM_PROMPT', $env['DEEPSEEK_SYSTEM_PROMPT']);
|
||||||
|
define('CSRF_SECRET', $env['CSRF_SECRET']);
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
HEADERS SÉCURITÉ
|
||||||
|
======================= */
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
header('X-Frame-Options: SAMEORIGIN');
|
||||||
|
header('X-XSS-Protection: 1; mode=block');
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
SESSION INIT
|
||||||
|
======================= */
|
||||||
|
if (!isset($_SESSION['csrf'])) {
|
||||||
|
$_SESSION['csrf'] = hash_hmac('sha256', session_id(), CSRF_SECRET);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION['message_count'])) {
|
||||||
|
$_SESSION['message_count'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
HELPERS
|
||||||
|
======================= */
|
||||||
|
function isAjax(): bool {
|
||||||
|
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||||
|
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeMessage(string $msg) {
|
||||||
|
$msg = trim($msg);
|
||||||
|
if ($msg === '' || mb_strlen($msg) > 500) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
ANTI PROMPT INJECTION
|
||||||
|
======================= */
|
||||||
|
function isPromptInjection(string $msg): bool {
|
||||||
|
// regex solide : détecte toutes les tentatives d'instructions systèmes, "ignore", "révèle", "prompt", etc.
|
||||||
|
$pattern = '/\b(ignore|oublie|révèle|prompt|instructions?|system|agis comme|tu es maintenant|roleplay|administrateur)\b/i';
|
||||||
|
return preg_match($pattern, $msg) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
API CALL
|
||||||
|
======================= */
|
||||||
|
function callDeepSeekAPI(string $userMessage): string {
|
||||||
|
$payload = [
|
||||||
|
'model' => 'deepseek-chat',
|
||||||
|
'messages' => [
|
||||||
|
['role' => 'system', 'content' => SYSTEM_PROMPT],
|
||||||
|
['role' => 'user', 'content' => $userMessage]
|
||||||
|
],
|
||||||
|
'max_tokens' => 800,
|
||||||
|
'temperature' => 0,
|
||||||
|
'top_p' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init('https://api.deepseek.com/v1/chat/completions');
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Authorization: Bearer ' . API_KEY
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 15
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($http !== 200 || !$response) {
|
||||||
|
return 'Erreur du service.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode($response, true);
|
||||||
|
return $json['choices'][0]['message']['content'] ?? 'Erreur de réponse.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
AJAX HANDLER
|
||||||
|
======================= */
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
|
||||||
|
|
||||||
|
if (!isAjax()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Requête interdite']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_POST['csrf']) || $_POST['csrf'] !== $_SESSION['csrf']) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'CSRF invalide']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SESSION['message_count'] >= 1) {
|
||||||
|
echo json_encode(['error' => 'LIMIT_REACHED']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = sanitizeMessage($_POST['message']);
|
||||||
|
if ($message === false) {
|
||||||
|
echo json_encode(['error' => 'Message invalide']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// anti prompt injection
|
||||||
|
if (isPromptInjection($message)) {
|
||||||
|
echo json_encode(['response' => 'Demande à Bouchra.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['message_count']++;
|
||||||
|
|
||||||
|
$reply = callDeepSeekAPI($message);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'response' => $reply
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Assistant IA</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body { margin:0; font-family:Inter,sans-serif; background:linear-gradient(135deg,#667eea,#764ba2); height:100vh; display:flex; justify-content:center; align-items:center; }
|
||||||
|
.chat-wrapper { width:100%; max-width:900px; height:700px; background:#fff; border-radius:24px; display:flex; flex-direction:column; overflow:hidden; }
|
||||||
|
.chat-header { padding:20px; background:linear-gradient(135deg,#667eea,#764ba2); color:#fff; font-weight:600; }
|
||||||
|
#chat { flex:1; padding:20px; background:#f8fafc; overflow-y:auto; }
|
||||||
|
.message { display:flex; margin-bottom:16px; }
|
||||||
|
.message.user { justify-content:flex-end; }
|
||||||
|
.message-bubble { max-width:70%; padding:12px 16px; border-radius:14px; font-size:14px; }
|
||||||
|
.user .message-bubble { background:#667eea; color:#fff; }
|
||||||
|
.bot .message-bubble { background:#fff; color:#1e293b; }
|
||||||
|
.input-area { padding:20px; border-top:1px solid #e5e7eb; }
|
||||||
|
#limitPopup { display:none; margin-bottom:10px; padding:10px; background:#fff3cd; color:#92400e; border-radius:10px; font-size:13px; text-align:center; }
|
||||||
|
form { display:flex; gap:10px; }
|
||||||
|
input { flex:1; padding:12px; border-radius:10px; border:1px solid #ccc; }
|
||||||
|
button { padding:12px 20px; border:none; border-radius:10px; background:#667eea; color:#fff; font-weight:600; cursor:pointer; }
|
||||||
|
button:disabled { opacity:.6; cursor:not-allowed; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="chat-wrapper">
|
||||||
|
<div class="chat-header">Assistant IA</div>
|
||||||
|
|
||||||
|
<div id="chat">
|
||||||
|
<div class="message bot">
|
||||||
|
<div class="message-bubble">
|
||||||
|
Bonjour, comment puis‑je vous aider ?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-area">
|
||||||
|
<div id="limitPopup">
|
||||||
|
Pour plus, prends l'abonnement en demandant à Bouchra
|
||||||
|
</div>
|
||||||
|
<form onsubmit="sendMsg();return false;">
|
||||||
|
<input type="hidden" id="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
|
||||||
|
<input id="userInput" type="text" placeholder="Votre message…" required>
|
||||||
|
<button id="sendBtn">Envoyer</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const chat = document.getElementById('chat');
|
||||||
|
const input = document.getElementById('userInput');
|
||||||
|
const btn = document.getElementById('sendBtn');
|
||||||
|
const csrf = document.getElementById('csrf').value;
|
||||||
|
const popup = document.getElementById('limitPopup');
|
||||||
|
|
||||||
|
function addMsg(text, isUser) {
|
||||||
|
const msg = document.createElement('div');
|
||||||
|
msg.className = 'message ' + (isUser ? 'user' : 'bot');
|
||||||
|
|
||||||
|
const bubble = document.createElement('div');
|
||||||
|
bubble.className = 'message-bubble';
|
||||||
|
bubble.textContent = text; // 🔒 XSS SAFE
|
||||||
|
|
||||||
|
msg.appendChild(bubble);
|
||||||
|
chat.appendChild(msg);
|
||||||
|
chat.scrollTop = chat.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMsg() {
|
||||||
|
const txt = input.value.trim();
|
||||||
|
if (!txt) return;
|
||||||
|
|
||||||
|
addMsg(txt, true);
|
||||||
|
input.value = '';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
const res = await fetch('', {
|
||||||
|
method:'POST',
|
||||||
|
headers:{
|
||||||
|
'Content-Type':'application/x-www-form-urlencoded',
|
||||||
|
'X-Requested-With':'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body:'message='+encodeURIComponent(txt)+'&csrf='+encodeURIComponent(csrf)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.error === 'LIMIT_REACHED') {
|
||||||
|
popup.style.display = 'block';
|
||||||
|
input.disabled = true;
|
||||||
|
btn.disabled = true;
|
||||||
|
addMsg('Accès limité.', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addMsg(data.response || 'Erreur.', false);
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user