Server Saving
Een project om spelersprofielen veilig extern op te slaan, zodat spelers op elk apparaat verder kunnen spelen.
- Beveiliging:
- Inloggen met bcrypt‑gehashte wachtwoorden
- JWT tokens (verlopen na 2 uur)
- Profielen versleuteld met AES‑256‑GCM (authenticatie + integriteit)
- Datamodel:
- Users en Players tabellen
- Unieke combinatie
user_id + name - Profielnaam‑normalisatie (spaties/hoofdletters/tekens eenduidig)
- API:
- Endpoints voor register, login, save, load, list en delete
- Unity client:
- TMP‑UI en standaard UI voorbeelden
- Save/Load/List met UnityWebRequest
- Schermmeldingen en foutafhandeling
Code highlights
Server – versleutelen/ontsleutelen
- Laat zien dat profielen niet in plain text op de server staan en hoe IV + auth tag worden beheerd.
// server/server.js
function encrypt(plain) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-gcm", ENCRYPTION_KEY, iv);
let enc = cipher.update(plain, "utf8", "hex");
enc += cipher.final("hex");
const tag = cipher.getAuthTag();
return `${iv.toString("hex")}:${tag.toString("hex")}:${enc}`;
}
function decrypt(blob) {
const [ivHex, tagHex, dataHex] = blob.split(":");
const iv = Buffer.from(ivHex, "hex");
const tag = Buffer.from(tagHex, "hex");
const decipher = crypto.createDecipheriv("aes-256-gcm", ENCRYPTION_KEY, iv);
decipher.setAuthTag(tag);
let dec = decipher.update(dataHex, "hex", "utf8");
dec += decipher.final("utf8");
return dec;
}
Server – profielnaam normaliseren + auth middleware
- Toont input‑sanitatie en toegangscontrole zonder veel boilerplate.
// server/server.js
function normalizeName(raw) {
if (typeof raw !== "string") return null;
let s = raw.replace(/\+/g, " ");
try { s = decodeURIComponent(s); } catch {}
s = s.trim().toLowerCase();
if (s.length === 0 || s.length > 32) return null;
return /^[a-z0-9 _-]+$/.test(s) ? s : null;
}
function authenticate(req, res, next) {
const header = req.headers.authorization;
if (!header) return res.status(401).json({ error: "Missing token" });
const [type, token] = header.split(" ");
if (type !== "Bearer" || !token) return res.status(401).json({ error: "Invalid auth header" });
try {
req.user = jwt.verify(token, JWT_SECRET);
next();
} catch {
res.status(401).json({ error: "Invalid token" });
}
}
Server – opslaan van een profiel (upsert + versleuteling)
- Concreet hoe data veilig wordt opgeslagen en hoe dubbele profielen netjes worden geüpdatet.
// server/server.js
app.post("/player/:name", authenticate, (req, res) => {
const user_id = req.user.user_id;
const nameKey = normalizeName(req.params.name);
if (!nameKey) return res.status(400).json({ error: "Invalid profile name" });
let { money, level } = req.body || {};
money = Number.isFinite(+money) ? +money : 0;
level = Number.isFinite(+level) ? +level : 1;
const encrypted = encrypt(JSON.stringify({ money, level }));
db.run(
`INSERT INTO players (user_id, name, data)
VALUES (?, ?, ?)
ON CONFLICT(user_id, name) DO UPDATE SET data=excluded.data`,
[user_id, nameKey, encrypted],
(err) => err ? res.status(500).json({ error: err.message }) : res.json({ success: true })
);
});
Unity client – inloggen en token opslaan
- Laat zien hoe de client een token krijgt en UI‑toegang vrijgeeft na inloggen.
// server-saving/Assets/Scripts/SecureServerClientTMP.cs
IEnumerator LoginCoroutine(string username, string password)
{
string url = baseUrl.TrimEnd('/') + "/auth/login";
string json = $"{{\"username\":\"{username}\",\"password\":\"{password}\"}}";
using (UnityWebRequest req = new UnityWebRequest(url, "POST"))
{
req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json");
yield return req.SendWebRequest();
if (req.result == UnityWebRequest.Result.Success)
{
var resp = JsonUtility.FromJson(req.downloadHandler.text);
jwtToken = resp?.token ?? "";
SetActionsInteractable(!string.IsNullOrEmpty(jwtToken));
SetInfo(string.IsNullOrEmpty(jwtToken) ? "Login failed" : "Login success!");
}
else
{
SetInfo($"Login error: {req.responseCode} {req.error} {req.downloadHandler.text}");
SetActionsInteractable(false);
}
}
}
Unity client – save profiel met Bearer token
- Kort en zelfstandig: toont de essentie van authenticated save.
// server-saving/Assets/Scripts/SecureServerClientTMP.cs
IEnumerator SavePlayerDataCoroutine(string playerName, int money, int level)
{
if (string.IsNullOrWhiteSpace(jwtToken)) { SetInfo("Login eerst."); yield break; }
string url = baseUrl.TrimEnd('/') + "/player/" + UnityWebRequest.EscapeURL(playerName);
string json = $"{{\"money\":{money},\"level\":{level}}}";
using (UnityWebRequest req = new UnityWebRequest(url, "POST"))
{
req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json");
req.SetRequestHeader("Authorization", "Bearer " + jwtToken);
yield return req.SendWebRequest();
SetInfo(req.result == UnityWebRequest.Result.Success ? "Data saved!" :
$"Save error: {req.responseCode} {req.error} {req.downloadHandler.text}");
}
}