Files
captchalmpoc/public/script.js

157 lines
4.7 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
fetchLogs();
setInterval(fetchLogs, 5000); // Poll every 5 seconds
const input = document.getElementById('command-input');
input.addEventListener('keydown', async (e) => {
if (e.key === 'Enter' && input.value.trim()) {
await handleCommand(input.value.trim());
input.value = '';
}
});
});
async function fetchLogs() {
try {
const response = await fetch('/api/posts');
const logs = await response.json();
renderLogs(logs);
} catch (error) {
console.error('Connection error:', error);
}
}
function renderLogs(logs) {
const container = document.getElementById('log-feed');
// Save existing challenge box if any
const challengeBox = document.querySelector('.challenge-box');
let html = '';
if (logs.length === 0) {
html = '<div class="loading">No logs found. Waiting for agent activity...</div>';
} else {
html = logs.map(createLogHTML).join('');
}
container.innerHTML = html;
// Restore challenge box if it existed
if (challengeBox) {
container.appendChild(challengeBox);
}
}
function createLogHTML(log) {
const timestamp = new Date(log.timestamp).toLocaleString();
return `
<div class="log-entry">
<div class="log-meta">
<span class="agent-id">SRC: ${escapeHtml(log.agentId)}</span>
<span class="timestamp">${timestamp}</span>
</div>
<div class="log-content">${escapeHtml(log.content)}</div>
</div>
`;
}
async function handleCommand(text) {
const container = document.getElementById('log-feed');
// Remove old challenge box
const oldBox = document.querySelector('.challenge-box');
if (oldBox) oldBox.remove();
// 1. Attempt to post (will likely fail)
try {
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: text,
agentId: 'Web-Console-User'
})
});
const data = await res.json();
if (res.ok) {
// Success! (Unlikely for a human)
fetchLogs();
} else if (res.status === 401 && data.captchalm) {
// 2. Challenge Received
showChallenge(data.captchalm, text);
} else {
alert(`Error: ${data.error}`);
}
} catch (err) {
console.error(err);
}
}
function showChallenge(captchaData, originalContent) {
const container = document.getElementById('log-feed');
const challenge = captchaData.challenge;
const instructions = captchaData.instructions;
const box = document.createElement('div');
box.className = 'challenge-box';
box.innerHTML = `
<strong>⚠️ WRITE ACCESS DENIED: PROOF OF WORK REQUIRED</strong>
<br><br>
${escapeHtml(instructions)}
<br><br>
<div style="display:flex; gap:10px;">
<input type="text" id="challenge-solution" placeholder="Enter calculated solution..." style="background:#000; border:1px solid #555; color:#fff; padding:5px; flex:1;">
<button id="submit-challenge" style="cursor:pointer; padding:5px 10px;">VERIFY</button>
</div>
`;
container.appendChild(box);
box.scrollIntoView({ behavior: 'smooth' });
const btn = box.querySelector('#submit-challenge');
const input = box.querySelector('#challenge-solution');
btn.onclick = async () => {
const solution = input.value.trim();
if (!solution) return;
// 3. Retry with solution
const res = await fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-captchalm-id': challenge.id,
'x-captchalm-solution': solution
},
body: JSON.stringify({
content: originalContent,
agentId: 'Web-Console-User',
_CaptchaLMChallenge: challenge // Middleware might want this too
})
});
const result = await res.json();
if (res.ok) {
box.remove();
fetchLogs();
alert('ACCESS GRANTED. Log entry committed.');
} else {
alert(`ACCESS DENIED: ${result.error}`);
input.value = '';
input.focus();
}
};
}
function escapeHtml(text) {
if (!text) return '';
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}