This repository has been archived on 2022-11-26. You can view files and clone it, but cannot push or open issues or pull requests.
IcyNet.eu/server/routes/index.js

887 lines
24 KiB
JavaScript
Raw Normal View History

import fs from 'fs-extra'
2017-08-03 15:49:31 +00:00
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'
import { ensureLogin } from '../../scripts/ensureLogin'
2017-08-02 21:24:01 +00:00
import config from '../../scripts/load-config'
import wrap from '../../scripts/asyncRoute'
import { httpPOST } from '../../scripts/http'
import { User, Login, Reset, Register } from '../api'
import * as News from '../api/news'
import { pushMail } from '../api/emailer'
2017-08-02 21:24:01 +00:00
import apiRouter from './api'
2017-08-23 20:13:45 +00:00
import oauthRouter from './oauth2'
2017-08-27 18:39:30 +00:00
import adminRouter from './admin'
2020-05-28 18:30:21 +00:00
const router = express.Router()
2017-08-02 21:24:01 +00:00
2017-08-27 11:48:47 +00:00
// Restrict account creation
2020-05-28 18:30:21 +00:00
const accountLimiter = new RateLimit({
2017-08-23 22:25:52 +00:00
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-27 11:48:47 +00:00
// Set the user session
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,
2017-08-29 12:00:36 +00:00
privilege: user.nw_privilege,
session_refresh: Date.now() + 1800000 // 30 minutes
}
}
2017-08-30 12:23:45 +00:00
function redirectLogin (req, res) {
let uri = '/'
2017-08-30 12:23:45 +00:00
if (req.session.redirectUri) {
uri = req.session.redirectUri
delete req.session.redirectUri
}
res.redirect(uri)
}
2017-08-02 21:24:01 +00:00
router.use(wrap(async (req, res, next) => {
2017-08-27 11:48:47 +00:00
// Add form messages into the template rendering if present
2017-08-02 21:24:01 +00:00
let messages = req.flash('message')
if (!messages || !messages.length) {
messages = {}
} else {
messages = messages[0]
}
res.locals.message = messages
// 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()) {
2017-08-28 22:36:13 +00:00
console.debug('User session update')
// Check for bans
const banStatus = await User.getBanStatus(req.session.user.id)
if (banStatus.length) {
delete req.session.user
return next()
}
// Update user session
const udata = await User.get(req.session.user)
2017-08-29 12:00:36 +00:00
setSession(req, udata)
2017-08-28 22:36:13 +00:00
// Update IP address
await User.update(udata, { ip_address: req.realIP })
}
}
2017-08-02 21:24:01 +00:00
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
2019-08-08 12:33:58 +00:00
function extraButtons (recheck) {
2020-05-28 18:30:21 +00:00
const et = config.external
2019-08-08 12:33:58 +00:00
return function (req, res, next) {
if (!et) return next()
res.locals.auth = {
registrations: et.registrations
}
2019-08-08 12:33:58 +00:00
if (recheck && et.registrations !== true) return next()
2017-08-03 15:49:31 +00:00
2019-08-08 12:33:58 +00:00
if (et.twitter && et.twitter.api) {
res.locals.auth.twitter = true
}
2019-08-08 12:33:58 +00:00
if (et.discord && et.discord.api) {
res.locals.auth.discord = true
}
2017-10-13 16:18:17 +00:00
2019-08-08 12:33:58 +00:00
if (et.facebook && et.facebook.client) {
res.locals.auth.facebook = et.facebook.client
}
if (et.google && et.google.api) {
res.locals.auth.google = et.google.api
}
next()
}
2017-08-23 22:25:52 +00:00
}
2017-08-30 12:23:45 +00:00
// Retrieve form data if formError was called
function formKeep (req, res, next) {
let dataSave = req.flash('formkeep')
if (dataSave.length) {
dataSave = dataSave[0]
} else {
dataSave = {}
}
res.locals.formkeep = dataSave
next()
}
// Password reset request endpoint
2019-08-08 12:33:58 +00:00
router.get('/login/reset', extraButtons(false), (req, res) => {
if (req.session.user) return redirectLogin(req, res)
2020-05-28 18:30:21 +00:00
res.render('user/reset_password', { sent: req.query.success != null })
})
// Password reset endpoint (emailed link)
router.get('/reset/:token', wrap(async (req, res) => {
if (req.session.user) return res.redirect('/login')
2020-05-28 18:30:21 +00:00
const token = req.params.token
const success = await Reset.resetToken(token)
if (!success) {
2020-05-28 18:30:21 +00:00
req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
res.redirect('/login')
return
}
2020-05-28 18:30:21 +00:00
res.render('user/password_new', { token: true })
}))
router.get('/login', extraButtons(false), formKeep, (req, res) => {
2017-08-30 12:23:45 +00:00
if (req.session.user) return redirectLogin(req, res)
2017-10-09 14:38:27 +00:00
if (req.query.returnTo) {
req.session.redirectUri = req.query.returnTo
}
2017-08-23 22:25:52 +00:00
res.render('user/login')
2017-08-23 22:25:52 +00:00
})
2017-08-02 21:24:01 +00:00
2019-08-08 12:33:58 +00:00
router.get('/register', extraButtons(true), formKeep, (req, res) => {
2017-08-30 12:23:45 +00:00
if (req.session.user) return redirectLogin(req, res)
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-02 21:24:01 +00:00
res.render('user/register')
2017-08-23 22:25:52 +00:00
})
2017-08-02 21:24:01 +00:00
// User activation endpoint (emailed link)
router.get('/activate/:token', wrap(async (req, res) => {
if (req.session.user) return res.redirect('/login')
2020-05-28 18:30:21 +00:00
const token = req.params.token
const success = await Login.activationToken(token)
if (!success) {
2020-05-28 18:30:21 +00:00
req.flash('message', { error: true, text: 'Invalid or expired activation token.' })
} else {
2020-05-28 18:30:21 +00:00
req.flash('message', { error: false, text: 'Your account has been activated! You may now log in.' })
}
res.redirect('/login')
}))
2017-08-27 11:48:47 +00:00
// View for enabling Two-Factor Authentication
2017-08-25 11:37:34 +00:00
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
const twoFaEnabled = await Login.totpTokenRequired(req.session.user)
2017-08-02 22:35:10 +00:00
if (twoFaEnabled) return res.redirect('/')
const newToken = await Login.totpAquire(req.session.user)
if (!newToken) return res.redirect('/')
2017-08-24 16:23:03 +00:00
res.render('user/totp', { uri: newToken })
2017-08-02 22:35:10 +00:00
}))
2017-08-27 11:48:47 +00:00
// View for disabling Two-Factor Authentication
2017-08-25 11:37:34 +00:00
router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => {
const twoFaEnabled = await Login.totpTokenRequired(req.session.user)
2017-08-02 22:35:10 +00:00
if (!twoFaEnabled) return res.redirect('/')
res.render('user/password')
2017-08-02 22:35:10 +00:00
}))
2017-08-27 11:48:47 +00:00
// Two-Factor Authentication verification on login
2017-08-23 22:25:52 +00:00
router.get('/login/verify', (req, res) => {
res.render('user/totp-check')
2017-08-23 22:25:52 +00:00
})
2017-08-02 22:35:10 +00:00
2017-08-27 11:48:47 +00:00
// User settings page
2017-08-25 11:37:34 +00:00
router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
let totpEnabled = false
const socialStatus = await User.socialStatus(req.session.user)
if (socialStatus.password) {
totpEnabled = await Login.totpTokenRequired(req.session.user)
}
2020-05-28 18:30:21 +00:00
const et = config.external
2019-08-08 12:37:52 +00:00
if (et) {
2019-08-08 12:39:42 +00:00
res.locals.auth = {}
2019-08-08 12:37:52 +00:00
// Decide whether we need a disconnect or a log in with button for social account logins
if (et.twitter && et.twitter.api) {
if (!socialStatus.enabled.twitter) {
2019-08-08 12:39:42 +00:00
res.locals.auth.twitter = true
2019-08-08 12:37:52 +00:00
} else if (socialStatus.source !== 'twitter') {
2019-08-08 12:39:42 +00:00
res.locals.auth.twitter = false
2019-08-08 12:37:52 +00:00
}
}
2019-08-08 12:37:52 +00:00
if (et.discord && et.discord.api) {
if (!socialStatus.enabled.discord) {
2019-08-08 12:39:42 +00:00
res.locals.auth.discord = true
2019-08-08 12:37:52 +00:00
} else if (socialStatus.source !== 'discord') {
2019-08-08 12:39:42 +00:00
res.locals.auth.discord = false
2019-08-08 12:37:52 +00:00
}
}
2019-08-08 12:37:52 +00:00
if (et.facebook && et.facebook.client) {
if (!socialStatus.enabled.facebook) {
2019-08-08 12:39:42 +00:00
res.locals.auth.facebook = et.facebook.client
2019-08-08 12:37:52 +00:00
} else if (socialStatus.source !== 'facebook') {
2019-08-08 12:39:42 +00:00
res.locals.auth.facebook = false
2019-08-08 12:37:52 +00:00
}
}
2019-08-08 12:37:52 +00:00
if (et.google && et.google.api) {
if (!socialStatus.enabled.google) {
2019-08-08 12:39:42 +00:00
res.locals.auth.google = et.google.api
2019-08-08 12:37:52 +00:00
} else if (socialStatus.source !== 'google') {
2019-08-08 12:39:42 +00:00
res.locals.auth.google = false
2019-08-08 12:37:52 +00:00
}
2017-10-13 16:18:17 +00:00
}
}
2020-05-28 18:30:21 +00:00
res.render('user/settings', { totp: totpEnabled, password: socialStatus.password })
}))
2017-08-27 11:48:47 +00:00
// Change password
2017-08-25 11:37:34 +00:00
router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => {
const socialStatus = await User.socialStatus(req.session.user)
2020-05-28 18:30:21 +00:00
res.render('user/password_new', { token: !socialStatus.password })
}))
2017-08-27 11:48:47 +00:00
// Change email
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) {
2020-05-28 18:30:21 +00:00
const split = obfuscated.split('@')
const rep = split[0].charAt(0) + '***' + split[0].charAt(split[0].length - 1)
2017-08-24 20:02:52 +00:00
obfuscated = rep + '@' + split[1]
}
const socialStatus = await User.socialStatus(req.session.user)
2017-08-25 16:42:30 +00:00
2020-05-28 18:30:21 +00:00
res.render('user/email_change', { email: obfuscated, password: socialStatus.password })
2017-08-24 20:02:52 +00:00
}))
router.get('/donate', wrap(async (req, res, next) => {
if (!config.donations || !config.donations.business) return next()
res.render('donate', config.donations)
}))
2017-08-02 21:24:01 +00:00
/*
=================
POST HANDLING
=================
*/
2017-08-27 11:48:47 +00:00
// Used to display errors on forms and save data
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
2020-05-28 18:30:21 +00:00
for (const 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 || {})
2020-05-28 18:30:21 +00:00
req.flash('message', { error: true, text: error })
2017-08-27 11:48:47 +00:00
res.redirect(redirect || req.originalUrl)
2017-08-02 21:24:01 +00:00
}
2017-08-27 11:48:47 +00:00
// Make sure characters are UTF-8
2017-08-25 11:37:34 +00:00
function cleanString (input) {
let output = ''
2017-08-25 11:37:34 +00:00
for (let i = 0; i < input.length; i++) {
output += input.charCodeAt(i) <= 127 ? input.charAt(i) : ''
}
2017-08-25 11:37:34 +00:00
return output
}
// Make sure CSRF tokens are present and valid in every form
function csrfValidation (req, res, next) {
if (req.body.csrf !== req.session.csrf) {
return formError(req, res, 'Invalid session! Try reloading the page.')
}
next()
}
2017-08-27 11:48:47 +00:00
// Enabling 2fa
router.post('/user/two-factor', csrfValidation, wrap(async (req, res, next) => {
2017-08-25 11:37:34 +00:00
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.')
}
const verified = await Login.totpCheck(req.session.user, req.body.code)
2017-08-02 22:35:10 +00:00
if (!verified) {
2017-08-27 11:48:47 +00:00
return formError(req, res, 'Something went wrong! Try scanning the code again.')
2017-08-02 22:35:10 +00:00
}
res.redirect('/')
}))
2017-08-27 11:48:47 +00:00
// Disabling 2fa
router.post('/user/two-factor/disable', csrfValidation, wrap(async (req, res, next) => {
2017-08-25 11:37:34 +00:00
if (!req.session.user) return next()
2017-08-02 22:35:10 +00:00
if (!req.body.password) {
return formError(req, res, 'Please enter your password.')
}
const purge = await Login.purgeTotp(req.session.user, req.body.password)
2017-08-02 22:35:10 +00:00
if (!purge) {
return formError(req, res, 'Invalid password.')
}
res.redirect('/')
}))
2017-08-27 11:48:47 +00:00
// Verify 2FA for login
router.post('/login/verify', csrfValidation, wrap(async (req, res, next) => {
2017-08-25 11:37:34 +00:00
if (req.session.user) return next()
2017-08-02 22:35:10 +00:00
if (req.session.totp_check === null) return res.redirect('/login')
2017-08-02 22:35:10 +00:00
if (!req.body.code && !req.body.recovery) {
return formError(req, res, 'You need to enter the code.')
}
const totpCheck = await Login.totpCheck(req.session.totp_check, req.body.code, req.body.recovery || false)
2017-08-02 22:35:10 +00:00
if (!totpCheck) {
return formError(req, res, 'Invalid code!')
}
const user = await User.get(req.session.totp_check)
2017-08-02 22:35:10 +00:00
delete req.session.totp_check
2017-08-30 12:23:45 +00:00
setSession(req, user)
redirectLogin(req, res)
2017-08-02 22:35:10 +00:00
}))
2017-08-30 12:23:45 +00:00
// Log the user in. Limited resource
router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
2017-08-25 11:37:34 +00:00
if (req.session.user) return next()
2017-08-25 11:37:34 +00:00
if (!req.body.username || !req.body.password || req.body.username === '') {
2017-08-02 21:24:01 +00:00
return res.redirect('/login')
}
const user = await User.get(req.body.username)
2017-08-02 21:24:01 +00:00
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.')
const pwMatch = await Login.password(user, req.body.password)
2017-08-02 21:24:01 +00:00
if (!pwMatch) return formError(req, res, 'Invalid username or password.')
2019-09-08 15:52:12 +00:00
if (user.activated === 0) return formError(req, res, 'Please activate your account first. If you did not receive an email, please contact an administrator.')
2019-09-08 15:52:12 +00:00
if (user.locked === 1) return formError(req, res, 'This account has been locked. Please contact an administrator for more information.')
2017-08-02 21:24:01 +00:00
// Check if the user is banned
const banStatus = await User.getBanStatus(user.id)
if (banStatus.length) {
2020-05-28 18:30:21 +00:00
return res.render('user/banned', { bans: banStatus, ipban: false })
}
2017-08-27 11:48:47 +00:00
// Redirect to the verification dialog if 2FA is enabled
const totpRequired = await Login.totpTokenRequired(user)
2017-08-02 22:35:10 +00:00
if (totpRequired) {
req.session.totp_check = user.id
return res.redirect('/login/verify')
}
2017-08-02 21:24:01 +00:00
// Set session
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)
}))
// Password reset
router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
if (req.session.user) return next()
if (!req.body.email) {
return formError(req, res, 'You need to enter your email address.')
}
2020-05-28 18:30:21 +00:00
const email = req.body.email
const validEmail = await Register.validateEmail(email)
if (!validEmail) {
return formError(req, res, 'You need to enter a valid email address.')
}
try {
await Reset.reset(email, false)
2017-11-30 21:56:27 +00:00
2020-05-28 18:30:21 +00:00
req.flash('message', { error: false, text: 'We\'ve sent a link to your email address. Please check spam folders, too!' })
res.redirect('/login/reset?success=true')
} catch (e) {
return formError(req, res, e.message)
}
}))
// Password reset endpoint (emailed link)
router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
if (req.session.user) return res.redirect('/login')
2020-05-28 18:30:21 +00:00
const token = req.params.token
const user = await Reset.resetToken(token)
if (!user) {
2020-05-28 18:30:21 +00:00
req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
res.redirect('/login')
return
}
// 4th Check: Password length
2020-05-28 18:30:21 +00:00
const password = req.body.password
if (!password || password.length < 8) {
return formError(req, res, 'Invalid password! Please use at least 8 characters!')
}
// 5th Check: Password match
2020-05-28 18:30:21 +00:00
const passwordAgain = req.body.password_repeat
if (!passwordAgain || password !== passwordAgain) {
return formError(req, res, 'Passwords do not match!')
}
try {
await Reset.changePassword(user, password, token)
2017-11-30 21:45:21 +00:00
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
2020-05-28 18:30:21 +00:00
req.flash('message', { error: false, text: 'Your password has been changed successfully. You may now log in!' })
res.redirect('/login')
} catch (e) {
return formError(req, res, e.message)
}
}))
2017-08-27 11:48:47 +00:00
// Protected & Limited resource: Account registration
router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
2017-08-25 11:37:34 +00:00
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.')
}
2017-08-30 12:23:45 +00:00
// Ban check
const banStatus = await User.getBanStatus(req.realIP, true)
2017-08-30 12:23:45 +00:00
if (banStatus.length) {
2020-05-28 18:30:21 +00:00
return res.render('user/banned', { bans: banStatus, ipban: true })
2017-08-30 12:23:45 +00:00
}
2017-08-02 21:24:01 +00:00
// 1st Check: Username Characters and length
2020-05-28 18:30:21 +00:00
const 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
2020-05-28 18:30:21 +00:00
const displayName = req.body.display_name
2017-08-24 16:23:03 +00:00
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
2020-05-28 18:30:21 +00:00
const email = req.body.email
if (!email || !Register.validateEmail(email)) {
2017-08-02 21:24:01 +00:00
return formError(req, res, 'Invalid email address!')
}
// 4th Check: Password length
2020-05-28 18:30:21 +00:00
const password = req.body.password
2017-09-26 19:47:11 +00:00
if (!password || password.length < 8) {
return formError(req, res, 'Invalid password! Please use at least 8 characters!')
2017-08-02 21:24:01 +00:00
}
// 5th Check: Password match
2020-05-28 18:30:21 +00:00
const passwordAgain = req.body.password_repeat
2017-08-02 21:24:01 +00:00
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)
2017-08-29 12:00:36 +00:00
if (config.security && config.security.recaptcha && config.security.recaptcha.site_key) {
2017-08-23 22:25:52 +00:00
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 httpPOST('https://www.google.com/recaptcha/api/siteverify', {}, {
2017-08-23 22:25:52 +00:00
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
const hash = await Register.hashPassword(password)
let newUser
2017-08-02 21:24:01 +00:00
// Attempt to create the user
try {
newUser = await Register.newAccount({
username: username,
display_name: cleanString(displayName),
password: hash,
email: email,
ip_address: req.realIP
})
} catch (e) {
return formError(req, res, e.message)
2017-08-02 21:24:01 +00:00
}
2017-08-27 11:48:47 +00:00
// Do not include activation link message when the user is already activated
let registerMessage = 'Account created successfully!'
2019-09-08 15:52:12 +00:00
if (newUser && newUser.activated !== 1) {
2017-08-30 12:23:45 +00:00
registerMessage += ' Please check your inbox for an activation link. Also, make sure to look into spam folders.'
2017-08-27 11:48:47 +00:00
}
2020-05-28 18:30:21 +00:00
req.flash('message', { error: false, text: registerMessage })
2017-08-02 21:24:01 +00:00
res.redirect('/login')
}))
2017-08-27 11:48:47 +00:00
// Change display name
router.post('/user/manage', csrfValidation, wrap(async (req, res, next) => {
if (!req.session.user) return next()
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)
// No change
if (displayName === req.session.user.display_name) {
return res.redirect('/user/manage')
}
try {
await User.update(req.session.user, {
display_name: displayName
})
} catch (e) {
return formError(req, res, e.message)
}
req.session.user.display_name = displayName
2020-05-28 18:30:21 +00:00
req.flash('message', { error: false, text: 'Settings changed successfully.' })
res.redirect('/user/manage')
}))
2017-08-27 11:48:47 +00:00
// Change user password
router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
if (!req.session.user) return next()
2020-05-28 18:30:21 +00:00
const user = req.session.user
const socialStatus = await User.socialStatus(user)
if (!req.body.password_old && socialStatus.password) {
return formError(req, res, 'Please enter your current password.')
}
if (socialStatus.password) {
const passwordMatch = await Login.password(user, req.body.password_old)
if (!passwordMatch) {
return formError(req, res, 'The password you provided is incorrect.')
}
}
let password = req.body.password
2017-09-26 19:47:11 +00:00
if (!password || password.length < 8) {
return formError(req, res, 'Invalid password! Please use at least 8 characters!')
}
2020-05-28 18:30:21 +00:00
const passwordAgain = req.body.password_repeat
if (!passwordAgain || password !== passwordAgain) {
return formError(req, res, 'The passwords do not match!')
}
password = await Register.hashPassword(password)
try {
await User.update(user, {
password: password
})
} catch (e) {
return formError(req, res, e.message)
}
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
if (config.email && config.email.enabled) {
await pushMail('password_alert', user.email, {
display_name: user.display_name,
ip: req.realIP
})
}
2020-05-28 18:30:21 +00:00
req.flash('message', { error: false, text: 'Password changed successfully.' })
return res.redirect('/user/manage')
}))
2017-08-27 11:48:47 +00:00
// Change email address
router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
2017-08-24 20:02:52 +00:00
if (!req.session.user) return next()
const user = await User.get(req.session.user)
2020-05-28 18:30:21 +00:00
const email = req.body.email
const newEmail = req.body.email_new
const password = req.body.password
2017-08-24 20:02:52 +00:00
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.')
}
const passwordMatch = await Login.password(user, password)
2017-08-25 16:42:30 +00:00
if (!passwordMatch) {
return formError(req, res, 'The password you provided is incorrect.')
}
2017-08-24 20:02:52 +00:00
}
const emailValid = Register.validateEmail(newEmail)
2017-08-24 20:02:52 +00:00
if (!emailValid) {
return formError(req, res, 'Invalid email address.')
}
const emailTaken = await User.get(newEmail)
2017-08-25 16:42:30 +00:00
if (emailTaken) {
return formError(req, res, 'This email is already taken.')
}
try {
await User.update(user, {
email: newEmail
})
} catch (e) {
return formError(req, res, e.message)
2017-08-24 20:02:52 +00:00
}
console.warn('[SECURITY AUDIT] User \'%s\' email has been changed from %s', user.username, req.realIP)
req.session.user.email = newEmail
2020-05-28 18:30:21 +00:00
req.flash('message', { error: false, text: 'Email changed successfully.' })
2017-08-24 20:02:52 +00:00
return res.redirect('/user/manage')
}))
2017-08-03 15:49:31 +00:00
/*
=============
DOCUMENTS
=============
*/
2017-08-27 11:48:47 +00:00
// Serve a document form the documents directory, cache it.
2017-08-03 15:49:31 +00:00
const docsDir = path.join(__dirname, '../../documents')
router.get('/docs/:name', wrap(async (req, res, next) => {
2020-05-28 18:30:21 +00:00
const doc = path.join(docsDir, req.params.name + '.html')
if (!await fs.exists(docsDir) || !await fs.exists(doc)) {
return next()
2017-08-03 15:49:31 +00:00
}
2020-05-28 18:30:21 +00:00
fs.readFile(doc, { encoding: 'utf8' }, (err, contents) => {
2017-09-15 11:22:48 +00:00
if (err) return next(err)
res.header('Cache-Control', 'max-age=' + 7 * 24 * 60 * 60 * 1000) // 1 week
2020-05-28 18:30:21 +00:00
res.render('document', { doc: contents })
2017-09-15 11:22:48 +00:00
})
}))
2017-08-03 15:49:31 +00:00
2017-08-29 12:00:36 +00:00
/*
========
NEWS
========
*/
2017-08-30 12:23:45 +00:00
function newsPrivilege (req, res, next) {
2017-08-29 12:00:36 +00:00
if (!req.session.user) return res.redirect('/news')
if (req.session.user.privilege < 1) return res.redirect('/news')
next()
}
router.get('/news/compose', newsPrivilege, formKeep, (req, res) => {
2017-08-29 12:00:36 +00:00
res.render('news/composer')
})
2017-08-29 12:00:36 +00:00
router.post('/news/compose', newsPrivilege, csrfValidation, wrap(async (req, res) => {
2017-08-29 12:00:36 +00:00
if (!req.body.title || !req.body.content) {
return formError(req, res, 'Required fields missing!')
}
let result
try {
result = await News.compose(req.session.user, req.body)
} catch (e) {
return formError(req, res, e.message)
2017-08-29 12:00:36 +00:00
}
res.redirect('/news/' + result.id + '-' + result.slug)
}))
2017-08-27 11:48:47 +00:00
// Serve news
2017-08-24 10:52:12 +00:00
router.get('/news/:id?-*', wrap(async (req, res) => {
2020-05-28 18:30:21 +00:00
const id = parseInt(req.params.id)
2017-08-24 10:52:12 +00:00
if (isNaN(id)) {
2020-05-28 18:30:21 +00:00
return res.status(404).render('article', { article: null })
2017-08-24 10:52:12 +00:00
}
2020-05-28 18:30:21 +00:00
const article = await News.article(id)
2017-08-24 10:52:12 +00:00
if (!article.id) {
2020-05-28 18:30:21 +00:00
return res.status(404).render('article', { article: null })
2017-08-24 10:52:12 +00:00
}
2017-08-29 12:00:36 +00:00
let editing = false
if (req.query.edit === '1' && req.session.user && req.session.user.privilege > 1) {
editing = true
}
2020-05-28 18:30:21 +00:00
res.render('news/article', { article: article, editing: editing })
2017-08-24 10:52:12 +00:00
}))
router.get('/news/', wrap(async (req, res) => {
let page = parseInt(req.query.page)
if (isNaN(page)) {
page = 1
}
2020-05-28 18:30:21 +00:00
const news = await News.listNews(page)
2017-08-24 16:23:03 +00:00
2020-05-28 18:30:21 +00:00
res.render('news/news', { news: news })
2017-08-24 10:52:12 +00:00
}))
2018-01-27 11:07:36 +00:00
router.get('/news/atom.xml', wrap(async (req, res) => {
2020-05-28 18:30:21 +00:00
const feed = await News.generateFeed()
2018-01-27 11:07:36 +00:00
res.set('Content-Type', 'application/atom+xml')
res.send(feed.atom1())
}))
router.get('/news/feed.json', wrap(async (req, res) => {
2020-05-28 18:30:21 +00:00
const feed = await News.generateFeed()
2018-01-27 11:07:36 +00:00
res.set('Content-Type', 'application/json')
res.send(feed.json1())
}))
2017-08-27 11:48:47 +00:00
// Render partials
router.get('/partials/:view', (req, res, next) => {
2017-08-25 11:37:34 +00:00
if (!req.params.view) return next()
res.render('user/partials/' + req.params.view)
})
2017-08-25 11:37:34 +00:00
2017-08-02 21:24:01 +00:00
/*
=========
OTHER
=========
*/
router.get('/logout', (req, res) => {
2017-08-28 15:42:16 +00:00
req.session.destroy()
2017-08-02 21:24:01 +00:00
res.redirect('/')
})
2017-08-02 21:24:01 +00:00
router.use('/api', apiRouter)
2017-08-27 18:39:30 +00:00
router.use('/admin', adminRouter)
2017-08-25 11:37:34 +00:00
/*
FALLBACK ROUTES BEYOND THIS POINT
2017-08-25 11:37:34 +00:00
*/
// 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
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()
})
export default router