2017-08-03 15:49:31 +00:00
|
|
|
import fs from 'fs'
|
|
|
|
import path from 'path'
|
2017-08-02 21:24:01 +00:00
|
|
|
import express from 'express'
|
2017-08-23 22:25:52 +00:00
|
|
|
import RateLimit from 'express-rate-limit'
|
2017-08-02 21:24:01 +00:00
|
|
|
import parseurl from 'parseurl'
|
|
|
|
import config from '../../scripts/load-config'
|
|
|
|
import wrap from '../../scripts/asyncRoute'
|
2017-08-23 22:25:52 +00:00
|
|
|
import http from '../../scripts/http'
|
2017-08-02 21:24:01 +00:00
|
|
|
import API from '../api'
|
2017-08-24 10:52:12 +00:00
|
|
|
import News from '../api/news'
|
2017-08-24 18:36:40 +00:00
|
|
|
import emailer from '../api/emailer'
|
2017-08-02 21:24:01 +00:00
|
|
|
|
2017-08-03 12:57:17 +00:00
|
|
|
import apiRouter from './api'
|
2017-08-23 20:13:45 +00:00
|
|
|
import oauthRouter from './oauth2'
|
2017-08-03 12:57:17 +00:00
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
let router = express.Router()
|
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
let accountLimiter = new RateLimit({
|
|
|
|
windowMs: 60 * 60 * 1000, // 1 hour
|
|
|
|
max: 10,
|
|
|
|
delayMs: 0,
|
|
|
|
message: 'Whoa, slow down there, buddy! You just hit our rate limits. Try again in 1 hour.'
|
|
|
|
})
|
|
|
|
|
2017-08-24 18:36:40 +00:00
|
|
|
function setSession (req, user) {
|
|
|
|
req.session.user = {
|
|
|
|
id: user.id,
|
|
|
|
username: user.username,
|
|
|
|
display_name: user.display_name,
|
|
|
|
email: user.email,
|
|
|
|
avatar_file: user.avatar_file,
|
|
|
|
session_refresh: Date.now() + 1800000 // 30 minutes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
router.use(wrap(async (req, res, next) => {
|
|
|
|
let messages = req.flash('message')
|
|
|
|
if (!messages || !messages.length) {
|
|
|
|
messages = {}
|
|
|
|
} else {
|
|
|
|
messages = messages[0]
|
|
|
|
}
|
|
|
|
|
2017-08-24 18:36:40 +00:00
|
|
|
// Update user session every 30 minutes
|
|
|
|
if (req.session.user) {
|
|
|
|
if (!req.session.user.session_refresh) {
|
|
|
|
req.session.user.session_refresh = Date.now() + 1800000
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.session.user.session_refresh < Date.now()) {
|
|
|
|
let udata = await API.User.get(req.session.user.id)
|
|
|
|
setSession(req, udata)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
res.locals.message = messages
|
|
|
|
next()
|
|
|
|
}))
|
|
|
|
|
2017-08-23 20:13:45 +00:00
|
|
|
router.use('/oauth2', oauthRouter)
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
/*
|
|
|
|
================
|
|
|
|
RENDER VIEWS
|
|
|
|
================
|
|
|
|
*/
|
2017-08-23 22:25:52 +00:00
|
|
|
router.get('/', (req, res) => {
|
2017-08-02 21:24:01 +00:00
|
|
|
res.render('index')
|
2017-08-23 22:25:52 +00:00
|
|
|
})
|
2017-08-02 21:24:01 +00:00
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
// Add social media login buttons
|
|
|
|
function extraButtons (req, res, next) {
|
2017-08-03 12:57:17 +00:00
|
|
|
if (config.twitter && config.twitter.api) {
|
|
|
|
res.locals.twitter_auth = true
|
|
|
|
}
|
|
|
|
|
2017-08-03 15:49:31 +00:00
|
|
|
if (config.discord && config.discord.api) {
|
|
|
|
res.locals.discord_auth = true
|
|
|
|
}
|
|
|
|
|
2017-08-03 12:57:17 +00:00
|
|
|
if (config.facebook && config.facebook.client) {
|
|
|
|
res.locals.facebook_auth = config.facebook.client
|
|
|
|
}
|
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
next()
|
|
|
|
}
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
function ensureLogin (req, res, next) {
|
|
|
|
if (req.session.user) return next()
|
|
|
|
req.session.redirectUri = req.originalUrl
|
|
|
|
res.redirect('/login')
|
|
|
|
}
|
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
router.get('/login', extraButtons, (req, res) => {
|
|
|
|
if (req.session.user) {
|
|
|
|
let uri = '/'
|
|
|
|
if (req.session.redirectUri) {
|
|
|
|
uri = req.session.redirectUri
|
|
|
|
delete req.session.redirectUri
|
|
|
|
}
|
|
|
|
|
|
|
|
return res.redirect(uri)
|
|
|
|
}
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
res.render('login')
|
2017-08-23 22:25:52 +00:00
|
|
|
})
|
2017-08-02 21:24:01 +00:00
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
router.get('/register', extraButtons, (req, res) => {
|
2017-08-02 21:24:01 +00:00
|
|
|
if (req.session.user) return res.redirect('/')
|
|
|
|
|
|
|
|
let dataSave = req.flash('formkeep')
|
|
|
|
if (dataSave.length) {
|
|
|
|
dataSave = dataSave[0]
|
|
|
|
} else {
|
|
|
|
dataSave = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
res.locals.formkeep = dataSave
|
2017-08-03 12:57:17 +00:00
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
if (config.security.recaptcha && config.security.recaptcha.site_key) {
|
|
|
|
res.locals.recaptcha = config.security.recaptcha.site_key
|
2017-08-03 12:57:17 +00:00
|
|
|
}
|
2017-08-02 21:24:01 +00:00
|
|
|
|
|
|
|
res.render('register')
|
2017-08-23 22:25:52 +00:00
|
|
|
})
|
2017-08-02 21:24:01 +00:00
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
|
2017-08-02 22:35:10 +00:00
|
|
|
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
|
|
|
if (twoFaEnabled) return res.redirect('/')
|
2017-08-03 12:57:17 +00:00
|
|
|
|
2017-08-02 22:35:10 +00:00
|
|
|
let newToken = await API.User.Login.totpAquire(req.session.user)
|
2017-08-03 12:57:17 +00:00
|
|
|
if (!newToken) return res.redirect('/')
|
2017-08-24 16:23:03 +00:00
|
|
|
|
|
|
|
res.render('totp', { uri: newToken })
|
2017-08-02 22:35:10 +00:00
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => {
|
2017-08-02 22:35:10 +00:00
|
|
|
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
|
|
|
|
|
|
|
if (!twoFaEnabled) return res.redirect('/')
|
|
|
|
res.render('password')
|
|
|
|
}))
|
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
router.get('/login/verify', (req, res) => {
|
2017-08-02 22:35:10 +00:00
|
|
|
res.render('totp-check')
|
2017-08-23 22:25:52 +00:00
|
|
|
})
|
2017-08-02 22:35:10 +00:00
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
|
2017-08-24 18:36:40 +00:00
|
|
|
let totpEnabled = false
|
|
|
|
let socialStatus = await API.User.socialStatus(req.session.user)
|
|
|
|
|
|
|
|
if (socialStatus.password) {
|
|
|
|
totpEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.twitter && config.twitter.api) {
|
|
|
|
if (!socialStatus.enabled.twitter) {
|
|
|
|
res.locals.twitter_auth = true
|
|
|
|
} else if (!socialStatus.source && socialStatus.source !== 'twitter') {
|
|
|
|
res.locals.twitter_auth = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.discord && config.discord.api) {
|
|
|
|
if (!socialStatus.enabled.discord) {
|
|
|
|
res.locals.discord_auth = true
|
|
|
|
} else if (!socialStatus.source && socialStatus.source !== 'discord') {
|
|
|
|
res.locals.discord_auth = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.facebook && config.facebook.client) {
|
|
|
|
if (!socialStatus.enabled.fb) {
|
|
|
|
res.locals.facebook_auth = config.facebook.client
|
|
|
|
} else if (!socialStatus.source && socialStatus.source !== 'fb') {
|
|
|
|
res.locals.facebook_auth = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
res.render('settings', {totp: totpEnabled, password: socialStatus.password})
|
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => {
|
2017-08-24 18:36:40 +00:00
|
|
|
res.render('password_new')
|
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => {
|
2017-08-24 20:02:52 +00:00
|
|
|
let obfuscated = req.session.user.email
|
|
|
|
if (obfuscated) {
|
|
|
|
let split = obfuscated.split('@')
|
|
|
|
let rep = split[0].charAt(0) + '***' + split[0].charAt(split[0].length - 1)
|
|
|
|
obfuscated = rep + '@' + split[1]
|
|
|
|
}
|
|
|
|
|
2017-08-25 16:42:30 +00:00
|
|
|
let socialStatus = await API.User.socialStatus(req.session.user)
|
|
|
|
|
|
|
|
res.render('email_change', {email: obfuscated, password: socialStatus.password})
|
2017-08-24 20:02:52 +00:00
|
|
|
}))
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
POST HANDLING
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
|
2017-08-03 15:49:31 +00:00
|
|
|
function formError (req, res, error, redirect) {
|
2017-08-23 22:25:52 +00:00
|
|
|
// Security measures: never store any passwords in any session
|
2017-08-24 18:36:40 +00:00
|
|
|
for (let key in req.body) {
|
|
|
|
if (key.indexOf('password') !== -1) {
|
|
|
|
delete req.body[key]
|
2017-08-23 20:13:45 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-24 16:23:03 +00:00
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
req.flash('formkeep', req.body || {})
|
|
|
|
req.flash('message', {error: true, text: error})
|
2017-08-03 15:49:31 +00:00
|
|
|
res.redirect(redirect || parseurl(req).path)
|
2017-08-02 21:24:01 +00:00
|
|
|
}
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
function cleanString (input) {
|
|
|
|
let output = ''
|
|
|
|
for (let i = 0; i < input.length; i++) {
|
|
|
|
output += input.charCodeAt(i) <= 127 ? input.charAt(i) : ''
|
|
|
|
}
|
|
|
|
return output
|
|
|
|
}
|
|
|
|
|
|
|
|
router.post('/user/two-factor', wrap(async (req, res, next) => {
|
|
|
|
if (!req.session.user) return next()
|
2017-08-02 22:35:10 +00:00
|
|
|
if (!req.body.code) {
|
|
|
|
return formError(req, res, 'You need to enter the code.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
|
|
|
let verified = await API.User.Login.totpCheck(req.session.user, req.body.code)
|
|
|
|
if (!verified) {
|
|
|
|
return formError(req, res, 'Try again!')
|
|
|
|
}
|
|
|
|
|
|
|
|
res.redirect('/')
|
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.post('/user/two-factor/disable', wrap(async (req, res, next) => {
|
|
|
|
if (!req.session.user) return next()
|
2017-08-02 22:35:10 +00:00
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!req.body.password) {
|
|
|
|
return formError(req, res, 'Please enter your password.')
|
|
|
|
}
|
|
|
|
|
|
|
|
let purge = await API.User.Login.purgeTotp(req.session.user, req.body.password)
|
|
|
|
if (!purge) {
|
|
|
|
return formError(req, res, 'Invalid password.')
|
|
|
|
}
|
|
|
|
|
|
|
|
res.redirect('/')
|
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.post('/login/verify', wrap(async (req, res, next) => {
|
|
|
|
if (req.session.user) return next()
|
2017-08-02 22:35:10 +00:00
|
|
|
if (req.session.totp_check === null) return res.redirect('/login')
|
|
|
|
if (!req.body.code && !req.body.recovery) {
|
|
|
|
return formError(req, res, 'You need to enter the code.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
|
|
|
let totpCheck = await API.User.Login.totpCheck(req.session.totp_check, req.body.code, req.body.recovery || false)
|
|
|
|
if (!totpCheck) {
|
|
|
|
return formError(req, res, 'Invalid code!')
|
|
|
|
}
|
|
|
|
|
|
|
|
let user = await API.User.get(req.session.totp_check)
|
|
|
|
delete req.session.totp_check
|
|
|
|
|
|
|
|
// Set session
|
|
|
|
req.session.user = {
|
|
|
|
id: user.id,
|
|
|
|
username: user.username,
|
|
|
|
display_name: user.display_name,
|
|
|
|
email: user.email,
|
2017-08-03 12:57:17 +00:00
|
|
|
avatar_file: user.avatar_file,
|
|
|
|
session_refresh: Date.now() + 1800000 // 30 minutes
|
2017-08-02 22:35:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let uri = '/'
|
|
|
|
if (req.session.redirectUri) {
|
|
|
|
uri = req.session.redirectUri
|
|
|
|
delete req.session.redirectUri
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.query.redirect) {
|
|
|
|
uri = req.query.redirect
|
|
|
|
}
|
|
|
|
|
|
|
|
res.redirect(uri)
|
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.post('/login', wrap(async (req, res, next) => {
|
|
|
|
if (req.session.user) return next()
|
|
|
|
if (!req.body.username || !req.body.password || req.body.username === '') {
|
2017-08-02 21:24:01 +00:00
|
|
|
return res.redirect('/login')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
|
|
|
let user = await API.User.get(req.body.username)
|
|
|
|
if (!user) return formError(req, res, 'Invalid username or password.')
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
if (!user.password || user.password === '') return formError(req, res, 'Please log in using the buttons on the right.')
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
let pwMatch = await API.User.Login.password(user, req.body.password)
|
|
|
|
if (!pwMatch) return formError(req, res, 'Invalid username or password.')
|
|
|
|
|
|
|
|
if (user.activated === 0) return formError(req, res, 'Please activate your account first.')
|
|
|
|
if (user.locked === 1) return formError(req, res, 'This account has been locked.')
|
|
|
|
|
2017-08-02 22:35:10 +00:00
|
|
|
let totpRequired = await API.User.Login.totpTokenRequired(user)
|
|
|
|
if (totpRequired) {
|
|
|
|
req.session.totp_check = user.id
|
|
|
|
return res.redirect('/login/verify')
|
|
|
|
}
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
// TODO: Ban checks
|
|
|
|
|
|
|
|
// Set session
|
2017-08-24 18:36:40 +00:00
|
|
|
setSession(req, user)
|
2017-08-02 21:24:01 +00:00
|
|
|
|
|
|
|
let uri = '/'
|
|
|
|
if (req.session.redirectUri) {
|
|
|
|
uri = req.session.redirectUri
|
|
|
|
delete req.session.redirectUri
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.query.redirect) {
|
|
|
|
uri = req.query.redirect
|
|
|
|
}
|
|
|
|
|
|
|
|
res.redirect(uri)
|
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.post('/register', accountLimiter, wrap(async (req, res, next) => {
|
|
|
|
if (req.session.user) return next()
|
2017-08-02 21:24:01 +00:00
|
|
|
if (!req.body.username || !req.body.display_name || !req.body.password || !req.body.email) {
|
|
|
|
return formError(req, res, 'Please fill in all the fields.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1st Check: Username Characters and length
|
|
|
|
let username = req.body.username
|
2017-08-25 11:37:34 +00:00
|
|
|
if (!username || !username.match(/^([\w-_]{3,26})$/i)) {
|
2017-08-02 21:24:01 +00:00
|
|
|
return formError(req, res, 'Invalid username! Must be between 3-26 characters and composed of alphanumeric characters!')
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2nd Check: Display Name
|
2017-08-24 16:23:03 +00:00
|
|
|
let displayName = req.body.display_name
|
|
|
|
if (!displayName || !displayName.match(/^([^\\`]{3,32})$/i)) {
|
2017-08-02 21:24:01 +00:00
|
|
|
return formError(req, res, 'Invalid display name!')
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3rd Check: Email Address
|
|
|
|
let email = req.body.email
|
|
|
|
if (!email || !API.User.Register.validateEmail(email)) {
|
|
|
|
return formError(req, res, 'Invalid email address!')
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4th Check: Password length
|
|
|
|
let password = req.body.password
|
|
|
|
if (!password || password.length < 8 || password.length > 32) {
|
|
|
|
return formError(req, res, 'Invalid password! Keep it between 8 and 32 characters!')
|
|
|
|
}
|
|
|
|
|
|
|
|
// 5th Check: Password match
|
|
|
|
let passwordAgain = req.body.password_repeat
|
|
|
|
if (!passwordAgain || password !== passwordAgain) {
|
|
|
|
return formError(req, res, 'Passwords do not match!')
|
|
|
|
}
|
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
// 6th Check: reCAPTCHA (if configuration contains key)
|
|
|
|
if (config.security.recaptcha && config.security.recaptcha.site_key) {
|
|
|
|
if (!req.body['g-recaptcha-response']) return formError(req, res, 'Please complete the reCAPTCHA!')
|
2017-08-24 16:23:03 +00:00
|
|
|
|
2017-08-23 22:25:52 +00:00
|
|
|
try {
|
|
|
|
let data = await http.POST('https://www.google.com/recaptcha/api/siteverify', {}, {
|
|
|
|
secret: config.security.recaptcha.secret_key,
|
|
|
|
response: req.body['g-recaptcha-response']
|
|
|
|
})
|
|
|
|
|
|
|
|
data = JSON.parse(data)
|
|
|
|
if (!data.success) {
|
|
|
|
return formError(req, res, 'Please complete the reCAPTCHA!')
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
return formError(req, res, 'Internal server error')
|
|
|
|
}
|
|
|
|
}
|
2017-08-02 21:24:01 +00:00
|
|
|
|
|
|
|
// Hash the password
|
|
|
|
let hash = await API.User.Register.hashPassword(password)
|
|
|
|
|
|
|
|
// Attempt to create the user
|
|
|
|
let newUser = await API.User.Register.newAccount({
|
|
|
|
username: username,
|
2017-08-25 11:37:34 +00:00
|
|
|
display_name: cleanString(displayName),
|
2017-08-02 21:24:01 +00:00
|
|
|
password: hash,
|
|
|
|
email: email,
|
|
|
|
ip_address: req.realIP
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!newUser || newUser.error != null) {
|
|
|
|
return formError(req, res, newUser.error)
|
|
|
|
}
|
|
|
|
|
|
|
|
req.flash('message', {error: false, text: 'Account created successfully! Please check your email for an activation link.'})
|
|
|
|
res.redirect('/login')
|
|
|
|
}))
|
|
|
|
|
2017-08-24 18:36:40 +00:00
|
|
|
router.post('/user/manage', wrap(async (req, res, next) => {
|
|
|
|
if (!req.session.user) return next()
|
|
|
|
|
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!req.body.display_name) {
|
|
|
|
return formError(req, res, 'Display Name cannot be blank.')
|
|
|
|
}
|
|
|
|
|
|
|
|
let displayName = req.body.display_name
|
|
|
|
if (!displayName || !displayName.match(/^([^\\`]{3,32})$/i)) {
|
|
|
|
return formError(req, res, 'Invalid display name!')
|
|
|
|
}
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
displayName = cleanString(displayName)
|
|
|
|
|
2017-08-24 18:36:40 +00:00
|
|
|
// No change
|
|
|
|
if (displayName === req.session.user.display_name) {
|
|
|
|
return res.redirect('/user/manage')
|
|
|
|
}
|
|
|
|
|
|
|
|
let success = await API.User.update(req.session.user, {
|
|
|
|
display_name: displayName
|
|
|
|
})
|
|
|
|
|
|
|
|
if (success.error) {
|
|
|
|
return formError(req, res, success.error)
|
|
|
|
}
|
|
|
|
|
|
|
|
req.session.user.display_name = displayName
|
|
|
|
|
|
|
|
req.flash('message', {error: false, text: 'Settings changed successfully. Please note that it may take time to update on other websites and devices.'})
|
|
|
|
res.redirect('/user/manage')
|
|
|
|
}))
|
|
|
|
|
|
|
|
router.post('/user/manage/password', wrap(async (req, res, next) => {
|
|
|
|
if (!req.session.user) return next()
|
|
|
|
|
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!req.body.password_old) {
|
|
|
|
return formError(req, res, 'Please enter your current password.')
|
|
|
|
}
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
let user = req.session.user
|
|
|
|
let passwordMatch = await API.User.Login.password(user, req.body.password_old)
|
2017-08-24 18:36:40 +00:00
|
|
|
if (!passwordMatch) {
|
|
|
|
return formError(req, res, 'The password you provided is incorrect.')
|
|
|
|
}
|
|
|
|
|
|
|
|
let password = req.body.password
|
|
|
|
if (!password || password.length < 8 || password.length > 32) {
|
|
|
|
return formError(req, res, 'Invalid password! Keep it between 8 and 32 characters!')
|
|
|
|
}
|
|
|
|
|
|
|
|
let passwordAgain = req.body.password_repeat
|
|
|
|
if (!passwordAgain || password !== passwordAgain) {
|
|
|
|
return formError(req, res, 'The passwords do not match!')
|
|
|
|
}
|
|
|
|
|
|
|
|
password = await API.User.Register.hashPassword(password)
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
let success = await API.User.update(user, {
|
2017-08-24 18:36:40 +00:00
|
|
|
password: password
|
|
|
|
})
|
|
|
|
|
|
|
|
if (success.error) {
|
|
|
|
return formError(req, res, success.error)
|
|
|
|
}
|
|
|
|
|
|
|
|
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
|
|
|
|
|
|
|
|
if (config.email && config.email.enabled) {
|
|
|
|
await emailer.pushMail('password_alert', user.email, {
|
|
|
|
display_name: user.display_name,
|
|
|
|
ip: req.realIP
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
req.flash('message', {error: false, text: 'Password changed successfully.'})
|
|
|
|
return res.redirect('/user/manage')
|
|
|
|
}))
|
|
|
|
|
2017-08-24 20:02:52 +00:00
|
|
|
router.post('/user/manage/email', wrap(async (req, res, next) => {
|
|
|
|
if (!req.session.user) return next()
|
|
|
|
|
|
|
|
if (req.body.csrf !== req.session.csrf) {
|
|
|
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
|
|
}
|
|
|
|
|
2017-08-25 16:42:30 +00:00
|
|
|
let user = await API.User.get(req.session.user)
|
2017-08-24 20:02:52 +00:00
|
|
|
let email = req.body.email
|
|
|
|
let newEmail = req.body.email_new
|
|
|
|
let password = req.body.password
|
|
|
|
|
2017-08-25 16:42:30 +00:00
|
|
|
if (!newEmail || (!email && user.email !== '')) {
|
2017-08-24 20:02:52 +00:00
|
|
|
return formError(req, res, 'Please fill in all of the fields.')
|
|
|
|
}
|
|
|
|
|
2017-08-25 16:42:30 +00:00
|
|
|
if (req.session.user.email !== '' && email !== user.email) {
|
2017-08-24 20:02:52 +00:00
|
|
|
return formError(req, res, 'The email you provided is incorrect.')
|
|
|
|
}
|
|
|
|
|
2017-08-25 16:42:30 +00:00
|
|
|
if (user.password != null && user.password !== '') {
|
|
|
|
if (!password) {
|
|
|
|
return formError(req, res, 'Enter a password.')
|
|
|
|
}
|
|
|
|
|
|
|
|
let passwordMatch = await API.User.Login.password(user, password)
|
|
|
|
if (!passwordMatch) {
|
|
|
|
return formError(req, res, 'The password you provided is incorrect.')
|
|
|
|
}
|
2017-08-24 20:02:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let emailValid = API.User.Register.validateEmail(newEmail)
|
|
|
|
if (!emailValid) {
|
|
|
|
return formError(req, res, 'Invalid email address.')
|
|
|
|
}
|
|
|
|
|
2017-08-25 16:42:30 +00:00
|
|
|
let emailTaken = await API.User.get(newEmail)
|
|
|
|
if (emailTaken) {
|
|
|
|
return formError(req, res, 'This email is already taken.')
|
|
|
|
}
|
|
|
|
|
2017-08-24 20:02:52 +00:00
|
|
|
let success = await API.User.update(user, {
|
|
|
|
email: newEmail
|
|
|
|
})
|
|
|
|
|
|
|
|
if (success.error) {
|
|
|
|
return formError(req, res, success.error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Send necessary emails
|
|
|
|
console.warn('[SECURITY AUDIT] User \'%s\' email has been changed from %s', user.username, req.realIP)
|
|
|
|
|
|
|
|
req.session.user.email = newEmail
|
|
|
|
|
|
|
|
req.flash('message', {error: false, text: 'Email changed successfully.'})
|
|
|
|
return res.redirect('/user/manage')
|
|
|
|
}))
|
|
|
|
|
2017-08-03 15:49:31 +00:00
|
|
|
/*
|
|
|
|
=============
|
|
|
|
DOCUMENTS
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
|
|
|
|
const docsDir = path.join(__dirname, '../../documents')
|
2017-08-24 18:36:40 +00:00
|
|
|
router.get('/docs/:name', (req, res, next) => {
|
2017-08-03 15:49:31 +00:00
|
|
|
let doc = path.join(docsDir, req.params.name + '.html')
|
|
|
|
if (!fs.existsSync(docsDir) || !fs.existsSync(doc)) {
|
2017-08-24 18:36:40 +00:00
|
|
|
return next()
|
2017-08-03 15:49:31 +00:00
|
|
|
}
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
try {
|
|
|
|
doc = fs.readFileSync(doc, {encoding: 'utf8'})
|
|
|
|
} catch (e) {
|
|
|
|
return next(e)
|
|
|
|
}
|
2017-08-03 15:49:31 +00:00
|
|
|
|
|
|
|
res.render('document', {doc: doc})
|
2017-08-23 22:25:52 +00:00
|
|
|
})
|
2017-08-03 15:49:31 +00:00
|
|
|
|
2017-08-24 10:52:12 +00:00
|
|
|
router.get('/news/:id?-*', wrap(async (req, res) => {
|
|
|
|
let id = parseInt(req.params.id)
|
|
|
|
if (isNaN(id)) {
|
|
|
|
return res.status(404).render('article', {article: null})
|
|
|
|
}
|
|
|
|
|
|
|
|
let article = await News.article(id)
|
|
|
|
if (!article.id) {
|
|
|
|
return res.status(404).render('article', {article: null})
|
|
|
|
}
|
|
|
|
|
|
|
|
res.render('article', {article: article})
|
|
|
|
}))
|
|
|
|
|
|
|
|
router.get('/news/', wrap(async (req, res) => {
|
|
|
|
let page = parseInt(req.query.page)
|
|
|
|
if (isNaN(page)) {
|
|
|
|
page = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
let news = await News.listNews(page)
|
2017-08-24 16:23:03 +00:00
|
|
|
|
2017-08-24 10:52:12 +00:00
|
|
|
res.render('news', {news: news})
|
|
|
|
}))
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
router.get('/partials/:view', wrap(async (req, res, next) => {
|
|
|
|
if (!req.params.view) return next()
|
|
|
|
|
|
|
|
res.render('partials/' + req.params.view)
|
|
|
|
}))
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
/*
|
|
|
|
=========
|
|
|
|
OTHER
|
|
|
|
=========
|
|
|
|
*/
|
|
|
|
|
|
|
|
router.get('/logout', wrap(async (req, res) => {
|
|
|
|
if (req.session.user) {
|
|
|
|
delete req.session.user
|
|
|
|
}
|
|
|
|
|
|
|
|
res.redirect('/')
|
|
|
|
}))
|
|
|
|
|
2017-08-02 22:35:10 +00:00
|
|
|
router.get('/activate/:token', wrap(async (req, res) => {
|
|
|
|
if (req.session.user) return res.redirect('/login')
|
|
|
|
let token = req.params.token
|
|
|
|
|
|
|
|
let success = await API.User.Login.activationToken(token)
|
|
|
|
if (!success) return formError(req, res, 'Unknown or invalid activation token')
|
|
|
|
|
|
|
|
req.flash('message', {error: false, text: 'Your account has been activated! You may now log in.'})
|
|
|
|
res.redirect('/login')
|
|
|
|
}))
|
|
|
|
|
2017-08-03 12:57:17 +00:00
|
|
|
router.use('/api', apiRouter)
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
/*
|
|
|
|
NO ROUTES BEYOND THIS POINT
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Handle 'Failed to lookup view' errors
|
|
|
|
router.use((err, req, res, next) => {
|
|
|
|
if (err && err.stack) {
|
|
|
|
if (err.stack.indexOf('Failed to lookup view') !== -1) {
|
|
|
|
return next() // To 404
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
next(err) // To error handler
|
|
|
|
})
|
|
|
|
|
|
|
|
// 404 - last route
|
2017-08-24 18:36:40 +00:00
|
|
|
router.use((req, res) => {
|
|
|
|
res.status(404).render('404')
|
|
|
|
})
|
|
|
|
|
2017-08-25 11:37:34 +00:00
|
|
|
// Error handler
|
2017-08-02 21:24:01 +00:00
|
|
|
router.use((err, req, res, next) => {
|
|
|
|
console.error(err)
|
2017-08-25 11:37:34 +00:00
|
|
|
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
|
return res.end(err.stack)
|
|
|
|
}
|
|
|
|
|
2017-08-02 21:24:01 +00:00
|
|
|
next()
|
|
|
|
})
|
|
|
|
|
|
|
|
module.exports = router
|