try to fix this spaghetti of a code
This commit is contained in:
+89
@@ -0,0 +1,89 @@
|
||||
const express = require('express');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const path = require('node:path');
|
||||
const swaggerUi = require('swagger-ui-express');
|
||||
const {AppError} = require('./core');
|
||||
const {tokenFrom} = require('./transport');
|
||||
const {validate} = require('../public/protocol');
|
||||
function createHttpApp(repository, sessions, lobbies, config) {
|
||||
const app = express();
|
||||
app.disable('x-powered-by'); app.set('trust proxy', config.trustProxy);
|
||||
app.use(express.json({limit: '16kb'})); app.use(cookieParser());
|
||||
app.use((req, res, next) => {
|
||||
res.set('X-Content-Type-Options', 'nosniff');
|
||||
res.set('Referrer-Policy', 'same-origin');
|
||||
if (!['GET', 'HEAD', 'OPTIONS'].includes(req.method) && req.headers.origin) {
|
||||
const origin = config.origin || `${req.protocol}://${req.get('host')}`;
|
||||
if (req.headers.origin !== origin) return res.status(403).json({code: 'ORIGIN_DENIED', error: 'Origin denied'});
|
||||
}
|
||||
next();
|
||||
});
|
||||
const attempts = new Map();
|
||||
function rateLimit(req, res, next) {
|
||||
const now = config.now();
|
||||
for (const [key, value] of attempts) if (value.until <= now) attempts.delete(key);
|
||||
const key = req.ip;
|
||||
if (!attempts.has(key)) {
|
||||
if (attempts.size >= 10000) return res.status(503).json({code: 'BUSY', error: 'Try again later'});
|
||||
attempts.set(key, {count: 0, until: now + config.authWindowMs});
|
||||
}
|
||||
const record = attempts.get(key);
|
||||
if (++record.count > config.authLimit) {
|
||||
res.set('Retry-After', String(Math.ceil((record.until - now) / 1000)));
|
||||
return res.status(429).json({code: 'RATE_LIMITED', error: 'Too many authentication attempts'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
const cookie = req => ({httpOnly: true, sameSite: 'strict', secure: req.secure, path: '/', maxAge: config.sessionSeconds * 1000});
|
||||
const authorized = handler => async (req, res) => sessions.authorized(tokenFrom(req), user => handler(req, res, user));
|
||||
for (const method of ['register', 'login']) app.post(`/${method}`, rateLimit, async (req, res) => {
|
||||
const result = await sessions[method](req.body);
|
||||
res.cookie('authorization', result.token, cookie(req)); res.set('Cache-Control', 'no-store'); res.json(result);
|
||||
});
|
||||
app.post('/verify', authorized((req, res, user) => res.json({valid: true, user})));
|
||||
app.delete('/logout', authorized(async (req, res, user) => {
|
||||
const lobby = lobbies.membership(user.username);
|
||||
if (lobby) await lobbies.command(user.username, {type: 'lobby:leave', lobbyId: lobby.id, requestId: require('node:crypto').randomUUID()});
|
||||
await sessions.revoke(user.username);
|
||||
const options = cookie(req); delete options.maxAge;
|
||||
res.clearCookie('authorization', options); res.json({message: 'Logged out'});
|
||||
}));
|
||||
app.get('/balance', authorized(async (req, res, user) => res.json({balance: (await repository.user(user.username)).money})));
|
||||
app.get('/leaderboard', authorized(async (req, res, user) => {
|
||||
const users = await repository.users(); const index = users.findIndex(entry => entry.username === user.username);
|
||||
res.json({leaderboard: users.slice(0, 10), self: {...users[index], position: index + 1}});
|
||||
}));
|
||||
for (const [route, type] of [['/lobby/create', 'lobby:create'], ['/lobby/join', 'lobby:join'], ['/lobby/leave', 'lobby:leave'], ['/poker/start', 'poker:start'], ['/poker/action', 'poker:action']]) {
|
||||
app.post(route, authorized(async (req, res, user) => {
|
||||
const message = {...req.body, type};
|
||||
const invalid = validate(message);
|
||||
if (invalid) throw new AppError('INVALID_MESSAGE', invalid);
|
||||
res.json(await lobbies.command(user.username, message));
|
||||
}));
|
||||
app.get(route, (req, res) => res.set('Allow', 'POST').status(405).json({code: 'METHOD_NOT_ALLOWED', error: 'Use POST with a requestId'}));
|
||||
}
|
||||
app.get('/lobby/public', authorized((req, res) => res.json({lobbies: lobbies.publicList()})));
|
||||
app.get('/lobby/players', authorized((req, res, user) => {
|
||||
const lobby = lobbies.get(req.query.lobbyId);
|
||||
if (!lobby?.players.includes(user.username)) throw new AppError('NOT_SEATED', 'Join this lobby first', 403);
|
||||
res.json({players: lobby.players});
|
||||
}));
|
||||
const publicPath = path.join(__dirname, '..', 'public');
|
||||
app.get(['/', '/home.html', '/poker.html'], async (req, res) => {
|
||||
if (!await sessions.verify(tokenFrom(req))) return res.redirect('/login');
|
||||
res.set('Cache-Control', 'no-store');
|
||||
res.sendFile(path.join(publicPath, req.path === '/poker.html' ? 'poker.html' : 'home.html'));
|
||||
});
|
||||
app.get('/login', (req, res) => res.sendFile(path.join(publicPath, 'login.html')));
|
||||
app.use('/swagger-ui', swaggerUi.serve, swaggerUi.setup(require('../swagger.json')));
|
||||
app.use(express.static(publicPath, {index: false}));
|
||||
app.use((req, res) => res.status(404).json({code: 'NOT_FOUND', error: 'Not found'}));
|
||||
app.use((error, req, res, next) => {
|
||||
if (res.headersSent) return next(error);
|
||||
const status = error.status >= 400 && error.status < 600 ? error.status : 500;
|
||||
if (status === 500) console.error('Request failed:', error);
|
||||
res.status(status).json({code: error.code || (status === 500 ? 'INTERNAL_ERROR' : 'INVALID_BODY'), error: status === 500 ? 'Internal server error' : error.message});
|
||||
});
|
||||
return app;
|
||||
}
|
||||
module.exports = {createHttpApp};
|
||||
Reference in New Issue
Block a user