This commit is contained in:
cay 2026-04-10 16:58:46 +01:00
parent edf8a37ddc
commit 4810950d4e
3 changed files with 51 additions and 25 deletions

46
app.js
View File

@ -28,7 +28,6 @@ const { registerArenaHandlers } = require("./sockets/arena");
const { registerChatHandlers } = require("./sockets/chat"); const { registerChatHandlers } = require("./sockets/chat");
const boosterRoutes = require("./routes/booster.route"); const boosterRoutes = require("./routes/booster.route");
const pointsRoutes = require("./routes/points.route"); const pointsRoutes = require("./routes/points.route");
const shopRoutes = require("./routes/shop.route");
const compression = require("compression"); const compression = require("compression");
@ -58,14 +57,13 @@ app.use(
contentSecurityPolicy: { contentSecurityPolicy: {
directives: { directives: {
defaultSrc: ["'self'"], defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://js.stripe.com"], scriptSrc: ["'self'", "'unsafe-inline'"],
scriptSrcAttr: ["'unsafe-inline'"], scriptSrcAttr: ["'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"], fontSrc: ["'self'", "https://fonts.gstatic.com"],
imgSrc: ["'self'", "data:", "blob:", "https://*.stripe.com"], imgSrc: ["'self'", "data:", "blob:"],
connectSrc: ["'self'", "ws:", "wss:", "https://api.stripe.com"], connectSrc: ["'self'", "ws:", "wss:"],
frameSrc: ["https://js.stripe.com", "https://hooks.stripe.com"], frameAncestors: ["'self'"], // Erlaubt iframe von eigener Domain
frameAncestors: ["'self'"],
}, },
}, },
}), }),
@ -80,6 +78,9 @@ app.use(limiter);
/* ======================== /* ========================
Lösung 2: Session Config Lösung 2: Session Config
maxAge: 24h Sessions laufen
automatisch ab, auch wenn der
Browser einfach geschlossen wurde.
======================== */ ======================== */
app.use( app.use(
@ -90,7 +91,7 @@ app.use(
cookie: { cookie: {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === "production", secure: process.env.NODE_ENV === "production",
maxAge: 1000 * 60 * 60 * 24, maxAge: 1000 * 60 * 60 * 24, // 24 Stunden
}, },
}), }),
); );
@ -102,21 +103,30 @@ app.use(
app.set("view engine", "ejs"); app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views")); app.set("views", path.join(__dirname, "views"));
/* Webhook braucht raw body alle anderen json */ app.use(express.json());
app.use((req, res, next) => {
if (req.originalUrl === "/api/shop/webhook") {
express.raw({ type: "application/json" })(req, res, next);
} else {
express.json()(req, res, next);
}
});
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "public"))); app.use(express.static(path.join(__dirname, "public")));
/* ======================== /* ========================
Login Middleware Server Stats (öffentlich kein Login nötig)
Zählt online Spieler pro Server
via Socket.io Verbindungen
======================== */ ======================== */
app.get("/api/server-stats", (req, res) => {
const stats = {};
// Alle verbundenen Sockets durchgehen
const sockets = io.sockets.sockets;
sockets.forEach((socket) => {
if (socket.user && socket.serverId) {
stats[socket.serverId] = (stats[socket.serverId] || 0) + 1;
}
});
res.json(stats);
});
function requireLogin(req, res, next) { function requireLogin(req, res, next) {
if (!req.session.user) { if (!req.session.user) {
return res.status(401).json({ error: "Nicht eingeloggt" }); return res.status(401).json({ error: "Nicht eingeloggt" });
@ -325,7 +335,7 @@ app.get("/api/hud", requireLogin, async (req, res) => {
[userId], [userId],
); );
const [[currency]] = await db.query( const [[currency]] = await db.query(
"SELECT silver, gold, gems, wood, stone, iron FROM account_currency WHERE account_id = ?", "SELECT silver, gold, gems, wood, stone FROM account_currency WHERE account_id = ?",
[userId], [userId],
); );
res.json({ res.json({
@ -335,7 +345,6 @@ app.get("/api/hud", requireLogin, async (req, res) => {
gems: currency?.gems || 0, gems: currency?.gems || 0,
wood: currency?.wood || 0, wood: currency?.wood || 0,
stone: currency?.stone || 0, stone: currency?.stone || 0,
iron: currency?.iron || 0,
}); });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -392,7 +401,6 @@ app.use("/arena", arenaRoutes);
app.use("/api", boosterRoutes); app.use("/api", boosterRoutes);
app.use("/api", require("./routes/daily.route")); app.use("/api", require("./routes/daily.route"));
app.use("/api/points", pointsRoutes); app.use("/api/points", pointsRoutes);
app.use("/api", shopRoutes);
/* ======================== /* ========================
404 Handler 404 Handler

View File

@ -12,14 +12,15 @@ function registerChatHandlers(io, socket) {
/* ── Registrierung ── */ /* ── Registrierung ── */
socket.on("register", async (username) => { socket.on("register", async (username) => {
const [rows] = await db.query( const [rows] = await db.query(
"SELECT ingame_name FROM accounts WHERE username = ?", "SELECT ingame_name, server_id FROM accounts WHERE username = ?",
[username], [username],
); );
if (!rows.length) return; if (!rows.length) return;
const ingameName = rows[0].ingame_name; const ingameName = rows[0].ingame_name;
socket.user = ingameName; socket.user = ingameName;
socket.serverId = rows[0].server_id;
onlineUsers[ingameName] = socket.id; onlineUsers[ingameName] = socket.id;
}); });

View File

@ -52,17 +52,34 @@
<div class="server-status"> <div class="server-status">
<% servers.forEach(server => { %> <% servers.forEach(server => { %>
<%= server.name %>: <div class="server-status-row">
<span class="online">Online</span><br> <span class="server-name"><%= server.name %></span>
<span class="online">Online</span>
<span class="server-players" id="players-<%= server.id %>"> Spieler online</span>
</div>
<% }) %> <% }) %>
<% extraServers.forEach(server => { %> <% extraServers.forEach(server => { %>
<%= server.name %>: <div class="server-status-row">
<span class="offline">Offline</span><br> <span class="server-name"><%= server.name %></span>
<span class="offline">Offline</span>
</div>
<% }) %> <% }) %>
</div> </div>
<script>
fetch('/api/server-stats')
.then(r => r.json())
.then(stats => {
Object.entries(stats).forEach(([serverId, count]) => {
const el = document.getElementById('players-' + serverId);
if (el) el.textContent = ' ' + count + ' Spieler online';
});
})
.catch(() => {});
</script>
</div> </div>
<% if (typeof error !== 'undefined' && error) { %> <% if (typeof error !== 'undefined' && error) { %>