UNPKG

tamil-captcha

Version:

A lightweight math-based CAPTCHA generator in Tamil language for Node.js and browser applications

362 lines (325 loc) 12.4 kB
/** * Express.js server example with Tamil CAPTCHA * Run with: node examples/express-server.js * * Install dependencies first: * npm install express cors */ const express = require('express'); const cors = require('cors'); const { generateCaptcha, verifyCaptcha } = require('../index.js'); const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(express.json()); app.use(express.static('public')); // In-memory storage for active CAPTCHAs (use Redis in production) const activeCaptchas = new Map(); const CAPTCHA_EXPIRY = 5 * 60 * 1000; // 5 minutes // Clean up expired CAPTCHAs every minute setInterval(() => { const now = Date.now(); for (const [sessionId, data] of activeCaptchas.entries()) { if (now - data.timestamp > CAPTCHA_EXPIRY) { activeCaptchas.delete(sessionId); } } }, 60000); // Generate a new CAPTCHA app.get('/api/captcha', (req, res) => { try { const options = { minNumber: parseInt(req.query.min) || 1, maxNumber: parseInt(req.query.max) || 10, operations: req.query.operations ? req.query.operations.split(',') : ['+', '-', '*'] }; const captcha = generateCaptcha(options); const sessionId = `captcha_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Store the correct answer with timestamp activeCaptchas.set(sessionId, { answer: captcha.answer, timestamp: Date.now(), attempts: 0 }); res.json({ success: true, sessionId, question: captcha.question, metadata: { operation: captcha.metadata.operationWord, numbers: captcha.metadata.tamilNumbers } }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } }); // Verify CAPTCHA answer app.post('/api/captcha/verify', (req, res) => { const { sessionId, answer } = req.body; if (!sessionId || answer === undefined) { return res.status(400).json({ success: false, error: 'Session ID and answer are required' }); } const captchaData = activeCaptchas.get(sessionId); if (!captchaData) { return res.status(400).json({ success: false, error: 'Invalid or expired CAPTCHA session' }); } // Check if CAPTCHA has expired if (Date.now() - captchaData.timestamp > CAPTCHA_EXPIRY) { activeCaptchas.delete(sessionId); return res.status(400).json({ success: false, error: 'CAPTCHA has expired' }); } // Increment attempt counter captchaData.attempts++; // Rate limiting: max 3 attempts per CAPTCHA if (captchaData.attempts > 3) { activeCaptchas.delete(sessionId); return res.status(429).json({ success: false, error: 'Too many attempts. Please request a new CAPTCHA.' }); } const isValid = verifyCaptcha(answer, captchaData.answer); if (isValid) { // Clean up on successful verification activeCaptchas.delete(sessionId); res.json({ success: true, valid: true, message: 'CAPTCHA verified successfully!' }); } else { res.json({ success: true, valid: false, attemptsRemaining: 3 - captchaData.attempts, message: 'Incorrect answer. Please try again.' }); } }); // Get CAPTCHA statistics app.get('/api/captcha/stats', (req, res) => { res.json({ activeCaptchas: activeCaptchas.size, supportedOperations: ['+', '-', '*'], supportedRange: '1-10', expiryTime: `${CAPTCHA_EXPIRY / 1000} seconds` }); }); // Demo HTML page app.get('/', (req, res) => { res.send(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tamil CAPTCHA Demo</title> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } .container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } .captcha-box { border: 2px solid #ddd; padding: 20px; margin: 20px 0; border-radius: 8px; background: #f9f9f9; } .tamil-question { font-size: 24px; font-weight: bold; margin: 10px 0; color: #333; text-align: center; } input[type="number"] { padding: 10px; font-size: 16px; border: 2px solid #ddd; border-radius: 5px; margin: 10px 5px; width: 100px; } button { padding: 10px 20px; font-size: 16px; border: none; border-radius: 5px; cursor: pointer; margin: 5px; } .btn-primary { background: #007bff; color: white; } .btn-secondary { background: #6c757d; color: white; } .btn-success { background: #28a745; color: white; } .message { padding: 10px; margin: 10px 0; border-radius: 5px; } .success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } h1 { color: #333; text-align: center; } .stats { background: #e9ecef; padding: 15px; border-radius: 5px; margin: 20px 0; font-family: monospace; } </style> </head> <body> <div class="container"> <h1>🧪 Tamil CAPTCHA Demo</h1> <p>This demo shows the tamil-captcha NPM package in action. Solve the math problem in Tamil!</p> <div class="captcha-box"> <div id="captcha-question" class="tamil-question">Click "New CAPTCHA" to start</div> <div style="text-align: center;"> <input type="number" id="answer-input" placeholder="Your answer" disabled> <br> <button id="verify-btn" class="btn-primary" disabled>Verify Answer</button> <button id="new-captcha-btn" class="btn-secondary">New CAPTCHA</button> </div> </div> <div id="message"></div> <div class="stats"> <h3>Package Features:</h3> <ul> <li>✅ Tamil numerals (1-10): ஒன்று, இரண்டு, மூன்று, etc.</li> <li>✅ Math operations: கூட்டல் (+), கழித்தல் (-), பெருக்கல் (×)</li> <li>✅ Zero dependencies</li> <li>✅ Works in Node.js and browsers</li> <li>✅ TypeScript support</li> </ul> </div> <div style="text-align: center; margin-top: 30px;"> <p>Install: <code>npm install tamil-captcha</code></p> <p><a href="https://github.com/yourusername/tamil-captcha">GitHub Repository</a></p> </div> </div> <script> let currentSessionId = null; function showMessage(text, type = 'info') { const messageDiv = document.getElementById('message'); messageDiv.innerHTML = '<div class="message ' + type + '">' + text + '</div>'; } async function generateNewCaptcha() { try { const response = await fetch('/api/captcha'); const data = await response.json(); if (data.success) { document.getElementById('captcha-question').textContent = data.question; document.getElementById('answer-input').disabled = false; document.getElementById('verify-btn').disabled = false; document.getElementById('answer-input').value = ''; document.getElementById('answer-input').focus(); currentSessionId = data.sessionId; showMessage('New CAPTCHA generated! Please solve the math problem.', 'info'); } else { showMessage('Error: ' + data.error, 'error'); } } catch (error) { showMessage('Network error: ' + error.message, 'error'); } } async function verifyCaptcha() { if (!currentSessionId) { showMessage('Please generate a new CAPTCHA first.', 'error'); return; } const answer = document.getElementById('answer-input').value; if (!answer) { showMessage('Please enter your answer.', 'error'); return; } try { const response = await fetch('/api/captcha/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: currentSessionId, answer: answer }) }); const data = await response.json(); if (data.success) { if (data.valid) { showMessage('🎉 Correct! CAPTCHA verified successfully!', 'success'); document.getElementById('answer-input').disabled = true; document.getElementById('verify-btn').disabled = true; currentSessionId = null; } else { showMessage(data.message + (data.attemptsRemaining ? ' (' + data.attemptsRemaining + ' attempts remaining)' : ''), 'error'); if (data.attemptsRemaining === 0) { document.getElementById('answer-input').disabled = true; document.getElementById('verify-btn').disabled = true; currentSessionId = null; } } } else { showMessage('Error: ' + data.error, 'error'); if (response.status === 400 || response.status === 429) { document.getElementById('answer-input').disabled = true; document.getElementById('verify-btn').disabled = true; currentSessionId = null; } } } catch (error) { showMessage('Network error: ' + error.message, 'error'); } } // Event listeners document.getElementById('new-captcha-btn').addEventListener('click', generateNewCaptcha); document.getElementById('verify-btn').addEventListener('click', verifyCaptcha); document.getElementById('answer-input').addEventListener('keypress', function(e) { if (e.key === 'Enter') { verifyCaptcha(); } }); // Generate initial CAPTCHA generateNewCaptcha(); </script> </body> </html> `); }); // Error handling middleware app.use((error, req, res, next) => { console.error('Server Error:', error); res.status(500).json({ success: false, error: 'Internal server error' }); }); // Start server app.listen(PORT, () => { console.log(`🚀 Tamil CAPTCHA Server running on http://localhost:${PORT}`); console.log(`📱 Open http://localhost:${PORT} to see the demo`); console.log(`🔗 API endpoints:`); console.log(` GET /api/captcha - Generate new CAPTCHA`); console.log(` POST /api/captcha/verify - Verify answer`); console.log(` GET /api/captcha/stats - Get statistics`); });