- Fixed Challenge Modal overlap issue by adjusting the main stacking context in App.tsx
- Implemented "click-outside-to-close" functionality for both the Challenge Modal and User Dropdown - Added protocol-specific action buttons for challenges: "Open in new tab" for HTTP and "Copy to clipboard" for NC - Enhanced Scoreboard rankings with significantly larger, consistent font sizes (text-2xl) for better readability - Rebranded "TEAM_IDENTIFIER" to "PLAYER" and "TOTAL_POINTS" to "POINTS" across the platform (Scoreboard, Matrix, User Menu) - Updated navigation: Renamed "SCOREBOARD" to "SCORES" in the nav bar and dynamic page titles - Modernized User Dropdown menu with a dedicated "PLAYER" header and "LOGOUT" action - Improved Score Matrix and Score Graph titles for consistency with the new "Player" terminology - Added CAPTCHA human verification (svg-captcha) to Login and Registration flows for enhanced security - Optimized frontend assets by migrating Tailwind and JetBrains Mono to local hosting - Refactored Admin panel: Renamed "Operators" to "Users" and improved layout alignment
This commit is contained in:
44
server.js
44
server.js
@@ -6,10 +6,20 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const cors = require('cors');
|
||||
const crypto = require('crypto');
|
||||
const svgCaptcha = require('svg-captcha');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
// Memory storage for captchas (briefly)
|
||||
const captchas = new Map();
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [id, data] of captchas.entries()) {
|
||||
if (now - data.timestamp > 300000) captchas.delete(id); // 5 min expiry
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
// Password Hashing Helpers
|
||||
function hashPassword(password) {
|
||||
if (typeof password !== 'string') throw new Error('Password must be a string');
|
||||
@@ -223,9 +233,30 @@ apiRouter.use('/admin', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
apiRouter.get('/auth/captcha', (req, res) => {
|
||||
const captcha = svgCaptcha.create({
|
||||
size: 6,
|
||||
noise: 3,
|
||||
color: true,
|
||||
background: '#1a1a1a'
|
||||
});
|
||||
const id = crypto.randomUUID();
|
||||
captchas.set(id, { text: captcha.text, timestamp: Date.now() });
|
||||
res.json({ id, data: captcha.data });
|
||||
});
|
||||
|
||||
apiRouter.post('/auth/register', (req, res) => {
|
||||
const { name, password } = req.body;
|
||||
const { name, password, captchaId, captchaAnswer } = req.body;
|
||||
if (!name || !password) return res.status(400).json({ message: 'Missing credentials' });
|
||||
|
||||
// CAPTCHA Validation
|
||||
if (!captchaId || !captchaAnswer) return res.status(400).json({ message: 'Human verification required' });
|
||||
const stored = captchas.get(captchaId);
|
||||
if (!stored || stored.text.toLowerCase() !== captchaAnswer.toLowerCase()) {
|
||||
return res.status(400).json({ message: 'Invalid human verification code' });
|
||||
}
|
||||
captchas.delete(captchaId); // One-time use
|
||||
|
||||
const id = 'team-' + Math.random().toString(36).substr(2, 9);
|
||||
const hashedPass = hashPassword(password);
|
||||
db.run("INSERT INTO teams (id, name, password, isAdmin, isDisabled) VALUES (?, ?, ?, 0, 0)", [id, name, hashedPass], function(err) {
|
||||
@@ -238,7 +269,16 @@ apiRouter.post('/auth/register', (req, res) => {
|
||||
});
|
||||
|
||||
apiRouter.post('/auth/login', (req, res) => {
|
||||
const { name, password } = req.body;
|
||||
const { name, password, captchaId, captchaAnswer } = req.body;
|
||||
|
||||
// CAPTCHA Validation
|
||||
if (!captchaId || !captchaAnswer) return res.status(400).json({ message: 'Human verification required' });
|
||||
const stored = captchas.get(captchaId);
|
||||
if (!stored || stored.text.toLowerCase() !== captchaAnswer.toLowerCase()) {
|
||||
return res.status(400).json({ message: 'Invalid human verification code' });
|
||||
}
|
||||
captchas.delete(captchaId); // One-time use
|
||||
|
||||
db.get("SELECT * FROM teams WHERE name = ?", [name], (err, team) => {
|
||||
if (err || !team || !comparePassword(password, team.password)) return res.status(401).json({ message: 'Invalid credentials' });
|
||||
if (team.isDisabled) return res.status(403).json({ message: 'Account disabled' });
|
||||
|
||||
Reference in New Issue
Block a user