cubash-archive/routes/userutils.js

676 lines
22 KiB
JavaScript

let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
var Recaptcha = require('express-recaptcha').RecaptchaV3;
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
var speakeasy = require('speakeasy');
var secret = speakeasy.generateSecret();
let {
User, Post, ProfilePicture, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js');
const mailgun = require("mailgun-js");
const DOMAIN = 'mail.kaverti.com';
const mg = mailgun({apiKey: "9e4595674e8bf95ca45836587ea56105-0d2e38f7-c113b903", domain: DOMAIN});
const MailGen = require('mailgen')
const crypto = require("crypto")
const cryptoRandomString = require("crypto-random-string")
const rateLimit = require("express-rate-limit");
const moment = require("moment")
const emailLimiter = rateLimit({
windowMs: 60000,
max: 1, // limit each IP to 100 requests per windowMs
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
});
let conversationController = require('../controllers/conversation');
function setUserSession(req, res, username, UserId, admin) {
req.session.loggedIn = true
req.session.username = username
req.session.UserId = UserId
res.cookie('username', username)
if(admin) { req.session.admin = true }
}
router.post('/oidfhuisadhi8243', async (req, res) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#ffffff',
headColor: '#ffffff',
leftLegColor: '#ffffff',
rightLegColor: '#ffffff',
leftArmColor: '#ffffff',
rightArmColor: '#ffffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
emailVerified: false,
theme: 'light',
emailToken: cryptoRandomString({length: 16})
}
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
setUserSession(req, res, user.username, user.id, userParams.admin)
res.json(user.toJSON())
} catch (e) { next(e) }
})
router.post('/', async (req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#fffff',
headColor: '#fffff',
leftLegColor: '#fffff',
rightLegColor: '#fffff',
leftArmColor: '#fffff',
rightArmColor: '#fffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
booster: false,
theme: 'light',
emailToken: crypto(16)
}
throw Error.registrationsDisabled
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
setUserSession(req, res, user.username, user.id, userParams.admin)
res.json(user.toJSON())
} catch (e) { next(e) }
})
router.get('/leaderboard', async (req, res, next) => {
try {
let sortFields = {
createdAt: 'X.id',
username: 'X.username',
threadCount: 'threadCount',
postCount: 'postCount'
};
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
let havingClause = '';
if(req.query.role === 'admin') {
havingClause = 'HAVING Users.admin = true';
} else if(req.query.role === 'user') {
havingClause = 'HAVING Users.admin = false';
} else {
havingClause = '';
}
if(req.query.search) {
//I.e. if there is not already a HAVING clause
if(!havingClause.length) {
havingClause = 'HAVING ';
} else {
havingClause += ' AND ';
}
havingClause += 'Users.username LIKE $search';
}
let sql = `
SELECT X.username, X.postCount, COUNT(Threads.id) as threadCount
FROM (
SELECT Users.*, COUNT(Posts.id) as postCount
FROM Users
LEFT OUTER JOIN Posts
ON Users.id = Posts.UserId
GROUP BY Users.id
${havingClause}
) as X
LEFT OUTER JOIN threads
ON X.id = Threads.UserId
GROUP BY X.id
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'ASC' : 'DESC'}
LIMIT 5
OFFSET ${offset}
`;
let users = await sequelize.query(sql, {
model: User,
bind: { search: req.query.search + '%' }
});
res.json(users)
} catch (e) { next(e) }
})
router.post('/job-application', async (req, res, next) => {
try {
let userParams = {
username: req.body.username,
dob: req.body.dob,
email: req.body.email,
whyWork: req.body.whyWork,
otherForm: req.body.otherForm,
experience: req.body.experience,
selectedOption: req.body.selectedOption,
}
await StaffApplications.submitApplication(userParams)
} catch (e) { next(e) }
})
router.get('/reward', async (req, res, next) => {
try {
if (!req.session.username) {
throw Errors.requestNotAuthorized
}
let queryObj = {
attributes: {include: ['lastRewardDate', 'koins']},
where: {username: req.session.username}
}
let user = await User.findOne(queryObj)
let ms = Date.now() - user.lastRewardDate
let dayMs = 1000 * 60 * 60 * 24
let check = await ms / dayMs < 1
if (check) {
res.status(401)
res.json({
errors: [Errors.koinFail], koins: user.koins
})
} else {
user.update({koins: user.koins + 50, lastRewardDate: Date.now()})
res.status(200)
res.json({success: true, koins: user.koins})
}
} catch (err) { next(err) }
}),
router.post('/login', async (req, res, next) => {
try {
await Ban.isIpBanned(req.ip, req.body.email)
let user = await User.findOne({ where: {
username: req.body.username
}})
let userEmail = await User.findOne({ where: {
email: req.body.username
}})
if(user) {
if(await user.comparePassword(req.body.password)) {
await Ip.createIfNotExists(req.ip, user)
setUserSession(req, res, user.username, user.id, user.admin)
res.json({
username: user.username,
admin: user.admin,
success: true
})
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
} else {
if (userEmail) {
if (await userEmail.comparePassword(req.body.password)) {
await Ip.createIfNotExists(req.ip, userEmail)
setUserSession(req, res, userEmail.username, userEmail.id, userEmail.admin)
res.json({
username: userEmail.username,
admin: userEmail.admin,
success: true
})
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
}
} catch (err) { next(err) }
})
router.post('/recovery/send', emailLimiter, async (req, res, next) => {
const mailGenerator = new MailGen({
theme: 'salted',
product: {
name: 'Kaverti',
link: 'https://kaverti.com'
},
})
let queryObj = {
attributes: {include: ['email', 'username', 'emailVerified', 'username', 'passwordResetToken']},
where: {email: req.body.email}
}
let user = await User.findOne(queryObj)
await user.randPasswordReset()
const verifyEmail = {
body: {
name: user.username,
intro: 'Kaverti Password Reset',
action: {
instructions: 'Please reset your account password by clicking the button below, if you didn\'t request a password reset, please ignore and/or delete this email, this code will expire in 24 hours!',
button: {
color: '#33b5e5',
text: 'Reset password',
link: 'https://kaverti.com/recovery/?token=' + user.passwordResetToken,
},
},
},
}
const emailTemplate = mailGenerator.generate(verifyEmail)
const message = {
to: user.email,
from: 'automailer@kaverti.com',
subject: 'Kaverti account recovery',
html: emailTemplate
}
const sendMail = async () => {
try {
mg.messages().send(message, function (error, body) {
if (error) {
res.status(500)
res.send({ error: error});
}
else {
res.send({ message: 'Success, email is on its way.'})
}
});
} catch (error) {
throw new Error(error.message)
}
}
try {
if(user.passwordResetOptOut) {
throw Errors.passwordResetOptOut
} else {
const sent = await sendMail()
if (sent) {
res.send({ message: 'Email has been sent, check your spam if you can\'t find it!' })
}
}
} catch (error) {
res.status(400)
res.json({"errors":[{"name":"recoverySend","message":error.message}]})
}
});
router.put('/recovery/change', async (req, res, next) => {
try {
let user1 = await User.findOne({ where: {
username: req.body.username
}})
let ms = Date.now() - user1.passwordResetExpiry
let dayMs = 1000 * 60 * 60 * 24
let check = await ms / dayMs < 1
if(req.body.token !== undefined) {
if(check) {
let user = await User.findOne({ where: {
username: req.body.username
}})
if(req.body.token === user.passwordResetToken && user.passwordResetEnabled && !user.passwordResetOptOut) {
user.recoveryUpdatePassword(req.body.password)
res.status(200)
res.json({success: true})
} else {
throw Errors.invalidPasswordToken
}
} else {
throw Errors.passwordTokenExpiry
}
} else {
throw Errors.invalidPasswordToken
}
} catch (e) { next(e) }
})
router.all('*', (req, res, next) => {
if(req.session.username) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.get('/:userId/conversations', async (req, res, next) => {
try {
let id = +req.params.userId;
let conversations = await conversationController.getFromUser(id, +req.query.page, req.query.search);
res.json(conversations);
} catch (e) { next(e); }
});
router.put('/delete', emailLimiter, async (req, res, next) => {
const mailGenerator = new MailGen({
theme: 'salted',
product: {
name: 'Kaverti',
link: 'https://kaverti.com'
},
})
let queryObj = {
attributes: {include: ['email', 'username', 'emailVerified', 'username', 'deleteCode']},
where: {username: req.session.username}
}
let user = await User.findOne(queryObj)
await user.randAccountDelete()
const verifyEmail = {
body: {
name: user.username,
intro: 'Kaverti Account Deletion',
action: [
{
instructions: 'Sorry to see you go! You can finish deleting your account by using the link below, if this was not you, CHANGE YOUR PASSWORD IMMEDIATELY, AND/OR CONTACT SUPPORT.',
button: {
color: '#ec161d',
text: 'Delete account',
link: 'https://kaverti.com/delete/?token=' + user.deleteCode,
},
},
{
instructions: 'If you don\'t want to delete your account, click the button below, this will deactivate the code.',
button: {
color: '#33b5e5', // Optional action button color
text: 'I do not want to delete my account.',
link: 'https://kaverti.com/delete/undo'
}
}
]
},
}
const emailTemplate = mailGenerator.generate(verifyEmail)
const message = {
to: user.email,
from: 'automailer@kaverti.com',
subject: 'Kaverti account deletion request',
html: emailTemplate
}
const sendMail = async () => {
try {
mg.messages().send(message, function (error, body) {
if (error) {
res.status(500)
res.send({ error: error});
}
else {
res.send({ message: 'Success, email is on its way.'})
}
});
} catch (error) {
throw new Error(error.message)
}
}
try {
if(!user) {
throw Errors.requestNotAuthorized
} else {
const sent = await sendMail()
if (sent) {
res.send({ message: 'Email has been sent, check your spam if you can\'t find it!' })
}
}
} catch (error) {
res.status(400)
res.json({"errors":[{"name":"recoverySend","message":error.message}]})
}
});
router.post('/delete/verify', async (req, res, next) => {
try {
if(!req.session.username) {
throw Errors.requestNotAuthorized
}
let queryObj = {
attributes: {include: ['deleteCode', 'deleteEnabled']},
where: { username: req.session.username }
}
let user = await User.findOne(queryObj)
if (user.deleteCode === req.body.token && user.deleteEnabled) {
await user.destroyVerifyPassword(req.body.password)
res.status(200)
res.json({success: true})
} else {
res.status(400)
res.json({
errors: [Errors.invalidParameter('token', 'Invalid token')]
})
}
} catch (e) { next(e) }
})
router.post('/email-verify/send', emailLimiter, async (req, res, next) => {
const mailGenerator = new MailGen({
theme: 'salted',
product: {
name: 'Kaverti',
link: 'https://kaverti.com'
},
})
let queryObj = {
attributes: {include: ['email', 'emailVerified', 'emailToken', 'username']},
where: {username: req.session.username}
}
let user = await User.findOne(queryObj)
await user.rand()
const verifyEmail = {
body: {
name: user.username,
intro: 'Welcome to Kaverti',
action: {
instructions: 'Please verify your account via the button below',
button: {
color: '#33b5e5',
text: 'Verify account',
link: 'https://kaverti.com/verify/?token=' + user.emailToken,
},
},
},
}
const emailTemplate = mailGenerator.generate(verifyEmail)
const message = {
to: user.email,
from: 'Kaverti <automailer@kaverti.com>',
subject: 'Kaverti account verification',
html: emailTemplate
}
const sendMail = async () => {
try {
mg.messages().send(message, function (error, body) {
if (error) {
res.status(500)
res.send({ error: error});
}
else {
res.send({ message: 'Success, email is on its way.'})
}
});
} catch (error) {
throw new Error(error.message)
}
}
try {
if(user.emailVerified) {
throw Errors.alreadyVerified
} else {
await sendMail()
}
} catch (error) {
throw new Error(error.message)
}
});
router.get('/email-verify/:token', async (req, res) => {
try {
let queryObj = {
attributes: {include: ['emailToken', 'emailVerified']},
where: { username: req.session.username }
}
let user = await User.findOne(queryObj)
if (user.emailToken == req.params.token) {
user.emailVerify()
res.status(200)
res.json({
success: "true"
})
} else {
res.status(400)
res.json({
errors: [Errors.invalidParameter('token', 'Invalid token')]
})
}
} catch (e) {
res.status(400)
res.json({
errors: [Errors.invalidParameter('token', 'Invalid token')]
})
}
});
router.put('/preferences', async (req, res, next) => {
try {
if(!req.session.username) {
throw Errors.requestNotAuthorized
}
await Ban.ReadOnlyMode(req.session.username)
if(req.autosan.body.description !== undefined) {
let user = await User.update({ description: req.autosan.body.description }, { where: {
username: req.session.username
}})
res.json({ success: true })
} else if(
req.body.currentPassword !== undefined &&
req.body.newPassword !== undefined
) {
let user = await User.findOne({
where: {
username: req.session.username
}
})
await user.updatePassword(req.body.currentPassword, req.body.newPassword)
res.json({success: true})
} else if(
req.body.emailCurrentPassword !== undefined &&
req.body.newEmail !== undefined
) {
let user = await User.findOne({where: {
username: req.session.username
}})
await user.updateEmail(req.body.emailCurrentPassword, req.body.newEmail)
res.json({ success: true })
} else if(
req.body.developerMode !== undefined) {
let user = await User.update({developerMode: req.autosan.body.developerMode}, {
where: {
username: req.session.username
}
})
res.json({success: true})
} else if(
req.body.userWallOptOut !== undefined
) {
let user = await User.update({userWallOptOut: req.autosan.body.userWallOptOut}, {
where: {
username: req.session.username
}
})
res.json({ success: true })
} else if(
req.body.username !== undefined &&
req.body.changeUsername !== undefined &&
req.body.password !== undefined
) {
let user = await User.findOne({where: {
username: req.session.username
}})
if(user.koins >= 200) {
await user.updateUsername(req.body.username, req.body.password)
} else {
throw Errors.insufficientKoins
}
res.json({ success: true })
} else if(
req.body.twoFactorGetCode
) {
let user = await User.findOne({where: {
username: req.session.username
}})
throw Errors.featureDisabled
function getTwoFactorAuthenticationCode() {
const secretCode = speakeasy.generateSecret({
name: "Kaverti",
});
return {
otpauthUrl : secretCode.otpauth_url,
base32: secretCode.base32,
};
}
res.status(200)
res.json({code: getTwoFactorAuthenticationCode().base32, url: getTwoFactorAuthenticationCode().otpauthUrl})
} else {
res.json({ success: false })
}
} catch (e) { next(e) }
})
router.put('/experiments', async (req, res, next) => {
try {
if(!req.session.username) {
throw Errors.requestNotAuthorized
}
let queryObj = {
attributes: {include: ['developerMode']},
where: { username: req.session.username }
}
let user = await User.findOne(queryObj)
if(!user.developerMode) {
throw Errors.deniedExperiments
}
if(req.body.theme !== undefined) {
{
let user = await User.update({theme: req.autosan.body.theme}, {
where: {
username: req.session.username
}
})
res.json({success: true})
}
} else {
res.json({ success: false })
}
} catch (e) { next(e) }
})
module.exports = router;