mirror of
https://gitlab.com/MrFry/mrfrys-node-server
synced 2025-04-01 20:24:18 +02:00
449 lines
14 KiB
TypeScript
449 lines
14 KiB
TypeScript
/* ----------------------------------------------------------------------------
|
|
|
|
Question Server
|
|
GitLab: <https://gitlab.com/MrFry/mrfrys-node-server>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
------------------------------------------------------------------------- */
|
|
|
|
import { v4 as uuidv4, v5, validate as validateuuid } from 'uuid'
|
|
import type { Database } from 'better-sqlite3'
|
|
|
|
import logger from '../../../utils/logger'
|
|
import utils from '../../../utils/utils'
|
|
import {
|
|
Request,
|
|
SubmoduleData,
|
|
User,
|
|
Submodule,
|
|
} from '../../../types/basicTypes'
|
|
import dbtools from '../../../utils/dbtools'
|
|
import { queueWork } from '../../../worker/workerPool'
|
|
import constants from '../../../constants'
|
|
|
|
const minimumAlowwedSessions = 2 // how many sessions are allowed for a user
|
|
const usersDbBackupPath = 'data/dbs/backup'
|
|
|
|
const maxPWCount = 3
|
|
const daysAfterUserGetsPWs = 7 // days after user gets pw-s
|
|
|
|
interface Session {
|
|
id: string
|
|
userId: number
|
|
createDate: number
|
|
lastAccess: number
|
|
isScript: number
|
|
}
|
|
|
|
function BackupDB(usersDbBackupPath: string, userDB: Database) {
|
|
logger.Log('Backing up auth DB ...')
|
|
utils.CreatePath(usersDbBackupPath)
|
|
userDB
|
|
.backup(
|
|
`${usersDbBackupPath}/users.${utils
|
|
.GetDateString()
|
|
.replace(/ /g, '_')}.db`
|
|
)
|
|
.then(() => {
|
|
logger.Log('Auth DB backup complete!')
|
|
})
|
|
.catch((err: Error) => {
|
|
logger.Log('Auth DB backup failed!', logger.GetColor('redbg'))
|
|
console.error(err)
|
|
})
|
|
}
|
|
|
|
function createDefaultUser(userDb: Database) {
|
|
logger.Log('The user DB is empty, creating user #1', 'yellowbg')
|
|
const pw = uuidv4()
|
|
const insertRes = dbtools.Insert(userDb, 'users', {
|
|
pw: pw,
|
|
avaiblePWRequests: 50,
|
|
created: new Date().getTime(),
|
|
})
|
|
logger.Log('ID and PW for user #1: ', 'yellowbg')
|
|
console.log()
|
|
console.log(`ID: #${insertRes.lastInsertRowid}, PW: "${pw}"`)
|
|
console.log()
|
|
logger.Log('It can be also viewed in the users db file.')
|
|
}
|
|
|
|
// TODO: figure out if this is needed
|
|
// const validationTokenNameFile = 'data/validationTokenName'
|
|
// function readValidationTokenName() {
|
|
// if (utils.FileExists(validationTokenNameFile)) {
|
|
// return utils.ReadFile(validationTokenNameFile)
|
|
// } else {
|
|
// throw new Error(
|
|
// `Validation token file does not exist! Should be: "${validationTokenNameFile}", content should be: "name for uuidv5 (any text)"`
|
|
// )
|
|
// }
|
|
// }
|
|
const validationTokenName = 'qmining' // readValidationTokenName()
|
|
|
|
function setup(data: SubmoduleData): Submodule {
|
|
const { app, userDB } = data
|
|
|
|
const userCount = dbtools
|
|
.TableInfo(userDB, 'users')
|
|
.dataCount.toLocaleString()
|
|
logger.Log(`User count: ${userCount} users`, 'blue')
|
|
if (+userCount === 0) {
|
|
createDefaultUser(userDB)
|
|
}
|
|
|
|
app.get('/avaiblePWS', (req: Request, res: any) => {
|
|
logger.LogReq(req)
|
|
|
|
const user: User = req.session.user
|
|
|
|
res.json({
|
|
success: true,
|
|
userCreated: user.created,
|
|
availablePWS: user.avaiblePWRequests,
|
|
requestedPWS: user.pwRequestCount,
|
|
maxPWCount: maxPWCount,
|
|
daysAfterUserGetsPWs: daysAfterUserGetsPWs,
|
|
dayDiff: getDayDiff(user.created),
|
|
userCount: dbtools.TableInfo(userDB, 'users').dataCount,
|
|
})
|
|
})
|
|
|
|
app.post('/getpw', function (req: Request, res: any) {
|
|
logger.LogReq(req)
|
|
|
|
const requestingUser = req.session.user
|
|
|
|
if (requestingUser.avaiblePWRequests <= 0) {
|
|
res.json({
|
|
result: 'error',
|
|
success: false,
|
|
msg: 'Too many passwords requested or cant request password yet, try later',
|
|
})
|
|
logger.Log(
|
|
`User #${requestingUser.id} requested too much passwords`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
return
|
|
}
|
|
|
|
dbtools.Update(
|
|
userDB,
|
|
'users',
|
|
{
|
|
avaiblePWRequests: requestingUser.avaiblePWRequests - 1,
|
|
pwRequestCount: requestingUser.pwRequestCount + 1,
|
|
},
|
|
{
|
|
id: requestingUser.id,
|
|
}
|
|
)
|
|
|
|
const pw = uuidv4()
|
|
const newUser: Omit<User, 'id'> = {
|
|
pw: pw,
|
|
avaiblePWRequests: 0,
|
|
created: new Date().getTime(),
|
|
createdBy: requestingUser.id,
|
|
}
|
|
|
|
const insertRes = dbtools.Insert(userDB, 'users', newUser)
|
|
|
|
logger.Log(
|
|
`User #${requestingUser.id} created new user #${insertRes.lastInsertRowid}`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
|
|
res.json({
|
|
pw: pw,
|
|
success: true,
|
|
userCreated: requestingUser.created,
|
|
availablePWS: requestingUser.avaiblePWRequests,
|
|
requestedPWS: requestingUser.pwRequestCount,
|
|
maxPWCount: maxPWCount,
|
|
daysAfterUserGetsPWs: daysAfterUserGetsPWs,
|
|
dayDiff: getDayDiff(requestingUser.created),
|
|
userCount: dbtools.TableInfo(userDB, 'users').dataCount,
|
|
})
|
|
|
|
queueWork({
|
|
type: 'sendUsersToPeers',
|
|
data: {
|
|
newUsers: [newUser],
|
|
},
|
|
})
|
|
})
|
|
|
|
app.post('/login', (req: Request, res: any) => {
|
|
logger.LogReq(req)
|
|
const pw = req.body.pw
|
|
? req.body.pw.replace(/'/g, '').replace(/"/g, '').replace(/;/g, '')
|
|
: false
|
|
const isScript = req.body.script
|
|
const user: User = dbtools.Select(userDB, 'users', {
|
|
pw: pw,
|
|
})[0]
|
|
|
|
if (user) {
|
|
const sessionID = uuidv4()
|
|
|
|
const existingSessions = dbtools
|
|
.Select(userDB, 'sessions', {
|
|
userID: user.id,
|
|
isScript: isScript ? 1 : 0,
|
|
})
|
|
.sort((a: Session, b: Session) => {
|
|
return a.lastAccess - b.lastAccess
|
|
})
|
|
|
|
const diff = existingSessions.length - minimumAlowwedSessions
|
|
if (diff > 0) {
|
|
logger.Log(
|
|
`Multiple ${isScript ? 'script' : 'website'} sessions ( ${
|
|
existingSessions.length
|
|
} ) for #${user.id}, deleting olds`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
for (let i = 0; i < diff; i++) {
|
|
const id = existingSessions[i].id
|
|
dbtools.Delete(userDB, 'sessions', {
|
|
id: id,
|
|
isScript: isScript ? 1 : 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
dbtools.Update(
|
|
userDB,
|
|
'users',
|
|
{
|
|
loginCount: user.loginCount + 1,
|
|
lastLogin: new Date().getTime(),
|
|
},
|
|
{
|
|
id: user.id,
|
|
}
|
|
)
|
|
|
|
dbtools.Insert(userDB, 'sessions', {
|
|
id: sessionID,
|
|
userID: user.id,
|
|
isScript: isScript ? 1 : 0,
|
|
createDate: new Date().getTime(),
|
|
})
|
|
|
|
// https://www.npmjs.com/package/cookie
|
|
res.cookie('sessionID', sessionID, {
|
|
domain: constants.domain,
|
|
expires: new Date(
|
|
new Date().getTime() + 10 * 365 * 24 * 60 * 60 * 1000
|
|
),
|
|
sameSite: 'none',
|
|
secure: true,
|
|
})
|
|
res.cookie('sessionID', sessionID, {
|
|
expires: new Date(
|
|
new Date().getTime() + 10 * 365 * 24 * 60 * 60 * 1000
|
|
),
|
|
sameSite: 'none',
|
|
secure: true,
|
|
})
|
|
|
|
res.json({
|
|
result: 'success',
|
|
msg: 'you are now logged in',
|
|
})
|
|
logger.Log(
|
|
`Successfull login to ${
|
|
isScript ? 'script' : 'website'
|
|
} with user ID: #${user.id}`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
} else {
|
|
logger.Log(
|
|
`Login attempt with invalid pw: ${pw} to ${
|
|
isScript ? 'script' : 'website'
|
|
}`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
res.json({
|
|
result: 'error',
|
|
msg: 'Invalid password',
|
|
})
|
|
}
|
|
})
|
|
|
|
app.get('/logout', (req: Request, res: any) => {
|
|
logger.LogReq(req)
|
|
const sessionID = req.cookies.sessionID
|
|
const user: User = req.session.user
|
|
const { all } = req.query
|
|
|
|
if (!user) {
|
|
res.json({
|
|
msg: 'You are not logged in',
|
|
success: false,
|
|
})
|
|
return
|
|
}
|
|
|
|
logger.Log(
|
|
`Successfull logout with user ID: #${user.id}`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
|
|
if (all) {
|
|
dbtools.Delete(userDB, 'sessions', {
|
|
userID: user.id,
|
|
})
|
|
} else {
|
|
dbtools.Delete(userDB, 'sessions', {
|
|
id: sessionID,
|
|
})
|
|
}
|
|
|
|
res.clearCookie('sessionID').json({
|
|
msg: 'Successfull logout',
|
|
result: 'success',
|
|
})
|
|
})
|
|
|
|
app.get(
|
|
'/validationtoken',
|
|
(req: Request<{ token: string; userid: string }>, res: any) => {
|
|
logger.LogReq(req)
|
|
const user: User = req.session.user
|
|
const { token, userid } = req.query
|
|
const isQueryValid = validateuuid(token) && !Number.isNaN(+userid)
|
|
|
|
if (isQueryValid) {
|
|
const specifiedUser = dbtools.Select(userDB, 'users', {
|
|
id: +userid,
|
|
})
|
|
|
|
if (specifiedUser.length === 0 || !specifiedUser[0]) {
|
|
res.json({
|
|
result: 'nouserid',
|
|
msg: 'couldnt find user',
|
|
})
|
|
}
|
|
const key = v5(validationTokenName, specifiedUser[0].pw)
|
|
const isValid = key === token
|
|
|
|
res.json({
|
|
result: 'success',
|
|
isValid: isValid,
|
|
})
|
|
} else if ((token || userid) && !isQueryValid) {
|
|
res.json({
|
|
result: 'invalid',
|
|
msg: 'token or user id is not valid, or undefined',
|
|
})
|
|
} else if (!user) {
|
|
res.json({
|
|
result: 'nouser',
|
|
msg: 'you are not logged in',
|
|
})
|
|
} else {
|
|
const key = v5(validationTokenName, user.pw)
|
|
|
|
res.json({
|
|
result: 'newtoken',
|
|
key: key,
|
|
...((token || userid) && {
|
|
msg: 'userid or token was provided, but was invalid',
|
|
}),
|
|
})
|
|
}
|
|
}
|
|
)
|
|
|
|
function getDayDiff(dateString: string | Date | number) {
|
|
const msdiff = new Date().getTime() - new Date(dateString).getTime()
|
|
return Math.floor(msdiff / (1000 * 3600 * 24))
|
|
}
|
|
|
|
function IncrementAvaiblePWs() {
|
|
// FIXME: check this if this is legit and works
|
|
logger.Log('Incrementing avaible PW-s ...')
|
|
const users: Array<User> = dbtools.SelectAll(userDB, 'users')
|
|
const day = new Date().getDay()
|
|
|
|
if (day === 1) {
|
|
users.forEach((user) => {
|
|
const dayDiff = getDayDiff(user.created)
|
|
if (dayDiff < daysAfterUserGetsPWs) {
|
|
logger.Log(
|
|
`User #${user.id} is not registered long enough to get password ( ${dayDiff} days, ${daysAfterUserGetsPWs} needed)`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
return
|
|
}
|
|
|
|
if (user.avaiblePWRequests >= maxPWCount) {
|
|
return
|
|
}
|
|
|
|
logger.Log(
|
|
`Setting avaible PW-s for user #${user.id}: ${user.avaiblePWRequests} -> ${maxPWCount}`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
|
|
dbtools.Update(
|
|
userDB,
|
|
'users',
|
|
{
|
|
avaiblePWRequests: maxPWCount,
|
|
},
|
|
{
|
|
id: user.id,
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
users.forEach((user) => {
|
|
const dayDiff = getDayDiff(user.created)
|
|
if (dayDiff === daysAfterUserGetsPWs) {
|
|
logger.Log(
|
|
`Setting avaible PW-s for user #${user.id}: ${user.avaiblePWRequests} -> ${maxPWCount}`,
|
|
logger.GetColor('cyan')
|
|
)
|
|
|
|
dbtools.Update(
|
|
userDB,
|
|
'users',
|
|
{
|
|
avaiblePWRequests: maxPWCount,
|
|
},
|
|
{
|
|
id: user.id,
|
|
}
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
return {
|
|
dailyAction: () => {
|
|
BackupDB(usersDbBackupPath, userDB)
|
|
IncrementAvaiblePWs()
|
|
},
|
|
}
|
|
}
|
|
|
|
export default {
|
|
setup: setup,
|
|
}
|