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
JavaScript
/**
* 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(`
<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`);
});