forked from kaverti/website
541 lines
17 KiB
JavaScript
541 lines
17 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');
|
|
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}]}"
|
|
});
|
|
|
|
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 + 100, 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
|
|
}})
|
|
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 {
|
|
res.status(401)
|
|
res.json({
|
|
errors: [Errors.invalidLoginCredentials]
|
|
})
|
|
}
|
|
} catch (err) { next(err) }
|
|
})
|
|
router.post('/recovery/send', async (req, res, next) => {
|
|
try {
|
|
await Ban.isIpBanned(req.ip)
|
|
|
|
throw Errors.featureDisabled
|
|
let user = await User.create(userParams)
|
|
await Ip.createIfNotExists(req.ip, user)
|
|
|
|
res.json(user.toJSON())
|
|
} catch (e) { next(e) }
|
|
/*
|
|
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, you can opt out of password resets in User Settings as well.',
|
|
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 {
|
|
sgMail.setApiKey("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")
|
|
return sgMail.send(message)
|
|
} 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) {
|
|
throw new Error(error.message)
|
|
} */
|
|
});
|
|
|
|
router.put('/recovery/change', async (req, res, next) => {
|
|
try {
|
|
await Ban.isIpBanned(req.ip)
|
|
|
|
throw Errors.featureDisabled
|
|
let user = await User.create(userParams)
|
|
await Ip.createIfNotExists(req.ip, user)
|
|
|
|
res.json(user.toJSON())
|
|
} catch (e) { next(e) }
|
|
/*
|
|
try {
|
|
let queryObj = {
|
|
attributes: {include: ['passwordResetToken', 'passwordResetOptOut', 'passwordResetEnabled', 'emailVerified']},
|
|
where: {username: req.body.username}
|
|
}
|
|
let user = await User.findOne(queryObj)
|
|
if(user) {
|
|
|
|
if (user.passwordResetOptOut) {
|
|
throw Errors.passwordResetOptOut
|
|
}
|
|
if (!user.passwordResetEnabled) {
|
|
throw Errors.passwordResetDisabled
|
|
}
|
|
|
|
if (user.passwordResetToken == req.params.token) {
|
|
await user.recoveryUpdatePassword(req.body.password)
|
|
res.status(200)
|
|
res.json({
|
|
success: "true"
|
|
})
|
|
} else {
|
|
throw Errors.invalidPasswordToken
|
|
}
|
|
} else {
|
|
throw Errors.requestNotAuthorized
|
|
}
|
|
} catch (e) {
|
|
throw Errors.requestNotAuthorized
|
|
} */
|
|
})
|
|
|
|
router.all('*', (req, res, next) => {
|
|
if(req.session.username) {
|
|
next()
|
|
} else {
|
|
res.status(401)
|
|
res.json({
|
|
errors: [Errors.requestNotAuthorized]
|
|
})
|
|
}
|
|
})
|
|
|
|
router.put('/delete', async (req, res, next) => {
|
|
try {
|
|
if(req.session.username == "undefined") {
|
|
throw Errors.requestNotAuthorized
|
|
}
|
|
if(req.session.username == "null") {
|
|
throw Errors.requestNotAuthorized
|
|
}
|
|
if(req.session.username == "") {
|
|
throw Errors.requestNotAuthorized
|
|
}
|
|
|
|
let user = await User.findOne({ where: {
|
|
username: req.session.username
|
|
}})
|
|
await user.destroyVerifyPassword(req.body.password)
|
|
|
|
req.session.destroy(() => {
|
|
res.clearCookie('username')
|
|
res.clearCookie('admin')
|
|
res.json({ success: true })
|
|
})
|
|
|
|
} catch (e) { next(e) }
|
|
})
|
|
|
|
router.post('/email-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 {
|
|
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;
|