even more minor changes and reorganizing

This commit is contained in:
Evert Prants 2017-12-01 13:35:47 +02:00
parent e15dc7902c
commit f3d9435be0
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
12 changed files with 62 additions and 50 deletions

View File

@ -1,6 +1,7 @@
import {EmailTemplate} from 'email-templates' import {EmailTemplate} from 'email-templates'
import path from 'path' import path from 'path'
import nodemailer from 'nodemailer' import nodemailer from 'nodemailer'
import config from '../../scripts/load-config' import config from '../../scripts/load-config'
const templateDir = path.join(__dirname, '../../', 'templates') const templateDir = path.join(__dirname, '../../', 'templates')

View File

@ -1,12 +1,13 @@
import qs from 'querystring'
import oauth from 'oauth-libre'
import uuidV1 from 'uuid/v1'
import crypto from 'crypto'
import config from '../../scripts/load-config' import config from '../../scripts/load-config'
import http from '../../scripts/http' import http from '../../scripts/http'
import models from './models' import models from './models'
import Image from './image' import Image from './image'
import UAPI from './index' import UAPI from './index'
import qs from 'querystring'
import oauth from 'oauth-libre'
import uuidV1 from 'uuid/v1'
import crypto from 'crypto'
const userFields = ['username', 'email', 'avatar_file', 'display_name', 'ip_address'] const userFields = ['username', 'email', 'avatar_file', 'display_name', 'ip_address']
@ -74,7 +75,7 @@ const API = {
udataLimited.username = udataLimited.username.substring(0, 26) udataLimited.username = udataLimited.username.substring(0, 26)
// Check if the username is already taken // Check if the username is already taken
if (await UAPI.User.get(udataLimited.username) != null) { if (await UAPI.User.get(udataLimited.username) != null || udataLimited.username.length < 4) {
udataLimited.username = udataLimited.username + UAPI.Hash(4) udataLimited.username = udataLimited.username + UAPI.Hash(4)
} }

View File

@ -1,8 +1,7 @@
import gm from 'gm' import gm from 'gm'
import url from 'url' import url from 'url'
import path from 'path' import path from 'path'
import crypto from 'crypto' import uuid from 'uuid/v4'
import Promise from 'bluebird'
import http from '../../scripts/http' import http from '../../scripts/http'
@ -17,10 +16,6 @@ const imageTypes = {
'image/jpeg': '.jpeg' 'image/jpeg': '.jpeg'
} }
function imageUniquifier () {
return crypto.randomBytes(12).toString('hex')
}
function decodeBase64Image (dataString) { function decodeBase64Image (dataString) {
let matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/) let matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/)
let response = {} let response = {}
@ -60,7 +55,7 @@ async function imageBase64 (baseObj) {
if (!imgData) return null if (!imgData) return null
if (!imageTypes[imgData.type]) return null if (!imageTypes[imgData.type]) return null
let imageName = 'base64-' + imageUniquifier() let imageName = 'base64-' + uuid()
let ext = imageTypes[imgData.type] || '.png' let ext = imageTypes[imgData.type] || '.png'
imageName += ext imageName += ext
@ -81,7 +76,7 @@ async function downloadImage (imgUrl, designation) {
if (!imgUrl) return null if (!imgUrl) return null
if (!designation) designation = 'download' if (!designation) designation = 'download'
let imageName = designation + '-' + imageUniquifier() let imageName = designation + '-' + uuid()
let uridata = url.parse(imgUrl) let uridata = url.parse(imgUrl)
let pathdata = path.parse(uridata.path) let pathdata = path.parse(uridata.path)

View File

@ -1,15 +1,16 @@
import path from 'path' import path from 'path'
import cprog from 'child_process' import cprog from 'child_process'
import config from '../../scripts/load-config'
import http from '../../scripts/http'
import models from './models'
import crypto from 'crypto' import crypto from 'crypto'
import notp from 'notp' import notp from 'notp'
import base32 from 'thirty-two' import base32 from 'thirty-two'
import emailer from './emailer'
import uuidV1 from 'uuid/v1' import uuidV1 from 'uuid/v1'
import fs from 'fs-extra' import fs from 'fs-extra'
import config from '../../scripts/load-config'
import http from '../../scripts/http'
import models from './models'
import emailer from './emailer'
const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
// Fork a bcrypt process to hash and compare passwords // Fork a bcrypt process to hash and compare passwords
@ -533,7 +534,6 @@ const API = {
console.debug('IPN Verified Notification:', body) console.debug('IPN Verified Notification:', body)
} }
// TODO: add database field for this
if (body.txn_id) { if (body.txn_id) {
if (txnStore.indexOf(body.txn_id) !== -1) return true if (txnStore.indexOf(body.txn_id) !== -1) return true
txnStore.push(body.txn_id) txnStore.push(body.txn_id)

View File

@ -1,6 +1,7 @@
import crypto from 'crypto'
import API from './index' import API from './index'
import Model from './models' import Model from './models'
import crypto from 'crypto'
const mAPI = { const mAPI = {
getToken: async function (user) { getToken: async function (user) {

View File

@ -15,7 +15,7 @@ const args = {
function spawnWorkers () { function spawnWorkers () {
let workerCount = config.server.workers === 0 ? cpuCount : config.server.workers let workerCount = config.server.workers === 0 ? cpuCount : config.server.workers
console.log('Spinning up ' + workerCount + ' worker process' + (workerCount !== 1 ? 'es' : '')) console.log('Spinning up %d worker process%s', workerCount, (workerCount !== 1 ? 'es' : ''))
for (let i = 0; i < workerCount; i++) { for (let i = 0; i < workerCount; i++) {
spawnWorker() spawnWorker()
@ -68,14 +68,14 @@ function spawnWorker (oldWorker) {
w.process.stderr.on('data', (data) => { w.process.stderr.on('data', (data) => {
console.log(w.process.pid, data.toString().trim()) console.log(w.process.pid, data.toString().trim())
}) })
args.verbose && console.log('Starting worker process ' + w.process.pid + '...') args.verbose && console.log('Starting worker process %d...', w.process.pid)
w.on('message', (message) => { w.on('message', (message) => {
if (message === 'started') { if (message === 'started') {
workers.push(w) workers.push(w)
args.verbose && console.log('Started worker process ' + w.process.pid) args.verbose && console.log('Started worker process', w.process.pid)
if (oldWorker) { if (oldWorker) {
args.verbose && console.log('Stopping worker process ' + oldWorker.process.pid) args.verbose && console.log('Stopping worker process', oldWorker.process.pid)
oldWorker.send('stop') oldWorker.send('stop')
} }
} else { } else {
@ -99,7 +99,7 @@ cluster.setupMaster({
cluster.on('exit', (worker, code, signal) => { cluster.on('exit', (worker, code, signal) => {
let extra = ((code || '') + ' ' + (signal || '')).trim() let extra = ((code || '') + ' ' + (signal || '')).trim()
console.error('Worker process ' + worker.process.pid + ' exited ' + (extra ? '(' + extra + ')' : '')) console.error('Worker process %d exited %s', worker.process.pid, (extra ? '(' + extra + ')' : ''))
let index = workers.indexOf(worker) let index = workers.indexOf(worker)

View File

@ -1,8 +1,9 @@
import express from 'express' import express from 'express'
import ensureLogin from '../../scripts/ensureLogin' import ensureLogin from '../../scripts/ensureLogin'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
import {User} from '../api'
import API from '../api/admin' import API from '../api/admin'
import {User} from '../api'
const router = express.Router() const router = express.Router()
const apiRouter = express.Router() const apiRouter = express.Router()

View File

@ -1,12 +1,13 @@
import express from 'express' import express from 'express'
import RateLimit from 'express-rate-limit' import RateLimit from 'express-rate-limit'
import multiparty from 'multiparty' import multiparty from 'multiparty'
import config from '../../scripts/load-config' import config from '../../scripts/load-config'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
import API from '../api'
import News from '../api/news'
import Image from '../api/image'
import APIExtern from '../api/external' import APIExtern from '../api/external'
import Image from '../api/image'
import News from '../api/news'
import API from '../api'
let router = express.Router() let router = express.Router()
let dev = process.env.NODE_ENV !== 'production' let dev = process.env.NODE_ENV !== 'production'

View File

@ -2,6 +2,7 @@ import fs from 'fs-extra'
import path from 'path' import path from 'path'
import express from 'express' import express from 'express'
import RateLimit from 'express-rate-limit' import RateLimit from 'express-rate-limit'
import ensureLogin from '../../scripts/ensureLogin' import ensureLogin from '../../scripts/ensureLogin'
import config from '../../scripts/load-config' import config from '../../scripts/load-config'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
@ -78,7 +79,7 @@ router.use(wrap(async (req, res, next) => {
} }
// Update user session // Update user session
let udata = await API.User.get(req.session.user.id) let udata = await API.User.get(req.session.user)
setSession(req, udata) setSession(req, udata)
// Update IP address // Update IP address
@ -158,6 +159,7 @@ router.get('/reset/:token', wrap(async (req, res) => {
router.get('/login', extraButtons, (req, res) => { router.get('/login', extraButtons, (req, res) => {
if (req.session.user) return redirectLogin(req, res) if (req.session.user) return redirectLogin(req, res)
if (req.query.returnTo) { if (req.query.returnTo) {
req.session.redirectUri = req.query.returnTo req.session.redirectUri = req.query.returnTo
} }
@ -175,6 +177,21 @@ router.get('/register', extraButtons, formKeep, (req, res) => {
res.render('user/register') res.render('user/register')
}) })
// User activation endpoint (emailed link)
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) {
req.flash('message', {error: true, text: 'Invalid or expired activation token.'})
} else {
req.flash('message', {error: false, text: 'Your account has been activated! You may now log in.'})
}
res.redirect('/login')
}))
// View for enabling Two-Factor Authentication // View for enabling Two-Factor Authentication
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => { router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user) let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
@ -291,9 +308,11 @@ function formError (req, res, error, redirect) {
// Make sure characters are UTF-8 // Make sure characters are UTF-8
function cleanString (input) { function cleanString (input) {
let output = '' let output = ''
for (let i = 0; i < input.length; i++) { for (let i = 0; i < input.length; i++) {
output += input.charCodeAt(i) <= 127 ? input.charAt(i) : '' output += input.charCodeAt(i) <= 127 ? input.charAt(i) : ''
} }
return output return output
} }
@ -309,6 +328,7 @@ function csrfValidation (req, res, next) {
// Enabling 2fa // Enabling 2fa
router.post('/user/two-factor', csrfValidation, wrap(async (req, res, next) => { router.post('/user/two-factor', csrfValidation, wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
if (!req.body.code) { if (!req.body.code) {
return formError(req, res, 'You need to enter the code.') return formError(req, res, 'You need to enter the code.')
} }
@ -340,7 +360,9 @@ router.post('/user/two-factor/disable', csrfValidation, wrap(async (req, res, ne
// Verify 2FA for login // Verify 2FA for login
router.post('/login/verify', csrfValidation, wrap(async (req, res, next) => { router.post('/login/verify', csrfValidation, wrap(async (req, res, next) => {
if (req.session.user) return next() if (req.session.user) return next()
if (req.session.totp_check === null) return res.redirect('/login') if (req.session.totp_check === null) return res.redirect('/login')
if (!req.body.code && !req.body.recovery) { if (!req.body.code && !req.body.recovery) {
return formError(req, res, 'You need to enter the code.') return formError(req, res, 'You need to enter the code.')
} }
@ -360,6 +382,7 @@ router.post('/login/verify', csrfValidation, wrap(async (req, res, next) => {
// Log the user in. Limited resource // Log the user in. Limited resource
router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next) => { router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
if (req.session.user) return next() if (req.session.user) return next()
if (!req.body.username || !req.body.password || req.body.username === '') { if (!req.body.username || !req.body.password || req.body.username === '') {
return res.redirect('/login') return res.redirect('/login')
} }
@ -373,6 +396,7 @@ router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next
if (!pwMatch) return formError(req, res, 'Invalid username or 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.activated === 0) return formError(req, res, 'Please activate your account first.')
if (user.locked === 1) return formError(req, res, 'This account has been locked.') if (user.locked === 1) return formError(req, res, 'This account has been locked.')
// Check if the user is banned // Check if the user is banned
@ -407,6 +431,7 @@ router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next
// Password reset // Password reset
router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res, next) => { router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
if (req.session.user) return next() if (req.session.user) return next()
if (!req.body.email) { if (!req.body.email) {
return formError(req, res, 'You need to enter your email address.') return formError(req, res, 'You need to enter your email address.')
} }
@ -420,7 +445,7 @@ router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res
try { try {
await API.User.Reset.reset(email) await API.User.Reset.reset(email)
req.flash('message', {error: false, text: 'We\'ve sent a link to your email address. Please check spam folders too!'}) 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') res.redirect('/login/reset?success=true')
} catch (e) { } catch (e) {
return formError(req, res, e.message) return formError(req, res, e.message)
@ -466,6 +491,7 @@ router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
// Protected & Limited resource: Account registration // Protected & Limited resource: Account registration
router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, next) => { router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
if (req.session.user) return next() if (req.session.user) return next()
if (!req.body.username || !req.body.display_name || !req.body.password || !req.body.email) { if (!req.body.username || !req.body.display_name || !req.body.password || !req.body.email) {
return formError(req, res, 'Please fill in all the fields.') return formError(req, res, 'Please fill in all the fields.')
} }
@ -563,7 +589,7 @@ router.post('/user/manage', csrfValidation, wrap(async (req, res, next) => {
let displayName = req.body.display_name let displayName = req.body.display_name
if (!displayName || !displayName.match(/^([^\\`]{3,32})$/i)) { if (!displayName || !displayName.match(/^([^\\`]{3,32})$/i)) {
return formError(req, res, 'Invalid display name!') return formError(req, res, 'Invalid Display Name!')
} }
displayName = cleanString(displayName) displayName = cleanString(displayName)
@ -680,7 +706,6 @@ router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (re
return formError(req, res, e.message) return formError(req, res, e.message)
} }
// TODO: Send necessary emails
console.warn('[SECURITY AUDIT] User \'%s\' email has been changed from %s', user.username, req.realIP) console.warn('[SECURITY AUDIT] User \'%s\' email has been changed from %s', user.username, req.realIP)
req.session.user.email = newEmail req.session.user.email = newEmail
@ -790,21 +815,6 @@ router.get('/logout', (req, res) => {
res.redirect('/') res.redirect('/')
}) })
// User activation endpoint (emailed link)
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) {
req.flash('message', {error: true, text: 'Invalid or expired activation token.'})
} else {
req.flash('message', {error: false, text: 'Your account has been activated! You may now log in.'})
}
res.redirect('/login')
}))
router.use('/api', apiRouter) router.use('/api', apiRouter)
router.use('/admin', adminRouter) router.use('/admin', adminRouter)
router.use('/mc', mcRouter) router.use('/mc', mcRouter)

View File

@ -1,4 +1,5 @@
import express from 'express' import express from 'express'
import ensureLogin from '../../scripts/ensureLogin' import ensureLogin from '../../scripts/ensureLogin'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
import Minecraft from '../api/minecraft' import Minecraft from '../api/minecraft'

View File

@ -1,5 +1,6 @@
import express from 'express' import express from 'express'
import uapi from '../api'
import UAPI from '../api'
import OAuth2 from '../api/oauth2' import OAuth2 from '../api/oauth2'
import RateLimit from 'express-rate-limit' import RateLimit from 'express-rate-limit'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
@ -31,7 +32,7 @@ router.post('/introspect', oauth.controller.introspection)
// Protected user information resource // Protected user information resource
router.get('/user', oauth.bearer, wrap(async (req, res) => { router.get('/user', oauth.bearer, wrap(async (req, res) => {
let accessToken = req.oauth2.accessToken let accessToken = req.oauth2.accessToken
let user = await uapi.User.get(accessToken.user_id) let user = await UAPI.User.get(accessToken.user_id)
if (!user) { if (!user) {
return res.status(404).jsonp({ return res.status(404).jsonp({

View File

@ -68,7 +68,7 @@ module.exports = (args) => {
app.set('views', path.join(__dirname, '../views')) app.set('views', path.join(__dirname, '../views'))
if (args.dev) { if (args.dev) {
console.log('Worker is in development mode') console.warn('Worker is in development mode')
// Dev logger // Dev logger
const morgan = require('morgan') const morgan = require('morgan')