- 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:
m0rph3us1987
2026-03-11 17:47:46 +01:00
parent 27566a7813
commit 0d07264788
20 changed files with 753 additions and 489 deletions

View File

@@ -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' });