mirror of
https://github.com/MaciejkaG/statki.git
synced 2024-11-30 07:42:54 +01:00
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:
parent
dd963ec0a9
commit
3f97b240aa
75
index.js
75
index.js
@ -12,6 +12,9 @@ import { engine } from 'express-handlebars';
|
|||||||
import { createClient } from 'redis';
|
import { createClient } from 'redis';
|
||||||
import * as bships from './utils/battleships.js';
|
import * as bships from './utils/battleships.js';
|
||||||
import { MailAuth } from './utils/auth.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 __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@ -31,6 +34,17 @@ const redis = createClient();
|
|||||||
redis.on('error', err => console.log('Redis Client Error', err));
|
redis.on('error', err => console.log('Redis Client Error', err));
|
||||||
await redis.connect();
|
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 GInfo = new bships.GameInfo(redis, io);
|
||||||
const auth = new MailAuth(redis, {
|
const auth = new MailAuth(redis, {
|
||||||
host: process.env.mail_host,
|
host: process.env.mail_host,
|
||||||
@ -45,7 +59,14 @@ const auth = new MailAuth(redis, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
|
|
||||||
|
let sessionStore = new SessionRedisStore({
|
||||||
|
client: redis,
|
||||||
|
prefix: "statkiSession:",
|
||||||
|
});
|
||||||
|
|
||||||
const sessionMiddleware = session({
|
const sessionMiddleware = session({
|
||||||
|
store: sessionStore,
|
||||||
secret: uuidv4(),
|
secret: uuidv4(),
|
||||||
resave: true,
|
resave: true,
|
||||||
saveUninitialized: true,
|
saveUninitialized: true,
|
||||||
@ -117,17 +138,29 @@ app.post('/api/login', (req, res) => {
|
|||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
} else if (login == 0 && req.body.email != null && validateEmail(req.body.email)) {
|
} else if (login == 0 && req.body.email != null && validateEmail(req.body.email)) {
|
||||||
auth.startVerification(req.body.email).then(result => {
|
auth.startVerification(req.body.email).then(result => {
|
||||||
if (result.status) {
|
if (result.status === 1) {
|
||||||
req.session.userId = result.uid;
|
req.session.userId = result.uid;
|
||||||
|
|
||||||
req.session.loggedIn = 1;
|
req.session.loggedIn = 1;
|
||||||
res.redirect('/auth');
|
res.redirect('/auth');
|
||||||
|
} else if (result.status === -1) {
|
||||||
|
res.render("error", {
|
||||||
|
helpers: {
|
||||||
|
error: "Nie udało się zalogować",
|
||||||
|
fallback: "/login"
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} 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;
|
req.session.loggedIn = 2;
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(401);
|
res.render("error", {
|
||||||
|
helpers: {
|
||||||
|
error: "Niepoprawny kod logowania",
|
||||||
|
fallback: "/auth"
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} 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"
|
status: "bad_id"
|
||||||
});
|
});
|
||||||
} else {
|
} 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) {
|
if (socket.rooms.size === 1) {
|
||||||
io.to(msg).emit("joined", session.nickname); // Wyślij hostowi powiadomienie o dołączającym graczu
|
io.to(msg).emit("joined", session.nickname); // Wyślij hostowi powiadomienie o dołączającym graczu
|
||||||
// Zmienna opp zawiera socket hosta
|
// 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;
|
let oppNickname = opp.request.session.nickname;
|
||||||
|
|
||||||
socket.join(msg); // Dołącz gracza do grupy
|
socket.join(msg); // Dołącz gracza do grupy
|
||||||
@ -229,6 +280,7 @@ io.on('connection', async (socket) => {
|
|||||||
redis.json.set(`game:${gameId}`, '$', {
|
redis.json.set(`game:${gameId}`, '$', {
|
||||||
hostId: opp.request.session.id,
|
hostId: opp.request.session.id,
|
||||||
state: "pregame",
|
state: "pregame",
|
||||||
|
startTs: (new Date()).getTime() / 1000,
|
||||||
boards: [
|
boards: [
|
||||||
{ // typ 2 to trójmasztowiec pozycja i obrót na planszy które pola zostały trafione
|
{ // 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]}
|
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);
|
hostSocket.emit("game finished", !enemyIdx ? 1 : 0, guestNickname);
|
||||||
guestSocket.emit("game finished", !enemyIdx ? 1 : 0, hostNickname);
|
guestSocket.emit("game finished", !enemyIdx ? 1 : 0, hostNickname);
|
||||||
|
|
||||||
const stats = await GInfo.getStats(socket);
|
// const stats = await GInfo.getStats(socket);
|
||||||
auth.saveMatch(playerGame.id, "pvp", hostSocket.request.session.userId, guestSocket.request.session.userId, stats, !enemyIdx ? 1 : 0);
|
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);
|
GInfo.resetTimer(playerGame.id);
|
||||||
endGame(playerGame.id, !enemyIdx ? 1 : 0);
|
endGame(playerGame.id, !enemyIdx ? 1 : 0);
|
||||||
@ -464,10 +517,10 @@ function resetUserGame(req) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function endGame(gameId, winnerIdx = -1) {
|
function endGame(gameId) {
|
||||||
const boards = redis.json.get(`game:${gameId}`, { keys: [".boards"] });
|
// const boards = redis.json.get(`game:${gameId}`, { keys: [".boards"] });
|
||||||
const hostUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
|
// const hostUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
|
||||||
const guestUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
|
// const guestUid = redis.json.get(`game:${gameId}`, { keys: [".hostUserId"] });
|
||||||
|
|
||||||
let iterator = roomMemberIterator(gameId);
|
let iterator = roomMemberIterator(gameId);
|
||||||
if (iterator != null) {
|
if (iterator != null) {
|
||||||
|
136
package-lock.json
generated
136
package-lock.json
generated
@ -9,10 +9,15 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"connect-redis": "^7.1.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
"express-handlebars": "^7.1.2",
|
"express-handlebars": "^7.1.2",
|
||||||
|
"express-rate-limit": "^7.2.0",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"mysql": "^2.18.1",
|
||||||
|
"nodemailer": "^6.9.12",
|
||||||
|
"rate-limit-redis": "^4.2.0",
|
||||||
"redis": "^4.6.13",
|
"redis": "^4.6.13",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
@ -175,6 +180,14 @@
|
|||||||
"node": "^4.5.0 || >= 5.9"
|
"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": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"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": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
"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": {
|
"node_modules/cors": {
|
||||||
"version": "2.8.5",
|
"version": "2.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||||
@ -523,6 +552,20 @@
|
|||||||
"node": ">=v16"
|
"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": {
|
"node_modules/express-session": {
|
||||||
"version": "1.18.0",
|
"version": "1.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
|
||||||
@ -831,6 +874,11 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"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": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
"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": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
"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": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@ -1073,6 +1153,17 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/raw-body": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
@ -1087,6 +1178,25 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/redis": {
|
||||||
"version": "4.6.13",
|
"version": "4.6.13",
|
||||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz",
|
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz",
|
||||||
@ -1292,6 +1402,14 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@ -1300,6 +1418,19 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/string-width": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
@ -1444,6 +1575,11 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
@ -19,10 +19,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/MaciejkaG/statki-backend#readme",
|
"homepage": "https://github.com/MaciejkaG/statki-backend#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"connect-redis": "^7.1.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
"express-handlebars": "^7.1.2",
|
"express-handlebars": "^7.1.2",
|
||||||
|
"express-rate-limit": "^7.2.0",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"mysql": "^2.18.1",
|
||||||
|
"nodemailer": "^6.9.12",
|
||||||
|
"rate-limit-redis": "^4.2.0",
|
||||||
"redis": "^4.6.13",
|
"redis": "^4.6.13",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
@ -5,8 +5,8 @@ import path from 'node:path';
|
|||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import mysql from 'mysql';
|
import mysql from 'mysql';
|
||||||
import { createClient } from 'redis';
|
|
||||||
import readline from "node:readline";
|
import readline from "node:readline";
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout
|
||||||
@ -27,7 +27,7 @@ export class MailAuth {
|
|||||||
port: options.port ? options.port : "465",
|
port: options.port ? options.port : "465",
|
||||||
secure: options.secure ? options.secure : true,
|
secure: options.secure ? options.secure : true,
|
||||||
auth: {
|
auth: {
|
||||||
user: options.user,
|
user: options.user ? options.user : "root",
|
||||||
pass: options.pass,
|
pass: options.pass,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -36,8 +36,8 @@ export class MailAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async timer(tId, time, callback) {
|
async timer(tId, time, callback) {
|
||||||
await this.redis.set(`timer:${tId}`, new Date().getTime() / 1000);
|
await this.redis.set(`loginTimer:${tId}`, new Date().getTime() / 1000);
|
||||||
let localLastUpdate = await this.redis.get(`timer:${tId}`);
|
let localLastUpdate = await this.redis.get(`loginTimer:${tId}`);
|
||||||
|
|
||||||
let timeout = setTimeout(callback, time * 1000);
|
let timeout = setTimeout(callback, time * 1000);
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ export class MailAuth {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastUpdate = await this.redis.get(`timer:${tId}`);
|
let lastUpdate = await this.redis.get(`loginTimer:${tId}`);
|
||||||
if (localLastUpdate != lastUpdate) {
|
if (localLastUpdate != lastUpdate) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
@ -57,31 +57,34 @@ export class MailAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async resetTimer(tId) {
|
async resetTimer(tId) {
|
||||||
let lastUpdate = await this.redis.get(`timer:${tId}`);
|
let lastUpdate = await this.redis.get(`loginTimer:${tId}`);
|
||||||
await this.redis.set(`timer:${tId}`, -lastUpdate);
|
await this.redis.set(`loginTimer:${tId}`, -lastUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
startVerification(email) {
|
startVerification(email) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const conn = mysql.createConnection(this.mysqlOptions);
|
const conn = mysql.createConnection(this.mysqlOptions);
|
||||||
conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => {
|
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) {
|
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) => { if (error) reject(error) });
|
||||||
conn.query(`INSERT INTO accounts(email) VALUES (${conn.escape(email)});`, (error, response) => { if (error) reject(error) });
|
|
||||||
conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => {
|
conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => {
|
||||||
if (error) reject(error);
|
if (error) reject(error);
|
||||||
const row = response[0];
|
const row = response[0];
|
||||||
|
|
||||||
const html = fs.readFileSync(path.join(__dirname, 'mail/auth-code-firsttime.html'), 'utf8');
|
const html = fs.readFileSync(path.join(__dirname, 'mail/auth-code-firsttime.html'), 'utf8');
|
||||||
let authCode = genCode();
|
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.timer(row.user_id, 600, async () => {
|
||||||
await this.redis.json.del(`code_auth:${authCode}`);
|
await this.redis.json.del(`codeAuth:${authCode}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
authCode = authCode.slice(0, 4) + " " + authCode.slice(4);
|
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');
|
const html = fs.readFileSync(path.join(__dirname, 'mail/auth-code.html'), 'utf8');
|
||||||
let authCode = genCode();
|
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.timer(row.user_id, 600, async () => {
|
||||||
await this.redis.json.del(`code_auth:${authCode}`);
|
await this.redis.json.del(`codeAuth:${authCode}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
authCode = authCode.slice(0, 4) + " " + authCode.slice(4);
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const conn = mysql.createConnection(this.mysqlOptions);
|
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);
|
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);
|
if (error) reject(error);
|
||||||
resolve();
|
else resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -144,10 +146,10 @@ export class MailAuth {
|
|||||||
|
|
||||||
async finishVerification(uid, authCode) {
|
async finishVerification(uid, authCode) {
|
||||||
authCode = authCode.replace(/\s+/g, "");
|
authCode = authCode.replace(/\s+/g, "");
|
||||||
let redisRes = await this.redis.json.get(`code_auth:${authCode}`);
|
const rUid = await this.redis.get(`codeAuth:${authCode}`);
|
||||||
if (redisRes != null && redisRes.uid === uid) {
|
if (rUid != null && rUid === uid) {
|
||||||
this.resetTimer(redisRes.tid);
|
this.resetTimer(rUid);
|
||||||
await this.redis.del(`code_auth:${authCode}`);
|
await this.redis.del(`codeAuth:${authCode}`);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,22 +1,30 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body style="background: rgb(0, 0, 0);color: white;;font-family: 'Lato', sans-serif;padding: 30px;">
|
<body style="background: rgb(0, 0, 0);color: white;;font-family: 'Lato', sans-serif;padding: 30px;">
|
||||||
<div
|
<div style="margin:30px;text-align: center;font-size:1.3em;margin: auto;width: 35%;min-width:25em;padding:20px;">
|
||||||
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;">
|
|
||||||
<h2 style="font-family: 'Poppins', sans-serif;">Cześć!</h2>
|
<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>
|
<p>Ktoś próbował utworzyć konto w Statkach przy użyciu Twojego adresu e-mail. Jeśli to nie byłeś ty, zignoruj tą
|
||||||
<div style="border: solid white 1px; border-radius: 15px;font-family: 'Roboto Mono', monospace;padding:15px;font-size: 1.5em;">
|
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 }}
|
{{ CODE }}
|
||||||
</div>
|
</div>
|
||||||
<p>Powyższy kod wygasa po 10 minutach</p>
|
<p>Powyższy kod wygasa po 10 minutach</p>
|
||||||
</div>
|
</div>
|
||||||
<p style="color:white;margin-bottom: 20px;margin-top: 20px;text-align: center;"><a href="https://maciejka.xyz/"
|
<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:white;">Copyright © 2024 MCJK</a> | <a
|
||||||
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>
|
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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,22 +1,30 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body style="background: rgb(0, 0, 0);color: white;;font-family: 'Lato', sans-serif;padding: 30px;">
|
<body style="background: rgb(0, 0, 0);color: white;;font-family: 'Lato', sans-serif;padding: 30px;">
|
||||||
<div
|
<div style="margin:30px;text-align: center;font-size:1.3em;margin: auto;width: 35%;min-width:25em;padding:20px;">
|
||||||
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;">
|
|
||||||
<h2 style="font-family: 'Poppins', sans-serif;">Hej, {{ NICKNAME }}!</h2>
|
<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>
|
<p>Ktoś próbował się zalogować na twoje konto w Statkach, jeśli to nie byłeś ty - zignoruj tę
|
||||||
<div style="border: solid white 1px; border-radius: 15px;font-family: 'Roboto Mono', monospace;padding:15px;font-size: 1.5em;">
|
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 }}
|
{{ CODE }}
|
||||||
</div>
|
</div>
|
||||||
<p>Powyższy kod wygasa po 10 minutach</p>
|
<p>Powyższy kod wygasa po 10 minutach</p>
|
||||||
</div>
|
</div>
|
||||||
<p style="color:white;margin-bottom: 20px;margin-top: 20px;text-align: center;"><a href="https://maciejka.xyz/"
|
<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:white;">Copyright © 2024 MCJK</a> | <a
|
||||||
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>
|
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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
39
views/error.handlebars
Normal file
39
views/error.handlebars
Normal 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>
|
@ -52,7 +52,7 @@
|
|||||||
<div class="modes">
|
<div class="modes">
|
||||||
<div>
|
<div>
|
||||||
<form action="/api/joinme" id="pvpJoinForm">
|
<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">
|
<input type="submit" value="Dołącz">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user