Major changes

- Improved e-mail display on some of the major clients that do not handle flexboxes properly (Gmail)
- Added ratelimiting (with Redis store)
- Sessions are now stored in Redis
- Added some serious login problem display
- Improved match saving into the MySQL database
- Login system enhancements
- Minor design improvements
- Bug fixes
- User security improvements
This commit is contained in:
MaciejkaG 2024-03-18 18:43:13 +01:00
parent dd963ec0a9
commit 3f97b240aa
8 changed files with 303 additions and 52 deletions

View File

@ -12,6 +12,9 @@ import { engine } from 'express-handlebars';
import { createClient } from 'redis';
import * as bships from './utils/battleships.js';
import { MailAuth } from './utils/auth.js';
import { rateLimit } from 'express-rate-limit';
import { RedisStore as LimiterRedisStore } from 'rate-limit-redis';
import SessionRedisStore from 'connect-redis';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -31,6 +34,17 @@ const redis = createClient();
redis.on('error', err => console.log('Redis Client Error', err));
await redis.connect();
const limiter = rateLimit({
windowMs: 40 * 1000,
limit: 100,
standardHeaders: 'draft-7',
legacyHeaders: false,
store: new LimiterRedisStore({
sendCommand: (...args) => redis.sendCommand(args),
}),
});
app.use(limiter);
const GInfo = new bships.GameInfo(redis, io);
const auth = new MailAuth(redis, {
host: process.env.mail_host,
@ -45,7 +59,14 @@ const auth = new MailAuth(redis, {
});
app.set('trust proxy', 1);
let sessionStore = new SessionRedisStore({
client: redis,
prefix: "statkiSession:",
});
const sessionMiddleware = session({
store: sessionStore,
secret: uuidv4(),
resave: true,
saveUninitialized: true,
@ -117,17 +138,29 @@ app.post('/api/login', (req, res) => {
res.redirect('/');
} else if (login == 0 && req.body.email != null && validateEmail(req.body.email)) {
auth.startVerification(req.body.email).then(result => {
if (result.status) {
if (result.status === 1) {
req.session.userId = result.uid;
req.session.loggedIn = 1;
res.redirect('/auth');
} else if (result.status === -1) {
res.render("error", {
helpers: {
error: "Nie udało się zalogować",
fallback: "/login"
}
});
} else {
res.sendStatus(500);
}
});
} else {
res.sendStatus(403);
res.render("error", {
helpers: {
error: "Niepoprawny adres e-mail",
fallback: "/login"
}
});
}
});
@ -141,10 +174,20 @@ app.post('/api/auth', async (req, res) => {
req.session.loggedIn = 2;
res.redirect('/');
} else {
res.sendStatus(401);
res.render("error", {
helpers: {
error: "Niepoprawny kod logowania",
fallback: "/auth"
}
});
}
} else {
res.sendStatus(403);
res.render("error", {
helpers: {
error: "Niepoprawny kod logowania",
fallback: "/login"
}
});
}
});
@ -212,10 +255,18 @@ io.on('connection', async (socket) => {
status: "bad_id"
});
} else {
let opp = io.sockets.sockets.get(io.sockets.adapter.rooms.get(msg).values().next().value);
if (opp.request.session.userId == session.userId) {
callback({
status: "cantJoinYourself",
});
return;
}
if (socket.rooms.size === 1) {
io.to(msg).emit("joined", session.nickname); // Wyślij hostowi powiadomienie o dołączającym graczu
// Zmienna opp zawiera socket hosta
let opp = io.sockets.sockets.get(io.sockets.adapter.rooms.get(msg).values().next().value);
// let opp = io.sockets.sockets.get(io.sockets.adapter.rooms.get(msg).values().next().value);
let oppNickname = opp.request.session.nickname;
socket.join(msg); // Dołącz gracza do grupy
@ -229,6 +280,7 @@ io.on('connection', async (socket) => {
redis.json.set(`game:${gameId}`, '$', {
hostId: opp.request.session.id,
state: "pregame",
startTs: (new Date()).getTime() / 1000,
boards: [
{ // typ 2 to trójmasztowiec pozycja i obrót na planszy które pola zostały trafione
ships: [], // zawiera np. {type: 2, posX: 3, posY: 4, rot: 2, hits: [false, false, true]}
@ -416,8 +468,9 @@ io.on('connection', async (socket) => {
hostSocket.emit("game finished", !enemyIdx ? 1 : 0, guestNickname);
guestSocket.emit("game finished", !enemyIdx ? 1 : 0, hostNickname);
const stats = await GInfo.getStats(socket);
auth.saveMatch(playerGame.id, "pvp", hostSocket.request.session.userId, guestSocket.request.session.userId, stats, !enemyIdx ? 1 : 0);
// const stats = await GInfo.getStats(socket);
const playerGame = await GInfo.getPlayerGameData(socket);
auth.saveMatch(playerGame.id, (new Date).getTime() / 1000 - playerGame.data.startTs, "pvp", hostSocket.request.session.userId, guestSocket.request.session.userId, playerGame.data.boards, !enemyIdx ? 1 : 0);
GInfo.resetTimer(playerGame.id);
endGame(playerGame.id, !enemyIdx ? 1 : 0);
@ -464,10 +517,10 @@ function resetUserGame(req) {
});
}
function endGame(gameId, winnerIdx = -1) {
const boards = redis.json.get(`game:${gameId}`, { keys: [".boards"] });
const hostUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
const guestUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
function endGame(gameId) {
// const boards = redis.json.get(`game:${gameId}`, { keys: [".boards"] });
// const hostUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
// const guestUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
let iterator = roomMemberIterator(gameId);
if (iterator != null) {

136
package-lock.json generated
View File

@ -9,10 +9,15 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"connect-redis": "^7.1.1",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"express-handlebars": "^7.1.2",
"express-rate-limit": "^7.2.0",
"express-session": "^1.18.0",
"mysql": "^2.18.1",
"nodemailer": "^6.9.12",
"rate-limit-redis": "^4.2.0",
"redis": "^4.6.13",
"socket.io": "^4.7.4",
"uuid": "^9.0.1",
@ -175,6 +180,14 @@
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==",
"engines": {
"node": "*"
}
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
@ -269,6 +282,17 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/connect-redis": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-7.1.1.tgz",
"integrity": "sha512-M+z7alnCJiuzKa8/1qAYdGUXHYfDnLolOGAUjOioB07pP39qxjG+X9ibsud7qUBc4jMV5Mcy3ugGv8eFcgamJQ==",
"engines": {
"node": ">=16"
},
"peerDependencies": {
"express-session": ">=1"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -301,6 +325,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@ -523,6 +552,20 @@
"node": ">=v16"
}
},
"node_modules/express-rate-limit": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz",
"integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": "4 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/express-session": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
@ -831,6 +874,11 @@
"node": ">=8"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -947,6 +995,25 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mysql": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
"dependencies": {
"bignumber.js": "9.0.0",
"readable-stream": "2.3.7",
"safe-buffer": "5.1.2",
"sqlstring": "2.3.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mysql/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@ -960,6 +1027,14 @@
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"node_modules/nodemailer": {
"version": "6.9.12",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.12.tgz",
"integrity": "sha512-pnLo7g37Br3jXbF0bl5DekBJihm2q+3bB3l2o/B060sWmb5l+VqeScAQCBqaQ+5ezRZFzW5SciZNGdRDEbq89w==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -1031,6 +1106,11 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -1073,6 +1153,17 @@
"node": ">= 0.6"
}
},
"node_modules/rate-limit-redis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-4.2.0.tgz",
"integrity": "sha512-wV450NQyKC24NmPosJb2131RoczLdfIJdKCReNwtVpm5998U8SgKrAZrIHaN/NfQgqOHaan8Uq++B4sa5REwjA==",
"engines": {
"node": ">= 16"
},
"peerDependencies": {
"express-rate-limit": ">= 6"
}
},
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
@ -1087,6 +1178,25 @@
"node": ">= 0.8"
}
},
"node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/redis": {
"version": "4.6.13",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz",
@ -1292,6 +1402,14 @@
"node": ">=0.10.0"
}
},
"node_modules/sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
"integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -1300,6 +1418,19 @@
"node": ">= 0.8"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@ -1444,6 +1575,11 @@
"node": ">= 0.8"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View File

@ -19,10 +19,15 @@
},
"homepage": "https://github.com/MaciejkaG/statki-backend#readme",
"dependencies": {
"connect-redis": "^7.1.1",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"express-handlebars": "^7.1.2",
"express-rate-limit": "^7.2.0",
"express-session": "^1.18.0",
"mysql": "^2.18.1",
"nodemailer": "^6.9.12",
"rate-limit-redis": "^4.2.0",
"redis": "^4.6.13",
"socket.io": "^4.7.4",
"uuid": "^9.0.1",

View File

@ -5,8 +5,8 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import mysql from 'mysql';
import { createClient } from 'redis';
import readline from "node:readline";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
@ -27,7 +27,7 @@ export class MailAuth {
port: options.port ? options.port : "465",
secure: options.secure ? options.secure : true,
auth: {
user: options.user,
user: options.user ? options.user : "root",
pass: options.pass,
},
});
@ -36,8 +36,8 @@ export class MailAuth {
}
async timer(tId, time, callback) {
await this.redis.set(`timer:${tId}`, new Date().getTime() / 1000);
let localLastUpdate = await this.redis.get(`timer:${tId}`);
await this.redis.set(`loginTimer:${tId}`, new Date().getTime() / 1000);
let localLastUpdate = await this.redis.get(`loginTimer:${tId}`);
let timeout = setTimeout(callback, time * 1000);
@ -47,7 +47,7 @@ export class MailAuth {
return;
}
let lastUpdate = await this.redis.get(`timer:${tId}`);
let lastUpdate = await this.redis.get(`loginTimer:${tId}`);
if (localLastUpdate != lastUpdate) {
clearTimeout(timeout);
clearInterval(interval);
@ -57,31 +57,34 @@ export class MailAuth {
}
async resetTimer(tId) {
let lastUpdate = await this.redis.get(`timer:${tId}`);
await this.redis.set(`timer:${tId}`, -lastUpdate);
let lastUpdate = await this.redis.get(`loginTimer:${tId}`);
await this.redis.set(`loginTimer:${tId}`, -lastUpdate);
}
startVerification(email) {
return new Promise((resolve, reject) => {
const conn = mysql.createConnection(this.mysqlOptions);
conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => {
if (error) reject(error);
if (error) { reject(error); return; }
if (response.length !== 0 && await this.redis.get(`loginTimer:${response[0].user_id}`)) {
resolve({ status: -1 });
return;
}
if (response.length === 0 || response[0].nickname == null) {
conn.query(`DELETE FROM accounts WHERE email = ${conn.escape(email)};`, (error, response) => { if (error) reject(error) });
conn.query(`INSERT INTO accounts(email) VALUES (${conn.escape(email)});`, (error, response) => { if (error) reject(error) });
conn.query(`INSERT INTO accounts(email) VALUES (${conn.escape(email)});`, (error) => { if (error) reject(error) });
conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => {
if (error) reject(error);
const row = response[0];
const html = fs.readFileSync(path.join(__dirname, 'mail/auth-code-firsttime.html'), 'utf8');
let authCode = genCode();
let tId = uuid4();
await this.redis.json.set(`code_auth:${authCode}`, "$", { uid: row.user_id, tid: tId });
await this.redis.json.set(`codeAuth:${authCode}`, "$", { uid: row.user_id, tid: tId });
await this.timer(tId, 600, async () => {
await this.redis.json.del(`code_auth:${authCode}`);
await this.timer(row.user_id, 600, async () => {
await this.redis.json.del(`codeAuth:${authCode}`);
});
authCode = authCode.slice(0, 4) + " " + authCode.slice(4);
@ -107,12 +110,11 @@ export class MailAuth {
const html = fs.readFileSync(path.join(__dirname, 'mail/auth-code.html'), 'utf8');
let authCode = genCode();
let tId = uuid4();
await this.redis.json.set(`code_auth:${authCode}`, "$", { uid: row.user_id, tid: tId });
await this.redis.set(`codeAuth:${authCode}`, row.user_id);
await this.timer(tId, 600, async () => {
await this.redis.json.del(`code_auth:${authCode}`);
await this.timer(row.user_id, 600, async () => {
await this.redis.json.del(`codeAuth:${authCode}`);
});
authCode = authCode.slice(0, 4) + " " + authCode.slice(4);
@ -129,14 +131,14 @@ export class MailAuth {
});
}
saveMatch(matchId, type, hostId, guestId, stats, winnerIdx) {
saveMatch(matchId, duration, type, hostId, guestId, boards, winnerIdx) {
return new Promise((resolve, reject) => {
const conn = mysql.createConnection(this.mysqlOptions);
conn.query(`INSERT INTO matches(match_id, match_type, host_id, guest_id) VALUES (${conn.escape(matchId)}, ${conn.escape(type)}, ${conn.escape(hostId)}, ${conn.escape(guestId)})`, async (error, response) => {
conn.query(`INSERT INTO matches(match_id, match_type, host_id, guest_id, duration) VALUES (${conn.escape(matchId)}, ${conn.escape(type)}, ${conn.escape(hostId)}, ${conn.escape(guestId)}, ${conn.escape(duration)})`, async (error) => {
if (error) reject(error);
conn.query(`INSERT INTO statistics(match_id, user_id, shots, hits, placed_ships, sunk_ships, sunk_ships_by, won) VALUES (${conn.escape(matchId)}, ${conn.escape(hostId)}, ${conn.escape(stats[0].shots)}, ${conn.escape(stats[0].hits)}, ${conn.escape(stats[0].placedShips)}, ${conn.escape(stats[0].sunkShips)}, ${conn.escape(stats[0].sunkShipsBy)}, ${conn.escape(winnerIdx == 0)}), (${conn.escape(matchId)}, ${conn.escape(guestId)}, ${conn.escape(stats[1].shots)}, ${conn.escape(stats[1].hits)}, ${conn.escape(stats[1].placedShips)}, ${conn.escape(stats[1].sunkShips)}, ${conn.escape(stats[1].sunkShipsBy)}, ${conn.escape(winnerIdx == 1)})`).then((error) => {
else conn.query(`INSERT INTO statistics(match_id, user_id, board, won) VALUES (${conn.escape(matchId)}, ${conn.escape(hostId)}, ${conn.escape(JSON.stringify(boards[0]))}, ${conn.escape(winnerIdx == 0 ? 1 : 0)}), (${conn.escape(matchId)}, ${conn.escape(guestId)}, ${conn.escape(JSON.stringify(boards[1]))}, ${conn.escape(winnerIdx == 1 ? 1 : 0)})`, async (error, response) => {
if (error) reject(error);
resolve();
else resolve();
});
});
});
@ -144,10 +146,10 @@ export class MailAuth {
async finishVerification(uid, authCode) {
authCode = authCode.replace(/\s+/g, "");
let redisRes = await this.redis.json.get(`code_auth:${authCode}`);
if (redisRes != null && redisRes.uid === uid) {
this.resetTimer(redisRes.tid);
await this.redis.del(`code_auth:${authCode}`);
const rUid = await this.redis.get(`codeAuth:${authCode}`);
if (rUid != null && rUid === uid) {
this.resetTimer(rUid);
await this.redis.del(`codeAuth:${authCode}`);
return true;
} else {
return false;

View File

@ -1,22 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&family=Poppins:ital,wght@0,600;1,600&family=Roboto+Mono:ital@0;1&display=swap" rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&family=Poppins:ital,wght@0,600;1,600&family=Roboto+Mono:ital@0;1&display=swap"
rel="stylesheet">
</head>
<body style="background: rgb(0, 0, 0);color: white;;font-family: 'Lato', sans-serif;padding: 30px;">
<div
style="margin:30px;text-align: center;display:flex;flex-direction: column;justify-content: center;font-size:1.3em;margin: auto;width: 35%;min-width:25em;padding:20px;">
<div style="margin:30px;text-align: center;font-size:1.3em;margin: auto;width: 35%;min-width:25em;padding:20px;">
<h2 style="font-family: 'Poppins', sans-serif;">Cześć!</h2>
<p>Ktoś próbował utworzyć konto w Statkach za pomocą tego e-maila, jeśli to nie byłeś ty - zignoruj tę wiadomość.<br>Poniżej znajduje się kod autoryzacyjny, pamiętaj by nie udostępniać go nikomu.</p>
<div style="border: solid white 1px; border-radius: 15px;font-family: 'Roboto Mono', monospace;padding:15px;font-size: 1.5em;">
<p>Ktoś próbował utworzyć konto w Statkach przy użyciu Twojego adresu e-mail. Jeśli to nie byłeś ty, zignoruj tą
wiadomość.<br>Poniżej znajduje się kod autoryzacyjny, pamiętaj by nie udostępniać go nikomu.</p>
<div
style="border: solid white 1px; border-radius: 15px;font-family: 'Roboto Mono', monospace;padding:15px;font-size: 1.5em;">
{{ CODE }}
</div>
<p>Powyższy kod wygasa po 10 minutach</p>
</div>
<p style="color:white;margin-bottom: 20px;margin-top: 20px;text-align: center;"><a href="https://maciejka.xyz/"
style="text-decoration: none;color:white;">Copyright © 2024 MCJK</a> | <a href="https://statki.maciejka.xyz/"
style="text-decoration: none;color: rgb(85, 111, 255);">statki.maciejka.xyz</a><br>Ta wiadomość została wysłana automatycznie, nie odpowiadaj na nią.</p>
style="text-decoration: none;color:white;">Copyright © 2024 MCJK</a> | <a
href="https://statki.maciejka.xyz/"
style="text-decoration: none;color: rgb(85, 111, 255);">statki.maciejka.xyz</a><br>Ta wiadomość została
wysłana automatycznie, nie odpowiadaj na nią.</p>
</body>
</html>

View File

@ -1,22 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&family=Poppins:ital,wght@0,600;1,600&family=Roboto+Mono:ital@0;1&display=swap" rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&family=Poppins:ital,wght@0,600;1,600&family=Roboto+Mono:ital@0;1&display=swap"
rel="stylesheet">
</head>
<body style="background: rgb(0, 0, 0);color: white;;font-family: 'Lato', sans-serif;padding: 30px;">
<div
style="margin:30px;text-align: center;display:flex;flex-direction: column;justify-content: center;font-size:1.3em;margin: auto;width: 35%;min-width:25em;padding:20px;">
<div style="margin:30px;text-align: center;font-size:1.3em;margin: auto;width: 35%;min-width:25em;padding:20px;">
<h2 style="font-family: 'Poppins', sans-serif;">Hej, {{ NICKNAME }}!</h2>
<p>Ktoś próbował się zalogować na twoje konto w Statkach, jeśli to nie byłeś ty - zignoruj tę wiadomość.<br>Poniżej znajduje się kod autoryzacyjny, pamiętaj by nie udostępniać go nikomu.</p>
<div style="border: solid white 1px; border-radius: 15px;font-family: 'Roboto Mono', monospace;padding:15px;font-size: 1.5em;">
<p>Ktoś próbował się zalogować na twoje konto w Statkach, jeśli to nie byłeś ty - zignoruj tę
wiadomość.<br>Poniżej znajduje się kod autoryzacyjny, pamiętaj by nie udostępniać go nikomu.</p>
<div
style="border: solid white 1px; border-radius: 15px;font-family: 'Roboto Mono', monospace;padding:15px;font-size: 1.5em;">
{{ CODE }}
</div>
<p>Powyższy kod wygasa po 10 minutach</p>
</div>
<p style="color:white;margin-bottom: 20px;margin-top: 20px;text-align: center;"><a href="https://maciejka.xyz/"
style="text-decoration: none;color:white;">Copyright © 2024 MCJK</a> | <a href="https://statki.maciejka.xyz/"
style="text-decoration: none;color: rgb(85, 111, 255);">statki.maciejka.xyz</a><br>Ta wiadomość została wysłana automatycznie, nie odpowiadaj na nią.</p>
style="text-decoration: none;color:white;">Copyright © 2024 MCJK</a> | <a
href="https://statki.maciejka.xyz/"
style="text-decoration: none;color: rgb(85, 111, 255);">statki.maciejka.xyz</a><br>Ta wiadomość została
wysłana automatycznie, nie odpowiadaj na nią.</p>
</body>
</html>

39
views/error.handlebars Normal file
View File

@ -0,0 +1,39 @@
<style>
.container {
display: flex;
}
#pvpJoinView .modes div {
height: 13rem;
}
button {
background-color: black;
color: white;
border-radius: 15px;
border: 1px solid white;
text-align: center;
padding: 0.5rem 2rem;
outline: none;
cursor: pointer;
font-size: 1rem;
transition: all 0.3s;
}
button:hover {
background-color: white;
color: black;
}
</style>
<div class="container" id="pvpJoinView">
<div>
<h1>Statki</h1>
<h2>Błąd</h2>
<div class="modes">
<div>
<p>{{ error }}</p>
<button onclick="window.location.href = '{{ fallback }}'">Wróć</button>
</div>
</div>
</div>
</div>

View File

@ -52,7 +52,7 @@
<div class="modes">
<div>
<form action="/api/joinme" id="pvpJoinForm">
<input type="text" maxlength="6" id="pvpJoinCode" placeholder="Kod pokoju">
<input type="text" maxlength="6" id="pvpJoinCode" placeholder="Kod pokoju" autocomplete="off">
<input type="submit" value="Dołącz">
</form>
</div>