From fc01f72fa4f355d647ae43134f1e13f0a896bb01 Mon Sep 17 00:00:00 2001 From: MaciejkaG Date: Thu, 11 Apr 2024 19:10:45 +0200 Subject: [PATCH] Finishing touches to the new login --- index.js | 130 +++++++++++++++++++++++--- lang/en.json | 14 ++- lang/pl.json | 14 ++- public/assets/css/landing.css | 30 +++++- public/assets/css/main.css | 2 +- public/assets/js/landing.js | 167 ++++++++++++++++++++++++++++++---- public/assets/js/socket.js | 2 + utils/auth.js | 22 ++++- views/landing.handlebars | 9 +- 9 files changed, 347 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index cd4dc7d..20627a0 100644 --- a/index.js +++ b/index.js @@ -95,7 +95,10 @@ app.get('/privacy', (req, res) => { app.get('/', async (req, res) => { let login = loginState(req); - if (login != 2) { + if (login == 0) { + req.session.userAgent = req.get('user-agent'); + req.session.loggedIn = 0; + const locale = new Lang(req.acceptsLanguages()); res.render('landing', { @@ -104,6 +107,8 @@ app.get('/', async (req, res) => { } }); // res.redirect('/login'); + } else if (login != 2) { + res.redirect("/login"); } else if (req.session.nickname == null) { auth.getLanguage(req.session.userId).then(language => { var locale; @@ -359,17 +364,110 @@ io.on('connection', async (socket) => { const req = socket.request; const session = req.session; socket.session = session; - // if (session.loginState != 2) { - // socket.on('email login', (email, callback) => { - // callback(session.nickname); - // }); - // } - if (!await GInfo.isPlayerInGame(socket)) { - if (session.nickname == null) { - socket.disconnect(); - return; - } + if (!session.loggedIn) { + socket.on('email login', (email, callback) => { + let login = socket.request.session.loggedIn; + + if (login == 0 && email != null && validateEmail(email)) { + if (checkFlag('authless')) { + auth.loginAuthless(email).then(async result => { + req.session.reload((err) => { + if (err) return socket.disconnect(); + + req.session.userId = result.uid; + req.session.loggedIn = 2; + req.session.save(); + }); + + callback({ status: "ok", next: "done" }); + }); + + return; + } + + const locale = new Lang(session.langs); + + auth.startVerification(email, getIPSocket(socket), socket.client.request.headers["user-agent"], locale.lang).then(async result => { + if (result.status === 1 || result.status === -1) { + req.session.reload((err) => { + if (err) return socket.disconnect(); + + req.session.userId = result.uid; + req.session.loggedIn = 1; + req.session.save(); + }); + + callback({ status: "ok", next: "auth" }); + } else { + callback({ status: "SrvErr", error: locale.t("landing.Server error") }); + } + }).catch((err) => { + const locale = new Lang(session.langs); + + callback({ success: false, error: locale.t("landing.Unknown error") }); + throw err; + }); + } else { + const locale = new Lang(session.langs); + + auth.loginAuthless(email).then(async result => { + req.session.reload((err) => { + if (err) return socket.disconnect(); + + req.session.userId = result.uid; + req.session.loggedIn = 2; + req.session.save(); + }); + + callback({ success: false, error: locale.t("landing.Wrong email address") }); + }); + } + }); + + socket.on('email auth', async (code, callback) => { + let login = socket.request.session.loggedIn; + + if (login == 1 && code != null && code.length <= 10 && code.length >= 8) { + let finishResult = await auth.finishVerification(req.session.userId, code); + if (finishResult) { + req.session.reload((err) => { + if (err) return socket.disconnect(); + + req.session.loggedIn = 2; + req.session.save(); + }); + + callback({ status: "ok", next: "done" }); + } else { + const locale = new Lang(session.langs); + + callback({ success: false, error: locale.t("landing.Wrong authorisation code") }); + } + } else { + const locale = new Lang(session.langs); + + callback({ success: false, error: locale.t("landing.Wrong authorisation code") }); + } + }); + + socket.on('disconnecting', () => { + if (socket.request.session.loggedIn == 1) { + req.session.reload((err) => { + if (err) return socket.disconnect(); + + req.session.loggedIn = 0; + req.session.save(); + }); + } + }); + } + + if (!await GInfo.isPlayerInGame(socket) || session.nickname != null) { + // if (session.nickname == null) { + // socket.disconnect(); + // return; + // } socket.on('whats my nick', (callback) => { callback(session.nickname); @@ -529,7 +627,7 @@ io.on('connection', async (socket) => { io.to(socket.rooms[1]).emit("player left"); } }); - } else { + } else if (session.nickname != null) { const playerGame = await GInfo.getPlayerGameData(socket); if (playerGame.data.state === 'pregame') { @@ -784,6 +882,14 @@ function getIP(req) { } } +function getIPSocket(socket) { + if (checkFlag("cloudflare_mode")) { + return socket.client.request.headers['cf-connecting-ip']; + } else { + return socket.client.request.headers['x-forwarded-for'] || socket.handshake.address; + } +} + function checkFlag(key) { if (flags) { return flags.includes(key); diff --git a/lang/en.json b/lang/en.json index 796891e..1c2a4a2 100644 --- a/lang/en.json +++ b/lang/en.json @@ -9,11 +9,23 @@ "Connection error": "Connection error" }, + "email": { + "This is your Statki authorisation code": "%s is your Statki authorisation code" + }, + "landing": { "The #1 online multiplayer battleships game": "The #1 online multiplayer battleships game", "SCROLL DOWN": "SCROLL DOWN", - "Privacy policy": "Privacy policy" + "Privacy policy": "Privacy policy", + + "Server error": "Server error", + "Unkown error": "Unknown error", + "Wrong email address": "Wrong e-mail address", + "Wrong authorisation code": "Wrong authorisation code", + + "E-mail address is required": "E-mail address is required!", + "Auth code is required": "Auth code is required" }, "menu": { diff --git a/lang/pl.json b/lang/pl.json index c6e1017..777bdac 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -9,11 +9,23 @@ "Connection error": "Błąd połączenia" }, + "email": { + "This is your Statki authorisation code": "%s to twój kod autoryzacyjny do statków" + }, + "landing": { "The #1 online multiplayer battleships game": "Najlepsza wielosobowa gra w statki", "SCROLL DOWN": "SCROLLUJ W DÓŁ", - "Privacy policy": "Polityka prywatności" + "Privacy policy": "Polityka prywatności", + + "Server error": "Bład serwera", + "Unkown error": "Nieznany błąd", + "Wrong email address": "Niepoprawny adres e-mail", + "Wrong authorisation code": "Niepoprawny kod autoryzacji", + + "E-mail address is required": "Adres e-mail jest wymagany!", + "Auth code is required": "Kod autoryzacji jest wymagany!" }, "menu": { diff --git a/public/assets/css/landing.css b/public/assets/css/landing.css index e2764db..bb8d48f 100644 --- a/public/assets/css/landing.css +++ b/public/assets/css/landing.css @@ -4,6 +4,8 @@ position: absolute; top: 0; left: 0; + padding: 2rem; + box-sizing: border-box; display: flex; flex-direction: column; text-align: center; @@ -11,6 +13,8 @@ align-items: center; transform: translateY(-3rem); font-size: 1.25em; + + transition: opacity 0.3s; } .landing p { @@ -41,7 +45,7 @@ z-index: 1; - transition: translate 1.5s ease-in-out; + transition: translate 1s ease-in-out; } .bgShip#bgs1 { @@ -114,12 +118,21 @@ width: 100%; height: 100%; z-index: 5; - transition: all 0.5s 0.3s ease, top 0.6s ease-in-out; + transition: all 0.5s 0.3s, transform 0.5s 0.2s, opacity 0.5s 0.2s, top 0.6s ease-in-out; background-color: rgba(0, 0, 0, 0); backdrop-filter: blur(0px); } +body.closed .loginContainer { + opacity: 0; + transform: scale(0.8); +} + +body.closed .landing { + opacity: 0; +} + .loginContainer.active { top: 0; background-color: rgba(0, 0, 0, 0.6); @@ -249,8 +262,10 @@ /* margin: 0; */ left: 50vw; transform: translateX(-50%); + width: 50%; z-index: 50; font-size: 16px; + text-align: center; font-family: 'Roboto Mono', monospace; } @@ -262,4 +277,15 @@ .footer a:hover { text-decoration: underline; +} + +@media only screen and (max-width: 820px) { + .bgShip { + display: none; + } + + .footer { + bottom: 15px; + width: 90%; + } } \ No newline at end of file diff --git a/public/assets/css/main.css b/public/assets/css/main.css index a827de0..b406656 100644 --- a/public/assets/css/main.css +++ b/public/assets/css/main.css @@ -14,7 +14,7 @@ } .bshipstoast { - background-color: rgba(0, 0, 0, 0.6); + background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(15px); color: white; padding: 0.75rem 1rem; diff --git a/public/assets/js/landing.js b/public/assets/js/landing.js index 50bc155..233d66c 100644 --- a/public/assets/js/landing.js +++ b/public/assets/js/landing.js @@ -2,8 +2,12 @@ String.prototype.replaceAt = function (index, replacement) { return this.substring(0, index) + replacement + this.substring(index + replacement.length); } +const socket = io(); + const charset = ["0", "1", "!", "@", "#", "$", "%", "&"]; +const initialContent = $("#scrolldowntext").html(); + setInterval(() => { var content = $("#scrolldowntext").html(); const len = content.length; @@ -22,6 +26,11 @@ setInterval(() => { setTimeout(() => { content = content.replaceAt(i, previousChar); $("#scrolldowntext").html(content); + + if (i == len - 1) { + content = initialContent; + $("#scrolldowntext").html(initialContent); + } }, duration * len + duration * i); }, duration * i); } @@ -30,33 +39,155 @@ setInterval(() => { document.addEventListener("wheel", (event) => { if (event.deltaY > 0) { $(".loginContainer").addClass("active"); - setTimeout(() => { - for (let i = 1; i <= 10; i++) { - setTimeout(() => { - $(`#f${i}`).css("scale", "1"); - }, 100 * (i - 1)); - } - }, 400); - const fields = document.querySelectorAll(".field"); - fields.forEach(field => { - if (!field.id) { - $(field).css("scale", "1"); - } - }); + animateShips(); } else if (event.deltaY < 0) { $(".loginContainer").removeClass("active"); } }); +let touchStart = 0; + +window.addEventListener("touchstart", function (event) { + touchStart = event.touches[0].clientY; +}); + +window.addEventListener("touchend", function (event) { + touchEnd = event.changedTouches[0].clientY; + + if (touchStart - touchEnd > 50) { + $(".loginContainer").addClass("active"); + animateShips(); + } else if (touchStart - touchEnd < -50) { + $(".loginContainer").removeClass("active"); + } +}); + +function animateShips() { + setTimeout(() => { + for (let i = 1; i <= 10; i++) { + setTimeout(() => { + $(`#f${i}`).css("scale", "1"); + }, 100 * (i - 1)); + } + }, 400); + const fields = document.querySelectorAll(".field"); + fields.forEach(field => { + if (!field.id) { + $(field).css("scale", "1"); + } + }); +} + switchView("loginView"); -const form = document.getElementById('loginForm'); -form.addEventListener('submit', (e) => { +const loginForm = document.getElementById('loginForm'); +const emailInput = document.getElementById('email'); + +loginForm.addEventListener('submit', (e) => { e.preventDefault(); - console.log("a"); - switchView("authView"); - progressParalax(); + if (emailInput.value) { + lockUI(true); + console.log("Logging in with e-mail:", emailInput.value); + socket.emit("email login", emailInput.value, (response) => { + console.log(response); + switch (response.status) { + case "ok": + console.log("Logged in."); + + console.log(response); + + if (response.next === "done") { + console.log("No authorisation required."); + window.location.reload(); + return; + } + + switchView("authView"); + progressParalax(); + lockUI(false); + break; + + default: + Toastify({ + text: response.error, + duration: 5000, + newWindow: true, + gravity: "bottom", + position: "right", + stopOnFocus: true, + className: "bshipstoast", + }).showToast(); + + lockUI(false); + break; + } + }); + + emailInput.value = ''; + } else { + Toastify({ + text: window.locale["E-mail address is required"], + duration: 5000, + newWindow: true, + gravity: "bottom", + position: "right", + stopOnFocus: true, + className: "bshipstoast", + }).showToast(); + } +}); + +const authForm = document.getElementById('authForm'); +const codeInput = document.getElementById('authcode'); + +authForm.addEventListener('submit', (e) => { + e.preventDefault(); + + if (codeInput.value) { + lockUI(true); + console.log("Logging in with e-mail:", codeInput.value); + socket.emit("email auth", codeInput.value, (response) => { + switch (response.status) { + case "ok": + console.log("Authorised."); + lockUI(false); + + $("body").addClass("closed"); + + setTimeout(() => { + window.location.reload(); + }, 700); + break; + + default: + Toastify({ + text: response.error, + duration: 5000, + newWindow: true, + gravity: "bottom", + position: "right", + stopOnFocus: true, + className: "bshipstoast", + }).showToast(); + + lockUI(false); + break; + } + }); + + emailInput.value = ''; + } else { + Toastify({ + text: window.locale["Auth code is required"], + duration: 5000, + newWindow: true, + gravity: "bottom", + position: "right", + stopOnFocus: true, + className: "bshipstoast", + }).showToast(); + } }); var parallaxTranslateX = 0; diff --git a/public/assets/js/socket.js b/public/assets/js/socket.js index e5c0f13..1dd5953 100644 --- a/public/assets/js/socket.js +++ b/public/assets/js/socket.js @@ -1,3 +1,5 @@ +switchView("mainMenuView"); + const socket = io(); // Handling server-sent events diff --git a/utils/auth.js b/utils/auth.js index bb50f6d..2737791 100644 --- a/utils/auth.js +++ b/utils/auth.js @@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url'; import geoip from 'geoip-lite'; import mysql from 'mysql'; -import readline from "node:readline"; +import { Lang } from './localisation.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -105,6 +105,8 @@ export class MailAuth { startVerification(email, ip, agent, langId) { return new Promise((resolve, reject) => { const conn = mysql.createConnection(this.mysqlOptions); + const lang = new Lang([langId]); + conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => { if (error) { reject(error); conn.end(); return; } if (response.length !== 0) { @@ -138,13 +140,18 @@ export class MailAuth { const lookup = geoip.lookup(ip); - const lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`; + var lookupData; + if (lookup) { + lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`; + } else { + lookupData = `IP lookup failed`; + } try { await this.mail.sendMail({ from: this.mailFrom, to: email, - subject: `${authCode} to twój kod autoryzacji do Statków`, + subject: lang.t('email.This is your Statki authorisation code').replace("%s", authCode), html: html.replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData), }); } catch (e) { @@ -173,13 +180,18 @@ export class MailAuth { const lookup = geoip.lookup(ip); - const lookupData = `User-Agent: ${agent}\nIP address: ${ip}\nCountry: ${lookup.country}\nRegion: ${lookup.region}\nCity: ${lookup.city}`; + var lookupData; + if (lookup) { + lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`; + } else { + lookupData = `IP lookup failed`; + } try { await this.mail.sendMail({ from: this.mailFrom, to: email, - subject: `${authCode} to twój kod logowania do Statków`, + subject: lang.t('email.This is your Statki authorisation code').replace("%s", authCode), html: html.replace("{{ NICKNAME }}", row.nickname).replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData), }); } catch (e) { diff --git a/views/landing.handlebars b/views/landing.handlebars index 9fa9523..c99c969 100644 --- a/views/landing.handlebars +++ b/views/landing.handlebars @@ -41,7 +41,7 @@
+ style="font-size: 1rem;" id="email">
@@ -55,9 +55,9 @@

{{ t 'auth.Enter the code sent to your e-mail box' }}

-
+ + minlength="8" maxlength="10" autocomplete="off" id="authcode">
@@ -83,6 +83,9 @@ "Disconnected": "{{ t 'errors.Disconnected' }}", "Try to refresh the page if this error reoccurs": "{{ t 'errors.Try to refresh the page if this error reoccurs' }}", "Connection error": "{{ t 'errors.Connection error' }}", + + "E-mail address is required": "{{ t 'landing.E-mail address is required' }}", + "Auth code is required": "{{ t 'landing.Auth code is required' }}", };