Maintenance commit.

This commit is contained in:
Evert Prants 2020-05-28 21:30:21 +03:00
parent f608316e89
commit 787cc6fc73
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
42 changed files with 4242 additions and 4093 deletions

View File

@ -6,7 +6,7 @@ if (process.argv.indexOf('-d') === -1 && process.argv.indexOf('--development') =
} }
require('@babel/register')({ require('@babel/register')({
plugins: [ '@babel/plugin-transform-modules-commonjs' ] plugins: ['@babel/plugin-transform-modules-commonjs']
}) })
require(path.join(__dirname, 'server')) require(path.join(__dirname, 'server'))

7224
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -30,58 +30,58 @@
}, },
"homepage": "https://icynet.eu", "homepage": "https://icynet.eu",
"dependencies": { "dependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.8.3", "@babel/plugin-transform-modules-commonjs": "^7.10.1",
"@babel/register": "^7.8.3", "@babel/register": "^7.10.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"connect-redis": "^3.4.2", "connect-redis": "^4.0.4",
"connect-session-knex": "^1.5.0", "connect-session-knex": "^1.6.0",
"email-templates": "^2.7.1", "email-templates": "^7.0.5",
"express": "^4.17.1", "express": "^4.17.1",
"express-rate-limit": "^2.14.2", "express-rate-limit": "^5.1.3",
"express-session": "^1.17.0", "express-session": "^1.17.1",
"feed": "^1.1.1", "feed": "^4.2.0",
"fs-extra": "^4.0.3", "fs-extra": "^9.0.0",
"gm": "^1.23.1", "gm": "^1.23.1",
"knex": "^0.14.6", "knex": "^0.21.1",
"multiparty": "^4.2.1", "multiparty": "^4.2.1",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"nodemailer": "^4.7.0", "nodemailer": "^6.4.8",
"notp": "^2.0.3", "notp": "^2.0.3",
"oauth-libre": "^0.9.17", "oauth-libre": "^0.9.17",
"objection": "^0.8.9", "objection": "^2.1.5",
"redis": "^2.8.0", "redis": "^3.0.2",
"serve-favicon": "^2.5.0", "serve-favicon": "^2.5.0",
"stylus": "^0.54.7", "stylus": "^0.54.7",
"thirty-two": "^1.0.2", "thirty-two": "^1.0.2",
"toml": "^2.3.6", "toml": "^3.0.0",
"uuid": "^3.4.0", "uuid": "^8.1.0",
"vue": "^2.6.11" "vue": "^2.6.11"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.3", "@babel/core": "^7.10.1",
"@babel/preset-env": "^7.8.3", "@babel/preset-env": "^7.10.1",
"babel-loader": "^8.0.6", "babel-loader": "^8.1.0",
"bootstrap": "^4.4.1", "bootstrap": "^4.5.0",
"concurrently": "^5.1.0", "concurrently": "^5.2.0",
"eslint-plugin-import": "^2.20.0", "eslint-plugin-import": "^2.20.2",
"jquery": "^3.4.1", "jquery": "^3.5.1",
"morgan": "^1.9.1", "morgan": "^1.10.0",
"mustache": "^2.3.2", "mustache": "^4.0.1",
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"pug": "^2.0.4", "pug": "^3.0.0",
"pug-plain-loader": "^1.0.0", "pug-plain-loader": "^1.0.0",
"standard": "^10.0.3", "standard": "^14.3.4",
"terser-webpack-plugin": "^2.3.3", "terser-webpack-plugin": "^3.0.2",
"vue-clickaway": "^2.2.2", "vue-clickaway": "^2.2.2",
"vue-loader": "^15.8.3", "vue-loader": "^15.9.2",
"vue-resource": "^1.5.1", "vue-resource": "^1.5.1",
"vue-router": "^3.1.5", "vue-router": "^3.3.1",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.11",
"watch": "^1.0.2", "watch": "^1.0.2",
"webpack": "^4.41.5", "webpack": "^4.43.0",
"webpack-cli": "^3.3.10", "webpack-cli": "^3.3.11",
"webpack-merge": "^4.2.2" "webpack-merge": "^4.2.2"
}, },
"standard": { "standard": {

View File

@ -8,7 +8,7 @@ const format = util.format
module.exports = function (options) { module.exports = function (options) {
options = options || {} options = options || {}
let safe = (options.unsafe === undefined) ? true : !options.unsafe const safe = (options.unsafe === undefined) ? true : !options.unsafe
return function (req, res, next) { return function (req, res, next) {
if (req.flash && safe) { return next() } if (req.flash && safe) { return next() }
@ -20,10 +20,10 @@ module.exports = function (options) {
function _flash (type, msg) { function _flash (type, msg) {
if (this.session === undefined) throw Error('req.flash() requires sessions') if (this.session === undefined) throw Error('req.flash() requires sessions')
let msgs = this.session.flash = this.session.flash || {} const msgs = this.session.flash = this.session.flash || {}
if (type && msg) { if (type && msg) {
if (arguments.length > 2 && format) { if (arguments.length > 2 && format) {
let args = Array.prototype.slice.call(arguments, 1) const args = Array.prototype.slice.call(arguments, 1)
msg = format.apply(undefined, args) msg = format.apply(undefined, args)
} else if (Array.isArray(msg)) { } else if (Array.isArray(msg)) {
msg.forEach((val) => { msg.forEach((val) => {
@ -33,7 +33,7 @@ function _flash (type, msg) {
} }
return (msgs[type] = msgs[type] || []).push(msg) return (msgs[type] = msgs[type] || []).push(msg)
} else if (type) { } else if (type) {
let arr = msgs[type] const arr = msgs[type]
delete msgs[type] delete msgs[type]
return arr || [] return arr || []
} else { } else {

View File

@ -1,17 +1,17 @@
import url from 'url' import { URL } from 'url'
import qs from 'querystring' import qs from 'querystring'
import fs from 'fs' import fs from 'fs'
function HTTP_GET (link, headers = {}, lback) { function HTTP_GET (link, headers = {}, lback) {
if (lback && lback >= 4) throw new Error('infinite loop!') // Prevent infinite loop requests if (lback && lback >= 4) throw new Error('infinite loop!') // Prevent infinite loop requests
let parsed = url.parse(link) const parsed = new URL(link)
let opts = { const opts = {
host: parsed.hostname, host: parsed.hostname,
port: parsed.port, port: parsed.port,
path: parsed.path, path: parsed.path,
headers: { headers: {
'User-Agent': 'Icy Network Back-end (icynet.eu)', 'User-Agent': 'Icy Network Back-end (icynet.eu)',
'Accept': '*/*', Accept: '*/*',
'Accept-Language': 'en-GB,enq=0.5' 'Accept-Language': 'en-GB,enq=0.5'
} }
} }
@ -22,9 +22,9 @@ function HTTP_GET (link, headers = {}, lback) {
let reqTimeOut let reqTimeOut
let httpModule = parsed.protocol === 'https:' ? require('https') : require('http') const httpModule = parsed.protocol === 'https:' ? require('https') : require('http')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let req = httpModule.get(opts, (res) => { const req = httpModule.get(opts, (res) => {
if (res.statusCode === 302 || res.statusCode === 301) { if (res.statusCode === 302 || res.statusCode === 301) {
if (!lback) { if (!lback) {
lback = 1 lback = 1
@ -61,10 +61,10 @@ function HTTP_GET (link, headers = {}, lback) {
} }
function HTTP_POST (link, headers = {}, data) { function HTTP_POST (link, headers = {}, data) {
let parsed = url.parse(link) const parsed = new URL(link)
let postData = qs.stringify(data) let postData = qs.stringify(data)
let opts = { const opts = {
host: parsed.host, host: parsed.host,
port: parsed.port, port: parsed.port,
path: parsed.path, path: parsed.path,
@ -85,8 +85,8 @@ function HTTP_POST (link, headers = {}, data) {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let httpModule = parsed.protocol === 'https:' ? require('https') : require('http') const httpModule = parsed.protocol === 'https:' ? require('https') : require('http')
let req = httpModule.request(opts, (res) => { const req = httpModule.request(opts, (res) => {
res.setEncoding('utf8') res.setEncoding('utf8')
let data = '' let data = ''
@ -108,8 +108,8 @@ function HTTP_POST (link, headers = {}, data) {
async function Download (url, dest) { async function Download (url, dest) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let file = fs.createWriteStream(dest) const file = fs.createWriteStream(dest)
let protocol = url.indexOf('https:') === 0 ? require('https') : require('http') const protocol = url.indexOf('https:') === 0 ? require('https') : require('http')
protocol.get(url, function (response) { protocol.get(url, function (response) {
response.pipe(file) response.pipe(file)
file.on('finish', function () { file.on('finish', function () {

View File

@ -3,8 +3,8 @@ const knex = require('knex')
const objection = require('objection') const objection = require('objection')
const knexfile = require(path.join(__dirname, '../knexfile')) const knexfile = require(path.join(__dirname, '../knexfile'))
let knexDB = knex(knexfile) const knexDB = knex(knexfile)
let objectionModel = objection.Model const objectionModel = objection.Model
objectionModel.knex(knexDB) objectionModel.knex(knexDB)

View File

@ -20,11 +20,12 @@ function dateFormat (date) {
// Console.log/error/warn "middleware" - add timestamp and write to file // Console.log/error/warn "middleware" - add timestamp and write to file
function stampAndWrite (fnc, color, prfx, message) { function stampAndWrite (fnc, color, prfx, message) {
let prefix = '[' + prfx + '] [' + dateFormat(new Date()) + '] ' const prefix = '[' + prfx + '] [' + dateFormat(new Date()) + '] '
message = color + prefix + message + ((color && color !== '') ? '\x1b[0m' : '') message = color + prefix + message + ((color && color !== '') ? '\x1b[0m' : '')
message = message.replace(/\\u001b/g, '\x1b') message = message.replace(/\\u001b/g, '\x1b')
if (lfs) { if (lfs) {
// eslint-disable-next-line no-control-regex
lfs.write(message.replace(/(\u001b\[\d\d?m)/g, '') + '\n') lfs.write(message.replace(/(\u001b\[\d\d?m)/g, '') + '\n')
} }
@ -34,24 +35,24 @@ function stampAndWrite (fnc, color, prfx, message) {
// Reassign logger functions and send them to the "middleware" // Reassign logger functions and send them to the "middleware"
const realConsoleLog = console.log const realConsoleLog = console.log
console.log = function () { console.log = function () {
let message = util.format.apply(null, arguments) const message = util.format.apply(null, arguments)
stampAndWrite.call(this, realConsoleLog, '', 'info', message) stampAndWrite.call(this, realConsoleLog, '', 'info', message)
} }
const realConsoleWarn = console.warn const realConsoleWarn = console.warn
console.warn = function () { console.warn = function () {
let message = util.format.apply(null, arguments) const message = util.format.apply(null, arguments)
stampAndWrite.call(this, realConsoleWarn, '\x1b[33m', 'warn', message) stampAndWrite.call(this, realConsoleWarn, '\x1b[33m', 'warn', message)
} }
const realConsoleError = console.error const realConsoleError = console.error
console.error = function () { console.error = function () {
let message = util.format.apply(null, arguments) const message = util.format.apply(null, arguments)
stampAndWrite.call(this, realConsoleError, '\x1b[31m', ' err', message) stampAndWrite.call(this, realConsoleError, '\x1b[31m', ' err', message)
} }
async function initializeLogger () { async function initializeLogger () {
let logPath = path.resolve(config.logger.file) const logPath = path.resolve(config.logger.file)
// Only throw bad permission errors // Only throw bad permission errors
try { try {
@ -61,7 +62,7 @@ async function initializeLogger () {
} }
try { try {
lfs = fs.createWriteStream(logPath, {flags: 'a'}) lfs = fs.createWriteStream(logPath, { flags: 'a' })
} catch (e) { } catch (e) {
lfs = null lfs = null
console.error('Failed to initiate log file write stream') console.error('Failed to initiate log file write stream')

View File

@ -4,7 +4,7 @@ import Models from './models'
const perPage = 6 const perPage = 6
async function cleanUserObject (dbe, admin) { async function cleanUserObject (dbe, admin) {
let totp = await Users.User.Login.totpTokenRequired(dbe) const totp = await Users.User.Login.totpTokenRequired(dbe)
return { return {
id: dbe.id, id: dbe.id,
@ -25,7 +25,7 @@ async function cleanUserObject (dbe, admin) {
} }
async function cleanClientObject (dbe) { async function cleanClientObject (dbe) {
let user = await Users.User.get(dbe.user_id) const user = await Users.User.get(dbe.user_id)
return { return {
id: dbe.id, id: dbe.id,
title: dbe.title, title: dbe.title,
@ -46,8 +46,8 @@ async function cleanClientObject (dbe) {
} }
async function cleanBanObject (dbe) { async function cleanBanObject (dbe) {
let user = await Users.User.get(dbe.user_id) const user = await Users.User.get(dbe.user_id)
let admin = await Users.User.get(dbe.admin_id) const admin = await Users.User.get(dbe.admin_id)
return { return {
id: dbe.id, id: dbe.id,
reason: dbe.reason, reason: dbe.reason,
@ -68,14 +68,14 @@ async function cleanBanObject (dbe) {
function dataFilter (data, fields, optional = []) { function dataFilter (data, fields, optional = []) {
// Remove keys not listed in `fields` // Remove keys not listed in `fields`
for (let i in data) { for (const i in data) {
if (fields.indexOf(i) === -1) { if (fields.indexOf(i) === -1) {
delete data[i] delete data[i]
} }
} }
// Ensure all the entries in `fields` are present as keys in `data` or are `optional` // Ensure all the entries in `fields` are present as keys in `data` or are `optional`
for (let j in fields) { for (const j in fields) {
if (!data[fields[j]] && optional.indexOf(fields[j]) === -1) return null if (!data[fields[j]] && optional.indexOf(fields[j]) === -1) return null
} }
@ -86,18 +86,18 @@ const API = {
// List all users (paginated) // List all users (paginated)
getAllUsers: async function (page, adminId) { getAllUsers: async function (page, adminId) {
let count = await Models.User.query().count('id as ids') let count = await Models.User.query().count('id as ids')
if (!count.length || !count[0]['ids'] || isNaN(page)) { if (!count.length || !count[0].ids || isNaN(page)) {
return { error: 'No users found in database' } return { error: 'No users found in database' }
} }
count = count[0].ids count = count[0].ids
let paginated = Users.Pagination(perPage, parseInt(count), page) const paginated = Users.Pagination(perPage, parseInt(count), page)
let raw = await Models.User.query().offset(paginated.offset).limit(perPage) const raw = await Models.User.query().offset(paginated.offset).limit(perPage)
let admin = await Users.User.get(adminId) const admin = await Users.User.get(adminId)
let users = [] const users = []
for (let i in raw) { for (const i in raw) {
let entry = raw[i] const entry = raw[i]
users.push(await cleanUserObject(entry, admin)) users.push(await cleanUserObject(entry, admin))
} }
@ -108,16 +108,16 @@ const API = {
} }
}, },
getUser: async function (id) { getUser: async function (id) {
let user = await Users.User.get(id) const user = await Users.User.get(id)
if (!user) throw new Error('No such user') if (!user) throw new Error('No such user')
return cleanUserObject(user, null) return cleanUserObject(user, null)
}, },
editUser: async function (id, data) { editUser: async function (id, data) {
let user = await Users.User.get(id) const user = await Users.User.get(id)
if (!user) throw new Error('No such user') if (!user) throw new Error('No such user')
let fields = [ const fields = [
'username', 'display_name', 'email', 'nw_privilege', 'activated' 'username', 'display_name', 'email', 'nw_privilege', 'activated'
] ]
@ -129,7 +129,7 @@ const API = {
return {} return {}
}, },
resendActivationEmail: async function (id) { resendActivationEmail: async function (id) {
let user = await Users.User.get(id) const user = await Users.User.get(id)
if (!user) throw new Error('No such user') if (!user) throw new Error('No such user')
if (user.activated === 1) return {} if (user.activated === 1) return {}
@ -139,7 +139,7 @@ const API = {
return {} return {}
}, },
revokeTotpToken: async function (id) { revokeTotpToken: async function (id) {
let user = await Users.User.get(id) const user = await Users.User.get(id)
if (!user) throw new Error('No such user') if (!user) throw new Error('No such user')
await Models.TotpToken.query().delete().where('user_id', user.id) await Models.TotpToken.query().delete().where('user_id', user.id)
@ -147,12 +147,12 @@ const API = {
return {} return {}
}, },
sendPasswordEmail: async function (id) { sendPasswordEmail: async function (id) {
let user = await Users.User.get(id) const user = await Users.User.get(id)
if (!user) throw new Error('No such user') if (!user) throw new Error('No such user')
let token = await Users.User.Reset.reset(user.email, false, true) const token = await Users.User.Reset.reset(user.email, false, true)
return {token} return { token }
}, },
// Search for users by terms and fields // Search for users by terms and fields
searchUsers: async function (terms, fields = ['email']) { searchUsers: async function (terms, fields = ['email']) {
@ -167,12 +167,12 @@ const API = {
} }
} }
let rows = await qb.limit(8) const rows = await qb.limit(8)
if (!rows.length) return { error: 'No results' } if (!rows.length) return { error: 'No results' }
let cleaned = [] const cleaned = []
for (let i in rows) { for (const i in rows) {
let userRaw = rows[i] const userRaw = rows[i]
cleaned.push(await cleanUserObject(userRaw, null)) cleaned.push(await cleanUserObject(userRaw, null))
} }
@ -181,17 +181,17 @@ const API = {
// List all clients (paginated) // List all clients (paginated)
getAllClients: async function (page) { getAllClients: async function (page) {
let count = await Models.OAuth2Client.query().count('id as ids') let count = await Models.OAuth2Client.query().count('id as ids')
if (!count.length || !count[0]['ids'] || isNaN(page)) { if (!count.length || !count[0].ids || isNaN(page)) {
return { error: 'No clients' } return { error: 'No clients' }
} }
count = count[0].ids count = count[0].ids
let paginated = Users.Pagination(perPage, parseInt(count), page) const paginated = Users.Pagination(perPage, parseInt(count), page)
let raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage) const raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage)
let clients = [] const clients = []
for (let i in raw) { for (const i in raw) {
let entry = raw[i] const entry = raw[i]
clients.push(await cleanClientObject(entry)) clients.push(await cleanClientObject(entry))
} }
@ -202,14 +202,14 @@ const API = {
}, },
// Get information about a client via id // Get information about a client via id
getClient: async function (id) { getClient: async function (id) {
let raw = await Models.OAuth2Client.query().where('id', id) const raw = await Models.OAuth2Client.query().where('id', id)
if (!raw.length) throw new Error('No such client') if (!raw.length) throw new Error('No such client')
return cleanClientObject(raw[0]) return cleanClientObject(raw[0])
}, },
// Update a client `id` in database with `data` // Update a client `id` in database with `data`
updateClient: async function (id, data) { updateClient: async function (id, data) {
let fields = [ const fields = [
'title', 'description', 'url', 'redirect_url', 'scope', 'verified' 'title', 'description', 'url', 'redirect_url', 'scope', 'verified'
] ]
@ -228,10 +228,10 @@ const API = {
// Create a new secret for a client // Create a new secret for a client
newSecret: async function (id) { newSecret: async function (id) {
if (isNaN(id)) throw new Error('Invalid client ID') if (isNaN(id)) throw new Error('Invalid client ID')
let secret = Users.Hash(16) const secret = Users.Hash(16)
try { try {
await Models.OAuth2Client.query().patchAndFetchById(id, {secret: secret}) await Models.OAuth2Client.query().patchAndFetchById(id, { secret: secret })
} catch (e) { } catch (e) {
throw new Error('No such client') throw new Error('No such client')
} }
@ -240,14 +240,14 @@ const API = {
}, },
// Create a new client // Create a new client
createClient: async function (data, user) { createClient: async function (data, user) {
let fields = [ const fields = [
'title', 'description', 'url', 'redirect_url', 'scope' 'title', 'description', 'url', 'redirect_url', 'scope'
] ]
data = dataFilter(data, fields, ['scope']) data = dataFilter(data, fields, ['scope'])
if (!data) throw new Error('Missing fields') if (!data) throw new Error('Missing fields')
let obj = Object.assign({ const obj = Object.assign({
secret: Users.Hash(16), secret: Users.Hash(16),
grants: 'authorization_code', grants: 'authorization_code',
created_at: new Date(), created_at: new Date(),
@ -268,17 +268,17 @@ const API = {
// List all bans (paginated) // List all bans (paginated)
getAllBans: async function (page) { getAllBans: async function (page) {
let count = await Models.Ban.query().count('id as ids') let count = await Models.Ban.query().count('id as ids')
if (!count.length || !count[0]['ids'] || isNaN(page)) { if (!count.length || !count[0].ids || isNaN(page)) {
return { error: 'No bans on record' } return { error: 'No bans on record' }
} }
count = count[0].ids count = count[0].ids
let paginated = Users.Pagination(perPage, parseInt(count), page) const paginated = Users.Pagination(perPage, parseInt(count), page)
let raw = await Models.Ban.query().offset(paginated.offset).limit(perPage) const raw = await Models.Ban.query().offset(paginated.offset).limit(perPage)
let bans = [] const bans = []
for (let i in raw) { for (const i in raw) {
let entry = raw[i] const entry = raw[i]
bans.push(await cleanBanObject(entry)) bans.push(await cleanBanObject(entry))
} }
@ -293,16 +293,16 @@ const API = {
}, },
// Create a ban // Create a ban
addBan: async function (data, adminId) { addBan: async function (data, adminId) {
let user = await Users.User.get(parseInt(data.user_id)) const user = await Users.User.get(parseInt(data.user_id))
if (!user) throw new Error('No such user.') if (!user) throw new Error('No such user.')
if (user.id === adminId) throw new Error('Cannot ban yourself!') if (user.id === adminId) throw new Error('Cannot ban yourself!')
let admin = await Users.User.get(adminId) const admin = await Users.User.get(adminId)
if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.') if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.')
let banAdd = { const banAdd = {
reason: data.reason || 'Unspecified ban', reason: data.reason || 'Unspecified ban',
admin_id: adminId, admin_id: adminId,
user_id: user.id, user_id: user.id,
@ -315,13 +315,13 @@ const API = {
return {} return {}
}, },
lockAccount: async function (userId) { lockAccount: async function (userId) {
let user = await Users.User.get(userId) const user = await Users.User.get(userId)
if (user.id === 1 || user.nw_privilege > 2) { if (user.id === 1 || user.nw_privilege > 2) {
throw new Error('Cannot lock this user.') throw new Error('Cannot lock this user.')
} }
let lockId = Users.Hash(4) const lockId = Users.Hash(4)
let userObf = { const userObf = {
username: lockId, username: lockId,
display_name: user.username, display_name: user.username,
email: `${lockId}@icynet.eu`, email: `${lockId}@icynet.eu`,

View File

@ -1,4 +1,4 @@
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'
@ -10,7 +10,7 @@ tls.DEFAULT_ECDH_CURVE = 'auto'
const templateDir = path.join(__dirname, '../../', 'templates') const templateDir = path.join(__dirname, '../../', 'templates')
let templateCache = {} const templateCache = {}
let transporter let transporter
// Send an email to `email` with `headers` // Send an email to `email` with `headers`
@ -42,7 +42,7 @@ async function pushMail (template, email, context) {
templ = templateCache[template] templ = templateCache[template]
} }
let result = await templ.render(context) const result = await templ.render(context)
console.debug('Mail being sent: %s to %s', template, email) console.debug('Mail being sent: %s to %s', template, email)

View File

@ -1,6 +1,6 @@
import qs from 'querystring' import qs from 'querystring'
import oauth from 'oauth-libre' import oauth from 'oauth-libre'
import uuidV1 from 'uuid/v1' import { v1 as uuidV1 } from 'uuid'
import crypto from 'crypto' import crypto from 'crypto'
import config from '../../scripts/load-config' import config from '../../scripts/load-config'
@ -18,7 +18,7 @@ const API = {
Common: { Common: {
// Generate a hash based on the current session // Generate a hash based on the current session
stateGenerator: (req) => { stateGenerator: (req) => {
let sessionCrypto = req.session.id + ':' + config.server.session_secret const sessionCrypto = req.session.id + ':' + config.server.session_secret
return crypto.createHash('sha256').update(sessionCrypto).digest('hex') return crypto.createHash('sha256').update(sessionCrypto).digest('hex')
}, },
// Find an user with an external ID // Find an user with an external ID
@ -29,7 +29,7 @@ const API = {
extr.user = null extr.user = null
if (extr.user_id !== null) { if (extr.user_id !== null) {
let user = await UAPI.User.get(extr.user_id) const user = await UAPI.User.get(extr.user_id)
if (user) { if (user) {
extr.user = user extr.user = user
} }
@ -39,12 +39,12 @@ const API = {
}, },
// Get user ban status // Get user ban status
getBan: async (user, ipAddress) => { getBan: async (user, ipAddress) => {
let banList = await UAPI.User.getBanStatus(ipAddress || user.id, ipAddress != null) const banList = await UAPI.User.getBanStatus(ipAddress || user.id, ipAddress != null)
return banList return banList
}, },
// Create a new `external` instance for a user // Create a new `external` instance for a user
new: async (service, identifier, user) => { new: async (service, identifier, user) => {
let data = { const data = {
user_id: user.id, user_id: user.id,
service: service, service: service,
identifier: identifier, identifier: identifier,
@ -57,7 +57,7 @@ const API = {
// Create a new user // Create a new user
newUser: async (service, identifier, data) => { newUser: async (service, identifier, data) => {
if (config.external.registrations !== true) throw new Error('Registrations from third-party websites are not allowed.') if (config.external.registrations !== true) throw new Error('Registrations from third-party websites are not allowed.')
let udataLimited = Object.assign({ const udataLimited = Object.assign({
activated: 1, activated: 1,
created_at: new Date(), created_at: new Date(),
updated_at: new Date(), updated_at: new Date(),
@ -83,14 +83,14 @@ const API = {
// Check if the email given to us is already registered, if so, // Check if the email given to us is already registered, if so,
// tell them to log in first. // tell them to log in first.
if (udataLimited.email && udataLimited.email !== '') { if (udataLimited.email && udataLimited.email !== '') {
let getByEmail = await UAPI.User.get(udataLimited.email) const getByEmail = await UAPI.User.get(udataLimited.email)
if (getByEmail) { if (getByEmail) {
throw new Error('An user with this email address is already registered, but this external account is are not linked. If you wish to link the account, please log in first.') throw new Error('An user with this email address is already registered, but this external account is are not linked. If you wish to link the account, please log in first.')
} }
} }
// Create a new user based on the information we got from an external service // Create a new user based on the information we got from an external service
let newUser = await models.User.query().insert(udataLimited) const newUser = await models.User.query().insert(udataLimited)
await API.Common.new(service, identifier, newUser) await API.Common.new(service, identifier, newUser)
return newUser return newUser
@ -98,7 +98,7 @@ const API = {
// Remove an `external` object (thus unlinking from a service) // Remove an `external` object (thus unlinking from a service)
remove: async (user, service) => { remove: async (user, service) => {
user = await UAPI.User.ensureObject(user, ['password']) user = await UAPI.User.ensureObject(user, ['password'])
let userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id) const userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
if (!userExterns.length) { if (!userExterns.length) {
return false return false
@ -113,30 +113,30 @@ const API = {
}, },
// Common code for all auth callbacks // Common code for all auth callbacks
callback: async (identifier, uid, user, ipAddress, remoteData, avatarFunc) => { callback: async (identifier, uid, user, ipAddress, remoteData, avatarFunc) => {
let exists = await API.Common.getExternal(identifier, uid) const exists = await API.Common.getExternal(identifier, uid)
if (user) { if (user) {
// Get bans for user // Get bans for user
let bans = await API.Common.getBan(user) const bans = await API.Common.getBan(user)
if (bans.length) return { banned: bans, ip: false } if (bans.length) return { banned: bans, ip: false }
if (exists) return {error: null, user: user} if (exists) return { error: null, user: user }
await API.Common.new(identifier, uid, user) await API.Common.new(identifier, uid, user)
return {error: null, user: user} return { error: null, user: user }
} }
// Callback succeeded with user id and the external table exists, we log in the user // Callback succeeded with user id and the external table exists, we log in the user
if (exists) { if (exists) {
// Get bans for user // Get bans for user
let bans = await API.Common.getBan(exists.user) const bans = await API.Common.getBan(exists.user)
if (bans.length) return { banned: bans, ip: false } if (bans.length) return { banned: bans, ip: false }
return {error: null, user: exists.user} return { error: null, user: exists.user }
} }
// Get bans for IP address // Get bans for IP address
let bans = await API.Common.getBan(null, ipAddress) const bans = await API.Common.getBan(null, ipAddress)
if (bans.length) return { banned: bans, ip: true } if (bans.length) return { banned: bans, ip: true }
// Run the function for avatar fetching // Run the function for avatar fetching
@ -146,14 +146,14 @@ const API = {
} }
// Assign the data // Assign the data
let newUData = Object.assign({ const newUData = Object.assign({
email: remoteData.email || '', email: remoteData.email || '',
avatar_file: avatar, avatar_file: avatar,
ip_address: ipAddress ip_address: ipAddress
}, remoteData) }, remoteData)
// Remove unnecessary fields // Remove unnecessary fields
for (let i in newUData) { for (const i in newUData) {
if (userFields.indexOf(i) === -1) { if (userFields.indexOf(i) === -1) {
delete newUData[i] delete newUData[i]
} }
@ -163,10 +163,10 @@ const API = {
try { try {
newUser = await API.Common.newUser(identifier, uid, newUData) newUser = await API.Common.newUser(identifier, uid, newUData)
} catch (e) { } catch (e) {
return {error: e.message} return { error: e.message }
} }
return {error: null, user: newUser} return { error: null, user: newUser }
} }
}, },
Facebook: { Facebook: {
@ -175,7 +175,7 @@ const API = {
if (rawData.picture) { if (rawData.picture) {
if (rawData.picture.is_silhouette === false && rawData.picture.url) { if (rawData.picture.is_silhouette === false && rawData.picture.url) {
let imgdata = await Image.downloadImage(rawData.picture.url) const imgdata = await Image.downloadImage(rawData.picture.url)
if (imgdata && imgdata.fileName) { if (imgdata && imgdata.fileName) {
profilepic = imgdata.fileName profilepic = imgdata.fileName
} }
@ -186,17 +186,17 @@ const API = {
}, },
callback: async (user, authResponse, ipAddress) => { callback: async (user, authResponse, ipAddress) => {
if (!authResponse) { if (!authResponse) {
return {error: 'No Authorization'} return { error: 'No Authorization' }
} }
let uid = authResponse.userID const uid = authResponse.userID
if (!uid) { if (!uid) {
return {error: 'No Authorization'} return { error: 'No Authorization' }
} }
// Get facebook user information in order to create a new user or verify // Get facebook user information in order to create a new user or verify
let fbdata let fbdata
let intel = { const intel = {
access_token: authResponse.accessToken, access_token: authResponse.accessToken,
fields: 'name,email,picture,short_name' fields: 'name,email,picture,short_name'
} }
@ -205,14 +205,14 @@ const API = {
fbdata = await http.GET('https://graph.facebook.com/v2.10/' + uid + '?' + qs.stringify(intel)) fbdata = await http.GET('https://graph.facebook.com/v2.10/' + uid + '?' + qs.stringify(intel))
fbdata = JSON.parse(fbdata) fbdata = JSON.parse(fbdata)
} catch (e) { } catch (e) {
return {error: 'Could not get user information', errorObject: e} return { error: 'Could not get user information', errorObject: e }
} }
if (fbdata.error) { if (fbdata.error) {
return {error: fbdata.error.message} return { error: fbdata.error.message }
} }
let cleanedData = Object.assign(fbdata, { const cleanedData = Object.assign(fbdata, {
username: fbdata.short_name || 'FB' + UAPI.Hash(4), username: fbdata.short_name || 'FB' + UAPI.Hash(4),
display_name: fbdata.name, display_name: fbdata.name,
email: fbdata.email || '' email: fbdata.email || ''
@ -226,7 +226,7 @@ const API = {
let profilepic = null let profilepic = null
if (rawData.profile_image_url_https) { if (rawData.profile_image_url_https) {
let imgdata = await Image.downloadImage(rawData.profile_image_url_https) const imgdata = await Image.downloadImage(rawData.profile_image_url_https)
if (imgdata && imgdata.fileName) { if (imgdata && imgdata.fileName) {
profilepic = imgdata.fileName profilepic = imgdata.fileName
} }
@ -236,7 +236,7 @@ const API = {
}, },
oauthApp: function () { oauthApp: function () {
if (!twitterApp) { if (!twitterApp) {
let redirectUri = config.server.domain + '/api/external/twitter/callback' const redirectUri = config.server.domain + '/api/external/twitter/callback'
twitterApp = new oauth.PromiseOAuth( twitterApp = new oauth.PromiseOAuth(
'https://api.twitter.com/oauth/request_token', 'https://api.twitter.com/oauth/request_token',
'https://api.twitter.com/oauth/access_token', 'https://api.twitter.com/oauth/access_token',
@ -256,12 +256,12 @@ const API = {
tokens = await twitterApp.getOAuthRequestToken() tokens = await twitterApp.getOAuthRequestToken()
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return {error: 'No tokens returned'} return { error: 'No tokens returned' }
} }
if (tokens[2].oauth_callback_confirmed !== 'true') return {error: 'No tokens returned.'} if (tokens[2].oauth_callback_confirmed !== 'true') return { error: 'No tokens returned.' }
return {error: null, token: tokens[0], token_secret: tokens[1]} return { error: null, token: tokens[0], token_secret: tokens[1] }
}, },
getAccessTokens: async function (token, secret, verifier) { getAccessTokens: async function (token, secret, verifier) {
if (!twitterApp) API.Twitter.oauthApp() if (!twitterApp) API.Twitter.oauthApp()
@ -271,28 +271,28 @@ const API = {
tokens = await twitterApp.getOAuthAccessToken(token, secret, verifier) tokens = await twitterApp.getOAuthAccessToken(token, secret, verifier)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return {error: 'No tokens returned'} return { error: 'No tokens returned' }
} }
if (!tokens || !tokens.length) return {error: 'No tokens returned'} if (!tokens || !tokens.length) return { error: 'No tokens returned' }
return {error: null, access_token: tokens[0], access_token_secret: tokens[1]} return { error: null, access_token: tokens[0], access_token_secret: tokens[1] }
}, },
callback: async function (user, accessTokens, ipAddress) { callback: async function (user, accessTokens, ipAddress) {
if (!twitterApp) API.Twitter.oauthApp() if (!twitterApp) API.Twitter.oauthApp()
let twdata let twdata
try { try {
let resp = await twitterApp.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true', const resp = await twitterApp.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
accessTokens.access_token, accessTokens.access_token_secret) accessTokens.access_token, accessTokens.access_token_secret)
twdata = JSON.parse(resp[0]) twdata = JSON.parse(resp[0])
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return {error: 'Failed to verify user credentials.'} return { error: 'Failed to verify user credentials.' }
} }
let uid = twdata.id_str const uid = twdata.id_str
let cleanedData = Object.assign(twdata, { const cleanedData = Object.assign(twdata, {
username: twdata.screen_name, username: twdata.screen_name,
display_name: twdata.name, display_name: twdata.name,
email: twdata.email || '' email: twdata.email || ''
@ -305,7 +305,7 @@ const API = {
getAvatar: async (rawData) => { getAvatar: async (rawData) => {
let profilepic = null let profilepic = null
if (rawData.image) { if (rawData.image) {
let imgdata = await Image.downloadImage(rawData.image) const imgdata = await Image.downloadImage(rawData.image)
if (imgdata && imgdata.fileName) { if (imgdata && imgdata.fileName) {
profilepic = imgdata.fileName profilepic = imgdata.fileName
} }
@ -317,10 +317,10 @@ const API = {
let uid let uid
try { try {
let test = await http.GET('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + data.id_token) const test = await http.GET('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + data.id_token)
if (!test) throw new Error('No response!') if (!test) throw new Error('No response!')
let jsondata = JSON.parse(test) const jsondata = JSON.parse(test)
if (!jsondata || !jsondata.email || !jsondata.name) throw new Error('Please allow Basic Profile and Email.') if (!jsondata || !jsondata.email || !jsondata.name) throw new Error('Please allow Basic Profile and Email.')
if (jsondata.email !== data.email || jsondata.name !== data.name) throw new Error('Conflicting data. Please try again!') if (jsondata.email !== data.email || jsondata.name !== data.name) throw new Error('Conflicting data. Please try again!')
@ -329,10 +329,10 @@ const API = {
uid = jsondata.sub uid = jsondata.sub
} catch (e) { } catch (e) {
return {error: e.message} return { error: e.message }
} }
let cleanedData = Object.assign(data, { const cleanedData = Object.assign(data, {
username: data.name, username: data.name,
display_name: data.name, display_name: data.name,
email: data.email || '' email: data.email || ''
@ -344,10 +344,10 @@ const API = {
Discord: { Discord: {
getAvatar: async (rawData) => { getAvatar: async (rawData) => {
let profilepic = null let profilepic = null
let aviSnowflake = rawData.avatar const aviSnowflake = rawData.avatar
if (aviSnowflake) { if (aviSnowflake) {
try { try {
let avpt = await Image.downloadImage('https://cdn.discordapp.com/avatars/' + rawData.id + '/' + aviSnowflake + '.png') const avpt = await Image.downloadImage('https://cdn.discordapp.com/avatars/' + rawData.id + '/' + aviSnowflake + '.png')
if (avpt && avpt.fileName) { if (avpt && avpt.fileName) {
profilepic = avpt.fileName profilepic = avpt.fileName
} }
@ -372,54 +372,54 @@ const API = {
}, },
getAuthorizeURL: function (req) { getAuthorizeURL: function (req) {
if (!discordApp) API.Discord.oauth2App() if (!discordApp) API.Discord.oauth2App()
let state = API.Common.stateGenerator(req) const state = API.Common.stateGenerator(req)
let redirectUri = config.server.domain + '/api/external/discord/callback' const redirectUri = config.server.domain + '/api/external/discord/callback'
const params = { const params = {
'client_id': config.external.discord.api, client_id: config.external.discord.api,
'redirect_uri': redirectUri, redirect_uri: redirectUri,
'scope': 'identify email', scope: 'identify email',
'response_type': 'code', response_type: 'code',
'state': state state: state
} }
let url = discordApp.getAuthorizeUrl(params) const url = discordApp.getAuthorizeUrl(params)
return {error: null, state: state, url: url} return { error: null, state: state, url: url }
}, },
getAccessToken: async function (code) { getAccessToken: async function (code) {
if (!discordApp) API.Discord.oauth2App() if (!discordApp) API.Discord.oauth2App()
let redirectUri = config.server.domain + '/api/external/discord/callback' const redirectUri = config.server.domain + '/api/external/discord/callback'
let tokens let tokens
try { try {
tokens = await discordApp.getOAuthAccessToken(code, {grant_type: 'authorization_code', redirect_uri: redirectUri}) tokens = await discordApp.getOAuthAccessToken(code, { grant_type: 'authorization_code', redirect_uri: redirectUri })
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return {error: 'No Authorization'} return { error: 'No Authorization' }
} }
if (!tokens.length) return {error: 'No Tokens'} if (!tokens.length) return { error: 'No Tokens' }
tokens = tokens[2] tokens = tokens[2]
return {error: null, accessToken: tokens.access_token} return { error: null, accessToken: tokens.access_token }
}, },
callback: async function (user, accessToken, ipAddress) { callback: async function (user, accessToken, ipAddress) {
if (!discordApp) API.Discord.oauth2App() if (!discordApp) API.Discord.oauth2App()
let ddata let ddata
try { try {
let resp = await discordApp.get('https://discordapp.com/api/users/@me', accessToken) const resp = await discordApp.get('https://discordapp.com/api/users/@me', accessToken)
ddata = JSON.parse(resp) ddata = JSON.parse(resp)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return {error: 'Could not get user information'} return { error: 'Could not get user information' }
} }
let uid = ddata.id const uid = ddata.id
// Create a new user // Create a new user
let cleanedData = Object.assign(ddata, { const cleanedData = Object.assign(ddata, {
display_name: ddata.username, display_name: ddata.username,
email: ddata.email || '' email: ddata.email || ''
}) })

View File

@ -1,8 +1,8 @@
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 crypto from 'crypto'
import uuid from 'uuid/v4' import { v4 as uuid } from 'uuid'
import http from '../../scripts/http' import http from '../../scripts/http'
@ -20,8 +20,8 @@ const imageTypes = {
} }
function decodeBase64Image (dataString) { function decodeBase64Image (dataString) {
let matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/) const matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/)
let response = {} const response = {}
if (matches.length !== 3) { if (matches.length !== 3) {
return null return null
@ -34,10 +34,10 @@ function decodeBase64Image (dataString) {
} }
function saneFields (fields) { function saneFields (fields) {
let out = {} const out = {}
for (let i in fields) { for (const i in fields) {
let entry = fields[i] const entry = fields[i]
if (typeof entry === 'object' && entry.length === 1 && !isNaN(parseInt(entry[0]))) { if (typeof entry === 'object' && entry.length === 1 && !isNaN(parseInt(entry[0]))) {
out[i] = parseInt(entry[0]) out[i] = parseInt(entry[0])
} }
@ -53,17 +53,17 @@ async function bailOut (file, error) {
async function imageBase64 (baseObj) { async function imageBase64 (baseObj) {
if (!baseObj) return null if (!baseObj) return null
let imgData = decodeBase64Image(baseObj) const imgData = decodeBase64Image(baseObj)
if (!imgData) return null if (!imgData) return null
if (!imageTypes[imgData.type]) return null if (!imageTypes[imgData.type]) return null
let imageName = 'base64-' + uuid() let imageName = 'base64-' + uuid()
let ext = imageTypes[imgData.type] || '.png' const ext = imageTypes[imgData.type] || '.png'
imageName += ext imageName += ext
let fpath = path.join(images, imageName) const fpath = path.join(images, imageName)
try { try {
await fs.writeFile(fpath, imgData.data) await fs.writeFile(fpath, imgData.data)
@ -72,11 +72,11 @@ async function imageBase64 (baseObj) {
return null return null
} }
return {file: fpath} return { file: fpath }
} }
function gravatarURL (email) { function gravatarURL (email) {
let sum = crypto.createHash('md5').update(email).digest('hex') const sum = crypto.createHash('md5').update(email).digest('hex')
return gravatar + sum + '.jpg' return gravatar + sum + '.jpg'
} }
@ -85,8 +85,8 @@ async function downloadImage (imgUrl, designation) {
if (!designation) designation = 'download' if (!designation) designation = 'download'
let imageName = designation + '-' + uuid() let imageName = designation + '-' + uuid()
let uridata = url.parse(imgUrl) const uridata = new URL(imgUrl)
let pathdata = path.parse(uridata.path) const pathdata = path.parse(uridata.path)
imageName += pathdata.ext || '.png' imageName += pathdata.ext || '.png'
@ -108,23 +108,23 @@ async function uploadImage (identifier, fields, files) {
fields = saneFields(fields) fields = saneFields(fields)
// Get file info, generate a file name // Get file info, generate a file name
let fileHash = uuid() const fileHash = uuid()
let contentType = file.headers['content-type'] const contentType = file.headers['content-type']
if (!contentType) return bailOut(file.path, 'Invalid of missing content-type header') if (!contentType) return bailOut(file.path, 'Invalid of missing content-type header')
file = file.path file = file.path
// Make sure content type is allowed // Make sure content type is allowed
let match = false let match = false
for (let i in imageTypes) { for (const i in imageTypes) {
if (i === contentType) { if (i === contentType) {
match = true match = true
break break
} }
} }
if (!match) return bailOut(file, 'Invalid image type. Only PNG, JPG and JPEG files are allowed.') if (!match) return bailOut(file, 'Invalid image type. Only PNG, JPG and JPEG files are allowed.')
let extension = imageTypes[contentType] const extension = imageTypes[contentType]
let fileName = identifier + '-' + fileHash + extension const fileName = identifier + '-' + fileHash + extension
// Check for cropping // Check for cropping
if (fields.x == null || fields.y == null || fields.width == null || fields.height == null) { if (fields.x == null || fields.y == null || fields.width == null || fields.height == null) {
@ -172,7 +172,7 @@ async function uploadImage (identifier, fields, files) {
return bailOut(file, 'An error occured while cropping.') return bailOut(file, 'An error occured while cropping.')
} }
return {file: fileName} return { file: fileName }
} }
module.exports = { module.exports = {

View File

@ -3,7 +3,7 @@ import cprog from 'child_process'
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 uuidV1 from 'uuid/v1' import { v1 as uuidV1 } from 'uuid'
import fs from 'fs-extra' import fs from 'fs-extra'
import config from '../../scripts/load-config' import config from '../../scripts/load-config'
@ -16,12 +16,12 @@ const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\
// Fork a bcrypt process to hash and compare passwords // Fork a bcrypt process to hash and compare passwords
function bcryptTask (data) { function bcryptTask (data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let proc = cprog.fork(path.join(__dirname, '../../scripts', 'bcrypt.js')) const proc = cprog.fork(path.join(__dirname, '../../scripts', 'bcrypt.js'))
let done = false let done = false
proc.send(data.task + ' ' + JSON.stringify(data)) proc.send(data.task + ' ' + JSON.stringify(data))
proc.on('message', (chunk) => { proc.on('message', (chunk) => {
if (chunk == null) return reject(new Error('No response')) if (chunk == null) return reject(new Error('No response'))
let line = chunk.toString().trim() const line = chunk.toString().trim()
done = true done = true
if (line === 'error') { if (line === 'error') {
return reject(new Error(line)) return reject(new Error(line))
@ -43,8 +43,8 @@ function bcryptTask (data) {
function keysAvailable (object, required) { function keysAvailable (object, required) {
let found = true let found = true
for (let i in required) { for (const i in required) {
let key = required[i] const key = required[i]
if (object[key] == null) { if (object[key] == null) {
found = false found = false
} }
@ -65,7 +65,7 @@ async function cleanUpDonation (obj, mcOnly, timeframe) {
user = await API.User.get(obj.user_id) user = await API.User.get(obj.user_id)
} }
let result = { const result = {
trackId: obj.id, trackId: obj.id,
amount: obj.amount, amount: obj.amount,
donated: obj.created_at donated: obj.created_at
@ -75,10 +75,10 @@ async function cleanUpDonation (obj, mcOnly, timeframe) {
result.name = user.display_name result.name = user.display_name
} }
let sources = obj.source.split(',') const sources = obj.source.split(',')
for (let i in sources) { for (const i in sources) {
if (sources[i].indexOf('mcu:') === 0) { if (sources[i].indexOf('mcu:') === 0) {
let mcu = sources[i].split(':')[1] const mcu = sources[i].split(':')[1]
if (mcu.match(/^([\w_]{2,16})$/i)) { if (mcu.match(/^([\w_]{2,16})$/i)) {
result.minecraft_username = mcu result.minecraft_username = mcu
} }
@ -90,7 +90,7 @@ async function cleanUpDonation (obj, mcOnly, timeframe) {
return result return result
} }
let txnStore = [] const txnStore = []
const API = { const API = {
Hash: (len) => { Hash: (len) => {
@ -101,10 +101,10 @@ const API = {
if (!ppp) ppp = 5 if (!ppp) ppp = 5
if (!dcount) return null if (!dcount) return null
let pageCount = Math.ceil(dcount / ppp) const pageCount = Math.ceil(dcount / ppp)
if (page > pageCount) page = pageCount if (page > pageCount) page = pageCount
let offset = (page - 1) * ppp const offset = (page - 1) * ppp
return { return {
page: page, page: page,
@ -135,7 +135,7 @@ const API = {
} }
} }
let user = await models.User.query().where(scope, identifier) const user = await models.User.query().where(scope, identifier)
if (!user.length) return null if (!user.length) return null
return user[0] return user[0]
@ -154,16 +154,16 @@ const API = {
socialStatus: async function (user) { socialStatus: async function (user) {
user = await API.User.ensureObject(user, ['password']) user = await API.User.ensureObject(user, ['password'])
if (!user) return null if (!user) return null
let external = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id) const external = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
let enabled = {} const enabled = {}
for (let i in external) { for (const i in external) {
let ext = external[i] const ext = external[i]
enabled[ext.service] = true enabled[ext.service] = true
} }
let accountSourceIsExternal = user.password === null || user.password === '' const accountSourceIsExternal = user.password === null || user.password === ''
let obj = { const obj = {
enabled: enabled, enabled: enabled,
password: !accountSourceIsExternal password: !accountSourceIsExternal
} }
@ -186,8 +186,8 @@ const API = {
}, },
changeAvatar: async function (user, fileName) { changeAvatar: async function (user, fileName) {
user = await API.User.ensureObject(user, ['avatar_file']) user = await API.User.ensureObject(user, ['avatar_file'])
let uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images') const uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
let pathOf = path.join(uploadsDir, fileName) const pathOf = path.join(uploadsDir, fileName)
if (!await fs.exists(pathOf)) { if (!await fs.exists(pathOf)) {
throw new Error('No such file') throw new Error('No such file')
@ -195,26 +195,26 @@ const API = {
// Delete previous upload // Delete previous upload
if (user.avatar_file != null) { if (user.avatar_file != null) {
let file = path.join(uploadsDir, user.avatar_file) const file = path.join(uploadsDir, user.avatar_file)
if (await fs.exists(file)) { if (await fs.exists(file)) {
await fs.unlink(file) await fs.unlink(file)
} }
} }
await API.User.update(user, {avatar_file: fileName}) await API.User.update(user, { avatar_file: fileName })
return fileName return fileName
}, },
removeAvatar: async function (user) { removeAvatar: async function (user) {
user = await API.User.ensureObject(user, ['avatar_file']) user = await API.User.ensureObject(user, ['avatar_file'])
let uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images') const uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
if (!user.avatar_file) return {} if (!user.avatar_file) return {}
let file = path.join(uploadsDir, user.avatar_file) const file = path.join(uploadsDir, user.avatar_file)
if (await fs.exists(file)) { if (await fs.exists(file)) {
await fs.unlink(file) await fs.unlink(file)
} }
return API.User.update(user, {avatar_file: null}) return API.User.update(user, { avatar_file: null })
}, },
getBanStatus: async function (field, ip = false) { getBanStatus: async function (field, ip = false) {
let bans let bans
@ -224,15 +224,15 @@ const API = {
bans = await models.Ban.query().where('user_id', field) bans = await models.Ban.query().where('user_id', field)
} }
let bansActive = [] const bansActive = []
for (let i in bans) { for (const i in bans) {
let ban = bans[i] const ban = bans[i]
// Check expiry // Check expiry
if (ban.expires_at && new Date(ban.expires_at).getTime() < Date.now()) continue if (ban.expires_at && new Date(ban.expires_at).getTime() < Date.now()) continue
let banInfo = { const banInfo = {
banned: ban.created_at, banned: ban.created_at,
reason: ban.reason, reason: ban.reason,
expiry: ban.expires_at expiry: ban.expires_at
@ -247,7 +247,7 @@ const API = {
password: async function (user, password) { password: async function (user, password) {
user = await API.User.ensureObject(user, ['password']) user = await API.User.ensureObject(user, ['password'])
if (!user.password) return false if (!user.password) return false
return bcryptTask({task: 'compare', password: password, hash: user.password}) return bcryptTask({ task: 'compare', password: password, hash: user.password })
}, },
activationToken: async function (token) { activationToken: async function (token) {
let getToken = await models.Token.query().where('token', token).andWhere('type', 1) let getToken = await models.Token.query().where('token', token).andWhere('type', 1)
@ -257,15 +257,15 @@ const API = {
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return false if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return false
let user = await API.User.get(getToken.user_id) const user = await API.User.get(getToken.user_id)
if (!user) return false if (!user) return false
await models.User.query().patchAndFetchById(user.id, {activated: 1}) await models.User.query().patchAndFetchById(user.id, { activated: 1 })
await models.Token.query().delete().where('id', getToken.id) await models.Token.query().delete().where('id', getToken.id)
return true return true
}, },
totpTokenRequired: async function (user) { totpTokenRequired: async function (user) {
let getToken = await models.TotpToken.query().where('user_id', user.id) const getToken = await models.TotpToken.query().where('user_id', user.id)
if (!getToken || !getToken.length) return false if (!getToken || !getToken.length) return false
if (getToken[0].activated !== 1) return false if (getToken[0].activated !== 1) return false
@ -286,7 +286,7 @@ const API = {
return false return false
} }
let login = notp.totp.verify(code, getToken.token, {}) const login = notp.totp.verify(code, getToken.token, {})
if (login) { if (login) {
if (login.delta !== 0) { if (login.delta !== 0) {
@ -295,7 +295,7 @@ const API = {
if (getToken.activated !== 1) { if (getToken.activated !== 1) {
// TODO: Send an email including the recovery code to the user // TODO: Send an email including the recovery code to the user
await models.TotpToken.query().patchAndFetchById(getToken.id, {activated: true}) await models.TotpToken.query().patchAndFetchById(getToken.id, { activated: true })
} }
return true return true
@ -305,7 +305,7 @@ const API = {
}, },
purgeTotp: async function (user, password) { purgeTotp: async function (user, password) {
user = await API.User.ensureObject(user, ['password']) user = await API.User.ensureObject(user, ['password'])
let pwmatch = await API.User.Login.password(user, password) const pwmatch = await API.User.Login.password(user, password)
if (!pwmatch) return false if (!pwmatch) return false
// TODO: Inform user via email // TODO: Inform user via email
@ -320,12 +320,12 @@ const API = {
if (!user.password || user.password === '') return null if (!user.password || user.password === '') return null
// Get existing tokens for the user and delete them if found // Get existing tokens for the user and delete them if found
let getToken = await models.TotpToken.query().where('user_id', user.id) const getToken = await models.TotpToken.query().where('user_id', user.id)
if (getToken && getToken.length) { if (getToken && getToken.length) {
await models.TotpToken.query().delete().where('user_id', user.id) await models.TotpToken.query().delete().where('user_id', user.id)
} }
let newToken = { const newToken = {
user_id: user.id, user_id: user.id,
token: API.Hash(16), token: API.Hash(16),
recovery_code: API.Hash(8), recovery_code: API.Hash(8),
@ -333,44 +333,44 @@ const API = {
created_at: new Date() created_at: new Date()
} }
let hashed = base32.encode(newToken.token) const hashed = base32.encode(newToken.token)
let domain = 'icynet.eu' const domain = 'icynet.eu'
await models.TotpToken.query().insert(newToken) await models.TotpToken.query().insert(newToken)
let uri = encodeURIComponent('otpauth://totp/' + user.username + '@' + domain + '?secret=' + hashed) const uri = encodeURIComponent('otpauth://totp/' + user.username + '@' + domain + '?secret=' + hashed)
return uri return uri
} }
}, },
Register: { Register: {
hashPassword: async function (password) { hashPassword: async function (password) {
return bcryptTask({task: 'hash', password: password}) return bcryptTask({ task: 'hash', password: password })
}, },
validateEmail: (email) => { validateEmail: (email) => {
return emailRe.test(email) return emailRe.test(email)
}, },
newAccount: async function (regdata) { newAccount: async function (regdata) {
let email = config.email && config.email.enabled const email = config.email && config.email.enabled
let data = Object.assign(regdata, { const data = Object.assign(regdata, {
created_at: new Date(), created_at: new Date(),
updated_at: new Date(), updated_at: new Date(),
uuid: uuidV1(), uuid: uuidV1(),
activated: email ? 0 : 1 activated: email ? 0 : 1
}) })
let userTest = await API.User.get(regdata.username) const userTest = await API.User.get(regdata.username)
if (userTest) { if (userTest) {
throw new Error('This username is already taken!') throw new Error('This username is already taken!')
} }
let emailTest = await API.User.get(regdata.email) const emailTest = await API.User.get(regdata.email)
if (emailTest) { if (emailTest) {
throw new Error('This email address is already registered!') throw new Error('This email address is already registered!')
} }
// Create user // Create user
let user = await models.User.query().insert(data) const user = await models.User.query().insert(data)
if (email) { if (email) {
await API.User.Register.activationEmail(user, true) await API.User.Register.activationEmail(user, true)
@ -380,7 +380,7 @@ const API = {
}, },
activationEmail: async function (user, deleteOnFail = false) { activationEmail: async function (user, deleteOnFail = false) {
// Activation token // Activation token
let activationToken = API.Hash(16) const activationToken = API.Hash(16)
await models.Token.query().insert({ await models.Token.query().insert({
expires_at: new Date(Date.now() + 86400000), // 1 day expires_at: new Date(Date.now() + 86400000), // 1 day
@ -393,7 +393,7 @@ const API = {
// Send Activation Email // Send Activation Email
try { try {
let em = await emailer.pushMail('activate', user.email, { const em = await emailer.pushMail('activate', user.email, {
domain: config.server.domain, domain: config.server.domain,
display_name: user.display_name, display_name: user.display_name,
activation_token: activationToken activation_token: activationToken
@ -415,20 +415,20 @@ const API = {
}, },
Reset: { Reset: {
reset: async function (email, passRequired = true) { reset: async function (email, passRequired = true) {
let emailEnabled = config.email && config.email.enabled const emailEnabled = config.email && config.email.enabled
if (!emailEnabled) throw new Error('Cannot reset password.') if (!emailEnabled) throw new Error('Cannot reset password.')
let user = await API.User.get(email) const user = await API.User.get(email)
if (!user) throw new Error('This email address does not match any user in our database.') if (!user) throw new Error('This email address does not match any user in our database.')
if (!user.password && passRequired) throw new Error('The user associated with this email address has used an external website to log in, thus the password cannot be reset.') if (!user.password && passRequired) throw new Error('The user associated with this email address has used an external website to log in, thus the password cannot be reset.')
let recentTokens = await models.Token.query().where('user_id', user.id).andWhere('expires_at', '>', new Date()).andWhere('type', 2) const recentTokens = await models.Token.query().where('user_id', user.id).andWhere('expires_at', '>', new Date()).andWhere('type', 2)
if (recentTokens.length >= 2) { if (recentTokens.length >= 2) {
throw new Error('You\'ve made too many reset requests recently. Please slow down.') throw new Error('You\'ve made too many reset requests recently. Please slow down.')
} }
let resetToken = API.Hash(16) const resetToken = API.Hash(16)
await models.Token.query().insert({ await models.Token.query().insert({
expires_at: new Date(Date.now() + 86400000), // 1 day expires_at: new Date(Date.now() + 86400000), // 1 day
token: resetToken, token: resetToken,
@ -440,7 +440,7 @@ const API = {
console.debug('Reset token:', resetToken) console.debug('Reset token:', resetToken)
if (email) { if (email) {
try { try {
let em = await emailer.pushMail('reset_password', user.email, { const em = await emailer.pushMail('reset_password', user.email, {
domain: config.server.domain, domain: config.server.domain,
display_name: user.display_name, display_name: user.display_name,
reset_token: resetToken reset_token: resetToken
@ -463,15 +463,15 @@ const API = {
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return null if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return null
let user = await API.User.get(getToken.user_id) const user = await API.User.get(getToken.user_id)
if (!user) return null if (!user) return null
return user return user
}, },
changePassword: async function (user, password, token) { changePassword: async function (user, password, token) {
let hashed = await API.User.Register.hashPassword(password) const hashed = await API.User.Register.hashPassword(password)
await models.User.query().patchAndFetchById(user.id, {password: hashed, updated_at: new Date()}) await models.User.query().patchAndFetchById(user.id, { password: hashed, updated_at: new Date() })
await models.Token.query().delete().where('token', token) await models.Token.query().delete().where('token', token)
return true return true
@ -480,18 +480,18 @@ const API = {
OAuth2: { OAuth2: {
getUserAuthorizations: async function (user) { getUserAuthorizations: async function (user) {
user = await API.User.ensureObject(user) user = await API.User.ensureObject(user)
let auths = await models.OAuth2AuthorizedClient.query().where('user_id', user.id) const auths = await models.OAuth2AuthorizedClient.query().where('user_id', user.id)
let nicelist = [] const nicelist = []
for (let i in auths) { for (const i in auths) {
let auth = auths[i] const auth = auths[i]
let client = await models.OAuth2Client.query().where('id', auth.client_id) let client = await models.OAuth2Client.query().where('id', auth.client_id)
if (!client.length) continue if (!client.length) continue
client = client[0] client = client[0]
let obj = { const obj = {
id: client.id, id: client.id,
title: client.title, title: client.title,
description: client.description, description: client.description,
@ -508,13 +508,13 @@ const API = {
}, },
removeUserAuthorization: async function (user, clientId) { removeUserAuthorization: async function (user, clientId) {
user = await API.User.ensureObject(user) user = await API.User.ensureObject(user)
let auth = await models.OAuth2AuthorizedClient.query().where('user_id', user.id).andWhere('client_id', clientId) const auth = await models.OAuth2AuthorizedClient.query().where('user_id', user.id).andWhere('client_id', clientId)
if (!auth.length) return false if (!auth.length) return false
await models.OAuth2AccessToken.query().delete().where('client_id', clientId).andWhere('user_id', user.id) await models.OAuth2AccessToken.query().delete().where('client_id', clientId).andWhere('user_id', user.id)
await models.OAuth2RefreshToken.query().delete().where('client_id', clientId).andWhere('user_id', user.id) await models.OAuth2RefreshToken.query().delete().where('client_id', clientId).andWhere('user_id', user.id)
for (let i in auth) { for (const i in auth) {
await models.OAuth2AuthorizedClient.query().delete().where('id', auth[i].id) await models.OAuth2AuthorizedClient.query().delete().where('id', auth[i].id)
} }
@ -524,11 +524,11 @@ const API = {
}, },
Payment: { Payment: {
handleIPN: async function (body) { handleIPN: async function (body) {
let sandboxed = body.test_ipn === '1' const sandboxed = body.test_ipn === '1'
let url = 'https://ipnpb.' + (sandboxed ? 'sandbox.' : '') + 'paypal.com/cgi-bin/webscr' const url = 'https://ipnpb.' + (sandboxed ? 'sandbox.' : '') + 'paypal.com/cgi-bin/webscr'
console.debug('Incoming payment') console.debug('Incoming payment')
let verification = await http.POST(url, {}, Object.assign({ const verification = await http.POST(url, {}, Object.assign({
cmd: '_notify-validate' cmd: '_notify-validate'
}, body)) }, body))
@ -551,15 +551,15 @@ const API = {
} }
let user let user
let source = [] const source = []
if (sandboxed) { if (sandboxed) {
source.push('virtual') source.push('virtual')
} }
// TODO: add hooks // TODO: add hooks
let custom = body.custom.split(',') const custom = body.custom.split(',')
for (let i in custom) { for (const i in custom) {
let str = custom[i] const str = custom[i]
if (str.indexOf('userid:') === 0) { if (str.indexOf('userid:') === 0) {
body.user_id = parseInt(str.split(':')[1]) body.user_id = parseInt(str.split(':')[1])
} else if (str.indexOf('mcu:') === 0) { } else if (str.indexOf('mcu:') === 0) {
@ -573,7 +573,7 @@ const API = {
user = await API.User.get(body.payer_email) user = await API.User.get(body.payer_email)
} }
let donation = { const donation = {
user_id: user ? user.id : null, user_id: user ? user.id : null,
amount: (body.mc_gross || body.payment_gross || 'Unknown') + ' ' + (body.mc_currency || 'EUR'), amount: (body.mc_gross || body.payment_gross || 'Unknown') + ' ' + (body.mc_currency || 'EUR'),
source: source.join(','), source: source.join(','),
@ -589,21 +589,21 @@ const API = {
userContributions: async function (user) { userContributions: async function (user) {
user = await API.User.ensureObject(user) user = await API.User.ensureObject(user)
let dbq = await models.Donation.query().orderBy('created_at', 'desc').where('user_id', user.id) const dbq = await models.Donation.query().orderBy('created_at', 'desc').where('user_id', user.id)
let contribs = [] const contribs = []
for (let i in dbq) { for (const i in dbq) {
contribs.push(await cleanUpDonation(dbq[i])) contribs.push(await cleanUpDonation(dbq[i]))
} }
return contribs return contribs
}, },
allContributions: async function (count, mcOnly, timeframe = 0) { allContributions: async function (count, mcOnly, timeframe = 0) {
let dbq = await models.Donation.query().orderBy('created_at', 'desc').limit(count) const dbq = await models.Donation.query().orderBy('created_at', 'desc').limit(count)
let contribs = [] const contribs = []
for (let i in dbq) { for (const i in dbq) {
let obj = await cleanUpDonation(dbq[i], mcOnly, timeframe) const obj = await cleanUpDonation(dbq[i], mcOnly, timeframe)
if (!obj) continue if (!obj) continue
contribs.push(obj) contribs.push(obj)
} }

View File

@ -1,4 +1,4 @@
import {Model} from '../../scripts/load-database' import { Model } from '../../scripts/load-database'
class User extends Model { class User extends Model {
static get tableName () { static get tableName () {

View File

@ -13,8 +13,8 @@ function slugify (title) {
} }
async function cleanArticle (entry, shortenContent = false) { async function cleanArticle (entry, shortenContent = false) {
let poster = await API.User.get(entry.user_id) const poster = await API.User.get(entry.user_id)
let article = { const article = {
id: entry.id, id: entry.id,
slug: slugify(entry.title), slug: slugify(entry.title),
title: entry.title, title: entry.title,
@ -40,13 +40,13 @@ async function cleanArticle (entry, shortenContent = false) {
const News = { const News = {
preview: async () => { preview: async () => {
// Fetch 3 latest stories // Fetch 3 latest stories
let news = await Models.News.query().orderBy('created_at', 'desc').limit(3) const news = await Models.News.query().orderBy('created_at', 'desc').limit(3)
if (!news.length) return [] if (!news.length) return []
let articles = [] const articles = []
for (let i in news) { for (const i in news) {
let entry = news[i] const entry = news[i]
articles.push(await cleanArticle(entry)) articles.push(await cleanArticle(entry))
} }
@ -56,17 +56,17 @@ const News = {
let count = await Models.News.query().count('id as ids') let count = await Models.News.query().count('id as ids')
if (page < 1) page = 1 if (page < 1) page = 1
if (!count.length || !count[0]['ids'] || isNaN(page)) { if (!count.length || !count[0].ids || isNaN(page)) {
return { error: 'No news' } return { error: 'No news' }
} }
count = count[0].ids count = count[0].ids
let paginated = API.Pagination(perPage, parseInt(count), page) const paginated = API.Pagination(perPage, parseInt(count), page)
let news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage) const news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage)
let articles = [] const articles = []
for (let i in news) { for (const i in news) {
let entry = news[i] const entry = news[i]
articles.push(await cleanArticle(entry)) articles.push(await cleanArticle(entry))
} }
@ -84,7 +84,7 @@ const News = {
return cleanArticle(article) return cleanArticle(article)
}, },
compose: async (user, body) => { compose: async (user, body) => {
let article = { const article = {
title: body.title, title: body.title,
content: body.content, content: body.content,
tags: body.tags || '', tags: body.tags || '',
@ -93,28 +93,28 @@ const News = {
updated_at: new Date() updated_at: new Date()
} }
let result = await Models.News.query().insert(article) const result = await Models.News.query().insert(article)
result.slug = slugify(result.title) result.slug = slugify(result.title)
return result return result
}, },
edit: async (id, body) => { edit: async (id, body) => {
let patch = { const patch = {
content: body.content, content: body.content,
updated_at: new Date() updated_at: new Date()
} }
let result = await Models.News.query().patchAndFetchById(id, patch) const result = await Models.News.query().patchAndFetchById(id, patch)
if (!result) throw new Error('Something went wrong.') if (!result) throw new Error('Something went wrong.')
return {} return {}
}, },
generateFeed: async () => { generateFeed: async () => {
if (feed && new Date(feed.options.updated).getTime() > Date.now() - 3600000) return feed // Update feed hourly if (feed && new Date(feed.options.updated).getTime() > Date.now() - 3600000) return feed // Update feed hourly
let posts = await Models.News.query().orderBy('created_at', 'desc').limit(perPage) const posts = await Models.News.query().orderBy('created_at', 'desc').limit(perPage)
let cleanNews = [] const cleanNews = []
for (let i in posts) { for (const i in posts) {
cleanNews.push(await cleanArticle(posts[i])) cleanNews.push(await cleanArticle(posts[i]))
} }
@ -138,8 +138,8 @@ const News = {
} }
}) })
for (let i in cleanNews) { for (const i in cleanNews) {
let post = cleanNews[i] const post = cleanNews[i]
feed.addItem({ feed.addItem({
title: post.title, title: post.title,

View File

@ -52,7 +52,7 @@ module.exports = wrap(async (req, res, next) => {
} }
console.debug('Parameter response_type is', responseType) console.debug('Parameter response_type is', responseType)
let client = await req.oauth2.model.client.fetchById(clientId) const client = await req.oauth2.model.client.fetchById(clientId)
if (!client) { if (!client) {
return response.error(req, res, new error.InvalidClient('Client not found'), redirectUri) return response.error(req, res, new error.InvalidClient('Client not found'), redirectUri)
} }

View File

@ -9,9 +9,9 @@ module.exports = async (req, res, client, scope, user, redirectUri, createAllowF
} }
if (createAllowFuture) { if (createAllowFuture) {
if (!req.body || (typeof req.body['decision']) === 'undefined') { if (!req.body || (typeof req.body.decision) === 'undefined') {
throw new error.InvalidRequest('No decision parameter passed') throw new error.InvalidRequest('No decision parameter passed')
} else if (req.body['decision'] === '0') { } else if (req.body.decision === '0') {
throw new error.AccessDenied('User denied access to the resource') throw new error.AccessDenied('User denied access to the resource')
} else { } else {
console.debug('Decision check passed') console.debug('Decision check passed')

View File

@ -9,9 +9,9 @@ module.exports = async (req, res, client, scope, user, redirectUri, createAllowF
} }
if (createAllowFuture) { if (createAllowFuture) {
if (!req.body || (typeof req.body['decision']) === 'undefined') { if (!req.body || (typeof req.body.decision) === 'undefined') {
throw new error.InvalidRequest('No decision parameter passed') throw new error.InvalidRequest('No decision parameter passed')
} else if (req.body['decision'] === '0') { } else if (req.body.decision === '0') {
throw new error.AccessDenied('User denied access to the resource') throw new error.AccessDenied('User denied access to the resource')
} else { } else {
console.debug('Decision check passed') console.debug('Decision check passed')

View File

@ -38,13 +38,13 @@ module.exports = wrap(async function (req, res) {
return response.error(req, res, new error.InvalidRequest('Token not provided in request body')) return response.error(req, res, new error.InvalidRequest('Token not provided in request body'))
} }
let token = await req.oauth2.model.accessToken.fetchByToken(req.body.token) const token = await req.oauth2.model.accessToken.fetchByToken(req.body.token)
if (!token) { if (!token) {
return response.error(req, res, new error.InvalidRequest('Token does not exist')) return response.error(req, res, new error.InvalidRequest('Token does not exist'))
} }
let ttl = req.oauth2.model.accessToken.getTTL(token) const ttl = req.oauth2.model.accessToken.getTTL(token)
let resObj = { const resObj = {
token_type: 'bearer', token_type: 'bearer',
token: token.token, token: token.token,
expires_in: Math.floor(ttl / 1000) expires_in: Math.floor(ttl / 1000)

View File

@ -43,13 +43,13 @@ module.exports = wrap(async (req, res) => {
grantType = req.body.grant_type grantType = req.body.grant_type
console.debug('Parameter grant_type is', grantType) console.debug('Parameter grant_type is', grantType)
let client = await req.oauth2.model.client.fetchById(clientId) const client = await req.oauth2.model.client.fetchById(clientId)
if (!client) { if (!client) {
return response.error(req, res, new error.InvalidClient('Client not found')) return response.error(req, res, new error.InvalidClient('Client not found'))
} }
let valid = req.oauth2.model.client.checkSecret(client, clientSecret) const valid = req.oauth2.model.client.checkSecret(client, clientSecret)
if (!valid) { if (!valid) {
return response.error(req, res, new error.UnauthorizedClient('Invalid client secret')) return response.error(req, res, new error.UnauthorizedClient('Invalid client secret'))
} }

View File

@ -1,7 +1,7 @@
import error from '../../error' import error from '../../error'
module.exports = async (oauth2, client, providedCode, redirectUri) => { module.exports = async (oauth2, client, providedCode, redirectUri) => {
let respObj = { const respObj = {
token_type: 'bearer' token_type: 'bearer'
} }

View File

@ -3,7 +3,7 @@ import error from '../../error'
module.exports = async (oauth2, client, wantScope) => { module.exports = async (oauth2, client, wantScope) => {
let scope = null let scope = null
let resObj = { const resObj = {
token_type: 'bearer' token_type: 'bearer'
} }

View File

@ -2,7 +2,7 @@ import error from '../../error'
module.exports = async (oauth2, client, username, password, scope) => { module.exports = async (oauth2, client, username, password, scope) => {
let user = null let user = null
let resObj = { const resObj = {
token_type: 'bearer' token_type: 'bearer'
} }
@ -32,7 +32,7 @@ module.exports = async (oauth2, client, username, password, scope) => {
throw new error.InvalidClient('User not found') throw new error.InvalidClient('User not found')
} }
let valid = await oauth2.model.user.checkPassword(user, password) const valid = await oauth2.model.user.checkPassword(user, password)
if (!valid) { if (!valid) {
throw new error.InvalidClient('Wrong password') throw new error.InvalidClient('Wrong password')
} }

View File

@ -6,7 +6,7 @@ module.exports = async (oauth2, client, pRefreshToken, scope) => {
let refreshToken = null let refreshToken = null
let accessToken = null let accessToken = null
let resObj = { const resObj = {
token_type: 'bearer' token_type: 'bearer'
} }

View File

@ -22,18 +22,18 @@ const middleware = wrap(async function (req, res, next) {
token = pieces[1] token = pieces[1]
console.debug('Bearer token parsed from authorization header:', token) console.debug('Bearer token parsed from authorization header:', token)
} else if (req.query && req.query['access_token']) { } else if (req.query && req.query.access_token) {
token = req.query['access_token'] token = req.query.access_token
console.debug('Bearer token parsed from query params:', token) console.debug('Bearer token parsed from query params:', token)
} else if (req.body && req.body['access_token']) { } else if (req.body && req.body.access_token) {
token = req.body['access_token'] token = req.body.access_token
console.debug('Bearer token parsed from body params:', token) console.debug('Bearer token parsed from body params:', token)
} else { } else {
return response.error(req, res, new error.AccessDenied('Bearer token not found')) return response.error(req, res, new error.AccessDenied('Bearer token not found'))
} }
// Try to fetch access token // Try to fetch access token
let object = await req.oauth2.model.accessToken.fetchByToken(token) const object = await req.oauth2.model.accessToken.fetchByToken(token)
if (!object) { if (!object) {
response.error(req, res, new error.Forbidden('Token not found or has expired')) response.error(req, res, new error.Forbidden('Token not found or has expired'))
} else if (!req.oauth2.model.accessToken.checkTTL(object)) { } else if (!req.oauth2.model.accessToken.checkTTL(object)) {

View File

@ -32,7 +32,7 @@ const OAuthDB = {
created_at: new Date() created_at: new Date()
} }
let res = await Models.OAuth2AccessToken.query().insert(obj) const res = await Models.OAuth2AccessToken.query().insert(obj)
if (!res) return null if (!res) return null
return res.token return res.token
@ -54,7 +54,7 @@ const OAuthDB = {
return (object.expires_at - Date.now()) return (object.expires_at - Date.now())
}, },
fetchByUserIdClientId: async (userId, clientId) => { fetchByUserIdClientId: async (userId, clientId) => {
let tkn = await Models.OAuth2AccessToken.query().where('user_id', userId).andWhere('client_id', clientId) const tkn = await Models.OAuth2AccessToken.query().where('user_id', userId).andWhere('client_id', clientId)
if (!tkn.length) return null if (!tkn.length) return null
@ -66,7 +66,7 @@ const OAuthDB = {
return client.id return client.id
}, },
fetchById: async (id) => { fetchById: async (id) => {
let client = await Models.OAuth2Client.query().where('id', id) const client = await Models.OAuth2Client.query().where('id', id)
if (!client.length) return null if (!client.length) return null
@ -110,9 +110,9 @@ const OAuthDB = {
scope = OAuthDB.client.transformScope(scope) scope = OAuthDB.client.transformScope(scope)
} }
let clientScopes = client.scope.split(' ') const clientScopes = client.scope.split(' ')
for (let i in scope) { for (const i in scope) {
if (clientScopes.indexOf(scope[i]) === -1) { if (clientScopes.indexOf(scope[i]) === -1) {
return false return false
} }
@ -228,7 +228,7 @@ const OAuthDB = {
checkPassword: Users.User.Login.password, checkPassword: Users.User.Login.password,
fetchFromRequest: async (req) => { fetchFromRequest: async (req) => {
if (!req.session.user) return null if (!req.session.user) return null
let banStatus = await Users.User.getBanStatus(req.session.user.id) const banStatus = await Users.User.getBanStatus(req.session.user.id)
if (banStatus.length) { if (banStatus.length) {
delete req.session.user delete req.session.user
@ -242,11 +242,11 @@ const OAuthDB = {
scope = scope.join(' ') scope = scope.join(' ')
} }
let authorized = await Models.OAuth2AuthorizedClient.query().where('user_id', userId) const authorized = await Models.OAuth2AuthorizedClient.query().where('user_id', userId)
if (!authorized.length) return false if (!authorized.length) return false
let correct = false let correct = false
for (let i in authorized) { for (const i in authorized) {
if (authorized[i].client_id === clientId) { if (authorized[i].client_id === clientId) {
correct = authorized[i] correct = authorized[i]
} }
@ -271,7 +271,7 @@ const OAuthDB = {
scope = scope.join(' ') scope = scope.join(' ')
} }
let obj = { const obj = {
scope, scope,
user_id: userId, user_id: userId,
client_id: clientId, client_id: clientId,

View File

@ -24,7 +24,7 @@ module.exports.error = function (req, res, err, redirectUri) {
} }
if (redirectUri) { if (redirectUri) {
let obj = { const obj = {
error: err.code, error: err.code,
error_description: err.message error_description: err.message
} }
@ -36,7 +36,7 @@ module.exports.error = function (req, res, err, redirectUri) {
redirectUri += '?' + query.stringify(obj) redirectUri += '?' + query.stringify(obj)
redirect(req, res, redirectUri) redirect(req, res, redirectUri)
} else { } else {
data(req, res, err.status, {error: err.code, error_description: err.message}) data(req, res, err.status, { error: err.code, error_description: err.message })
} }
} }

View File

@ -14,7 +14,7 @@ const args = {
} }
function spawnWorkers () { function spawnWorkers () {
let workerCount = config.server.workers === 0 ? cpuCount : config.server.workers const workerCount = config.server.workers === 0 ? cpuCount : config.server.workers
console.log('Spinning up %d worker process%s', workerCount, (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++) {
@ -52,7 +52,7 @@ function watchFileTree () {
console.log('[WatchTask] %s changed, restarting workers', f) console.log('[WatchTask] %s changed, restarting workers', f)
if (workers.length) { if (workers.length) {
for (let i in workers) { for (const i in workers) {
workers[i].send('stop') workers[i].send('stop')
} }
} }
@ -97,11 +97,11 @@ cluster.setupMaster({
}) })
cluster.on('exit', (worker, code, signal) => { cluster.on('exit', (worker, code, signal) => {
let extra = ((code || '') + ' ' + (signal || '')).trim() const extra = ((code || '') + ' ' + (signal || '')).trim()
console.error('Worker process %d exited %s', worker.process.pid, (extra ? '(' + extra + ')' : '')) console.error('Worker process %d exited %s', worker.process.pid, (extra ? '(' + extra + ')' : ''))
let index = workers.indexOf(worker) const index = workers.indexOf(worker)
if (index !== -1) workers.splice(index, 1) if (index !== -1) workers.splice(index, 1)
if (code === 0) return if (code === 0) return
@ -119,7 +119,7 @@ process.on('SIGUSR2', () => {
console.log('Received SIGUSR2. Restarting workers.') console.log('Received SIGUSR2. Restarting workers.')
if (workers.length) { if (workers.length) {
for (let i in workers) { for (const i in workers) {
workers[i].send('stop') workers[i].send('stop')
} }
} }

View File

@ -4,7 +4,7 @@ import ensureLogin from '../../scripts/ensureLogin'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
import API from '../api/admin' import API from '../api/admin'
import Emailer from '../api/emailer' import Emailer from '../api/emailer'
import {User} from '../api' import { User } from '../api'
const router = express.Router() const router = express.Router()
const apiRouter = express.Router() const apiRouter = express.Router()
@ -12,7 +12,7 @@ const apiRouter = express.Router()
// Check for privilege required to access the admin panel // Check for privilege required to access the admin panel
router.use(ensureLogin, wrap(async (req, res, next) => { router.use(ensureLogin, wrap(async (req, res, next) => {
if (!req.session.privilege) { if (!req.session.privilege) {
let u = await User.get(req.session.user) const u = await User.get(req.session.user)
req.session.privilege = u.nw_privilege req.session.privilege = u.nw_privilege
} }
@ -31,10 +31,10 @@ router.use(ensureLogin, wrap(async (req, res, next) => {
router.get('/access', (req, res) => { router.get('/access', (req, res) => {
if (!req.session.accesstime || req.session.accesstime < Date.now()) { if (!req.session.accesstime || req.session.accesstime < Date.now()) {
return res.status(401).jsonp({error: 'Access expired'}) return res.status(401).jsonp({ error: 'Access expired' })
} }
res.jsonp({access: req.session.accesstime - Date.now()}) res.jsonp({ access: req.session.accesstime - Date.now() })
}) })
// Post password to continue // Post password to continue
@ -42,16 +42,16 @@ router.post('/', wrap(async (req, res, next) => {
if (!req.body.password) return next() if (!req.body.password) return next()
if (req.body.csrf !== req.session.csrf) { if (req.body.csrf !== req.session.csrf) {
req.flash('message', {error: true, text: 'Invalid session token'}) req.flash('message', { error: true, text: 'Invalid session token' })
return next() return next()
} }
let passReady = await User.Login.password(req.session.user, req.body.password) const passReady = await User.Login.password(req.session.user, req.body.password)
if (passReady) { if (passReady) {
req.session.accesstime = Date.now() + 600000 // 10 minutes req.session.accesstime = Date.now() + 600000 // 10 minutes
return res.redirect('/admin') return res.redirect('/admin')
} else { } else {
req.flash('message', {error: true, text: 'Invalid password'}) req.flash('message', { error: true, text: 'Invalid password' })
} }
next() next()
@ -68,7 +68,7 @@ router.use(wrap(async (req, res, next) => {
delete req.session.accesstime delete req.session.accesstime
} }
res.render('user/password', {post: '/admin'}) res.render('user/password', { post: '/admin' })
})) }))
/* ========= /* =========
@ -108,12 +108,12 @@ apiRouter.get('/users', wrap(async (req, res) => {
page = 1 page = 1
} }
let users = await API.getAllUsers(page, req.session.user.id) const users = await API.getAllUsers(page, req.session.user.id)
res.jsonp(users) res.jsonp(users)
})) }))
apiRouter.get('/user/:id', wrap(async (req, res) => { apiRouter.get('/user/:id', wrap(async (req, res) => {
let id = parseInt(req.params.id) const id = parseInt(req.params.id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid number') throw new Error('Invalid number')
} }
@ -122,7 +122,7 @@ apiRouter.get('/user/:id', wrap(async (req, res) => {
})) }))
apiRouter.post('/user', csrfVerify, wrap(async (req, res) => { apiRouter.post('/user', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.user_id) const id = parseInt(req.body.user_id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid or missing user ID') throw new Error('Invalid or missing user ID')
} }
@ -131,7 +131,7 @@ apiRouter.post('/user', csrfVerify, wrap(async (req, res) => {
})) }))
apiRouter.post('/user/resend_activation', csrfVerify, wrap(async (req, res) => { apiRouter.post('/user/resend_activation', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.user_id) const id = parseInt(req.body.user_id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid or missing user ID') throw new Error('Invalid or missing user ID')
} }
@ -140,7 +140,7 @@ apiRouter.post('/user/resend_activation', csrfVerify, wrap(async (req, res) => {
})) }))
apiRouter.post('/user/revoke_totp', csrfVerify, wrap(async (req, res) => { apiRouter.post('/user/revoke_totp', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.user_id) const id = parseInt(req.body.user_id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid or missing user ID') throw new Error('Invalid or missing user ID')
} }
@ -149,7 +149,7 @@ apiRouter.post('/user/revoke_totp', csrfVerify, wrap(async (req, res) => {
})) }))
apiRouter.post('/user/reset_password', csrfVerify, wrap(async (req, res) => { apiRouter.post('/user/reset_password', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.user_id) const id = parseInt(req.body.user_id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid or missing user ID') throw new Error('Invalid or missing user ID')
} }
@ -158,7 +158,7 @@ apiRouter.post('/user/reset_password', csrfVerify, wrap(async (req, res) => {
})) }))
apiRouter.post('/user/lock', csrfVerify, wrap(async (req, res) => { apiRouter.post('/user/lock', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.user_id) const id = parseInt(req.body.user_id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid or missing user ID') throw new Error('Invalid or missing user ID')
} }
@ -170,11 +170,11 @@ const availableScopes = ['uuid', 'email', 'username', 'display_name']
apiRouter.get('/search/users', wrap(async (req, res) => { apiRouter.get('/search/users', wrap(async (req, res) => {
if (!req.query.terms) throw new Error('Please specify search terms!') if (!req.query.terms) throw new Error('Please specify search terms!')
let scopes = [] const scopes = []
if (req.query.scopes) { if (req.query.scopes) {
let scq = req.query.scopes.split(',') const scq = req.query.scopes.split(',')
for (let i in scq) { for (const i in scq) {
scq[i] = scq[i].trim() scq[i] = scq[i].trim()
if (availableScopes.indexOf(scq[i]) !== -1) { if (availableScopes.indexOf(scq[i]) !== -1) {
@ -187,7 +187,7 @@ apiRouter.get('/search/users', wrap(async (req, res) => {
scopes.push('email') scopes.push('email')
} }
let results = await API.searchUsers(req.query.terms, scopes) const results = await API.searchUsers(req.query.terms, scopes)
res.jsonp(results) res.jsonp(results)
})) }))
@ -202,17 +202,17 @@ apiRouter.get('/clients', wrap(async (req, res) => {
page = 1 page = 1
} }
let clients = await API.getAllClients(page) const clients = await API.getAllClients(page)
res.jsonp(clients) res.jsonp(clients)
})) }))
apiRouter.get('/client/:id', wrap(async (req, res) => { apiRouter.get('/client/:id', wrap(async (req, res) => {
let id = parseInt(req.params.id) const id = parseInt(req.params.id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid number') throw new Error('Invalid number')
} }
let client = await API.getClient(id) const client = await API.getClient(id)
res.jsonp(client) res.jsonp(client)
})) }))
@ -224,7 +224,7 @@ apiRouter.post('/client/new', csrfVerify, wrap(async (req, res) => {
})) }))
apiRouter.post('/client/update', csrfVerify, wrap(async (req, res) => { apiRouter.post('/client/update', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.id) const id = parseInt(req.body.id)
if (!id || isNaN(id)) throw new Error('ID missing') if (!id || isNaN(id)) throw new Error('ID missing')
@ -234,23 +234,23 @@ apiRouter.post('/client/update', csrfVerify, wrap(async (req, res) => {
})) }))
apiRouter.post('/client/new_secret', csrfVerify, wrap(async (req, res) => { apiRouter.post('/client/new_secret', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.id) const id = parseInt(req.body.id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid client ID') throw new Error('Invalid client ID')
} }
let client = await API.newSecret(id) const client = await API.newSecret(id)
res.jsonp(client) res.jsonp(client)
})) }))
apiRouter.post('/client/delete', csrfVerify, wrap(async (req, res) => { apiRouter.post('/client/delete', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.id) const id = parseInt(req.body.id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid client ID') throw new Error('Invalid client ID')
} }
let client = await API.removeClient(id) const client = await API.removeClient(id)
res.jsonp(client) res.jsonp(client)
})) }))
@ -266,17 +266,17 @@ apiRouter.get('/bans', wrap(async (req, res) => {
page = 1 page = 1
} }
let bans = await API.getAllBans(page) const bans = await API.getAllBans(page)
res.jsonp(bans) res.jsonp(bans)
})) }))
apiRouter.post('/ban/pardon', csrfVerify, wrap(async (req, res) => { apiRouter.post('/ban/pardon', csrfVerify, wrap(async (req, res) => {
let id = parseInt(req.body.id) const id = parseInt(req.body.id)
if (isNaN(id)) { if (isNaN(id)) {
throw new Error('Invalid number') throw new Error('Invalid number')
} }
let ban = await API.removeBan(id) const ban = await API.removeBan(id)
res.jsonp(ban) res.jsonp(ban)
})) }))
@ -284,7 +284,7 @@ apiRouter.post('/ban/pardon', csrfVerify, wrap(async (req, res) => {
apiRouter.post('/ban', csrfVerify, wrap(async (req, res) => { apiRouter.post('/ban', csrfVerify, wrap(async (req, res) => {
if (!req.body.user_id) throw new Error('ID missing') if (!req.body.user_id) throw new Error('ID missing')
let result = await API.addBan(req.body, req.session.user.id) const result = await API.addBan(req.body, req.session.user.id)
res.jsonp(result) res.jsonp(result)
})) }))
@ -299,14 +299,14 @@ apiRouter.post('/email', csrfVerify, wrap(async (req, res) => {
html: req.body.content html: req.body.content
} }
let result = await Emailer.sendMail(req.body.email, message) const result = await Emailer.sendMail(req.body.email, message)
res.jsonp(result) res.jsonp(result)
})) }))
apiRouter.use((err, req, res, next) => { apiRouter.use((err, req, res, next) => {
console.error(err) console.error(err)
return res.status(400).jsonp({error: err.message}) return res.status(400).jsonp({ error: err.message })
}) })
router.use('/api', apiRouter) router.use('/api', apiRouter)

View File

@ -9,18 +9,18 @@ import Image from '../api/image'
import News from '../api/news' import News from '../api/news'
import API from '../api' import API from '../api'
let router = express.Router() const router = express.Router()
let dev = process.env.NODE_ENV !== 'production' const dev = process.env.NODE_ENV !== 'production'
// Restrict API usage // Restrict API usage
let apiLimiter = new RateLimit({ const apiLimiter = new RateLimit({
windowMs: 5 * 60 * 1000, // 5 minutes windowMs: 5 * 60 * 1000, // 5 minutes
max: 100, max: 100,
delayMs: 0 delayMs: 0
}) })
// Restrict image uploads // Restrict image uploads
let uploadLimiter = new RateLimit({ const uploadLimiter = new RateLimit({
windowMs: 60 * 60 * 1000, // 1 hour windowMs: 60 * 60 * 1000, // 1 hour
max: 10, max: 10,
delayMs: 0 delayMs: 0
@ -30,11 +30,11 @@ router.use(apiLimiter)
// Turn things like 'key1[key2]': 'value' into key1: {key2: 'value'} because facebook // Turn things like 'key1[key2]': 'value' into key1: {key2: 'value'} because facebook
function objectAssembler (insane) { function objectAssembler (insane) {
let object = {} const object = {}
for (let key in insane) { for (const key in insane) {
let value = insane[key] const value = insane[key]
if (key.indexOf('[') !== -1) { if (key.indexOf('[') !== -1) {
let subKey = key.match(/^([\w]+)\[(\w+)\]$/) const subKey = key.match(/^([\w]+)\[(\w+)\]$/)
if (subKey[1] && subKey[2]) { if (subKey[1] && subKey[2]) {
if (!object[subKey[1]]) { if (!object[subKey[1]]) {
object[subKey[1]] = {} object[subKey[1]] = {}
@ -63,7 +63,7 @@ function createSession (req, user) {
// Get either `uuid` or `id` from `:id` parameter // Get either `uuid` or `id` from `:id` parameter
function idParam (req) { function idParam (req) {
let id = req.params.id const id = req.params.id
if (id.length === 36 && id.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)) { if (id.length === 36 && id.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)) {
return id return id
} }
@ -77,17 +77,17 @@ function idParam (req) {
// Either give JSON or make a redirect // Either give JSON or make a redirect
function JsonData (req, res, error, redirect = '/') { function JsonData (req, res, error, redirect = '/') {
res.jsonp({error: error, redirect: redirect}) res.jsonp({ error: error, redirect: redirect })
} }
// Common middleware for all external account unlinks // Common middleware for all external account unlinks
function removeAuthMiddleware (identifier) { function removeAuthMiddleware (identifier) {
return wrap(async (req, res) => { return wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login') if (!req.session.user) return res.redirect('/login')
let done = await APIExtern.Common.remove(req.session.user, identifier) const done = await APIExtern.Common.remove(req.session.user, identifier)
if (!done) { if (!done) {
req.flash('message', {error: true, text: 'Unable to unlink social media account'}) req.flash('message', { error: true, text: 'Unable to unlink social media account' })
} }
res.redirect('/user/manage') res.redirect('/user/manage')
@ -102,12 +102,12 @@ router.post('/external/facebook/callback', wrap(async (req, res, next) => {
if (!config.external || !config.external.facebook || !config.external.facebook.client) return next() if (!config.external || !config.external.facebook || !config.external.facebook.client) return next()
// Fix up the retarded object Facebook sends us // Fix up the retarded object Facebook sends us
let sane = objectAssembler(req.body) const sane = objectAssembler(req.body)
if (!sane || !sane.authResponse) { if (!sane || !sane.authResponse) {
return next() return next()
} }
let response = await APIExtern.Facebook.callback(req.session.user, sane.authResponse, req.realIP) const response = await APIExtern.Facebook.callback(req.session.user, sane.authResponse, req.realIP)
if (response.banned) { if (response.banned) {
return JsonData(req, res, 'You are banned.') return JsonData(req, res, 'You are banned.')
@ -119,7 +119,7 @@ router.post('/external/facebook/callback', wrap(async (req, res, next) => {
// Create session // Create session
if (!req.session.user) { if (!req.session.user) {
let user = response.user const user = response.user
createSession(req, user) createSession(req, user)
} }
@ -134,10 +134,10 @@ router.get('/external/facebook/remove', removeAuthMiddleware('facebook'))
*/ */
router.get('/external/twitter/login', wrap(async (req, res) => { router.get('/external/twitter/login', wrap(async (req, res) => {
if (!config.external || !config.external.twitter || !config.external.twitter.api) return res.redirect('/') if (!config.external || !config.external.twitter || !config.external.twitter.api) return res.redirect('/')
let tokens = await APIExtern.Twitter.getRequestToken() const tokens = await APIExtern.Twitter.getRequestToken()
if (tokens.error) { if (tokens.error) {
return res.jsonp({error: tokens.error}) return res.jsonp({ error: tokens.error })
} }
req.session.twitter_auth = tokens req.session.twitter_auth = tokens
@ -148,38 +148,38 @@ router.get('/external/twitter/login', wrap(async (req, res) => {
router.get('/external/twitter/callback', wrap(async (req, res) => { router.get('/external/twitter/callback', wrap(async (req, res) => {
if (!config.external || !config.external.twitter || !config.external.twitter.api) return res.redirect('/login') if (!config.external || !config.external.twitter || !config.external.twitter.api) return res.redirect('/login')
if (!req.session.twitter_auth) return res.redirect('/login') if (!req.session.twitter_auth) return res.redirect('/login')
let ta = req.session.twitter_auth const ta = req.session.twitter_auth
let uri = '/login' const uri = '/login'
if (!req.query.oauth_verifier) { if (!req.query.oauth_verifier) {
req.flash('message', {error: true, text: 'Couldn\'t get a verifier'}) req.flash('message', { error: true, text: 'Couldn\'t get a verifier' })
return res.redirect(uri) return res.redirect(uri)
} }
let accessTokens = await APIExtern.Twitter.getAccessTokens(ta.token, ta.token_secret, req.query.oauth_verifier) const accessTokens = await APIExtern.Twitter.getAccessTokens(ta.token, ta.token_secret, req.query.oauth_verifier)
delete req.session.twitter_auth delete req.session.twitter_auth
if (accessTokens.error) { if (accessTokens.error) {
req.flash('message', {error: true, text: 'Couldn\'t get an access token'}) req.flash('message', { error: true, text: 'Couldn\'t get an access token' })
return res.redirect(uri) return res.redirect(uri)
} }
let response = await APIExtern.Twitter.callback(req.session.user, accessTokens, req.realIP) const response = await APIExtern.Twitter.callback(req.session.user, accessTokens, req.realIP)
if (response.banned) { if (response.banned) {
return res.render('user/banned', {bans: response.banned, ipban: response.ip}) return res.render('user/banned', { bans: response.banned, ipban: response.ip })
} }
if (response.error) { if (response.error) {
req.flash('message', {error: true, text: response.error}) req.flash('message', { error: true, text: response.error })
return res.redirect(uri) return res.redirect(uri)
} }
if (!req.session.user) { if (!req.session.user) {
let user = response.user const user = response.user
createSession(req, user) createSession(req, user)
} }
res.render('redirect', {url: uri}) res.render('redirect', { url: uri })
})) }))
router.get('/external/twitter/remove', removeAuthMiddleware('twitter')) router.get('/external/twitter/remove', removeAuthMiddleware('twitter'))
@ -191,7 +191,7 @@ router.get('/external/twitter/remove', removeAuthMiddleware('twitter'))
router.get('/external/discord/login', wrap(async (req, res) => { router.get('/external/discord/login', wrap(async (req, res) => {
if (!config.external || !config.external.discord || !config.external.discord.api) return res.redirect('/') if (!config.external || !config.external.discord || !config.external.discord.api) return res.redirect('/')
let infos = APIExtern.Discord.getAuthorizeURL(req) const infos = APIExtern.Discord.getAuthorizeURL(req)
res.redirect(infos.url) res.redirect(infos.url)
})) }))
@ -199,44 +199,44 @@ router.get('/external/discord/login', wrap(async (req, res) => {
router.get('/external/discord/callback', wrap(async (req, res) => { router.get('/external/discord/callback', wrap(async (req, res) => {
if (!config.external || !config.external.discord || !config.external.discord.api) return res.redirect('/login') if (!config.external || !config.external.discord || !config.external.discord.api) return res.redirect('/login')
let code = req.query.code const code = req.query.code
let state = req.query.state const state = req.query.state
let uri = '/login' const uri = '/login'
if (!code) { if (!code) {
req.flash('message', {error: true, text: 'No authorization.'}) req.flash('message', { error: true, text: 'No authorization.' })
return res.redirect(uri) return res.redirect(uri)
} }
if (!state || state !== APIExtern.Common.stateGenerator(req)) { if (!state || state !== APIExtern.Common.stateGenerator(req)) {
req.flash('message', {error: true, text: 'Request got intercepted, try again.'}) req.flash('message', { error: true, text: 'Request got intercepted, try again.' })
return res.redirect(uri) return res.redirect(uri)
} }
delete req.session.discord_auth delete req.session.discord_auth
let accessToken = await APIExtern.Discord.getAccessToken(code) const accessToken = await APIExtern.Discord.getAccessToken(code)
if (accessToken.error) { if (accessToken.error) {
req.flash('message', {error: true, text: accessToken.error}) req.flash('message', { error: true, text: accessToken.error })
return res.redirect(uri) return res.redirect(uri)
} }
let response = await APIExtern.Discord.callback(req.session.user, accessToken.accessToken, req.realIP) const response = await APIExtern.Discord.callback(req.session.user, accessToken.accessToken, req.realIP)
if (response.banned) { if (response.banned) {
return res.render('user/banned', {bans: response.banned, ipban: response.ip}) return res.render('user/banned', { bans: response.banned, ipban: response.ip })
} }
if (response.error) { if (response.error) {
req.flash('message', {error: true, text: response.error}) req.flash('message', { error: true, text: response.error })
return res.redirect(uri) return res.redirect(uri)
} }
if (!req.session.user) { if (!req.session.user) {
let user = response.user const user = response.user
createSession(req, user) createSession(req, user)
} }
res.render('redirect', {url: uri}) res.render('redirect', { url: uri })
})) }))
router.get('/external/discord/remove', removeAuthMiddleware('discord')) router.get('/external/discord/remove', removeAuthMiddleware('discord'))
@ -258,7 +258,7 @@ router.post('/external/google/callback', wrap(async (req, res) => {
return JsonData(req, res, 'Invalid or missing ID token!', '/login') return JsonData(req, res, 'Invalid or missing ID token!', '/login')
} }
let response = await APIExtern.Google.callback(req.session.user, req.body, req.realIP) const response = await APIExtern.Google.callback(req.session.user, req.body, req.realIP)
if (response.banned) { if (response.banned) {
return JsonData(req, res, 'You are banned.', '/login') return JsonData(req, res, 'You are banned.', '/login')
} }
@ -268,7 +268,7 @@ router.post('/external/google/callback', wrap(async (req, res) => {
} }
if (!req.session.user) { if (!req.session.user) {
let user = response.user const user = response.user
createSession(req, user) createSession(req, user)
} }
@ -291,11 +291,11 @@ router.get('/news', (req, res, next) => {
// Get a page of articles // Get a page of articles
router.get('/news/all/:page', wrap(async (req, res) => { router.get('/news/all/:page', wrap(async (req, res) => {
if (!req.params.page || isNaN(parseInt(req.params.page))) { if (!req.params.page || isNaN(parseInt(req.params.page))) {
return res.status(400).jsonp({error: 'Invalid page number.'}) return res.status(400).jsonp({ error: 'Invalid page number.' })
} }
let page = parseInt(req.params.page) const page = parseInt(req.params.page)
let articles = await News.listNews(page) const articles = await News.listNews(page)
res.jsonp(articles) res.jsonp(articles)
})) }))
@ -306,22 +306,22 @@ router.get('/news/all/', (req, res) => {
}) })
router.post('/news/edit/:id', wrap(async (req, res, next) => { router.post('/news/edit/:id', wrap(async (req, res, next) => {
let id = parseInt(req.params.id) const id = parseInt(req.params.id)
if (!req.session.user || req.session.user.privilege < 1) return next() if (!req.session.user || req.session.user.privilege < 1) return next()
if (!id || isNaN(id)) { if (!id || isNaN(id)) {
return res.status(400).jsonp({error: 'Invalid ID number.'}) return res.status(400).jsonp({ error: 'Invalid ID number.' })
} }
if (!req.body.content) { if (!req.body.content) {
return res.status(400).jsonp({error: 'Content is required.'}) return res.status(400).jsonp({ error: 'Content is required.' })
} }
try { try {
await News.edit(id, req.body) await News.edit(id, req.body)
} catch (e) { } catch (e) {
return res.status(400).jsonp({error: e.message}) return res.status(400).jsonp({ error: e.message })
} }
res.status(204).end() res.status(204).end()
@ -330,18 +330,18 @@ router.post('/news/edit/:id', wrap(async (req, res, next) => {
// Fetch article // Fetch article
router.get('/news/:id', wrap(async (req, res) => { router.get('/news/:id', wrap(async (req, res) => {
if (!req.params.id || isNaN(parseInt(req.params.id))) { if (!req.params.id || isNaN(parseInt(req.params.id))) {
return res.status(400).jsonp({error: 'Invalid ID number.'}) return res.status(400).jsonp({ error: 'Invalid ID number.' })
} }
let id = parseInt(req.params.id) const id = parseInt(req.params.id)
let article = await News.article(id) const article = await News.article(id)
res.jsonp(article) res.jsonp(article)
})) }))
// Preview endpoint // Preview endpoint
router.get('/news', wrap(async (req, res) => { router.get('/news', wrap(async (req, res) => {
let articles = await News.preview() const articles = await News.preview()
res.jsonp(articles) res.jsonp(articles)
})) }))
@ -352,11 +352,11 @@ router.get('/news', wrap(async (req, res) => {
*/ */
// Promisify multiparty form parser // Promisify multiparty form parser
async function promiseForm (req) { async function promiseForm (req) {
let form = new multiparty.Form() const form = new multiparty.Form()
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
form.parse(req, async (err, fields, files) => { form.parse(req, async (err, fields, files) => {
if (err) return reject(err) if (err) return reject(err)
resolve({fields: fields, files: files}) resolve({ fields: fields, files: files })
}) })
}) })
} }
@ -364,23 +364,23 @@ async function promiseForm (req) {
// Upload avatar image // Upload avatar image
router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => { router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let data = await promiseForm(req) const data = await promiseForm(req)
let avatarFile let avatarFile
try { try {
let result = await Image.uploadImage(req.session.user.username, data.fields, data.files) const result = await Image.uploadImage(req.session.user.username, data.fields, data.files)
avatarFile = await API.User.changeAvatar(req.session.user, result.file) avatarFile = await API.User.changeAvatar(req.session.user, result.file)
} catch (e) { } catch (e) {
return res.status(400).jsonp({error: e.message}) return res.status(400).jsonp({ error: e.message })
} }
if (avatarFile) { if (avatarFile) {
req.session.user.avatar_file = avatarFile req.session.user.avatar_file = avatarFile
} }
req.flash('message', {error: false, text: 'Success!'}) req.flash('message', { error: false, text: 'Success!' })
res.status(200).jsonp({}) res.status(200).jsonp({})
})) }))
@ -391,13 +391,13 @@ router.post('/avatar/remove', wrap(async (req, res, next) => {
await API.User.removeAvatar(req.session.user) await API.User.removeAvatar(req.session.user)
req.session.user.avatar_file = null req.session.user.avatar_file = null
res.status(200).jsonp({done: true}) res.status(200).jsonp({ done: true })
})) }))
// Get latest avatar of logged in user // Get latest avatar of logged in user
router.get('/avatar', wrap(async (req, res, next) => { router.get('/avatar', wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let user = req.session.user const user = req.session.user
if (!user.avatar_file) return next() if (!user.avatar_file) return next()
@ -407,10 +407,10 @@ router.get('/avatar', wrap(async (req, res, next) => {
// Get latest avatar of user by id // Get latest avatar of user by id
router.get('/avatar/:id', wrap(async (req, res, next) => { router.get('/avatar/:id', wrap(async (req, res, next) => {
let id = idParam(req) const id = idParam(req)
if (!id) return next() if (!id) return next()
let user = await API.User.get(id) const user = await API.User.get(id)
if (!user || !user.avatar_file) return next() if (!user || !user.avatar_file) return next()
@ -420,7 +420,7 @@ router.get('/avatar/:id', wrap(async (req, res, next) => {
router.get('/avatar/gravatar', (req, res, next) => { router.get('/avatar/gravatar', (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let email = req.session.user.email const email = req.session.user.email
res.set('Content-Type', 'text/plain') res.set('Content-Type', 'text/plain')
res.end(Image.gravatarURL(email)) res.end(Image.gravatarURL(email))
@ -428,17 +428,17 @@ router.get('/avatar/gravatar', (req, res, next) => {
router.post('/avatar/gravatar', wrap(async (req, res, next) => { router.post('/avatar/gravatar', wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let user = req.session.user const user = req.session.user
try { try {
let gravURL = await Image.downloadImage(Image.gravatarURL(user.email), 'GRAV-' + user.username) const gravURL = await Image.downloadImage(Image.gravatarURL(user.email), 'GRAV-' + user.username)
let file = await API.User.changeAvatar(user, gravURL) const file = await API.User.changeAvatar(user, gravURL)
req.session.user.avatar_file = file req.session.user.avatar_file = file
req.flash('message', {error: false, text: 'Success!'}) req.flash('message', { error: false, text: 'Success!' })
} catch (e) { } catch (e) {
req.flash('message', {error: true, text: 'Failed to use gravatar avatar.'}) req.flash('message', { error: true, text: 'Failed to use gravatar avatar.' })
} }
res.jsonp({}) res.jsonp({})
@ -458,7 +458,7 @@ router.use('/avatar', (req, res) => {
router.get('/oauth2/authorized-clients', wrap(async (req, res, next) => { router.get('/oauth2/authorized-clients', wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let list = await API.User.OAuth2.getUserAuthorizations(req.session.user) const list = await API.User.OAuth2.getUserAuthorizations(req.session.user)
if (!list) return next() if (!list) return next()
res.jsonp(list) res.jsonp(list)
@ -468,11 +468,11 @@ router.get('/oauth2/authorized-clients', wrap(async (req, res, next) => {
router.post('/oauth2/authorized-clients/revoke', wrap(async (req, res, next) => { router.post('/oauth2/authorized-clients/revoke', wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let clientId = parseInt(req.body.client_id) const clientId = parseInt(req.body.client_id)
if (isNaN(clientId)) return res.status(400).jsonp({error: 'Missing Client ID parameter'}) if (isNaN(clientId)) return res.status(400).jsonp({ error: 'Missing Client ID parameter' })
let done = await API.User.OAuth2.removeUserAuthorization(req.session.user, clientId) const done = await API.User.OAuth2.removeUserAuthorization(req.session.user, clientId)
if (!done) return res.status(400).jsonp({error: 'Failed to remove client authorization'}) if (!done) return res.status(400).jsonp({ error: 'Failed to remove client authorization' })
res.status(204).end() res.status(204).end()
})) }))
@ -483,7 +483,7 @@ router.post('/oauth2/authorized-clients/revoke', wrap(async (req, res, next) =>
*/ */
router.post('/paypal/ipn', wrap(async (req, res) => { router.post('/paypal/ipn', wrap(async (req, res) => {
let content = req.body const content = req.body
if (content && content.payment_status && content.payment_status === 'Completed') { if (content && content.payment_status && content.payment_status === 'Completed') {
await API.Payment.handleIPN(content) await API.Payment.handleIPN(content)
@ -495,7 +495,7 @@ router.post('/paypal/ipn', wrap(async (req, res) => {
router.get('/donations/user', wrap(async (req, res, next) => { router.get('/donations/user', wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let contribs = await API.Payment.userContributions(req.session.user) const contribs = await API.Payment.userContributions(req.session.user)
res.jsonp(contribs) res.jsonp(contribs)
})) }))
@ -509,22 +509,22 @@ router.get('/donations', wrap(async (req, res, next) => {
count = 10 count = 10
} }
let mcu = req.query.mcu === '1' || req.query.mcu === 'true' const mcu = req.query.mcu === '1' || req.query.mcu === 'true'
let timeFrame = parseInt(req.query.timeFrame) let timeFrame = parseInt(req.query.timeFrame)
if (isNaN(timeFrame)) timeFrame = 0 if (isNaN(timeFrame)) timeFrame = 0
let contribs = await API.Payment.allContributions(count, mcu, timeFrame) const contribs = await API.Payment.allContributions(count, mcu, timeFrame)
res.jsonp(contribs) res.jsonp(contribs)
})) }))
// 404 // 404
router.use((req, res) => { router.use((req, res) => {
res.status(404).jsonp({error: 'Not found'}) res.status(404).jsonp({ error: 'Not found' })
}) })
router.use((err, req, res, next) => { router.use((err, req, res, next) => {
console.error(err) console.error(err)
res.jsonp({error: 'Internal server error.'}) res.jsonp({ error: 'Internal server error.' })
}) })
module.exports = router module.exports = router

View File

@ -15,10 +15,10 @@ import apiRouter from './api'
import oauthRouter from './oauth2' import oauthRouter from './oauth2'
import adminRouter from './admin' import adminRouter from './admin'
let router = express.Router() const router = express.Router()
// Restrict account creation // Restrict account creation
let accountLimiter = new RateLimit({ const accountLimiter = new RateLimit({
windowMs: 60 * 60 * 1000, // 1 hour windowMs: 60 * 60 * 1000, // 1 hour
max: 10, max: 10,
delayMs: 0, delayMs: 0,
@ -70,7 +70,7 @@ router.use(wrap(async (req, res, next) => {
console.debug('User session update') console.debug('User session update')
// Check for bans // Check for bans
let banStatus = await API.User.getBanStatus(req.session.user.id) const banStatus = await API.User.getBanStatus(req.session.user.id)
if (banStatus.length) { if (banStatus.length) {
delete req.session.user delete req.session.user
@ -78,11 +78,11 @@ router.use(wrap(async (req, res, next) => {
} }
// Update user session // Update user session
let udata = await API.User.get(req.session.user) const udata = await API.User.get(req.session.user)
setSession(req, udata) setSession(req, udata)
// Update IP address // Update IP address
await API.User.update(udata, {ip_address: req.realIP}) await API.User.update(udata, { ip_address: req.realIP })
} }
} }
@ -103,7 +103,7 @@ router.get('/', (req, res) => {
// Add social media login buttons // Add social media login buttons
function extraButtons (recheck) { function extraButtons (recheck) {
let et = config.external const et = config.external
return function (req, res, next) { return function (req, res, next) {
if (!et) return next() if (!et) return next()
res.locals.auth = { res.locals.auth = {
@ -149,22 +149,22 @@ function formKeep (req, res, next) {
router.get('/login/reset', extraButtons(false), (req, res) => { router.get('/login/reset', extraButtons(false), (req, res) => {
if (req.session.user) return redirectLogin(req, res) if (req.session.user) return redirectLogin(req, res)
res.render('user/reset_password', {sent: req.query.success != null}) res.render('user/reset_password', { sent: req.query.success != null })
}) })
// Password reset endpoint (emailed link) // Password reset endpoint (emailed link)
router.get('/reset/:token', wrap(async (req, res) => { router.get('/reset/:token', wrap(async (req, res) => {
if (req.session.user) return res.redirect('/login') if (req.session.user) return res.redirect('/login')
let token = req.params.token const token = req.params.token
let success = await API.User.Reset.resetToken(token) const success = await API.User.Reset.resetToken(token)
if (!success) { if (!success) {
req.flash('message', {error: true, text: 'Invalid or expired reset token.'}) req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
res.redirect('/login') res.redirect('/login')
return return
} }
res.render('user/password_new', {token: true}) res.render('user/password_new', { token: true })
})) }))
router.get('/login', extraButtons(false), (req, res) => { router.get('/login', extraButtons(false), (req, res) => {
@ -190,13 +190,13 @@ router.get('/register', extraButtons(true), formKeep, (req, res) => {
// User activation endpoint (emailed link) // User activation endpoint (emailed link)
router.get('/activate/:token', wrap(async (req, res) => { router.get('/activate/:token', wrap(async (req, res) => {
if (req.session.user) return res.redirect('/login') if (req.session.user) return res.redirect('/login')
let token = req.params.token const token = req.params.token
let success = await API.User.Login.activationToken(token) const success = await API.User.Login.activationToken(token)
if (!success) { if (!success) {
req.flash('message', {error: true, text: 'Invalid or expired activation token.'}) req.flash('message', { error: true, text: 'Invalid or expired activation token.' })
} else { } else {
req.flash('message', {error: false, text: 'Your account has been activated! You may now log in.'}) req.flash('message', { error: false, text: 'Your account has been activated! You may now log in.' })
} }
res.redirect('/login') res.redirect('/login')
@ -204,10 +204,10 @@ router.get('/activate/:token', wrap(async (req, res) => {
// 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) const twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
if (twoFaEnabled) return res.redirect('/') if (twoFaEnabled) return res.redirect('/')
let newToken = await API.User.Login.totpAquire(req.session.user) const newToken = await API.User.Login.totpAquire(req.session.user)
if (!newToken) return res.redirect('/') if (!newToken) return res.redirect('/')
res.render('user/totp', { uri: newToken }) res.render('user/totp', { uri: newToken })
@ -215,7 +215,7 @@ router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
// View for disabling Two-Factor Authentication // View for disabling Two-Factor Authentication
router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => { router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => {
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user) const twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
if (!twoFaEnabled) return res.redirect('/') if (!twoFaEnabled) return res.redirect('/')
res.render('user/password') res.render('user/password')
@ -229,13 +229,13 @@ router.get('/login/verify', (req, res) => {
// User settings page // User settings page
router.get('/user/manage', ensureLogin, wrap(async (req, res) => { router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
let totpEnabled = false let totpEnabled = false
let socialStatus = await API.User.socialStatus(req.session.user) const socialStatus = await API.User.socialStatus(req.session.user)
if (socialStatus.password) { if (socialStatus.password) {
totpEnabled = await API.User.Login.totpTokenRequired(req.session.user) totpEnabled = await API.User.Login.totpTokenRequired(req.session.user)
} }
let et = config.external const et = config.external
if (et) { if (et) {
res.locals.auth = {} res.locals.auth = {}
// Decide whether we need a disconnect or a log in with button for social account logins // Decide whether we need a disconnect or a log in with button for social account logins
@ -272,28 +272,28 @@ router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
} }
} }
res.render('user/settings', {totp: totpEnabled, password: socialStatus.password}) res.render('user/settings', { totp: totpEnabled, password: socialStatus.password })
})) }))
// Change password // Change password
router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => { router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => {
let socialStatus = await API.User.socialStatus(req.session.user) const socialStatus = await API.User.socialStatus(req.session.user)
res.render('user/password_new', {token: !socialStatus.password}) res.render('user/password_new', { token: !socialStatus.password })
})) }))
// Change email // Change email
router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => { router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => {
let obfuscated = req.session.user.email let obfuscated = req.session.user.email
if (obfuscated) { if (obfuscated) {
let split = obfuscated.split('@') const split = obfuscated.split('@')
let rep = split[0].charAt(0) + '***' + split[0].charAt(split[0].length - 1) const rep = split[0].charAt(0) + '***' + split[0].charAt(split[0].length - 1)
obfuscated = rep + '@' + split[1] obfuscated = rep + '@' + split[1]
} }
let socialStatus = await API.User.socialStatus(req.session.user) const socialStatus = await API.User.socialStatus(req.session.user)
res.render('user/email_change', {email: obfuscated, password: socialStatus.password}) res.render('user/email_change', { email: obfuscated, password: socialStatus.password })
})) }))
router.get('/donate', wrap(async (req, res, next) => { router.get('/donate', wrap(async (req, res, next) => {
@ -311,14 +311,14 @@ router.get('/donate', wrap(async (req, res, next) => {
// Used to display errors on forms and save data // Used to display errors on forms and save data
function formError (req, res, error, redirect) { function formError (req, res, error, redirect) {
// Security measures: never store any passwords in any session // Security measures: never store any passwords in any session
for (let key in req.body) { for (const key in req.body) {
if (key.indexOf('password') !== -1) { if (key.indexOf('password') !== -1) {
delete req.body[key] delete req.body[key]
} }
} }
req.flash('formkeep', req.body || {}) req.flash('formkeep', req.body || {})
req.flash('message', {error: true, text: error}) req.flash('message', { error: true, text: error })
res.redirect(redirect || req.originalUrl) res.redirect(redirect || req.originalUrl)
} }
@ -350,7 +350,7 @@ router.post('/user/two-factor', csrfValidation, wrap(async (req, res, next) => {
return formError(req, res, 'You need to enter the code.') return formError(req, res, 'You need to enter the code.')
} }
let verified = await API.User.Login.totpCheck(req.session.user, req.body.code) const verified = await API.User.Login.totpCheck(req.session.user, req.body.code)
if (!verified) { if (!verified) {
return formError(req, res, 'Something went wrong! Try scanning the code again.') return formError(req, res, 'Something went wrong! Try scanning the code again.')
} }
@ -366,7 +366,7 @@ router.post('/user/two-factor/disable', csrfValidation, wrap(async (req, res, ne
return formError(req, res, 'Please enter your password.') return formError(req, res, 'Please enter your password.')
} }
let purge = await API.User.Login.purgeTotp(req.session.user, req.body.password) const purge = await API.User.Login.purgeTotp(req.session.user, req.body.password)
if (!purge) { if (!purge) {
return formError(req, res, 'Invalid password.') return formError(req, res, 'Invalid password.')
} }
@ -384,12 +384,12 @@ router.post('/login/verify', csrfValidation, wrap(async (req, res, next) => {
return formError(req, res, 'You need to enter the code.') return formError(req, res, 'You need to enter the code.')
} }
let totpCheck = await API.User.Login.totpCheck(req.session.totp_check, req.body.code, req.body.recovery || false) const totpCheck = await API.User.Login.totpCheck(req.session.totp_check, req.body.code, req.body.recovery || false)
if (!totpCheck) { if (!totpCheck) {
return formError(req, res, 'Invalid code!') return formError(req, res, 'Invalid code!')
} }
let user = await API.User.get(req.session.totp_check) const user = await API.User.get(req.session.totp_check)
delete req.session.totp_check delete req.session.totp_check
setSession(req, user) setSession(req, user)
@ -404,12 +404,12 @@ router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next
return res.redirect('/login') return res.redirect('/login')
} }
let user = await API.User.get(req.body.username) const user = await API.User.get(req.body.username)
if (!user) return formError(req, res, 'Invalid username or password.') if (!user) return formError(req, res, 'Invalid username or password.')
if (!user.password || user.password === '') return formError(req, res, 'Please log in using the buttons on the right.') if (!user.password || user.password === '') return formError(req, res, 'Please log in using the buttons on the right.')
let pwMatch = await API.User.Login.password(user, req.body.password) const pwMatch = await API.User.Login.password(user, req.body.password)
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 you did not receive an email, please contact an administrator.') if (user.activated === 0) return formError(req, res, 'Please activate your account first. If you did not receive an email, please contact an administrator.')
@ -417,13 +417,13 @@ router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next
if (user.locked === 1) return formError(req, res, 'This account has been locked. Please contact an administrator for more information.') if (user.locked === 1) return formError(req, res, 'This account has been locked. Please contact an administrator for more information.')
// Check if the user is banned // Check if the user is banned
let banStatus = await API.User.getBanStatus(user.id) const banStatus = await API.User.getBanStatus(user.id)
if (banStatus.length) { if (banStatus.length) {
return res.render('user/banned', {bans: banStatus, ipban: false}) return res.render('user/banned', { bans: banStatus, ipban: false })
} }
// Redirect to the verification dialog if 2FA is enabled // Redirect to the verification dialog if 2FA is enabled
let totpRequired = await API.User.Login.totpTokenRequired(user) const totpRequired = await API.User.Login.totpTokenRequired(user)
if (totpRequired) { if (totpRequired) {
req.session.totp_check = user.id req.session.totp_check = user.id
return res.redirect('/login/verify') return res.redirect('/login/verify')
@ -453,8 +453,8 @@ router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res
return formError(req, res, 'You need to enter your email address.') return formError(req, res, 'You need to enter your email address.')
} }
let email = req.body.email const email = req.body.email
let validEmail = await API.User.Register.validateEmail(email) const validEmail = await API.User.Register.validateEmail(email)
if (!validEmail) { if (!validEmail) {
return formError(req, res, 'You need to enter a valid email address.') return formError(req, res, 'You need to enter a valid email address.')
} }
@ -462,7 +462,7 @@ router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res
try { try {
await API.User.Reset.reset(email, false) await API.User.Reset.reset(email, false)
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)
@ -472,23 +472,23 @@ router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res
// Password reset endpoint (emailed link) // Password reset endpoint (emailed link)
router.post('/reset/:token', csrfValidation, wrap(async (req, res) => { router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
if (req.session.user) return res.redirect('/login') if (req.session.user) return res.redirect('/login')
let token = req.params.token const token = req.params.token
let user = await API.User.Reset.resetToken(token) const user = await API.User.Reset.resetToken(token)
if (!user) { if (!user) {
req.flash('message', {error: true, text: 'Invalid or expired reset token.'}) req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
res.redirect('/login') res.redirect('/login')
return return
} }
// 4th Check: Password length // 4th Check: Password length
let password = req.body.password const password = req.body.password
if (!password || password.length < 8) { if (!password || password.length < 8) {
return formError(req, res, 'Invalid password! Please use at least 8 characters!') return formError(req, res, 'Invalid password! Please use at least 8 characters!')
} }
// 5th Check: Password match // 5th Check: Password match
let passwordAgain = req.body.password_repeat const passwordAgain = req.body.password_repeat
if (!passwordAgain || password !== passwordAgain) { if (!passwordAgain || password !== passwordAgain) {
return formError(req, res, 'Passwords do not match!') return formError(req, res, 'Passwords do not match!')
} }
@ -498,7 +498,7 @@ router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP) console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
req.flash('message', {error: false, text: 'Your password has been changed successfully. You may now log in!'}) req.flash('message', { error: false, text: 'Your password has been changed successfully. You may now log in!' })
res.redirect('/login') res.redirect('/login')
} catch (e) { } catch (e) {
return formError(req, res, e.message) return formError(req, res, e.message)
@ -514,37 +514,37 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
} }
// Ban check // Ban check
let banStatus = await API.User.getBanStatus(req.realIP, true) const banStatus = await API.User.getBanStatus(req.realIP, true)
if (banStatus.length) { if (banStatus.length) {
return res.render('user/banned', {bans: banStatus, ipban: true}) return res.render('user/banned', { bans: banStatus, ipban: true })
} }
// 1st Check: Username Characters and length // 1st Check: Username Characters and length
let username = req.body.username const username = req.body.username
if (!username || !username.match(/^([\w-_]{3,26})$/i)) { if (!username || !username.match(/^([\w-_]{3,26})$/i)) {
return formError(req, res, 'Invalid username! Must be between 3-26 characters and composed of alphanumeric characters!') return formError(req, res, 'Invalid username! Must be between 3-26 characters and composed of alphanumeric characters!')
} }
// 2nd Check: Display Name // 2nd Check: Display Name
let displayName = req.body.display_name const 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!')
} }
// 3rd Check: Email Address // 3rd Check: Email Address
let email = req.body.email const email = req.body.email
if (!email || !API.User.Register.validateEmail(email)) { if (!email || !API.User.Register.validateEmail(email)) {
return formError(req, res, 'Invalid email address!') return formError(req, res, 'Invalid email address!')
} }
// 4th Check: Password length // 4th Check: Password length
let password = req.body.password const password = req.body.password
if (!password || password.length < 8) { if (!password || password.length < 8) {
return formError(req, res, 'Invalid password! Please use at least 8 characters!') return formError(req, res, 'Invalid password! Please use at least 8 characters!')
} }
// 5th Check: Password match // 5th Check: Password match
let passwordAgain = req.body.password_repeat const passwordAgain = req.body.password_repeat
if (!passwordAgain || password !== passwordAgain) { if (!passwordAgain || password !== passwordAgain) {
return formError(req, res, 'Passwords do not match!') return formError(req, res, 'Passwords do not match!')
} }
@ -570,7 +570,7 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
} }
// Hash the password // Hash the password
let hash = await API.User.Register.hashPassword(password) const hash = await API.User.Register.hashPassword(password)
let newUser let newUser
// Attempt to create the user // Attempt to create the user
@ -592,7 +592,7 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
registerMessage += ' Please check your inbox for an activation link. Also, make sure to look into spam folders.' registerMessage += ' Please check your inbox for an activation link. Also, make sure to look into spam folders.'
} }
req.flash('message', {error: false, text: registerMessage}) req.flash('message', { error: false, text: registerMessage })
res.redirect('/login') res.redirect('/login')
})) }))
@ -626,22 +626,22 @@ router.post('/user/manage', csrfValidation, wrap(async (req, res, next) => {
req.session.user.display_name = displayName req.session.user.display_name = displayName
req.flash('message', {error: false, text: 'Settings changed successfully.'}) req.flash('message', { error: false, text: 'Settings changed successfully.' })
res.redirect('/user/manage') res.redirect('/user/manage')
})) }))
// Change user password // Change user password
router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async (req, res, next) => { router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let user = req.session.user const user = req.session.user
let socialStatus = await API.User.socialStatus(user) const socialStatus = await API.User.socialStatus(user)
if (!req.body.password_old && socialStatus.password) { if (!req.body.password_old && socialStatus.password) {
return formError(req, res, 'Please enter your current password.') return formError(req, res, 'Please enter your current password.')
} }
if (socialStatus.password) { if (socialStatus.password) {
let passwordMatch = await API.User.Login.password(user, req.body.password_old) const passwordMatch = await API.User.Login.password(user, req.body.password_old)
if (!passwordMatch) { if (!passwordMatch) {
return formError(req, res, 'The password you provided is incorrect.') return formError(req, res, 'The password you provided is incorrect.')
} }
@ -652,7 +652,7 @@ router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async
return formError(req, res, 'Invalid password! Please use at least 8 characters!') return formError(req, res, 'Invalid password! Please use at least 8 characters!')
} }
let passwordAgain = req.body.password_repeat const passwordAgain = req.body.password_repeat
if (!passwordAgain || password !== passwordAgain) { if (!passwordAgain || password !== passwordAgain) {
return formError(req, res, 'The passwords do not match!') return formError(req, res, 'The passwords do not match!')
} }
@ -676,7 +676,7 @@ router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async
}) })
} }
req.flash('message', {error: false, text: 'Password changed successfully.'}) req.flash('message', { error: false, text: 'Password changed successfully.' })
return res.redirect('/user/manage') return res.redirect('/user/manage')
})) }))
@ -684,10 +684,10 @@ router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async
router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (req, res, next) => { router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
if (!req.session.user) return next() if (!req.session.user) return next()
let user = await API.User.get(req.session.user) const user = await API.User.get(req.session.user)
let email = req.body.email const email = req.body.email
let newEmail = req.body.email_new const newEmail = req.body.email_new
let password = req.body.password const password = req.body.password
if (!newEmail || (!email && user.email !== '')) { if (!newEmail || (!email && user.email !== '')) {
return formError(req, res, 'Please fill in all of the fields.') return formError(req, res, 'Please fill in all of the fields.')
@ -702,18 +702,18 @@ router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (re
return formError(req, res, 'Enter a password.') return formError(req, res, 'Enter a password.')
} }
let passwordMatch = await API.User.Login.password(user, password) const passwordMatch = await API.User.Login.password(user, password)
if (!passwordMatch) { if (!passwordMatch) {
return formError(req, res, 'The password you provided is incorrect.') return formError(req, res, 'The password you provided is incorrect.')
} }
} }
let emailValid = API.User.Register.validateEmail(newEmail) const emailValid = API.User.Register.validateEmail(newEmail)
if (!emailValid) { if (!emailValid) {
return formError(req, res, 'Invalid email address.') return formError(req, res, 'Invalid email address.')
} }
let emailTaken = await API.User.get(newEmail) const emailTaken = await API.User.get(newEmail)
if (emailTaken) { if (emailTaken) {
return formError(req, res, 'This email is already taken.') return formError(req, res, 'This email is already taken.')
} }
@ -730,7 +730,7 @@ router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (re
req.session.user.email = newEmail req.session.user.email = newEmail
req.flash('message', {error: false, text: 'Email changed successfully.'}) req.flash('message', { error: false, text: 'Email changed successfully.' })
return res.redirect('/user/manage') return res.redirect('/user/manage')
})) }))
@ -743,15 +743,15 @@ router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (re
// Serve a document form the documents directory, cache it. // Serve a document form the documents directory, cache it.
const docsDir = path.join(__dirname, '../../documents') const docsDir = path.join(__dirname, '../../documents')
router.get('/docs/:name', wrap(async (req, res, next) => { router.get('/docs/:name', wrap(async (req, res, next) => {
let doc = path.join(docsDir, req.params.name + '.html') const doc = path.join(docsDir, req.params.name + '.html')
if (!await fs.exists(docsDir) || !await fs.exists(doc)) { if (!await fs.exists(docsDir) || !await fs.exists(doc)) {
return next() return next()
} }
fs.readFile(doc, {encoding: 'utf8'}, (err, contents) => { fs.readFile(doc, { encoding: 'utf8' }, (err, contents) => {
if (err) return next(err) if (err) return next(err)
res.header('Cache-Control', 'max-age=' + 7 * 24 * 60 * 60 * 1000) // 1 week res.header('Cache-Control', 'max-age=' + 7 * 24 * 60 * 60 * 1000) // 1 week
res.render('document', {doc: contents}) res.render('document', { doc: contents })
}) })
})) }))
@ -788,14 +788,14 @@ router.post('/news/compose', newsPrivilege, csrfValidation, wrap(async (req, res
// Serve news // Serve news
router.get('/news/:id?-*', wrap(async (req, res) => { router.get('/news/:id?-*', wrap(async (req, res) => {
let id = parseInt(req.params.id) const id = parseInt(req.params.id)
if (isNaN(id)) { if (isNaN(id)) {
return res.status(404).render('article', {article: null}) return res.status(404).render('article', { article: null })
} }
let article = await News.article(id) const article = await News.article(id)
if (!article.id) { if (!article.id) {
return res.status(404).render('article', {article: null}) return res.status(404).render('article', { article: null })
} }
let editing = false let editing = false
@ -803,7 +803,7 @@ router.get('/news/:id?-*', wrap(async (req, res) => {
editing = true editing = true
} }
res.render('news/article', {article: article, editing: editing}) res.render('news/article', { article: article, editing: editing })
})) }))
router.get('/news/', wrap(async (req, res) => { router.get('/news/', wrap(async (req, res) => {
@ -812,20 +812,20 @@ router.get('/news/', wrap(async (req, res) => {
page = 1 page = 1
} }
let news = await News.listNews(page) const news = await News.listNews(page)
res.render('news/news', {news: news}) res.render('news/news', { news: news })
})) }))
router.get('/news/atom.xml', wrap(async (req, res) => { router.get('/news/atom.xml', wrap(async (req, res) => {
let feed = await News.generateFeed() const feed = await News.generateFeed()
res.set('Content-Type', 'application/atom+xml') res.set('Content-Type', 'application/atom+xml')
res.send(feed.atom1()) res.send(feed.atom1())
})) }))
router.get('/news/feed.json', wrap(async (req, res) => { router.get('/news/feed.json', wrap(async (req, res) => {
let feed = await News.generateFeed() const feed = await News.generateFeed()
res.set('Content-Type', 'application/json') res.set('Content-Type', 'application/json')
res.send(feed.json1()) res.send(feed.json1())

View File

@ -6,12 +6,12 @@ import RateLimit from 'express-rate-limit'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
import config from '../../scripts/load-config.js' import config from '../../scripts/load-config.js'
let router = express.Router() const router = express.Router()
let oauth = new OAuth2() const oauth = new OAuth2()
router.use(oauth.express()) router.use(oauth.express())
let oauthLimiter = new RateLimit({ const oauthLimiter = new RateLimit({
windowMs: 5 * 60 * 1000, // 5 minutes windowMs: 5 * 60 * 1000, // 5 minutes
max: 10, max: 10,
delayMs: 0 delayMs: 0
@ -32,8 +32,8 @@ 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 const accessToken = req.oauth2.accessToken
let user = await UAPI.User.get(accessToken.user_id) const user = await UAPI.User.get(accessToken.user_id)
if (!user) { if (!user) {
return res.status(404).jsonp({ return res.status(404).jsonp({
@ -41,7 +41,7 @@ router.get('/user', oauth.bearer, wrap(async (req, res) => {
}) })
} }
let udata = { const udata = {
id: user.id, id: user.id,
uuid: user.uuid, uuid: user.uuid,
username: user.username, username: user.username,

View File

@ -12,8 +12,8 @@ import flash from '../scripts/flash'
import config from '../scripts/load-config' import config from '../scripts/load-config'
import email from './api/emailer' import email from './api/emailer'
let app = express() const app = express()
let SessionStore = connectSession(session) const SessionStore = connectSession(session)
app.enable('trust proxy', 1) app.enable('trust proxy', 1)
@ -68,7 +68,7 @@ app.use((req, res, next) => {
app.use(favicon(path.join(__dirname, '..', 'static', 'image', 'icynet.ico'))) app.use(favicon(path.join(__dirname, '..', 'static', 'image', 'icynet.ico')))
module.exports = (args) => { module.exports = (args) => {
app.set('view options', {layout: false}) app.set('view options', { layout: false })
app.set('view engine', 'pug') app.set('view engine', 'pug')
app.set('views', path.join(__dirname, '../views')) app.set('views', path.join(__dirname, '../views'))
@ -80,7 +80,7 @@ module.exports = (args) => {
app.use(morgan('dev')) app.use(morgan('dev'))
} }
let staticAge = args.dev ? 1000 : 7 * 24 * 60 * 60 * 1000 // 1 week of cache in production const staticAge = args.dev ? 1000 : 7 * 24 * 60 * 60 * 1000 // 1 week of cache in production
// Static content directories, cache these requests. // Static content directories, cache these requests.
// It is also a good idea to use nginx to serve these directories in order to save on computing power // It is also a good idea to use nginx to serve these directories in order to save on computing power

View File

@ -4,7 +4,7 @@ const path = require('path')
const util = require('util') const util = require('util')
require('@babel/register')({ require('@babel/register')({
plugins: [ '@babel/plugin-transform-modules-commonjs' ] plugins: ['@babel/plugin-transform-modules-commonjs']
}) })
process.once('message', (args) => { process.once('message', (args) => {

View File

@ -34,5 +34,5 @@ block body
input#custominfo(type="hidden", name="custom", value="") input#custominfo(type="hidden", name="custom", value="")
.buttoncont .buttoncont
a.btn.btn-primary.text-light(name="submit", onclick="$(this).closest('form').submit()") a.btn.btn-primary.text-light(name="submit", onclick="$(this).closest('form').submit()")
i.fa.fa-fw.fa-paypal i.fab.fa-fw.fa-paypal
|&nbsp;Donate |&nbsp;Donate

View File

@ -18,7 +18,7 @@ block body
input(type="hidden", name="csrf", value=csrf) input(type="hidden", name="csrf", value=csrf)
.form-group .form-group
label(for="username") Username or Email Address label(for="username") Username or Email Address
input.form-control(type="text", name="username", id="username") input.form-control(type="text", name="username", id="username", autofocus)
.form-group .form-group
label(for="password") Password label(for="password") Password
input.form-control(type="password", name="password", id="password") input.form-control(type="password", name="password", id="password")

View File

@ -17,5 +17,5 @@ block body
input(type="hidden", name="csrf", value=csrf) input(type="hidden", name="csrf", value=csrf)
.form-group .form-group
label(for="password") Password label(for="password") Password
input.form-control#password(type="password", name="password") input.form-control#password(type="password", name="password", autofocus)
button.btn.btn-primary(type="submit") Continue button.btn.btn-primary(type="submit") Continue

View File

@ -16,7 +16,7 @@ block body
input(type="hidden", name="csrf", value=csrf) input(type="hidden", name="csrf", value=csrf)
.form-group .form-group
label(for="username") Username label(for="username") Username
input.form-control#username(type="text", name="username", value=formkeep.username, aria-labelledby="usernameText") input.form-control#username(type="text", name="username", value=formkeep.username, aria-labelledby="usernameText", autofocus)
small#usernameText English characters, numbers and -_ only. small#usernameText English characters, numbers and -_ only.
.form-group .form-group
label(for="display_name") Display Name label(for="display_name") Display Name

View File

@ -18,5 +18,5 @@ block body
input(type="hidden", name="csrf", value=csrf) input(type="hidden", name="csrf", value=csrf)
.form-group .form-group
label(for="email") Email Address label(for="email") Email Address
input.form-control#email(type="email", name="email") input.form-control#email(type="email", name="email", autofocus)
button.btn.btn-primary(type="submit") Continue button.btn.btn-primary(type="submit") Continue

View File

@ -17,5 +17,5 @@ block body
input(type="hidden", name="csrf", value=csrf) input(type="hidden", name="csrf", value=csrf)
.form-group .form-group
label(for="code") Code label(for="code") Code
input.form-control#code(type="text", name="code", autocomplete="off") input.form-control#code(type="text", name="code", autocomplete="off", autofocus)
button.btn.btn-primary(type="submit") Log in button.btn.btn-primary(type="submit") Log in

View File

@ -25,7 +25,7 @@ block body
form#totpForm(method="POST", action="") form#totpForm(method="POST", action="")
input(type="hidden", name="csrf", value=csrf) input(type="hidden", name="csrf", value=csrf)
.form-group .form-group
label(for="code") Enter the Code label(for="code",autofocus) Enter the Code
input.form-control#code(type="text", name="code", autocomplete="off") input.form-control#code(type="text", name="code", autocomplete="off")
button.btn.btn-primary(type="submit") Enable Now button.btn.btn-primary(type="submit") Enable Now
aside.col aside.col

View File

@ -12,7 +12,7 @@ module.exports = {
}, },
resolve: { resolve: {
alias: { alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1 vue$: 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
} }
}, },
module: { module: {

View File

@ -12,6 +12,6 @@ module.exports = merge(common, {
], ],
optimization: { optimization: {
minimize: true, minimize: true,
minimizer: [ new TerserPlugin() ] minimizer: [new TerserPlugin()]
} }
}) })