statki/utils/auth.js

275 lines
11 KiB
JavaScript
Raw Normal View History

2024-03-13 21:40:24 +01:00
import nodemailer from 'nodemailer';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import geoip from 'geoip-lite';
2024-03-13 21:40:24 +01:00
import mysql from 'mysql';
import readline from "node:readline";
2024-03-13 21:40:24 +01:00
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export class MailAuth {
constructor(redis, options, mysqlOptions) {
this.redis = redis;
2024-03-24 19:51:49 +01:00
mysqlOptions.multipleStatements = true;
2024-03-13 21:40:24 +01:00
this.mysqlOptions = mysqlOptions;
this.mail = nodemailer.createTransport({
host: options.host ? options.host : "localhost",
port: options.port ? options.port : "465",
secure: options.secure ? options.secure : true,
auth: {
user: options.user ? options.user : "root",
2024-03-13 21:40:24 +01:00
pass: options.pass,
},
});
this.mailFrom = `"Statki" <${options.user}>`
}
async timer(tId, time, callback) {
await this.redis.set(`loginTimer:${tId}`, new Date().getTime() / 1000);
let localLastUpdate = await this.redis.get(`loginTimer:${tId}`);
2024-03-13 21:40:24 +01:00
let timeout = setTimeout(callback, time * 1000);
let interval = setInterval(async () => {
if (timeout._destroyed) {
clearInterval(interval);
return;
}
let lastUpdate = await this.redis.get(`loginTimer:${tId}`);
2024-03-13 21:40:24 +01:00
if (localLastUpdate != lastUpdate) {
clearTimeout(timeout);
clearInterval(interval);
return;
}
}, 200);
}
async resetTimer(tId) {
let lastUpdate = await this.redis.get(`loginTimer:${tId}`);
await this.redis.set(`loginTimer:${tId}`, -lastUpdate);
2024-03-13 21:40:24 +01:00
}
loginAuthless(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); return; }
if (response.length === 0 || response[0].nickname == null) {
if (response.length === 0) {
conn.query(`INSERT INTO accounts(email) VALUES (${conn.escape(email)});`, (error) => { if (error) reject(error) });
}
conn.query(`SELECT user_id FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => {
if (error) reject(error);
const row = response[0];
resolve({ status: 1, uid: row.user_id });
});
return;
}
const row = response[0];
resolve({ status: 1, uid: row.user_id });
});
conn.end();
});
}
2024-03-28 16:47:48 +01:00
getLanguage(userId) {
return new Promise((resolve, reject) => {
const conn = mysql.createConnection(this.mysqlOptions);
conn.query(`SELECT language FROM accounts WHERE user_id = ${conn.escape(userId)}`, (error, response) => {
if (error) { reject(error); return; }
if (response.length !== 0) {
resolve(response[0].language);
} else {
resolve(null);
}
});
conn.end();
2024-03-28 16:47:48 +01:00
});
}
startVerification(email, ip, agent) {
2024-03-13 21:40:24 +01:00
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); return; }
2024-03-24 15:50:39 +01:00
if (response.length !== 0) {
let timer = await this.redis.get(`loginTimer:${response[0].user_id}`);
if (timer && timer > 0) {
resolve({ status: -1, uid: response[0].user_id, });
2024-03-24 15:50:39 +01:00
return;
}
}
2024-03-13 21:40:24 +01:00
if (response.length === 0 || response[0].nickname == null) {
2024-03-24 15:50:39 +01:00
if (response.length === 0) {
conn.query(`INSERT INTO accounts(email) VALUES (${conn.escape(email)});`, (error) => { if (error) reject(error) });
}
2024-03-13 21:40:24 +01:00
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();
2024-03-24 15:50:39 +01:00
await this.redis.set(`codeAuth:${authCode}`, row.user_id);
2024-03-13 21:40:24 +01:00
await this.timer(row.user_id, 600, async () => {
await this.redis.json.del(`codeAuth:${authCode}`);
2024-03-13 21:40:24 +01:00
});
authCode = authCode.slice(0, 4) + " " + authCode.slice(4);
const lookup = geoip.lookup(ip);
const lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`;
2024-03-13 21:40:24 +01:00
try {
await this.mail.sendMail({
from: this.mailFrom,
to: email,
subject: `${authCode} to twój kod autoryzacji do Statków`,
html: html.replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData),
2024-03-13 21:40:24 +01:00
});
} catch (e) {
reject(e);
}
resolve({ status: 1, uid: row.user_id, code: authCode });
2024-03-13 21:40:24 +01:00
});
return;
}
const row = response[0];
const html = fs.readFileSync(path.join(__dirname, 'mail/auth-code.html'), 'utf8');
let authCode = genCode();
await this.redis.set(`codeAuth:${authCode}`, row.user_id);
2024-03-13 21:40:24 +01:00
await this.timer(row.user_id, 600, async () => {
await this.redis.json.del(`codeAuth:${authCode}`);
2024-03-13 21:40:24 +01:00
});
authCode = authCode.slice(0, 4) + " " + authCode.slice(4);
const lookup = geoip.lookup(ip);
const lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`;
try {
await this.mail.sendMail({
from: this.mailFrom,
to: email,
subject: `${authCode} to twój kod logowania do Statków`,
html: html.replace("{{ NICKNAME }}", row.nickname).replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData),
});
} catch (e) {
reject(e);
}
2024-03-13 21:40:24 +01:00
resolve({ status: 1, uid: row.user_id });
});
conn.end();
2024-03-13 21:40:24 +01:00
});
}
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, duration) VALUES (${conn.escape(matchId)}, ${conn.escape(type)}, ${conn.escape(hostId)}, ${conn.escape(guestId)}, ${conn.escape(duration)})`, async (error) => {
if (error) reject(error);
2024-04-05 13:41:18 +02:00
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 ? 1 : 0)}), (${conn.escape(matchId)}, ${conn.escape(guestId)}, ${conn.escape(JSON.stringify(boards[1]))}, ${conn.escape(winnerIdx ? 1 : 0)})`, async (error, response) => {
if (error) reject(error);
else resolve();
});
});
conn.end();
});
}
2024-03-24 19:51:49 +01:00
getProfile(userId) {
return new Promise((resolve, reject) => {
const conn = mysql.createConnection(this.mysqlOptions);
conn.query(`SELECT nickname, account_creation FROM accounts WHERE user_id = ${conn.escape(userId)}; SELECT ROUND((AVG(statistics.won)) * 100) AS winrate, COUNT(statistics.match_id) AS alltime_matches, COUNT(CASE WHEN (YEAR(matches.date) = YEAR(NOW()) AND MONTH(matches.date) = MONTH(NOW())) THEN matches.match_id END) AS monthly_matches FROM accounts NATURAL JOIN statistics NATURAL JOIN matches WHERE accounts.user_id = ${conn.escape(userId)}; SELECT statistics.match_id, accounts.nickname AS opponent, matches.match_type, statistics.won, matches.duration, matches.date FROM statistics JOIN matches ON matches.match_id = statistics.match_id JOIN accounts ON accounts.user_id = (CASE WHEN matches.host_id != statistics.user_id THEN matches.host_id ELSE matches.guest_id END) WHERE statistics.user_id = ${conn.escape(userId)} ORDER BY matches.date DESC LIMIT 10;`, async (error, response) => {
2024-03-24 19:51:49 +01:00
if (error) reject(error);
else {
if (response[0].length === 0 || response[1].length === 0) {
reject(0);
return;
}
const [[profile], [stats], matchHistory] = response;
resolve({ profile, stats, matchHistory });
}
});
conn.end();
2024-03-24 19:51:49 +01:00
});
}
2024-03-13 21:40:24 +01:00
async finishVerification(uid, authCode) {
authCode = authCode.replace(/\s+/g, "");
const rUid = await this.redis.get(`codeAuth:${authCode}`);
if (rUid != null && rUid === uid) {
this.resetTimer(rUid);
await this.redis.del(`codeAuth:${authCode}`);
2024-03-13 21:40:24 +01:00
return true;
} else {
return false;
}
}
setNickname(uid, nickname) {
return new Promise((resolve, reject) => {
const conn = mysql.createConnection(this.mysqlOptions);
conn.query(`UPDATE accounts SET nickname = ${conn.escape(nickname)} WHERE user_id = ${conn.escape(uid)}`, (error) => {
if (error) reject(error);
resolve();
});
conn.end();
2024-03-13 21:40:24 +01:00
});
}
getNickname(uid) {
return new Promise((resolve, reject) => {
const conn = mysql.createConnection(this.mysqlOptions);
conn.query(`SELECT nickname FROM accounts WHERE user_id = ${conn.escape(uid)}`, (error, response) => {
if (error) reject(error);
resolve(response[0].nickname);
});
conn.end();
2024-03-13 21:40:24 +01:00
});
}
}
function genCode() {
return Math.floor(10000000 + Math.random() * 90000000).toString();
}