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' ;
2024-03-26 12:41:28 +01:00
import geoip from 'geoip-lite' ;
2024-03-13 21:40:24 +01:00
import mysql from 'mysql' ;
import readline from "node:readline" ;
2024-03-18 18:43:13 +01:00
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 : {
2024-03-18 18:43:13 +01:00
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 ) {
2024-03-18 18:43:13 +01:00
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 ;
}
2024-03-18 18:43:13 +01:00
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 ) {
2024-03-18 18:43:13 +01:00
let lastUpdate = await this . redis . get ( ` loginTimer: ${ tId } ` ) ;
await this . redis . set ( ` loginTimer: ${ tId } ` , - lastUpdate ) ;
2024-03-13 21:40:24 +01:00
}
2024-03-26 12:41:28 +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 } ) ;
2024-04-04 16:08:27 +02:00
conn . end ( ) ;
2024-03-26 12:41:28 +01:00
} ) ;
} ) ;
}
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 ) ;
}
2024-04-04 16:08:27 +02:00
conn . end ( ) ;
2024-03-28 16:47:48 +01:00
} ) ;
} ) ;
}
2024-03-26 12:41:28 +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 ) => {
2024-03-18 18:43:13 +01:00
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 ) {
2024-03-26 12:41:28 +01:00
resolve ( { status : - 1 , uid : response [ 0 ] . user _id , } ) ;
2024-03-24 15:50:39 +01:00
return ;
}
2024-03-18 18:43:13 +01:00
}
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
2024-03-18 18:43:13 +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 ) ;
2024-03-31 15:08:31 +02:00
const lookup = geoip . lookup ( ip ) ;
const lookupData = ` User-Agent: ${ agent } \n Adres IP: ${ ip } \n Kraj: ${ lookup . country } \n Region: ${ lookup . region } \n Miasto: ${ 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 ` ,
2024-03-31 15:08:31 +02:00
html : html . replace ( "{{ CODE }}" , authCode ) . replace ( "{{ LOOKUP }}" , lookupData ) ,
2024-03-13 21:40:24 +01:00
} ) ;
} catch ( e ) {
reject ( e ) ;
}
2024-03-26 12:41:28 +01:00
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 ( ) ;
2024-03-18 18:43:13 +01:00
await this . redis . set ( ` codeAuth: ${ authCode } ` , row . user _id ) ;
2024-03-13 21:40:24 +01:00
2024-03-18 18:43:13 +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 ) ;
2024-03-26 12:41:28 +01:00
const lookup = geoip . lookup ( ip ) ;
2024-03-31 15:08:31 +02:00
const lookupData = ` User-Agent: ${ agent } \n Adres IP: ${ ip } \n Kraj: ${ lookup . country } \n Region: ${ lookup . region } \n Miasto: ${ lookup . city } ` ;
2024-03-26 12:41:28 +01:00
2024-03-31 15:08:31 +02:00
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 } ) ;
2024-04-04 16:08:27 +02:00
conn . end ( ) ;
2024-03-13 21:40:24 +01:00
} ) ;
} ) ;
}
2024-03-18 18:43:13 +01:00
saveMatch ( matchId , duration , type , hostId , guestId , boards , winnerIdx ) {
2024-03-18 12:41:50 +01:00
return new Promise ( ( resolve , reject ) => {
const conn = mysql . createConnection ( this . mysqlOptions ) ;
2024-03-18 18:43:13 +01:00
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 ) => {
2024-03-18 12:41:50 +01:00
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 ) => {
2024-03-18 12:41:50 +01:00
if ( error ) reject ( error ) ;
2024-03-18 18:43:13 +01:00
else resolve ( ) ;
2024-04-04 16:08:27 +02:00
conn . end ( ) ;
2024-03-18 12:41:50 +01:00
} ) ;
} ) ;
} ) ;
}
2024-03-24 19:51:49 +01:00
getProfile ( userId ) {
return new Promise ( ( resolve , reject ) => {
const conn = mysql . createConnection ( this . mysqlOptions ) ;
2024-04-04 16:08:27 +02:00
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 } ) ;
}
2024-04-04 16:08:27 +02:00
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 , "" ) ;
2024-03-18 18:43:13 +01:00
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 ( ) ;
2024-04-04 16:08:27 +02:00
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 ) ;
2024-04-04 16:08:27 +02:00
conn . end ( ) ;
2024-03-13 21:40:24 +01:00
} ) ;
} ) ;
}
}
function genCode ( ) {
return Math . floor ( 10000000 + Math . random ( ) * 90000000 ) . toString ( ) ;
}