🔒 Mängu Turvalisus
Serveri-poolne valideerimine, anti-cheat, andmekaitse, XSS/CSRF, turvaline WebSocket — kaitse oma mängu!
🛡️ Samm 1 — Serveri-poolne Valideerimine
Kõik, mis tuleb kliendilt, võib olla võltsitud. Klient saadab sisendi (klahvivajutused), server arvutab tulemuse (positsioon, HP, skoor).
// ======= SERVER (Node.js + Express) =======
interface PlayerState {
id: string;
x: number;
y: number;
hp: number;
score: number;
lastMoveTime: number;
speed: number; // max lubatud kiirus
}
class GameServer {
private players: Map<string, PlayerState> = new Map();
handleMove(playerId: string, input: { dx: number; dy: number }): void {
const player = this.players.get(playerId);
if (!player) return;
const now = Date.now();
const delta = (now - player.lastMoveTime) / 1000;
player.lastMoveTime = now;
// ✅ Valideeri sisend
const dx = Math.max(-1, Math.min(1, input.dx)); // Clamp -1..1
const dy = Math.max(-1, Math.min(1, input.dy));
// ✅ Kiiruse kontroll
const moveX = dx * player.speed * delta;
const moveY = dy * player.speed * delta;
const distance = Math.sqrt(moveX * moveX + moveY * moveY);
const maxDistance = player.speed * delta * 1.1; // 10% tolerants
if (distance > maxDistance) {
console.warn(`[CHEAT] Mängija ${playerId}: liiga kiire liikumine!`);
this.flagPlayer(playerId, 'speed_hack');
return;
}
// ✅ Seinade kontroll
const newX = player.x + moveX;
const newY = player.y + moveY;
if (this.isWalkable(newX, newY)) {
player.x = newX;
player.y = newY;
}
}
handleDamage(attackerId: string, targetId: string): void {
const attacker = this.players.get(attackerId);
const target = this.players.get(targetId);
if (!attacker || !target) return;
// ✅ Kauguse kontroll
const dist = Math.hypot(attacker.x - target.x, attacker.y - target.y);
if (dist > 3) { // Max rünnaku kaugus
console.warn(`[CHEAT] ${attackerId}: ründab kaugelt (${dist})`);
return;
}
// ✅ Cooldown kontroll
// ✅ Server arvutab damage, MITTE klient
const damage = this.calculateDamage(attacker);
target.hp = Math.max(0, target.hp - damage);
}
private flagPlayer(id: string, reason: string): void {
// Loggi, ära bänni kohe (false positives)
console.log(`[ANTI-CHEAT] Flag: ${id} — ${reason}`);
}
private isWalkable(x: number, y: number): boolean { return true; }
private calculateDamage(attacker: PlayerState): number { return 10; }
}
🔐 Samm 2 — Anti-Cheat Meetodid
class AntiCheatSystem {
private violations: Map<string, number> = new Map();
private readonly MAX_VIOLATIONS = 5;
// 1. Kiiruse kontroll (speedhack tuvastamine)
checkSpeed(player: PlayerState, newPos: { x: number; y: number }, delta: number): boolean {
const dist = Math.hypot(newPos.x - player.x, newPos.y - player.y);
const maxDist = player.speed * delta * 1.2; // 20% tolerants latency jaoks
if (dist > maxDist) {
this.addViolation(player.id, 'speed');
return false;
}
return true;
}
// 2. Teleporti tuvastamine
checkTeleport(player: PlayerState, newPos: { x: number; y: number }): boolean {
const dist = Math.hypot(newPos.x - player.x, newPos.y - player.y);
if (dist > 50) { // Mitte keegi ei liigu 50 ühikut ühe frame'iga
this.addViolation(player.id, 'teleport');
return false;
}
return true;
}
// 3. Sõnumite sageduse kontroll (firerate hack)
private messageTimestamps: Map<string, number[]> = new Map();
checkMessageRate(playerId: string, maxPerSecond = 30): boolean {
const now = Date.now();
if (!this.messageTimestamps.has(playerId)) {
this.messageTimestamps.set(playerId, []);
}
const timestamps = this.messageTimestamps.get(playerId)!;
timestamps.push(now);
// Eemalda vanemad kui 1s
while (timestamps.length > 0 && timestamps[0] < now - 1000) {
timestamps.shift();
}
if (timestamps.length > maxPerSecond) {
this.addViolation(playerId, 'message_flood');
return false;
}
return true;
}
// 4. HP manipulatsiooni tuvastamine
checkHealth(player: PlayerState, reportedHp: number): boolean {
if (reportedHp > player.hp) {
this.addViolation(player.id, 'hp_hack');
return false; // HP ei saa kasvada ilma heal'imata
}
return true;
}
private addViolation(playerId: string, type: string): void {
const count = (this.violations.get(playerId) || 0) + 1;
this.violations.set(playerId, count);
console.warn(`[ANTI-CHEAT] ${playerId}: ${type} (${count}/${this.MAX_VIOLATIONS})`);
if (count >= this.MAX_VIOLATIONS) {
this.banPlayer(playerId);
}
}
private banPlayer(playerId: string): void {
console.log(`[BAN] Mängija ${playerId} banned!`);
// Disconnect + blacklist
}
}
🌐 Samm 3 — Veebiturvalisus
// ====== 1. XSS KAITSE ======
// HALB: Mängija nimi otse HTML-i
// element.innerHTML = player.name; // ❌ <script>hack()</script>
// HEA: Sanitiseeri sisend
function sanitize(input: string): string {
const div = document.createElement('div');
div.textContent = input; // Automaatne escaping
return div.innerHTML;
}
// Või kasuta DOMPurify:
// import DOMPurify from 'dompurify';
// element.innerHTML = DOMPurify.sanitize(player.name);
// ====== 2. TURVALINE WebSocket ======
import { WebSocketServer } from 'ws';
import jwt from 'jsonwebtoken';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws, req) => {
// Autentimise token päisest
const token = new URL(req.url!, 'http://localhost').searchParams.get('token');
try {
const payload = jwt.verify(token!, process.env.JWT_SECRET!);
ws.userId = (payload as any).userId;
} catch {
ws.close(4001, 'Unauthorized');
return;
}
// Rate limiting
let messageCount = 0;
const resetInterval = setInterval(() => { messageCount = 0; }, 1000);
ws.on('message', (raw) => {
messageCount++;
if (messageCount > 60) {
ws.close(4002, 'Rate limit exceeded');
return;
}
// Valideeri JSON
try {
const msg = JSON.parse(raw.toString());
if (!isValidMessage(msg)) throw new Error('Invalid format');
handleMessage(ws, msg);
} catch {
ws.close(4003, 'Invalid message');
}
});
ws.on('close', () => clearInterval(resetInterval));
});
// ====== 3. CSP HEADER ======
// Express middleware:
// app.use((req, res, next) => {
// res.setHeader('Content-Security-Policy',
// "default-src 'self'; " +
// "script-src 'self'; " + // Ainult oma skriptid
// "style-src 'self' 'unsafe-inline'; " +
// "img-src 'self' data: blob:; " +
// "connect-src 'self' wss://yourserver.com"
// );
// next();
// });
📝 Harjutused
-
Serveri-poolne valideerimine
Loo lihtne mänguserver: mängija liikumine ainult serveris. Klient saadab input, server vastab positsiooniga. Testi DevToolsiga manipuleerimist.
-
Anti-cheat süsteem
Loo AntiCheat: speed check, teleport detection, rate limiting. Violation counter → ban. Testi: modifitseeri kliendi koodi ja vaata kas server tuvastab.
-
Turvaline chat süsteem
Mängusisene chat: XSS kaitse (sanitize), profanity filter, rate limiting (max 5 sõnumit/10s). Serveri-poolne logi. Mute funktsioon.
-
JWT autentimine mängus
Login → JWT token → WebSocket autentimine. Token expiry + refresh. Turvaline salvestamine (httpOnly cookie vs memory). Logout kõigist seadmetest.
-
Skooride turvaline salvestamine
Leaderboard: skoorid ainult serveris. Klient saadab mängutegevused, server arvutab skoori. HMAC allkiri lisaturvalisuseks. Replay valideerimine.