commit
5575e9e4f6
26
package-lock.json
generated
26
package-lock.json
generated
@ -2104,6 +2104,16 @@
|
|||||||
"version": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
|
"version": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
|
||||||
"integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0="
|
"integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0="
|
||||||
},
|
},
|
||||||
|
"fs-extra": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz",
|
||||||
|
"integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=",
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "4.1.11",
|
||||||
|
"jsonfile": "4.0.0",
|
||||||
|
"universalify": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"fs.realpath": {
|
"fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@ -2269,8 +2279,7 @@
|
|||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"graceful-readlink": {
|
"graceful-readlink": {
|
||||||
"version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
"version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||||
@ -2822,6 +2831,14 @@
|
|||||||
"version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
"version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
||||||
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
|
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
|
||||||
},
|
},
|
||||||
|
"jsonfile": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "4.1.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"jsonify": {
|
"jsonify": {
|
||||||
"version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
"version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
||||||
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
|
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
|
||||||
@ -5082,6 +5099,11 @@
|
|||||||
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
|
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"universalify": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc="
|
||||||
|
},
|
||||||
"unpipe": {
|
"unpipe": {
|
||||||
"version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
"express": "^4.15.3",
|
"express": "^4.15.3",
|
||||||
"express-rate-limit": "^2.9.0",
|
"express-rate-limit": "^2.9.0",
|
||||||
"express-session": "^1.15.3",
|
"express-session": "^1.15.3",
|
||||||
|
"fs-extra": "^4.0.2",
|
||||||
"gm": "^1.23.0",
|
"gm": "^1.23.0",
|
||||||
"knex": "^0.13.0",
|
"knex": "^0.13.0",
|
||||||
"multiparty": "^4.1.3",
|
"multiparty": "^4.1.3",
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import Promise from 'bluebird'
|
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
const access = Promise.promisify(fs.access)
|
|
||||||
|
|
||||||
async function exists (fpath) {
|
|
||||||
try {
|
|
||||||
await access(path.resolve(fpath))
|
|
||||||
} catch (e) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = exists
|
|
@ -1,9 +1,7 @@
|
|||||||
import config from './load-config'
|
import config from './load-config'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import util from 'util'
|
import util from 'util'
|
||||||
|
import fs from 'fs-extra'
|
||||||
import Promise from 'bluebird'
|
|
||||||
const fs = Promise.promisifyAll(require('fs'))
|
|
||||||
|
|
||||||
let lfs
|
let lfs
|
||||||
|
|
||||||
@ -21,16 +19,15 @@ 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, prfx, message) {
|
function stampAndWrite (fnc, color, prfx, message) {
|
||||||
let prefix = '[' + prfx + '] [' + dateFormat(new Date()) + '] '
|
let prefix = '[' + prfx + '] [' + dateFormat(new Date()) + '] '
|
||||||
message = prefix + message
|
message = color + prefix + message
|
||||||
|
message = message.replace(/\\u001b/g, '\x1b')
|
||||||
|
|
||||||
if (lfs) {
|
if (lfs) {
|
||||||
lfs.write(message + '\n')
|
lfs.write(message.replace(/(\u001b\[\d\d?m)/g, '') + '\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
message = message.replace(/\\u001b/g, '\x1b')
|
|
||||||
|
|
||||||
fnc.call(this, message)
|
fnc.call(this, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,26 +35,26 @@ function stampAndWrite (fnc, prfx, message) {
|
|||||||
const realConsoleLog = console.log
|
const realConsoleLog = console.log
|
||||||
console.log = function () {
|
console.log = function () {
|
||||||
let message = util.format.apply(null, arguments)
|
let 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)
|
let message = util.format.apply(null, arguments)
|
||||||
stampAndWrite.call(this, realConsoleWarn, '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)
|
let message = util.format.apply(null, arguments)
|
||||||
stampAndWrite.call(this, realConsoleError, ' err', message)
|
stampAndWrite.call(this, realConsoleError, '\x1b[31m', ' err', message)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initializeLogger () {
|
async function initializeLogger () {
|
||||||
let logPath = path.resolve(config.logger.file)
|
let logPath = path.resolve(config.logger.file)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.accessAsync(logPath, fs.W_OK)
|
await fs.access(logPath, fs.W_OK)
|
||||||
lfs = fs.createWriteStream(logPath, {flags: 'a'})
|
lfs = fs.createWriteStream(logPath, {flags: 'a'})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
lfs = null
|
lfs = null
|
||||||
|
@ -84,7 +84,7 @@ const API = {
|
|||||||
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' }
|
return { error: 'No users found in database' }
|
||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
count = count[0].ids
|
||||||
@ -108,7 +108,7 @@ const API = {
|
|||||||
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 found' }
|
return { error: 'No clients' }
|
||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
count = count[0].ids
|
||||||
@ -141,7 +141,7 @@ const API = {
|
|||||||
]
|
]
|
||||||
|
|
||||||
data = dataFilter(data, fields, ['scope', 'verified'])
|
data = dataFilter(data, fields, ['scope', 'verified'])
|
||||||
if (!data) return { error: 'Missing fields' }
|
if (!data) throw new Error('Missing fields')
|
||||||
|
|
||||||
data.verified = (data.verified != null ? 1 : 0)
|
data.verified = (data.verified != null ? 1 : 0)
|
||||||
|
|
||||||
@ -149,20 +149,20 @@ const API = {
|
|||||||
await Models.OAuth2Client.query().patchAndFetchById(id, data)
|
await Models.OAuth2Client.query().patchAndFetchById(id, data)
|
||||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { error: 'No such client' }
|
throw new Error('No such client')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
// 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)) return { error: 'Invalid client ID' }
|
if (isNaN(id)) throw new Error('Invalid client ID')
|
||||||
let secret = Users.Hash(16)
|
let 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) {
|
||||||
return { error: 'No such client' }
|
throw new Error('No such client')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
@ -174,7 +174,7 @@ const API = {
|
|||||||
]
|
]
|
||||||
|
|
||||||
data = dataFilter(data, fields, ['scope'])
|
data = dataFilter(data, fields, ['scope'])
|
||||||
if (!data) return { error: 'Missing fields' }
|
if (!data) throw new Error('Missing fields')
|
||||||
|
|
||||||
let obj = Object.assign({
|
let obj = Object.assign({
|
||||||
secret: Users.Hash(16),
|
secret: Users.Hash(16),
|
||||||
@ -187,7 +187,7 @@ const API = {
|
|||||||
},
|
},
|
||||||
// Remove a client and all associated data
|
// Remove a client and all associated data
|
||||||
removeClient: async function (id) {
|
removeClient: async function (id) {
|
||||||
if (isNaN(id)) return {error: 'Invalid ID number'}
|
if (isNaN(id)) throw new Error('Invalid ID number')
|
||||||
await Models.OAuth2Client.query().delete().where('id', id)
|
await Models.OAuth2Client.query().delete().where('id', id)
|
||||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||||
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
|
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
|
||||||
@ -198,7 +198,7 @@ const API = {
|
|||||||
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
|
||||||
@ -225,12 +225,12 @@ const API = {
|
|||||||
addBan: async function (data, adminId) {
|
addBan: async function (data, adminId) {
|
||||||
let user = await Users.User.get(parseInt(data.user_id))
|
let user = await Users.User.get(parseInt(data.user_id))
|
||||||
|
|
||||||
if (!user) return {error: 'No such user.'}
|
if (!user) throw new Error('No such user.')
|
||||||
if (user.id === adminId) return {error: 'Cannot ban yourself!'}
|
if (user.id === adminId) throw new Error('Cannot ban yourself!')
|
||||||
|
|
||||||
let admin = await Users.User.get(adminId)
|
let admin = await Users.User.get(adminId)
|
||||||
|
|
||||||
if (user.nw_privilege > admin.nw_privilege) return {error: 'Cannot ban user.'}
|
if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.')
|
||||||
|
|
||||||
let banAdd = {
|
let banAdd = {
|
||||||
reason: data.reason || 'Unspecified ban',
|
reason: data.reason || 'Unspecified ban',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {EmailTemplate} from 'email-templates'
|
import {EmailTemplate} from 'email-templates'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import nodemailer from 'nodemailer'
|
import nodemailer from 'nodemailer'
|
||||||
|
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
|
|
||||||
const templateDir = path.join(__dirname, '../../', 'templates')
|
const templateDir = path.join(__dirname, '../../', 'templates')
|
||||||
|
@ -1,20 +1,27 @@
|
|||||||
|
import qs from 'querystring'
|
||||||
|
import oauth from 'oauth-libre'
|
||||||
|
import uuidV1 from 'uuid/v1'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import http from '../../scripts/http'
|
import http from '../../scripts/http'
|
||||||
import models from './models'
|
import models from './models'
|
||||||
|
import Image from './image'
|
||||||
import UAPI from './index'
|
import UAPI from './index'
|
||||||
import qs from 'querystring'
|
|
||||||
import oauth from 'oauth-libre'
|
|
||||||
import path from 'path'
|
|
||||||
import url from 'url'
|
|
||||||
import uuidV1 from 'uuid/v1'
|
|
||||||
|
|
||||||
const imgdir = path.join(__dirname, '../../', 'usercontent', 'images')
|
const userFields = ['username', 'email', 'avatar_file', 'display_name', 'ip_address']
|
||||||
|
|
||||||
let twitterApp
|
let twitterApp
|
||||||
let discordApp
|
let discordApp
|
||||||
|
|
||||||
const API = {
|
const API = {
|
||||||
Common: {
|
Common: {
|
||||||
|
// Generate a hash based on the current session
|
||||||
|
stateGenerator: (req) => {
|
||||||
|
let sessionCrypto = req.session.id + ':' + config.server.session_secret
|
||||||
|
return crypto.createHash('sha256').update(sessionCrypto).digest('hex')
|
||||||
|
},
|
||||||
|
// Find an user with an external ID
|
||||||
getExternal: async (service, identifier) => {
|
getExternal: async (service, identifier) => {
|
||||||
let extr = await models.External.query().where('service', service).andWhere('identifier', identifier)
|
let extr = await models.External.query().where('service', service).andWhere('identifier', identifier)
|
||||||
if (!extr || !extr.length) return null
|
if (!extr || !extr.length) return null
|
||||||
@ -30,10 +37,12 @@ const API = {
|
|||||||
|
|
||||||
return extr
|
return extr
|
||||||
},
|
},
|
||||||
|
// Get user ban status
|
||||||
getBan: async (user, ipAddress) => {
|
getBan: async (user, ipAddress) => {
|
||||||
let banList = await UAPI.User.getBanStatus(ipAddress || user.id, ipAddress != null)
|
let banList = await UAPI.User.getBanStatus(ipAddress || user.id, ipAddress != null)
|
||||||
return banList
|
return banList
|
||||||
},
|
},
|
||||||
|
// Create a new `external` instance for a user
|
||||||
new: async (service, identifier, user) => {
|
new: async (service, identifier, user) => {
|
||||||
let data = {
|
let data = {
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
@ -45,6 +54,7 @@ const API = {
|
|||||||
await await models.External.query().insert(data)
|
await await models.External.query().insert(data)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
// Create a new user
|
||||||
newUser: async (service, identifier, data) => {
|
newUser: async (service, identifier, data) => {
|
||||||
let udataLimited = Object.assign({
|
let udataLimited = Object.assign({
|
||||||
activated: 1,
|
activated: 1,
|
||||||
@ -53,27 +63,38 @@ const API = {
|
|||||||
uuid: uuidV1()
|
uuid: uuidV1()
|
||||||
}, data)
|
}, data)
|
||||||
|
|
||||||
|
// Some data cleanups
|
||||||
|
|
||||||
|
// Limit display name length
|
||||||
|
udataLimited.display_name = udataLimited.display_name.substring(0, 32)
|
||||||
|
|
||||||
|
// Remove illegal characters from the username
|
||||||
|
udataLimited.username = udataLimited.username.replace(/\W+/gi, '')
|
||||||
|
|
||||||
|
// Limit user name length
|
||||||
|
udataLimited.username = udataLimited.username.substring(0, 26)
|
||||||
|
|
||||||
// Check if the username is already taken
|
// Check if the username is already taken
|
||||||
if (await UAPI.User.get(udataLimited.username) != null) {
|
if (await UAPI.User.get(udataLimited.username) != null || udataLimited.username.length < 4) {
|
||||||
udataLimited.username = udataLimited.username + UAPI.Hash(4)
|
udataLimited.username = udataLimited.username + UAPI.Hash(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the email given to us is already registered, if so,
|
// Check if the email given to us is already registered, if so,
|
||||||
// associate an external node with the user bearing the email
|
// tell them to log in first.
|
||||||
if (udataLimited.email && udataLimited.email !== '') {
|
if (udataLimited.email && udataLimited.email !== '') {
|
||||||
let getByEmail = await UAPI.User.get(udataLimited.email)
|
let getByEmail = await UAPI.User.get(udataLimited.email)
|
||||||
if (getByEmail) {
|
if (getByEmail) {
|
||||||
await API.Common.new(service, identifier, 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.')
|
||||||
return {error: null, user: getByEmail}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new user based on the information we got from Facebook
|
// Create a new user based on the information we got from an external service
|
||||||
let newUser = await models.User.query().insert(udataLimited)
|
let 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
|
||||||
},
|
},
|
||||||
|
// 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)
|
let userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
||||||
@ -89,30 +110,85 @@ const API = {
|
|||||||
|
|
||||||
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
|
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
|
||||||
},
|
},
|
||||||
saveAvatar: async (avatarUrl) => {
|
// Common code for all auth callbacks
|
||||||
if (!avatarUrl) return null
|
callback: async (identifier, uid, user, ipAddress, remoteData, avatarFunc) => {
|
||||||
let imageName = 'download-' + UAPI.Hash(12)
|
let exists = await API.Common.getExternal(identifier, uid)
|
||||||
let uridata = url.parse(avatarUrl)
|
|
||||||
let pathdata = path.parse(uridata.path)
|
|
||||||
|
|
||||||
imageName += pathdata.ext || '.png'
|
if (user) {
|
||||||
|
// Get bans for user
|
||||||
|
let bans = await API.Common.getBan(user)
|
||||||
|
if (bans.length) return { banned: bans, ip: false }
|
||||||
|
|
||||||
try {
|
if (exists) return {error: null, user: user}
|
||||||
await http.Download(avatarUrl, path.join(imgdir, imageName))
|
|
||||||
} catch (e) {
|
await API.Common.new(identifier, uid, user)
|
||||||
return null
|
return {error: null, user: user}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {fileName: imageName}
|
// Callback succeeded with user id and the external table exists, we log in the user
|
||||||
|
if (exists) {
|
||||||
|
// Get bans for user
|
||||||
|
let bans = await API.Common.getBan(exists.user)
|
||||||
|
|
||||||
|
if (bans.length) return { banned: bans, ip: false }
|
||||||
|
return {error: null, user: exists.user}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get bans for IP address
|
||||||
|
let bans = await API.Common.getBan(null, ipAddress)
|
||||||
|
if (bans.length) return { banned: bans, ip: true }
|
||||||
|
|
||||||
|
// Run the function for avatar fetching
|
||||||
|
let avatar = null
|
||||||
|
if (avatarFunc) {
|
||||||
|
avatar = await avatarFunc(remoteData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the data
|
||||||
|
let newUData = Object.assign({
|
||||||
|
email: remoteData.email || '',
|
||||||
|
avatar_file: avatar,
|
||||||
|
ip_address: ipAddress
|
||||||
|
}, remoteData)
|
||||||
|
|
||||||
|
// Remove unnecessary fields
|
||||||
|
for (let i in newUData) {
|
||||||
|
if (userFields.indexOf(i) === -1) {
|
||||||
|
delete newUData[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newUser
|
||||||
|
try {
|
||||||
|
newUser = await API.Common.newUser(identifier, uid, newUData)
|
||||||
|
} catch (e) {
|
||||||
|
return {error: e.message}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {error: null, user: newUser}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Facebook: {
|
Facebook: {
|
||||||
callback: async (user, data) => {
|
getAvatar: async (rawData) => {
|
||||||
if (!data.authResponse || data.status !== 'connected') {
|
let profilepic = null
|
||||||
|
|
||||||
|
if (rawData.picture) {
|
||||||
|
if (rawData.picture.is_silhouette === false && rawData.picture.url) {
|
||||||
|
let imgdata = await Image.downloadImage(rawData.picture.url)
|
||||||
|
if (imgdata && imgdata.fileName) {
|
||||||
|
profilepic = imgdata.fileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profilepic
|
||||||
|
},
|
||||||
|
callback: async (user, authResponse, ipAddress) => {
|
||||||
|
if (!authResponse) {
|
||||||
return {error: 'No Authorization'}
|
return {error: 'No Authorization'}
|
||||||
}
|
}
|
||||||
|
|
||||||
let uid = data.authResponse.userID
|
let uid = authResponse.userID
|
||||||
if (!uid) {
|
if (!uid) {
|
||||||
return {error: 'No Authorization'}
|
return {error: 'No Authorization'}
|
||||||
}
|
}
|
||||||
@ -120,7 +196,7 @@ const API = {
|
|||||||
// 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 = {
|
let intel = {
|
||||||
access_token: data.authResponse.accessToken,
|
access_token: authResponse.accessToken,
|
||||||
fields: 'name,email,picture,short_name'
|
fields: 'name,email,picture,short_name'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,57 +211,28 @@ const API = {
|
|||||||
return {error: fbdata.error.message}
|
return {error: fbdata.error.message}
|
||||||
}
|
}
|
||||||
|
|
||||||
let exists = await API.Common.getExternal('fb', uid)
|
let cleanedData = Object.assign(fbdata, {
|
||||||
|
username: fbdata.short_name || 'FB' + UAPI.Hash(4),
|
||||||
|
display_name: fbdata.name,
|
||||||
|
email: fbdata.email || ''
|
||||||
|
})
|
||||||
|
|
||||||
if (user) {
|
return API.Common.callback('facebook', uid, user, ipAddress, cleanedData, API.Facebook.getAvatar)
|
||||||
// Get bans for user
|
|
||||||
let bans = await API.Common.getBan(user)
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
|
||||||
|
|
||||||
if (exists) return {error: null, user: user}
|
|
||||||
|
|
||||||
await API.Common.new('fb', uid, user)
|
|
||||||
return {error: null, user: user}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
// Callback succeeded with user id and the external table exists, we log in the user
|
Twitter: {
|
||||||
if (exists) {
|
getAvatar: async function (rawData) {
|
||||||
// Get bans for user
|
|
||||||
let bans = await API.Common.getBan(exists.user)
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
|
||||||
return {error: null, user: exists.user}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bans for IP address
|
|
||||||
let bans = await API.Common.getBan(null, data.ip_address)
|
|
||||||
if (bans.length) return { banned: bans, ip: true }
|
|
||||||
|
|
||||||
// Determine profile picture
|
|
||||||
let profilepic = null
|
let profilepic = null
|
||||||
if (fbdata.picture) {
|
|
||||||
if (fbdata.picture.is_silhouette === false && fbdata.picture.url) {
|
if (rawData.profile_image_url_https) {
|
||||||
let imgdata = await API.Common.saveAvatar(fbdata.picture.url)
|
let imgdata = await Image.downloadImage(rawData.profile_image_url_https)
|
||||||
if (imgdata && imgdata.fileName) {
|
if (imgdata && imgdata.fileName) {
|
||||||
profilepic = imgdata.fileName
|
profilepic = imgdata.fileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let newUData = {
|
return profilepic
|
||||||
username: fbdata.short_name || 'FB' + UAPI.Hash(4),
|
|
||||||
display_name: fbdata.name,
|
|
||||||
email: fbdata.email || '',
|
|
||||||
avatar_file: profilepic,
|
|
||||||
ip_address: data.ip_address
|
|
||||||
}
|
|
||||||
|
|
||||||
let newUser = await API.Common.newUser('fb', uid, newUData)
|
|
||||||
if (!newUser) return {error: 'Failed to create user.'}
|
|
||||||
|
|
||||||
return {error: null, user: newUser}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Twitter: {
|
|
||||||
oauthApp: function () {
|
oauthApp: function () {
|
||||||
if (!twitterApp) {
|
if (!twitterApp) {
|
||||||
let redirectUri = config.server.domain + '/api/external/twitter/callback'
|
let redirectUri = config.server.domain + '/api/external/twitter/callback'
|
||||||
@ -243,56 +290,28 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let uid = twdata.id_str
|
let uid = twdata.id_str
|
||||||
let exists = await API.Common.getExternal('twitter', uid)
|
|
||||||
|
|
||||||
if (user) {
|
let cleanedData = Object.assign(twdata, {
|
||||||
// Get bans for user
|
username: twdata.screen_name,
|
||||||
let bans = await API.Common.getBan(user)
|
display_name: twdata.name,
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
email: twdata.email || ''
|
||||||
|
})
|
||||||
|
|
||||||
if (exists) return {error: null, user: user}
|
return API.Common.callback('twitter', uid, user, ipAddress, cleanedData, API.Twitter.getAvatar)
|
||||||
|
|
||||||
await API.Common.new('twitter', uid, user)
|
|
||||||
return {error: null, user: user}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
// Callback succeeded with user id and the external table exists, we log in the user
|
Google: {
|
||||||
if (exists) {
|
getAvatar: async (rawData) => {
|
||||||
// Get bans for user
|
|
||||||
let bans = await API.Common.getBan(exists.user)
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
|
||||||
return {error: null, user: exists.user}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bans for IP
|
|
||||||
let bans = await API.Common.getBan(null, ipAddress)
|
|
||||||
if (bans.length) return { banned: bans, ip: true }
|
|
||||||
|
|
||||||
// Determine profile picture
|
|
||||||
let profilepic = null
|
let profilepic = null
|
||||||
if (twdata.profile_image_url_https) {
|
if (rawData.image) {
|
||||||
let imgdata = await API.Common.saveAvatar(twdata.profile_image_url_https)
|
let imgdata = await Image.downloadImage(rawData.image)
|
||||||
if (imgdata && imgdata.fileName) {
|
if (imgdata && imgdata.fileName) {
|
||||||
profilepic = imgdata.fileName
|
profilepic = imgdata.fileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new user
|
return profilepic
|
||||||
let newUData = {
|
|
||||||
username: twdata.screen_name,
|
|
||||||
display_name: twdata.name,
|
|
||||||
email: twdata.email || '',
|
|
||||||
avatar_file: profilepic,
|
|
||||||
ip_address: ipAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
let newUser = await API.Common.newUser('twitter', uid, newUData)
|
|
||||||
if (!newUser) return {error: 'Failed to create user.'}
|
|
||||||
|
|
||||||
return {error: null, user: newUser}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Google: {
|
|
||||||
callback: async (user, data, ipAddress) => {
|
callback: async (user, data, ipAddress) => {
|
||||||
let uid
|
let uid
|
||||||
|
|
||||||
@ -312,56 +331,32 @@ const API = {
|
|||||||
return {error: e.message}
|
return {error: e.message}
|
||||||
}
|
}
|
||||||
|
|
||||||
let exists = await API.Common.getExternal('google', uid)
|
let cleanedData = Object.assign(data, {
|
||||||
|
username: data.name,
|
||||||
if (user) {
|
|
||||||
// Get bans for user
|
|
||||||
let bans = await API.Common.getBan(user)
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
|
||||||
|
|
||||||
if (exists) return {error: null, user: user}
|
|
||||||
|
|
||||||
await API.Common.new('google', uid, user)
|
|
||||||
return {error: null, user: user}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback succeeded with user id and the external table exists, we log in the user
|
|
||||||
if (exists) {
|
|
||||||
// Get bans for user
|
|
||||||
let bans = await API.Common.getBan(exists.user)
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
|
||||||
return {error: null, user: exists.user}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bans for IP
|
|
||||||
let bans = await API.Common.getBan(null, ipAddress)
|
|
||||||
if (bans.length) return { banned: bans, ip: true }
|
|
||||||
|
|
||||||
// Determine profile picture
|
|
||||||
let profilepic = null
|
|
||||||
if (data.image) {
|
|
||||||
let imgdata = await API.Common.saveAvatar(data.image)
|
|
||||||
if (imgdata && imgdata.fileName) {
|
|
||||||
profilepic = imgdata.fileName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new user
|
|
||||||
let newUData = {
|
|
||||||
username: data.name.replace(/\W+/gi, ''),
|
|
||||||
display_name: data.name,
|
display_name: data.name,
|
||||||
email: data.email || '',
|
email: data.email || ''
|
||||||
avatar_file: profilepic,
|
})
|
||||||
ip_address: ipAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
let newUser = await API.Common.newUser('google', uid, newUData)
|
return API.Common.callback('google', uid, user, ipAddress, cleanedData, API.Google.getAvatar)
|
||||||
if (!newUser) return {error: 'Failed to create user.'}
|
|
||||||
|
|
||||||
return {error: null, user: newUser}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Discord: {
|
Discord: {
|
||||||
|
getAvatar: async (rawData) => {
|
||||||
|
let profilepic = null
|
||||||
|
let aviSnowflake = rawData.avatar
|
||||||
|
if (aviSnowflake) {
|
||||||
|
try {
|
||||||
|
let avpt = await Image.downloadImage('https://cdn.discordapp.com/avatars/' + rawData.id + '/' + aviSnowflake + '.png')
|
||||||
|
if (avpt && avpt.fileName) {
|
||||||
|
profilepic = avpt.fileName
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
profilepic = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profilepic
|
||||||
|
},
|
||||||
oauth2App: function () {
|
oauth2App: function () {
|
||||||
if (discordApp) return
|
if (discordApp) return
|
||||||
discordApp = new oauth.PromiseOAuth2(
|
discordApp = new oauth.PromiseOAuth2(
|
||||||
@ -374,9 +369,9 @@ const API = {
|
|||||||
|
|
||||||
discordApp.useAuthorizationHeaderforGET(true)
|
discordApp.useAuthorizationHeaderforGET(true)
|
||||||
},
|
},
|
||||||
getAuthorizeURL: function () {
|
getAuthorizeURL: function (req) {
|
||||||
if (!discordApp) API.Discord.oauth2App()
|
if (!discordApp) API.Discord.oauth2App()
|
||||||
let state = UAPI.Hash(6)
|
let state = API.Common.stateGenerator(req)
|
||||||
let redirectUri = config.server.domain + '/api/external/discord/callback'
|
let redirectUri = config.server.domain + '/api/external/discord/callback'
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
@ -421,58 +416,14 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let uid = ddata.id
|
let uid = ddata.id
|
||||||
let exists = await API.Common.getExternal('discord', uid)
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
// Get bans for user
|
|
||||||
let bans = await API.Common.getBan(user)
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
|
||||||
|
|
||||||
if (exists) return {error: null, user: user}
|
|
||||||
|
|
||||||
await API.Common.new('discord', uid, user)
|
|
||||||
return {error: null, user: user}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback succeeded with user id and the external table exists, we log in the user
|
|
||||||
if (exists) {
|
|
||||||
// Get bans for user
|
|
||||||
let bans = await API.Common.getBan(exists.user)
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
|
||||||
return {error: null, user: exists.user}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bans for IP
|
|
||||||
let bans = await API.Common.getBan(null, ipAddress)
|
|
||||||
if (bans.length) return { banned: bans, ip: true }
|
|
||||||
|
|
||||||
// Download profile picture
|
|
||||||
let profilepic = null
|
|
||||||
let aviSnowflake = ddata.avatar
|
|
||||||
if (aviSnowflake) {
|
|
||||||
try {
|
|
||||||
let avpt = await API.Common.saveAvatar('https://cdn.discordapp.com/avatars/' + ddata.id + '/' + aviSnowflake + '.png')
|
|
||||||
if (avpt && avpt.fileName) {
|
|
||||||
profilepic = avpt.fileName
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
profilepic = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new user
|
// Create a new user
|
||||||
let newUData = {
|
let cleanedData = Object.assign(ddata, {
|
||||||
username: ddata.username.replace(/\W+/gi, '_'),
|
|
||||||
display_name: ddata.username,
|
display_name: ddata.username,
|
||||||
email: ddata.email || '',
|
email: ddata.email || ''
|
||||||
avatar_file: profilepic,
|
})
|
||||||
ip_address: ipAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
let newUser = await API.Common.newUser('discord', uid, newUData)
|
return API.Common.callback('discord', uid, user, ipAddress, cleanedData, API.Discord.getAvatar)
|
||||||
if (!newUser) return {error: 'Failed to create user.'}
|
|
||||||
|
|
||||||
return {error: null, user: newUser}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import gm from 'gm'
|
import gm from 'gm'
|
||||||
|
import url from 'url'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import crypto from 'crypto'
|
import uuid from 'uuid/v4'
|
||||||
import Promise from 'bluebird'
|
|
||||||
|
|
||||||
const fs = Promise.promisifyAll(require('fs'))
|
import http from '../../scripts/http'
|
||||||
|
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
|
||||||
const uploads = path.join(__dirname, '../../', 'usercontent')
|
const uploads = path.join(__dirname, '../../', 'usercontent')
|
||||||
const images = path.join(uploads, 'images')
|
const images = path.join(uploads, 'images')
|
||||||
@ -42,8 +44,8 @@ function saneFields (fields) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bailOut (file, error) {
|
async function bailOut (file, error) {
|
||||||
await fs.unlinkAsync(file)
|
await fs.unlink(file)
|
||||||
return { error: error }
|
throw new Error(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function imageBase64 (baseObj) {
|
async function imageBase64 (baseObj) {
|
||||||
@ -53,7 +55,7 @@ async function imageBase64 (baseObj) {
|
|||||||
if (!imgData) return null
|
if (!imgData) return null
|
||||||
if (!imageTypes[imgData.type]) return null
|
if (!imageTypes[imgData.type]) return null
|
||||||
|
|
||||||
let imageName = 'base64-' + crypto.randomBytes(12).toString('hex')
|
let imageName = 'base64-' + uuid()
|
||||||
let ext = imageTypes[imgData.type] || '.png'
|
let ext = imageTypes[imgData.type] || '.png'
|
||||||
|
|
||||||
imageName += ext
|
imageName += ext
|
||||||
@ -61,7 +63,7 @@ async function imageBase64 (baseObj) {
|
|||||||
let fpath = path.join(images, imageName)
|
let fpath = path.join(images, imageName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.writeFileAsync(fpath, imgData.data)
|
await fs.writeFile(fpath, imgData.data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return null
|
return null
|
||||||
@ -70,8 +72,27 @@ async function imageBase64 (baseObj) {
|
|||||||
return {file: fpath}
|
return {file: fpath}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function downloadImage (imgUrl, designation) {
|
||||||
|
if (!imgUrl) return null
|
||||||
|
if (!designation) designation = 'download'
|
||||||
|
|
||||||
|
let imageName = designation + '-' + uuid()
|
||||||
|
let uridata = url.parse(imgUrl)
|
||||||
|
let pathdata = path.parse(uridata.path)
|
||||||
|
|
||||||
|
imageName += pathdata.ext || '.png'
|
||||||
|
|
||||||
|
try {
|
||||||
|
await http.Download(imgUrl, path.join(images, imageName))
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageName
|
||||||
|
}
|
||||||
|
|
||||||
async function uploadImage (identifier, fields, files) {
|
async function uploadImage (identifier, fields, files) {
|
||||||
if (!files.image) return {error: 'No image file'}
|
if (!files.image) throw new Error('No image file')
|
||||||
|
|
||||||
let file = files.image[0]
|
let file = files.image[0]
|
||||||
if (file.size > maxFileSize) return bailOut(file.path, 'Image is too large! 1 MB max')
|
if (file.size > maxFileSize) return bailOut(file.path, 'Image is too large! 1 MB max')
|
||||||
@ -132,7 +153,7 @@ async function uploadImage (identifier, fields, files) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await fs.unlinkAsync(file)
|
await fs.unlink(file)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return bailOut(file, 'An error occured while cropping.')
|
return bailOut(file, 'An error occured while cropping.')
|
||||||
@ -142,6 +163,7 @@ async function uploadImage (identifier, fields, files) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
downloadImage: downloadImage,
|
||||||
uploadImage: uploadImage,
|
uploadImage: uploadImage,
|
||||||
imageBase64: imageBase64,
|
imageBase64: imageBase64,
|
||||||
types: imageTypes
|
types: imageTypes
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import cprog from 'child_process'
|
import cprog from 'child_process'
|
||||||
import config from '../../scripts/load-config'
|
|
||||||
import http from '../../scripts/http'
|
|
||||||
import exists from '../../scripts/existsSync'
|
|
||||||
import models from './models'
|
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import notp from 'notp'
|
import notp from 'notp'
|
||||||
import base32 from 'thirty-two'
|
import base32 from 'thirty-two'
|
||||||
import emailer from './emailer'
|
|
||||||
import uuidV1 from 'uuid/v1'
|
import uuidV1 from 'uuid/v1'
|
||||||
|
import fs from 'fs-extra'
|
||||||
|
|
||||||
import Promise from 'bluebird'
|
import config from '../../scripts/load-config'
|
||||||
const fs = Promise.promisifyAll(require('fs'))
|
import http from '../../scripts/http'
|
||||||
|
import models from './models'
|
||||||
|
import emailer from './emailer'
|
||||||
|
|
||||||
const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
@ -178,7 +176,7 @@ const API = {
|
|||||||
},
|
},
|
||||||
update: async function (user, data) {
|
update: async function (user, data) {
|
||||||
user = await API.User.ensureObject(user)
|
user = await API.User.ensureObject(user)
|
||||||
if (!user) return {error: 'No such user.'}
|
if (!user) throw new Error('No such user.')
|
||||||
|
|
||||||
data = Object.assign({
|
data = Object.assign({
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
@ -191,20 +189,20 @@ const API = {
|
|||||||
let uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
|
let uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
|
||||||
let pathOf = path.join(uploadsDir, fileName)
|
let pathOf = path.join(uploadsDir, fileName)
|
||||||
|
|
||||||
if (!await exists(pathOf)) {
|
if (!await fs.exists(pathOf)) {
|
||||||
return {error: 'No such file'}
|
throw new Error('No such file')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete previous upload
|
// Delete previous upload
|
||||||
if (user.avatar_file != null) {
|
if (user.avatar_file != null) {
|
||||||
let file = path.join(uploadsDir, user.avatar_file)
|
let file = path.join(uploadsDir, user.avatar_file)
|
||||||
if (await exists(file)) {
|
if (await fs.exists(file)) {
|
||||||
await fs.unlinkAsync(file)
|
await fs.unlink(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await API.User.update(user, {avatar_file: fileName})
|
await API.User.update(user, {avatar_file: fileName})
|
||||||
return { file: 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'])
|
||||||
@ -212,8 +210,8 @@ const API = {
|
|||||||
if (!user.avatar_file) return {}
|
if (!user.avatar_file) return {}
|
||||||
|
|
||||||
let file = path.join(uploadsDir, user.avatar_file)
|
let file = path.join(uploadsDir, user.avatar_file)
|
||||||
if (await exists(file)) {
|
if (await fs.exists(file)) {
|
||||||
await fs.unlinkAsync(file)
|
await fs.unlink(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
return API.User.update(user, {avatar_file: null})
|
return API.User.update(user, {avatar_file: null})
|
||||||
@ -252,14 +250,18 @@ const API = {
|
|||||||
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)
|
let getToken = await models.Token.query().where('token', token).andWhere('type', 1)
|
||||||
if (!getToken || !getToken.length) return false
|
if (!getToken || !getToken.length) return false
|
||||||
|
|
||||||
let user = await API.User.get(getToken[0].user_id)
|
getToken = getToken[0]
|
||||||
|
|
||||||
|
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return false
|
||||||
|
|
||||||
|
let 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[0].id)
|
await models.Token.query().delete().where('id', getToken.id)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
totpTokenRequired: async function (user) {
|
totpTokenRequired: async function (user) {
|
||||||
@ -359,12 +361,12 @@ const API = {
|
|||||||
|
|
||||||
let userTest = await API.User.get(regdata.username)
|
let userTest = await API.User.get(regdata.username)
|
||||||
if (userTest) {
|
if (userTest) {
|
||||||
return {error: 'This username is already taken!'}
|
throw new Error('This username is already taken!')
|
||||||
}
|
}
|
||||||
|
|
||||||
let emailTest = await API.User.get(regdata.email)
|
let emailTest = await API.User.get(regdata.email)
|
||||||
if (emailTest) {
|
if (emailTest) {
|
||||||
return {error: 'This email address is already registered!'}
|
throw new Error('This email address is already registered!')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
@ -393,11 +395,75 @@ const API = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
await models.User.query().delete().where('id', user.id)
|
await models.User.query().delete().where('id', user.id)
|
||||||
return {error: 'Invalid email address!'}
|
throw new Error('Invalid email address!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {error: null, user: user}
|
return user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Reset: {
|
||||||
|
reset: async function (email, passRequired = true) {
|
||||||
|
let emailEnabled = config.email && config.email.enabled
|
||||||
|
|
||||||
|
if (!emailEnabled) throw new Error('Cannot reset password.')
|
||||||
|
|
||||||
|
let user = await API.User.get(email)
|
||||||
|
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.')
|
||||||
|
|
||||||
|
let recentTokens = await models.Token.query().where('user_id', user.id).andWhere('expires_at', '>', new Date()).andWhere('type', 2)
|
||||||
|
if (recentTokens.length >= 2) {
|
||||||
|
throw new Error('You\'ve made too many reset requests recently. Please slow down.')
|
||||||
|
}
|
||||||
|
|
||||||
|
let resetToken = API.Hash(16)
|
||||||
|
await models.Token.query().insert({
|
||||||
|
expires_at: new Date(Date.now() + 86400000), // 1 day
|
||||||
|
token: resetToken,
|
||||||
|
user_id: user.id,
|
||||||
|
type: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
// Send Reset Email
|
||||||
|
console.debug('Reset token:', resetToken)
|
||||||
|
if (email) {
|
||||||
|
try {
|
||||||
|
let em = await emailer.pushMail('reset_password', user.email, {
|
||||||
|
domain: config.server.domain,
|
||||||
|
display_name: user.display_name,
|
||||||
|
reset_token: resetToken
|
||||||
|
})
|
||||||
|
|
||||||
|
console.debug(em)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
throw new Error('Invalid email address!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resetToken
|
||||||
|
},
|
||||||
|
resetToken: async function (token) {
|
||||||
|
let getToken = await models.Token.query().where('token', token).andWhere('type', 2)
|
||||||
|
if (!getToken || !getToken.length) return null
|
||||||
|
|
||||||
|
getToken = getToken[0]
|
||||||
|
|
||||||
|
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return null
|
||||||
|
|
||||||
|
let user = await API.User.get(getToken.user_id)
|
||||||
|
if (!user) return null
|
||||||
|
|
||||||
|
return user
|
||||||
|
},
|
||||||
|
changePassword: async function (user, password, token) {
|
||||||
|
let hashed = await API.User.Register.hashPassword(password)
|
||||||
|
|
||||||
|
await models.User.query().patchAndFetchById(user.id, {password: hashed, updated_at: new Date()})
|
||||||
|
await models.Token.query().delete().where('token', token)
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
OAuth2: {
|
OAuth2: {
|
||||||
@ -468,7 +534,6 @@ const API = {
|
|||||||
console.debug('IPN Verified Notification:', body)
|
console.debug('IPN Verified Notification:', body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add database field for this
|
|
||||||
if (body.txn_id) {
|
if (body.txn_id) {
|
||||||
if (txnStore.indexOf(body.txn_id) !== -1) return true
|
if (txnStore.indexOf(body.txn_id) !== -1) return true
|
||||||
txnStore.push(body.txn_id)
|
txnStore.push(body.txn_id)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
import API from './index'
|
import API from './index'
|
||||||
import Model from './models'
|
import Model from './models'
|
||||||
import crypto from 'crypto'
|
|
||||||
|
|
||||||
const mAPI = {
|
const mAPI = {
|
||||||
getToken: async function (user) {
|
getToken: async function (user) {
|
||||||
|
@ -53,7 +53,7 @@ const News = {
|
|||||||
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 articles found'}
|
return { error: 'No news' }
|
||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
count = count[0].ids
|
||||||
@ -100,7 +100,7 @@ const News = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = await Models.News.query().patchAndFetchById(id, patch)
|
let result = await Models.News.query().patchAndFetchById(id, patch)
|
||||||
if (!result) return {error: 'Something went wrong.'}
|
if (!result) throw new Error('Something went wrong.')
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ const args = {
|
|||||||
|
|
||||||
function spawnWorkers () {
|
function spawnWorkers () {
|
||||||
let workerCount = config.server.workers === 0 ? cpuCount : config.server.workers
|
let workerCount = config.server.workers === 0 ? cpuCount : config.server.workers
|
||||||
console.log('Spinning up ' + workerCount + ' worker process' + (workerCount !== 1 ? 'es' : ''))
|
console.log('Spinning up %d worker process%s', workerCount, (workerCount !== 1 ? 'es' : ''))
|
||||||
|
|
||||||
for (let i = 0; i < workerCount; i++) {
|
for (let i = 0; i < workerCount; i++) {
|
||||||
spawnWorker()
|
spawnWorker()
|
||||||
@ -68,14 +68,14 @@ function spawnWorker (oldWorker) {
|
|||||||
w.process.stderr.on('data', (data) => {
|
w.process.stderr.on('data', (data) => {
|
||||||
console.log(w.process.pid, data.toString().trim())
|
console.log(w.process.pid, data.toString().trim())
|
||||||
})
|
})
|
||||||
args.verbose && console.log('Starting worker process ' + w.process.pid + '...')
|
args.verbose && console.log('Starting worker process %d...', w.process.pid)
|
||||||
|
|
||||||
w.on('message', (message) => {
|
w.on('message', (message) => {
|
||||||
if (message === 'started') {
|
if (message === 'started') {
|
||||||
workers.push(w)
|
workers.push(w)
|
||||||
args.verbose && console.log('Started worker process ' + w.process.pid)
|
args.verbose && console.log('Started worker process', w.process.pid)
|
||||||
if (oldWorker) {
|
if (oldWorker) {
|
||||||
args.verbose && console.log('Stopping worker process ' + oldWorker.process.pid)
|
args.verbose && console.log('Stopping worker process', oldWorker.process.pid)
|
||||||
oldWorker.send('stop')
|
oldWorker.send('stop')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -99,7 +99,7 @@ cluster.setupMaster({
|
|||||||
cluster.on('exit', (worker, code, signal) => {
|
cluster.on('exit', (worker, code, signal) => {
|
||||||
let extra = ((code || '') + ' ' + (signal || '')).trim()
|
let extra = ((code || '') + ' ' + (signal || '')).trim()
|
||||||
|
|
||||||
console.error('Worker process ' + worker.process.pid + ' exited ' + (extra ? '(' + extra + ')' : ''))
|
console.error('Worker process %d exited %s', worker.process.pid, (extra ? '(' + extra + ')' : ''))
|
||||||
|
|
||||||
let index = workers.indexOf(worker)
|
let index = workers.indexOf(worker)
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
|
||||||
import ensureLogin from '../../scripts/ensureLogin'
|
import ensureLogin from '../../scripts/ensureLogin'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import {User} from '../api'
|
|
||||||
import API from '../api/admin'
|
import API from '../api/admin'
|
||||||
|
import {User} from '../api'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const apiRouter = express.Router()
|
const apiRouter = express.Router()
|
||||||
@ -128,10 +129,7 @@ apiRouter.post('/client/new', wrap(async (req, res) => {
|
|||||||
return res.status(400).jsonp({error: 'Invalid session'})
|
return res.status(400).jsonp({error: 'Invalid session'})
|
||||||
}
|
}
|
||||||
|
|
||||||
let update = await API.createClient(req.body, req.session.user)
|
await API.createClient(req.body, req.session.user)
|
||||||
if (update.error) {
|
|
||||||
return res.status(400).jsonp({error: update.error})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(204).end()
|
res.status(204).end()
|
||||||
}))
|
}))
|
||||||
@ -145,10 +143,7 @@ apiRouter.post('/client/update', wrap(async (req, res) => {
|
|||||||
return res.status(400).jsonp({error: 'Invalid session'})
|
return res.status(400).jsonp({error: 'Invalid session'})
|
||||||
}
|
}
|
||||||
|
|
||||||
let update = await API.updateClient(id, req.body)
|
await API.updateClient(id, req.body)
|
||||||
if (update.error) {
|
|
||||||
return res.status(400).jsonp({error: update.error})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(204).end()
|
res.status(204).end()
|
||||||
}))
|
}))
|
||||||
@ -160,9 +155,6 @@ apiRouter.post('/client/new_secret/:id', wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = await API.newSecret(id)
|
let client = await API.newSecret(id)
|
||||||
if (client.error) {
|
|
||||||
return res.status(400).jsonp({error: client.error})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.jsonp(client)
|
res.jsonp(client)
|
||||||
}))
|
}))
|
||||||
@ -174,9 +166,6 @@ apiRouter.post('/client/delete/:id', wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client = await API.removeClient(id)
|
let client = await API.removeClient(id)
|
||||||
if (client.error) {
|
|
||||||
return res.status(400).jsonp({error: client.error})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.jsonp(client)
|
res.jsonp(client)
|
||||||
}))
|
}))
|
||||||
@ -203,9 +192,6 @@ apiRouter.post('/ban/pardon/:id', wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ban = await API.removeBan(id)
|
let ban = await API.removeBan(id)
|
||||||
if (ban.error) {
|
|
||||||
return res.status(400).jsonp({error: ban.error})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.jsonp(ban)
|
res.jsonp(ban)
|
||||||
}))
|
}))
|
||||||
@ -217,16 +203,13 @@ apiRouter.post('/ban', wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = await API.addBan(req.body, req.session.user.id)
|
let result = await API.addBan(req.body, req.session.user.id)
|
||||||
if (result.error) {
|
|
||||||
return res.status(400).jsonp({error: result.error})
|
|
||||||
}
|
|
||||||
|
|
||||||
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(500).jsonp({error: 'Internal server error'})
|
return res.status(400).jsonp({error: err.message})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.use('/api', apiRouter)
|
router.use('/api', apiRouter)
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import RateLimit from 'express-rate-limit'
|
import RateLimit from 'express-rate-limit'
|
||||||
import multiparty from 'multiparty'
|
import multiparty from 'multiparty'
|
||||||
|
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import API from '../api'
|
|
||||||
import News from '../api/news'
|
|
||||||
import Image from '../api/image'
|
|
||||||
import APIExtern from '../api/external'
|
import APIExtern from '../api/external'
|
||||||
|
import Image from '../api/image'
|
||||||
|
import News from '../api/news'
|
||||||
|
import API from '../api'
|
||||||
|
|
||||||
let router = express.Router()
|
let router = express.Router()
|
||||||
let dev = process.env.NODE_ENV !== 'production'
|
let dev = process.env.NODE_ENV !== 'production'
|
||||||
@ -79,16 +80,34 @@ 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
|
||||||
|
function removeAuthMiddleware (identifier) {
|
||||||
|
return wrap(async (req, res) => {
|
||||||
|
if (!req.session.user) return res.redirect('/login')
|
||||||
|
let done = await APIExtern.Common.remove(req.session.user, identifier)
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect('/user/manage')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** FACEBOOK LOGIN
|
/** FACEBOOK LOGIN
|
||||||
* Ajax POST only <in-page javascript handeled>
|
* Ajax POST only <in-page javascript handeled>
|
||||||
* No tokens saved in configs, everything works out-of-the-box
|
* No tokens saved in configs, everything works out-of-the-box
|
||||||
*/
|
*/
|
||||||
router.post('/external/facebook/callback', wrap(async (req, res, next) => {
|
router.post('/external/facebook/callback', wrap(async (req, res, next) => {
|
||||||
if (!config.facebook || !config.facebook.client) return next()
|
if (!config.facebook || !config.facebook.client) return next()
|
||||||
let sane = objectAssembler(req.body)
|
|
||||||
sane.ip_address = req.realIP
|
|
||||||
|
|
||||||
let response = await APIExtern.Facebook.callback(req.session.user, sane)
|
// Fix up the retarded object Facebook sends us
|
||||||
|
let sane = objectAssembler(req.body)
|
||||||
|
if (!sane || !sane.authResponse) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
let 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.')
|
||||||
@ -107,16 +126,7 @@ router.post('/external/facebook/callback', wrap(async (req, res, next) => {
|
|||||||
JsonData(req, res, null, '/login')
|
JsonData(req, res, null, '/login')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
router.get('/external/facebook/remove', wrap(async (req, res) => {
|
router.get('/external/facebook/remove', removeAuthMiddleware('facebook'))
|
||||||
if (!req.session.user) return res.redirect('/login')
|
|
||||||
let done = await APIExtern.Common.remove(req.session.user, 'fb')
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/user/manage')
|
|
||||||
}))
|
|
||||||
|
|
||||||
/** TWITTER LOGIN
|
/** TWITTER LOGIN
|
||||||
* OAuth1.0a flows
|
* OAuth1.0a flows
|
||||||
@ -172,17 +182,7 @@ router.get('/external/twitter/callback', wrap(async (req, res) => {
|
|||||||
res.render('redirect', {url: uri})
|
res.render('redirect', {url: uri})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
router.get('/external/twitter/remove', wrap(async (req, res) => {
|
router.get('/external/twitter/remove', removeAuthMiddleware('twitter'))
|
||||||
if (!req.session.user) return res.redirect('/login')
|
|
||||||
|
|
||||||
let done = await APIExtern.Common.remove(req.session.user, 'twitter')
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/user/manage')
|
|
||||||
}))
|
|
||||||
|
|
||||||
/** DISCORD LOGIN
|
/** DISCORD LOGIN
|
||||||
* OAuth2 flows
|
* OAuth2 flows
|
||||||
@ -191,22 +191,16 @@ router.get('/external/twitter/remove', wrap(async (req, res) => {
|
|||||||
router.get('/external/discord/login', wrap(async (req, res) => {
|
router.get('/external/discord/login', wrap(async (req, res) => {
|
||||||
if (!config.discord || !config.discord.api) return res.redirect('/')
|
if (!config.discord || !config.discord.api) return res.redirect('/')
|
||||||
|
|
||||||
let infos = APIExtern.Discord.getAuthorizeURL()
|
let infos = APIExtern.Discord.getAuthorizeURL(req)
|
||||||
|
|
||||||
req.session.discord_auth = {
|
|
||||||
state: infos.state
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect(infos.url)
|
res.redirect(infos.url)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
router.get('/external/discord/callback', wrap(async (req, res) => {
|
router.get('/external/discord/callback', wrap(async (req, res) => {
|
||||||
if (!config.discord || !config.discord.api) return res.redirect('/login')
|
if (!config.discord || !config.discord.api) return res.redirect('/login')
|
||||||
if (!req.session.discord_auth) return res.redirect('/login')
|
|
||||||
|
|
||||||
let code = req.query.code
|
let code = req.query.code
|
||||||
let state = req.query.state
|
let state = req.query.state
|
||||||
let da = req.session.discord_auth
|
|
||||||
let uri = '/login'
|
let uri = '/login'
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
@ -214,7 +208,7 @@ router.get('/external/discord/callback', wrap(async (req, res) => {
|
|||||||
return res.redirect(uri)
|
return res.redirect(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state || state !== da.state) {
|
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)
|
||||||
}
|
}
|
||||||
@ -245,29 +239,7 @@ router.get('/external/discord/callback', wrap(async (req, res) => {
|
|||||||
res.render('redirect', {url: uri})
|
res.render('redirect', {url: uri})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
router.get('/external/discord/remove', wrap(async (req, res) => {
|
router.get('/external/discord/remove', removeAuthMiddleware('discord'))
|
||||||
if (!req.session.user) return res.redirect('/login')
|
|
||||||
|
|
||||||
let done = await APIExtern.Common.remove(req.session.user, 'discord')
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/user/manage')
|
|
||||||
}))
|
|
||||||
|
|
||||||
router.get('/external/discord/remove', wrap(async (req, res) => {
|
|
||||||
if (!req.session.user) return res.redirect('/login')
|
|
||||||
|
|
||||||
let done = await APIExtern.Common.remove(req.session.user, 'discord')
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/user/manage')
|
|
||||||
}))
|
|
||||||
|
|
||||||
/** GOOGLE LOGIN
|
/** GOOGLE LOGIN
|
||||||
* Google Token Verification
|
* Google Token Verification
|
||||||
@ -303,17 +275,7 @@ router.post('/external/google/callback', wrap(async (req, res) => {
|
|||||||
JsonData(req, res, null, '/login')
|
JsonData(req, res, null, '/login')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
router.get('/external/google/remove', wrap(async (req, res) => {
|
router.get('/external/google/remove', removeAuthMiddleware('google'))
|
||||||
if (!req.session.user) return res.redirect('/login')
|
|
||||||
|
|
||||||
let done = await APIExtern.Common.remove(req.session.user, 'google')
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/user/manage')
|
|
||||||
}))
|
|
||||||
|
|
||||||
/* ========
|
/* ========
|
||||||
* NEWS
|
* NEWS
|
||||||
@ -356,9 +318,10 @@ router.post('/news/edit/:id', wrap(async (req, res, next) => {
|
|||||||
return res.status(400).jsonp({error: 'Content is required.'})
|
return res.status(400).jsonp({error: 'Content is required.'})
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = await News.edit(id, req.body)
|
try {
|
||||||
if (result.error) {
|
await News.edit(id, req.body)
|
||||||
return res.status(400).jsonp({error: result.error})
|
} catch (e) {
|
||||||
|
return res.status(400).jsonp({error: e.message})
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(204).end()
|
res.status(204).end()
|
||||||
@ -402,19 +365,19 @@ async function promiseForm (req) {
|
|||||||
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)
|
let data = await promiseForm(req)
|
||||||
|
|
||||||
|
let avatarFile
|
||||||
|
|
||||||
|
try {
|
||||||
let result = await Image.uploadImage(req.session.user.username, data.fields, data.files)
|
let result = await Image.uploadImage(req.session.user.username, data.fields, data.files)
|
||||||
|
|
||||||
if (result.error) {
|
avatarFile = await API.User.changeAvatar(req.session.user, result.file)
|
||||||
return res.status(400).jsonp({error: result.error})
|
} catch (e) {
|
||||||
|
return res.status(400).jsonp({error: e.message})
|
||||||
}
|
}
|
||||||
|
|
||||||
let avatarUpdate = await API.User.changeAvatar(req.session.user, result.file)
|
if (avatarFile) {
|
||||||
if (avatarUpdate.error) {
|
req.session.user.avatar_file = avatarFile
|
||||||
return res.status(400).jsonp({error: avatarUpdate.error})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avatarUpdate.file) {
|
|
||||||
req.session.user.avatar_file = avatarUpdate.file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).jsonp({})
|
res.status(200).jsonp({})
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs-extra'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import RateLimit from 'express-rate-limit'
|
import RateLimit from 'express-rate-limit'
|
||||||
|
|
||||||
import ensureLogin from '../../scripts/ensureLogin'
|
import ensureLogin from '../../scripts/ensureLogin'
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import exists from '../../scripts/existsSync'
|
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import http from '../../scripts/http'
|
import http from '../../scripts/http'
|
||||||
import API from '../api'
|
import API from '../api'
|
||||||
@ -79,7 +79,7 @@ router.use(wrap(async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update user session
|
// Update user session
|
||||||
let udata = await API.User.get(req.session.user.id)
|
let udata = await API.User.get(req.session.user)
|
||||||
setSession(req, udata)
|
setSession(req, udata)
|
||||||
|
|
||||||
// Update IP address
|
// Update IP address
|
||||||
@ -135,8 +135,31 @@ function formKeep (req, res, next) {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Password reset request endpoint
|
||||||
|
router.get('/login/reset', extraButtons, (req, res) => {
|
||||||
|
if (req.session.user) return redirectLogin(req, res)
|
||||||
|
|
||||||
|
res.render('user/reset_password', {sent: req.query.success != null})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Password reset endpoint (emailed link)
|
||||||
|
router.get('/reset/:token', wrap(async (req, res) => {
|
||||||
|
if (req.session.user) return res.redirect('/login')
|
||||||
|
let token = req.params.token
|
||||||
|
let success = await API.User.Reset.resetToken(token)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
req.flash('message', {error: true, text: 'Invalid or expired reset token.'})
|
||||||
|
res.redirect('/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render('user/password_new', {token: true})
|
||||||
|
}))
|
||||||
|
|
||||||
router.get('/login', extraButtons, (req, res) => {
|
router.get('/login', extraButtons, (req, res) => {
|
||||||
if (req.session.user) return redirectLogin(req, res)
|
if (req.session.user) return redirectLogin(req, res)
|
||||||
|
|
||||||
if (req.query.returnTo) {
|
if (req.query.returnTo) {
|
||||||
req.session.redirectUri = req.query.returnTo
|
req.session.redirectUri = req.query.returnTo
|
||||||
}
|
}
|
||||||
@ -154,6 +177,21 @@ router.get('/register', extraButtons, formKeep, (req, res) => {
|
|||||||
res.render('user/register')
|
res.render('user/register')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// User activation endpoint (emailed link)
|
||||||
|
router.get('/activate/:token', wrap(async (req, res) => {
|
||||||
|
if (req.session.user) return res.redirect('/login')
|
||||||
|
let token = req.params.token
|
||||||
|
let success = await API.User.Login.activationToken(token)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
req.flash('message', {error: true, text: 'Invalid or expired activation token.'})
|
||||||
|
} else {
|
||||||
|
req.flash('message', {error: false, text: 'Your account has been activated! You may now log in.'})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect('/login')
|
||||||
|
}))
|
||||||
|
|
||||||
// View for enabling Two-Factor Authentication
|
// View for enabling Two-Factor Authentication
|
||||||
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
|
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
|
||||||
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
let twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
||||||
@ -205,9 +243,9 @@ router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.facebook && config.facebook.client) {
|
if (config.facebook && config.facebook.client) {
|
||||||
if (!socialStatus.enabled.fb) {
|
if (!socialStatus.enabled.facebook) {
|
||||||
res.locals.facebook_auth = config.facebook.client
|
res.locals.facebook_auth = config.facebook.client
|
||||||
} else if (socialStatus.source !== 'fb') {
|
} else if (socialStatus.source !== 'facebook') {
|
||||||
res.locals.facebook_auth = false
|
res.locals.facebook_auth = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,23 +308,31 @@ function formError (req, res, error, redirect) {
|
|||||||
// Make sure characters are UTF-8
|
// Make sure characters are UTF-8
|
||||||
function cleanString (input) {
|
function cleanString (input) {
|
||||||
let output = ''
|
let output = ''
|
||||||
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
output += input.charCodeAt(i) <= 127 ? input.charAt(i) : ''
|
output += input.charCodeAt(i) <= 127 ? input.charAt(i) : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enabling 2fa
|
// Make sure CSRF tokens are present and valid in every form
|
||||||
router.post('/user/two-factor', wrap(async (req, res, next) => {
|
function csrfValidation (req, res, next) {
|
||||||
if (!req.session.user) return next()
|
|
||||||
if (!req.body.code) {
|
|
||||||
return formError(req, res, 'You need to enter the code.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
if (req.body.csrf !== req.session.csrf) {
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabling 2fa
|
||||||
|
router.post('/user/two-factor', csrfValidation, wrap(async (req, res, next) => {
|
||||||
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
|
if (!req.body.code) {
|
||||||
|
return formError(req, res, 'You need to enter the code.')
|
||||||
|
}
|
||||||
|
|
||||||
let verified = await API.User.Login.totpCheck(req.session.user, req.body.code)
|
let 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.')
|
||||||
@ -296,11 +342,8 @@ router.post('/user/two-factor', wrap(async (req, res, next) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Disabling 2fa
|
// Disabling 2fa
|
||||||
router.post('/user/two-factor/disable', wrap(async (req, res, next) => {
|
router.post('/user/two-factor/disable', csrfValidation, wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return next()
|
if (!req.session.user) return next()
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.body.password) {
|
if (!req.body.password) {
|
||||||
return formError(req, res, 'Please enter your password.')
|
return formError(req, res, 'Please enter your password.')
|
||||||
@ -315,17 +358,15 @@ router.post('/user/two-factor/disable', wrap(async (req, res, next) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Verify 2FA for login
|
// Verify 2FA for login
|
||||||
router.post('/login/verify', wrap(async (req, res, next) => {
|
router.post('/login/verify', csrfValidation, wrap(async (req, res, next) => {
|
||||||
if (req.session.user) return next()
|
if (req.session.user) return next()
|
||||||
|
|
||||||
if (req.session.totp_check === null) return res.redirect('/login')
|
if (req.session.totp_check === null) return res.redirect('/login')
|
||||||
|
|
||||||
if (!req.body.code && !req.body.recovery) {
|
if (!req.body.code && !req.body.recovery) {
|
||||||
return formError(req, res, 'You need to enter the code.')
|
return formError(req, res, 'You need to enter the code.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
||||||
}
|
|
||||||
|
|
||||||
let totpCheck = await API.User.Login.totpCheck(req.session.totp_check, req.body.code, req.body.recovery || false)
|
let 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!')
|
||||||
@ -339,16 +380,13 @@ router.post('/login/verify', wrap(async (req, res, next) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Log the user in. Limited resource
|
// Log the user in. Limited resource
|
||||||
router.post('/login', accountLimiter, wrap(async (req, res, next) => {
|
router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
||||||
if (req.session.user) return next()
|
if (req.session.user) return next()
|
||||||
|
|
||||||
if (!req.body.username || !req.body.password || req.body.username === '') {
|
if (!req.body.username || !req.body.password || req.body.username === '') {
|
||||||
return res.redirect('/login')
|
return res.redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await API.User.get(req.body.username)
|
let 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.')
|
||||||
|
|
||||||
@ -358,6 +396,7 @@ router.post('/login', accountLimiter, wrap(async (req, res, next) => {
|
|||||||
if (!pwMatch) return formError(req, res, 'Invalid username or password.')
|
if (!pwMatch) return formError(req, res, 'Invalid username or password.')
|
||||||
|
|
||||||
if (user.activated === 0) return formError(req, res, 'Please activate your account first.')
|
if (user.activated === 0) return formError(req, res, 'Please activate your account first.')
|
||||||
|
|
||||||
if (user.locked === 1) return formError(req, res, 'This account has been locked.')
|
if (user.locked === 1) return formError(req, res, 'This account has been locked.')
|
||||||
|
|
||||||
// Check if the user is banned
|
// Check if the user is banned
|
||||||
@ -389,15 +428,72 @@ router.post('/login', accountLimiter, wrap(async (req, res, next) => {
|
|||||||
res.redirect(uri)
|
res.redirect(uri)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Protected & Limited resource: Account registration
|
// Password reset
|
||||||
router.post('/register', accountLimiter, wrap(async (req, res, next) => {
|
router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
||||||
if (req.session.user) return next()
|
if (req.session.user) return next()
|
||||||
if (!req.body.username || !req.body.display_name || !req.body.password || !req.body.email) {
|
|
||||||
return formError(req, res, 'Please fill in all the fields.')
|
if (!req.body.email) {
|
||||||
|
return formError(req, res, 'You need to enter your email address.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
let email = req.body.email
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
let validEmail = await API.User.Register.validateEmail(email)
|
||||||
|
if (!validEmail) {
|
||||||
|
return formError(req, res, 'You need to enter a valid email address.')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await API.User.Reset.reset(email)
|
||||||
|
|
||||||
|
req.flash('message', {error: false, text: 'We\'ve sent a link to your email address. Please check spam folders, too!'})
|
||||||
|
res.redirect('/login/reset?success=true')
|
||||||
|
} catch (e) {
|
||||||
|
return formError(req, res, e.message)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Password reset endpoint (emailed link)
|
||||||
|
router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
|
||||||
|
if (req.session.user) return res.redirect('/login')
|
||||||
|
let token = req.params.token
|
||||||
|
let user = await API.User.Reset.resetToken(token)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
req.flash('message', {error: true, text: 'Invalid or expired reset token.'})
|
||||||
|
res.redirect('/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4th Check: Password length
|
||||||
|
let password = req.body.password
|
||||||
|
if (!password || password.length < 8) {
|
||||||
|
return formError(req, res, 'Invalid password! Please use at least 8 characters!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5th Check: Password match
|
||||||
|
let passwordAgain = req.body.password_repeat
|
||||||
|
if (!passwordAgain || password !== passwordAgain) {
|
||||||
|
return formError(req, res, 'Passwords do not match!')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await API.User.Reset.changePassword(user, password, token)
|
||||||
|
|
||||||
|
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!'})
|
||||||
|
res.redirect('/login')
|
||||||
|
} catch (e) {
|
||||||
|
return formError(req, res, e.message)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Protected & Limited resource: Account registration
|
||||||
|
router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
||||||
|
if (req.session.user) return next()
|
||||||
|
|
||||||
|
if (!req.body.username || !req.body.display_name || !req.body.password || !req.body.email) {
|
||||||
|
return formError(req, res, 'Please fill in all the fields.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ban check
|
// Ban check
|
||||||
@ -458,18 +554,19 @@ router.post('/register', accountLimiter, wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
// Hash the password
|
// Hash the password
|
||||||
let hash = await API.User.Register.hashPassword(password)
|
let hash = await API.User.Register.hashPassword(password)
|
||||||
|
let newUser
|
||||||
|
|
||||||
// Attempt to create the user
|
// Attempt to create the user
|
||||||
let newUser = await API.User.Register.newAccount({
|
try {
|
||||||
|
newUser = await API.User.Register.newAccount({
|
||||||
username: username,
|
username: username,
|
||||||
display_name: cleanString(displayName),
|
display_name: cleanString(displayName),
|
||||||
password: hash,
|
password: hash,
|
||||||
email: email,
|
email: email,
|
||||||
ip_address: req.realIP
|
ip_address: req.realIP
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
if (!newUser || newUser.error != null) {
|
return formError(req, res, e.message)
|
||||||
return formError(req, res, newUser.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not include activation link message when the user is already activated
|
// Do not include activation link message when the user is already activated
|
||||||
@ -483,20 +580,16 @@ router.post('/register', accountLimiter, wrap(async (req, res, next) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Change display name
|
// Change display name
|
||||||
router.post('/user/manage', wrap(async (req, res, next) => {
|
router.post('/user/manage', csrfValidation, wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return next()
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.body.display_name) {
|
if (!req.body.display_name) {
|
||||||
return formError(req, res, 'Display Name cannot be blank.')
|
return formError(req, res, 'Display Name cannot be blank.')
|
||||||
}
|
}
|
||||||
|
|
||||||
let displayName = req.body.display_name
|
let displayName = req.body.display_name
|
||||||
if (!displayName || !displayName.match(/^([^\\`]{3,32})$/i)) {
|
if (!displayName || !displayName.match(/^([^\\`]{3,32})$/i)) {
|
||||||
return formError(req, res, 'Invalid display name!')
|
return formError(req, res, 'Invalid Display Name!')
|
||||||
}
|
}
|
||||||
|
|
||||||
displayName = cleanString(displayName)
|
displayName = cleanString(displayName)
|
||||||
@ -506,12 +599,12 @@ router.post('/user/manage', wrap(async (req, res, next) => {
|
|||||||
return res.redirect('/user/manage')
|
return res.redirect('/user/manage')
|
||||||
}
|
}
|
||||||
|
|
||||||
let success = await API.User.update(req.session.user, {
|
try {
|
||||||
|
await API.User.update(req.session.user, {
|
||||||
display_name: displayName
|
display_name: displayName
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
if (success.error) {
|
return formError(req, res, e.message)
|
||||||
return formError(req, res, success.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.session.user.display_name = displayName
|
req.session.user.display_name = displayName
|
||||||
@ -521,13 +614,9 @@ router.post('/user/manage', wrap(async (req, res, next) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Change user password
|
// Change user password
|
||||||
router.post('/user/manage/password', accountLimiter, 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()
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.body.password_old) {
|
if (!req.body.password_old) {
|
||||||
return formError(req, res, 'Please enter your current password.')
|
return formError(req, res, 'Please enter your current password.')
|
||||||
}
|
}
|
||||||
@ -550,12 +639,12 @@ router.post('/user/manage/password', accountLimiter, wrap(async (req, res, next)
|
|||||||
|
|
||||||
password = await API.User.Register.hashPassword(password)
|
password = await API.User.Register.hashPassword(password)
|
||||||
|
|
||||||
let success = await API.User.update(user, {
|
try {
|
||||||
|
await API.User.update(user, {
|
||||||
password: password
|
password: password
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
if (success.error) {
|
return formError(req, res, e.message)
|
||||||
return formError(req, res, success.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -572,13 +661,9 @@ router.post('/user/manage/password', accountLimiter, wrap(async (req, res, next)
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Change email address
|
// Change email address
|
||||||
router.post('/user/manage/email', accountLimiter, 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()
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await API.User.get(req.session.user)
|
let user = await API.User.get(req.session.user)
|
||||||
let email = req.body.email
|
let email = req.body.email
|
||||||
let newEmail = req.body.email_new
|
let newEmail = req.body.email_new
|
||||||
@ -613,15 +698,14 @@ router.post('/user/manage/email', accountLimiter, wrap(async (req, res, next) =>
|
|||||||
return formError(req, res, 'This email is already taken.')
|
return formError(req, res, 'This email is already taken.')
|
||||||
}
|
}
|
||||||
|
|
||||||
let success = await API.User.update(user, {
|
try {
|
||||||
|
await API.User.update(user, {
|
||||||
email: newEmail
|
email: newEmail
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
if (success.error) {
|
return formError(req, res, e.message)
|
||||||
return formError(req, res, success.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Send necessary emails
|
|
||||||
console.warn('[SECURITY AUDIT] User \'%s\' email has been changed from %s', user.username, req.realIP)
|
console.warn('[SECURITY AUDIT] User \'%s\' email has been changed from %s', user.username, req.realIP)
|
||||||
|
|
||||||
req.session.user.email = newEmail
|
req.session.user.email = newEmail
|
||||||
@ -640,7 +724,7 @@ router.post('/user/manage/email', accountLimiter, wrap(async (req, res, next) =>
|
|||||||
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')
|
let doc = path.join(docsDir, req.params.name + '.html')
|
||||||
if (!await exists(docsDir) || !await exists(doc)) {
|
if (!await fs.exists(docsDir) || !await fs.exists(doc)) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,18 +751,16 @@ router.get('/news/compose', newsPrivilege, formKeep, (req, res) => {
|
|||||||
res.render('news/composer')
|
res.render('news/composer')
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/news/compose', newsPrivilege, wrap(async (req, res) => {
|
router.post('/news/compose', newsPrivilege, csrfValidation, wrap(async (req, res) => {
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
|
||||||
return formError(req, res, 'Invalid session! Try reloading the page.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.body.title || !req.body.content) {
|
if (!req.body.title || !req.body.content) {
|
||||||
return formError(req, res, 'Required fields missing!')
|
return formError(req, res, 'Required fields missing!')
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = await News.compose(req.session.user, req.body)
|
let result
|
||||||
if (result.error) {
|
try {
|
||||||
return formError(req, res, result.error)
|
result = await News.compose(req.session.user, req.body)
|
||||||
|
} catch (e) {
|
||||||
|
return formError(req, res, e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.redirect('/news/' + result.id + '-' + result.slug)
|
res.redirect('/news/' + result.id + '-' + result.slug)
|
||||||
@ -733,21 +815,6 @@ router.get('/logout', (req, res) => {
|
|||||||
res.redirect('/')
|
res.redirect('/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// User activation endpoint (emailed link)
|
|
||||||
router.get('/activate/:token', wrap(async (req, res) => {
|
|
||||||
if (req.session.user) return res.redirect('/login')
|
|
||||||
let token = req.params.token
|
|
||||||
let success = await API.User.Login.activationToken(token)
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
req.flash('message', {error: true, text: 'Invalid or expired activation token.'})
|
|
||||||
} else {
|
|
||||||
req.flash('message', {error: false, text: 'Your account has been activated! You may now log in.'})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/login')
|
|
||||||
}))
|
|
||||||
|
|
||||||
router.use('/api', apiRouter)
|
router.use('/api', apiRouter)
|
||||||
router.use('/admin', adminRouter)
|
router.use('/admin', adminRouter)
|
||||||
router.use('/mc', mcRouter)
|
router.use('/mc', mcRouter)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
|
||||||
import ensureLogin from '../../scripts/ensureLogin'
|
import ensureLogin from '../../scripts/ensureLogin'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import Minecraft from '../api/minecraft'
|
import Minecraft from '../api/minecraft'
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import uapi from '../api'
|
|
||||||
|
import UAPI from '../api'
|
||||||
import OAuth2 from '../api/oauth2'
|
import OAuth2 from '../api/oauth2'
|
||||||
import RateLimit from 'express-rate-limit'
|
import RateLimit from 'express-rate-limit'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
@ -31,7 +32,7 @@ router.post('/introspect', oauth.controller.introspection)
|
|||||||
// Protected user information resource
|
// Protected user information resource
|
||||||
router.get('/user', oauth.bearer, wrap(async (req, res) => {
|
router.get('/user', oauth.bearer, wrap(async (req, res) => {
|
||||||
let accessToken = req.oauth2.accessToken
|
let accessToken = req.oauth2.accessToken
|
||||||
let user = await uapi.User.get(accessToken.user_id)
|
let user = await UAPI.User.get(accessToken.user_id)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).jsonp({
|
return res.status(404).jsonp({
|
||||||
|
@ -68,7 +68,7 @@ module.exports = (args) => {
|
|||||||
app.set('views', path.join(__dirname, '../views'))
|
app.set('views', path.join(__dirname, '../views'))
|
||||||
|
|
||||||
if (args.dev) {
|
if (args.dev) {
|
||||||
console.log('Worker is in development mode')
|
console.warn('Worker is in development mode')
|
||||||
|
|
||||||
// Dev logger
|
// Dev logger
|
||||||
const morgan = require('morgan')
|
const morgan = require('morgan')
|
||||||
|
@ -12,15 +12,20 @@ function buildTemplateScript (id, ctx) {
|
|||||||
function paginationButton (pages) {
|
function paginationButton (pages) {
|
||||||
var html = '<div class="pgn">'
|
var html = '<div class="pgn">'
|
||||||
html += '<span class="pagenum">Page ' + pages.page + ' of ' + pages.pages + '</span>'
|
html += '<span class="pagenum">Page ' + pages.page + ' of ' + pages.pages + '</span>'
|
||||||
|
|
||||||
if (pages.page > 1) {
|
if (pages.page > 1) {
|
||||||
html += '<div class="button" data-page="' + (pages.page - 1) + '">Previous</div>'
|
html += '<div class="button" data-page="' + (pages.page - 1) + '">Previous</div>'
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < pages.pages; i++) {
|
for (var i = 0; i < pages.pages; i++) {
|
||||||
html += '<div class="button" data-page="' + (i + 1) + '">' + (i + 1) + '</div>'
|
html += '<div class="button' + (i + 1 === pages.page ? ' active' : '') + '" data-page="' + (i + 1) + '">' + (i + 1) + '</div>'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pages.pages > pages.page) {
|
if (pages.pages > pages.page) {
|
||||||
html += '<div class="button" data-page="' + (pages.page + 1) + '">Next</div>'
|
html += '<div class="button" data-page="' + (pages.page + 1) + '">Next</div>'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html += '<span class="pagenum total">(' + pages.total + ' Total Entries)</span>'
|
||||||
html += '</div>'
|
html += '</div>'
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
@ -271,11 +276,9 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
$.get({
|
$.get({
|
||||||
url: '/admin/access',
|
url: '/admin/access'
|
||||||
success: function (data) {
|
}).fail(function () {
|
||||||
if (data && data.access) return
|
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}, 30000)
|
}, 30000)
|
||||||
})
|
})
|
||||||
|
@ -206,6 +206,8 @@ input:not([type="submit"])
|
|||||||
font-size: 120%
|
font-size: 120%
|
||||||
margin: 10px 0
|
margin: 10px 0
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
|
&.active
|
||||||
|
background-color: #ddd
|
||||||
|
|
||||||
.button
|
.button
|
||||||
display: inline-block
|
display: inline-block
|
||||||
@ -254,6 +256,9 @@ input:not([type="submit"])
|
|||||||
.pagenum
|
.pagenum
|
||||||
display: inline-block
|
display: inline-block
|
||||||
padding: 10px
|
padding: 10px
|
||||||
|
&.total
|
||||||
|
color: #a9a9a9
|
||||||
|
font-style: italic
|
||||||
|
|
||||||
.dlbtn
|
.dlbtn
|
||||||
display: block
|
display: block
|
||||||
|
6
templates/reset_password/html.pug
Normal file
6
templates/reset_password/html.pug
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
h1 Hello, #{display_name}!
|
||||||
|
p You've requested to reset your password on Icy Network.
|
||||||
|
p Click on or copy the following link into your URL bar in order to reset your Icy Network account password:
|
||||||
|
a.activate(href=domain + "/reset/" + reset_token, target="_blank", rel="nofollow")= domain + "/reset/" + reset_token
|
||||||
|
p If you did not request a password reset on Icy Network, please ignore this email.
|
||||||
|
small This email has been sent to you because of an action performed on the IcyNet.eu website.
|
1
templates/reset_password/subject.pug
Normal file
1
templates/reset_password/subject.pug
Normal file
@ -0,0 +1 @@
|
|||||||
|
|Icy Network - Password reset request
|
@ -23,5 +23,7 @@ block body
|
|||||||
input(type="password", name="password", id="password")
|
input(type="password", name="password", id="password")
|
||||||
input(type="submit", value="Log in")
|
input(type="submit", value="Log in")
|
||||||
a#create(href="/register") Create an account
|
a#create(href="/register") Create an account
|
||||||
|
span.divider ·
|
||||||
|
a#create(href="/login/reset") Forgot password?
|
||||||
.right
|
.right
|
||||||
include ../includes/external.pug
|
include ../includes/external.pug
|
||||||
|
@ -19,8 +19,6 @@ block body
|
|||||||
if !token
|
if !token
|
||||||
label(for="password_old") Current Password
|
label(for="password_old") Current Password
|
||||||
input(type="password", name="password_old", id="password_old")
|
input(type="password", name="password_old", id="password_old")
|
||||||
else
|
|
||||||
input(type="hidden", name="token", value=token)
|
|
||||||
label(for="password") New Password
|
label(for="password") New Password
|
||||||
input(type="password", name="password", id="password")
|
input(type="password", name="password", id="password")
|
||||||
label(for="password_repeat") Repeat New Password
|
label(for="password_repeat") Repeat New Password
|
||||||
|
23
views/user/reset_password.pug
Normal file
23
views/user/reset_password.pug
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
extends ../layout.pug
|
||||||
|
block title
|
||||||
|
|Icy Network - Reset Password
|
||||||
|
|
||||||
|
block body
|
||||||
|
.wrapper
|
||||||
|
.boxcont
|
||||||
|
.box#totpcheck
|
||||||
|
h1 Reset your password
|
||||||
|
p Enter your email below in order to reset your password.
|
||||||
|
if message.text
|
||||||
|
if message.error
|
||||||
|
.message.error
|
||||||
|
span #{message.text}
|
||||||
|
else
|
||||||
|
.message
|
||||||
|
span #{message.text}
|
||||||
|
if !sent
|
||||||
|
form#loginForm(method="POST", action=post)
|
||||||
|
input(type="hidden", name="csrf", value=csrf)
|
||||||
|
label(for="email") Email Address
|
||||||
|
input(type="email", name="email", id="email")
|
||||||
|
input(type="submit", value="Continue")
|
Reference in New Issue
Block a user