A whole load of stuff - Added password reset, changed error returns to throws and some other things
This commit is contained in:
parent
b4b88a5657
commit
6e13dce845
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",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@ -2269,8 +2279,7 @@
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||
},
|
||||
"graceful-readlink": {
|
||||
"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",
|
||||
"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": {
|
||||
"version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
||||
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
|
||||
@ -5082,6 +5099,11 @@
|
||||
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
|
||||
"dev": true
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
|
||||
"integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
|
@ -41,6 +41,7 @@
|
||||
"express": "^4.15.3",
|
||||
"express-rate-limit": "^2.9.0",
|
||||
"express-session": "^1.15.3",
|
||||
"fs-extra": "^4.0.2",
|
||||
"gm": "^1.23.0",
|
||||
"knex": "^0.13.0",
|
||||
"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 path from 'path'
|
||||
import util from 'util'
|
||||
|
||||
import Promise from 'bluebird'
|
||||
const fs = Promise.promisifyAll(require('fs'))
|
||||
import fs from 'fs-extra'
|
||||
|
||||
let lfs
|
||||
|
||||
@ -57,7 +55,7 @@ async function initializeLogger () {
|
||||
let logPath = path.resolve(config.logger.file)
|
||||
|
||||
try {
|
||||
await fs.accessAsync(logPath, fs.W_OK)
|
||||
await fs.access(logPath, fs.W_OK)
|
||||
lfs = fs.createWriteStream(logPath, {flags: 'a'})
|
||||
} catch (e) {
|
||||
lfs = null
|
||||
|
@ -84,7 +84,7 @@ const API = {
|
||||
getAllUsers: async function (page, adminId) {
|
||||
let count = await Models.User.query().count('id as ids')
|
||||
if (!count.length || !count[0]['ids'] || isNaN(page)) {
|
||||
return { error: 'No users found' }
|
||||
throw new Error('No users found')
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
@ -108,7 +108,7 @@ const API = {
|
||||
getAllClients: async function (page) {
|
||||
let count = await Models.OAuth2Client.query().count('id as ids')
|
||||
if (!count.length || !count[0]['ids'] || isNaN(page)) {
|
||||
return { error: 'No clients found' }
|
||||
return { error: 'No clients' }
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
@ -141,7 +141,7 @@ const API = {
|
||||
]
|
||||
|
||||
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)
|
||||
|
||||
@ -149,20 +149,20 @@ const API = {
|
||||
await Models.OAuth2Client.query().patchAndFetchById(id, data)
|
||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||
} catch (e) {
|
||||
return { error: 'No such client' }
|
||||
throw new Error('No such client')
|
||||
}
|
||||
|
||||
return {}
|
||||
},
|
||||
// Create a new secret for a client
|
||||
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)
|
||||
|
||||
try {
|
||||
await Models.OAuth2Client.query().patchAndFetchById(id, {secret: secret})
|
||||
} catch (e) {
|
||||
return { error: 'No such client' }
|
||||
throw new Error('No such client')
|
||||
}
|
||||
|
||||
return {}
|
||||
@ -174,7 +174,7 @@ const API = {
|
||||
]
|
||||
|
||||
data = dataFilter(data, fields, ['scope'])
|
||||
if (!data) return { error: 'Missing fields' }
|
||||
if (!data) throw new Error('Missing fields')
|
||||
|
||||
let obj = Object.assign({
|
||||
secret: Users.Hash(16),
|
||||
@ -187,7 +187,7 @@ const API = {
|
||||
},
|
||||
// Remove a client and all associated data
|
||||
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.OAuth2AuthorizedClient.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) {
|
||||
let count = await Models.Ban.query().count('id as ids')
|
||||
if (!count.length || !count[0]['ids'] || isNaN(page)) {
|
||||
return {error: 'No bans on record'}
|
||||
throw new Error('No bans on record')
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
@ -225,12 +225,12 @@ const API = {
|
||||
addBan: async function (data, adminId) {
|
||||
let user = await Users.User.get(parseInt(data.user_id))
|
||||
|
||||
if (!user) return {error: 'No such user.'}
|
||||
if (user.id === adminId) return {error: 'Cannot ban yourself!'}
|
||||
if (!user) throw new Error('No such user.')
|
||||
if (user.id === adminId) throw new Error('Cannot ban yourself!')
|
||||
|
||||
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 = {
|
||||
reason: data.reason || 'Unspecified ban',
|
||||
|
@ -79,12 +79,11 @@ const API = {
|
||||
}
|
||||
|
||||
// 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 !== '') {
|
||||
let getByEmail = await UAPI.User.get(udataLimited.email)
|
||||
if (getByEmail) {
|
||||
await API.Common.new(service, identifier, getByEmail)
|
||||
return {error: null, user: 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.')
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,8 +157,12 @@ const API = {
|
||||
}
|
||||
}
|
||||
|
||||
let newUser = await API.Common.newUser(identifier, uid, newUData)
|
||||
if (!newUser) return {error: 'Failed to create user.'}
|
||||
let newUser
|
||||
try {
|
||||
newUser = await API.Common.newUser(identifier, uid, newUData)
|
||||
} catch (e) {
|
||||
return {error: e.message}
|
||||
}
|
||||
|
||||
return {error: null, user: newUser}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import Promise from 'bluebird'
|
||||
|
||||
import http from '../../scripts/http'
|
||||
|
||||
const fs = Promise.promisifyAll(require('fs'))
|
||||
const fs = require('fs-extra')
|
||||
|
||||
const uploads = path.join(__dirname, '../../', 'usercontent')
|
||||
const images = path.join(uploads, 'images')
|
||||
@ -49,8 +49,8 @@ function saneFields (fields) {
|
||||
}
|
||||
|
||||
async function bailOut (file, error) {
|
||||
await fs.unlinkAsync(file)
|
||||
return { error: error }
|
||||
await fs.unlink(file)
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
async function imageBase64 (baseObj) {
|
||||
@ -68,7 +68,7 @@ async function imageBase64 (baseObj) {
|
||||
let fpath = path.join(images, imageName)
|
||||
|
||||
try {
|
||||
await fs.writeFileAsync(fpath, imgData.data)
|
||||
await fs.writeFile(fpath, imgData.data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
@ -93,11 +93,11 @@ async function downloadImage (imgUrl, designation) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {fileName: imageName}
|
||||
return imageName
|
||||
}
|
||||
|
||||
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]
|
||||
if (file.size > maxFileSize) return bailOut(file.path, 'Image is too large! 1 MB max')
|
||||
@ -158,7 +158,7 @@ async function uploadImage (identifier, fields, files) {
|
||||
})
|
||||
})
|
||||
|
||||
await fs.unlinkAsync(file)
|
||||
await fs.unlink(file)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return bailOut(file, 'An error occured while cropping.')
|
||||
|
@ -2,16 +2,13 @@ import path from 'path'
|
||||
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 notp from 'notp'
|
||||
import base32 from 'thirty-two'
|
||||
import emailer from './emailer'
|
||||
import uuidV1 from 'uuid/v1'
|
||||
|
||||
import Promise from 'bluebird'
|
||||
const fs = Promise.promisifyAll(require('fs'))
|
||||
import fs from 'fs-extra'
|
||||
|
||||
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 +175,7 @@ const API = {
|
||||
},
|
||||
update: async function (user, data) {
|
||||
user = await API.User.ensureObject(user)
|
||||
if (!user) return {error: 'No such user.'}
|
||||
if (!user) throw new Error('No such user.')
|
||||
|
||||
data = Object.assign({
|
||||
updated_at: new Date()
|
||||
@ -191,20 +188,20 @@ const API = {
|
||||
let uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
|
||||
let pathOf = path.join(uploadsDir, fileName)
|
||||
|
||||
if (!await exists(pathOf)) {
|
||||
return {error: 'No such file'}
|
||||
if (!await fs.exists(pathOf)) {
|
||||
throw new Error('No such file')
|
||||
}
|
||||
|
||||
// Delete previous upload
|
||||
if (user.avatar_file != null) {
|
||||
let file = path.join(uploadsDir, user.avatar_file)
|
||||
if (await exists(file)) {
|
||||
await fs.unlinkAsync(file)
|
||||
if (await fs.exists(file)) {
|
||||
await fs.unlink(file)
|
||||
}
|
||||
}
|
||||
|
||||
await API.User.update(user, {avatar_file: fileName})
|
||||
return { file: fileName }
|
||||
return fileName
|
||||
},
|
||||
removeAvatar: async function (user) {
|
||||
user = await API.User.ensureObject(user, ['avatar_file'])
|
||||
@ -212,7 +209,7 @@ const API = {
|
||||
if (!user.avatar_file) return {}
|
||||
|
||||
let file = path.join(uploadsDir, user.avatar_file)
|
||||
if (await exists(file)) {
|
||||
if (await fs.exists(file)) {
|
||||
await fs.unlinkAsync(file)
|
||||
}
|
||||
|
||||
@ -252,7 +249,7 @@ const API = {
|
||||
return bcryptTask({task: 'compare', password: password, hash: user.password})
|
||||
},
|
||||
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
|
||||
|
||||
let user = await API.User.get(getToken[0].user_id)
|
||||
@ -359,12 +356,12 @@ const API = {
|
||||
|
||||
let userTest = await API.User.get(regdata.username)
|
||||
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)
|
||||
if (emailTest) {
|
||||
return {error: 'This email address is already registered!'}
|
||||
throw new Error('This email address is already registered!')
|
||||
}
|
||||
|
||||
// Create user
|
||||
@ -393,11 +390,66 @@ const API = {
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
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 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
|
||||
|
||||
let user = await API.User.get(getToken[0].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: {
|
||||
|
@ -53,7 +53,7 @@ const News = {
|
||||
if (page < 1) page = 1
|
||||
|
||||
if (!count.length || !count[0]['ids'] || isNaN(page)) {
|
||||
return {error: 'No articles found'}
|
||||
return { error: 'No news' }
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
@ -100,7 +100,7 @@ const News = {
|
||||
}
|
||||
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
|
@ -128,10 +128,7 @@ apiRouter.post('/client/new', wrap(async (req, res) => {
|
||||
return res.status(400).jsonp({error: 'Invalid session'})
|
||||
}
|
||||
|
||||
let update = await API.createClient(req.body, req.session.user)
|
||||
if (update.error) {
|
||||
return res.status(400).jsonp({error: update.error})
|
||||
}
|
||||
await API.createClient(req.body, req.session.user)
|
||||
|
||||
res.status(204).end()
|
||||
}))
|
||||
@ -145,10 +142,7 @@ apiRouter.post('/client/update', wrap(async (req, res) => {
|
||||
return res.status(400).jsonp({error: 'Invalid session'})
|
||||
}
|
||||
|
||||
let update = await API.updateClient(id, req.body)
|
||||
if (update.error) {
|
||||
return res.status(400).jsonp({error: update.error})
|
||||
}
|
||||
await API.updateClient(id, req.body)
|
||||
|
||||
res.status(204).end()
|
||||
}))
|
||||
@ -160,9 +154,6 @@ apiRouter.post('/client/new_secret/:id', wrap(async (req, res) => {
|
||||
}
|
||||
|
||||
let client = await API.newSecret(id)
|
||||
if (client.error) {
|
||||
return res.status(400).jsonp({error: client.error})
|
||||
}
|
||||
|
||||
res.jsonp(client)
|
||||
}))
|
||||
@ -174,9 +165,6 @@ apiRouter.post('/client/delete/:id', wrap(async (req, res) => {
|
||||
}
|
||||
|
||||
let client = await API.removeClient(id)
|
||||
if (client.error) {
|
||||
return res.status(400).jsonp({error: client.error})
|
||||
}
|
||||
|
||||
res.jsonp(client)
|
||||
}))
|
||||
@ -203,9 +191,6 @@ apiRouter.post('/ban/pardon/:id', wrap(async (req, res) => {
|
||||
}
|
||||
|
||||
let ban = await API.removeBan(id)
|
||||
if (ban.error) {
|
||||
return res.status(400).jsonp({error: ban.error})
|
||||
}
|
||||
|
||||
res.jsonp(ban)
|
||||
}))
|
||||
@ -217,16 +202,13 @@ apiRouter.post('/ban', wrap(async (req, res) => {
|
||||
}
|
||||
|
||||
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)
|
||||
}))
|
||||
|
||||
apiRouter.use((err, req, res, next) => {
|
||||
console.error(err)
|
||||
return res.status(500).jsonp({error: 'Internal server error'})
|
||||
return res.status(400).jsonp({error: err.message})
|
||||
})
|
||||
|
||||
router.use('/api', apiRouter)
|
||||
|
@ -317,9 +317,10 @@ router.post('/news/edit/:id', wrap(async (req, res, next) => {
|
||||
return res.status(400).jsonp({error: 'Content is required.'})
|
||||
}
|
||||
|
||||
let result = await News.edit(id, req.body)
|
||||
if (result.error) {
|
||||
return res.status(400).jsonp({error: result.error})
|
||||
try {
|
||||
await News.edit(id, req.body)
|
||||
} catch (e) {
|
||||
return res.status(400).jsonp({error: e.message})
|
||||
}
|
||||
|
||||
res.status(204).end()
|
||||
@ -363,19 +364,19 @@ async function promiseForm (req) {
|
||||
router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => {
|
||||
if (!req.session.user) return next()
|
||||
let data = await promiseForm(req)
|
||||
let result = await Image.uploadImage(req.session.user.username, data.fields, data.files)
|
||||
|
||||
if (result.error) {
|
||||
return res.status(400).jsonp({error: result.error})
|
||||
let avatarFile
|
||||
|
||||
try {
|
||||
let result = await Image.uploadImage(req.session.user.username, data.fields, data.files)
|
||||
|
||||
avatarFile = await API.User.changeAvatar(req.session.user, result.file)
|
||||
} catch (e) {
|
||||
return res.status(400).jsonp({error: e.message})
|
||||
}
|
||||
|
||||
let avatarUpdate = await API.User.changeAvatar(req.session.user, result.file)
|
||||
if (avatarUpdate.error) {
|
||||
return res.status(400).jsonp({error: avatarUpdate.error})
|
||||
}
|
||||
|
||||
if (avatarUpdate.file) {
|
||||
req.session.user.avatar_file = avatarUpdate.file
|
||||
if (avatarFile) {
|
||||
req.session.user.avatar_file = avatarFile
|
||||
}
|
||||
|
||||
res.status(200).jsonp({})
|
||||
|
@ -1,10 +1,9 @@
|
||||
import fs from 'fs'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import express from 'express'
|
||||
import RateLimit from 'express-rate-limit'
|
||||
import ensureLogin from '../../scripts/ensureLogin'
|
||||
import config from '../../scripts/load-config'
|
||||
import exists from '../../scripts/existsSync'
|
||||
import wrap from '../../scripts/asyncRoute'
|
||||
import http from '../../scripts/http'
|
||||
import API from '../api'
|
||||
@ -135,6 +134,28 @@ function formKeep (req, res, 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) => {
|
||||
if (req.session.user) return redirectLogin(req, res)
|
||||
if (req.query.returnTo) {
|
||||
@ -383,6 +404,61 @@ router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next
|
||||
res.redirect(uri)
|
||||
}))
|
||||
|
||||
// Password reset
|
||||
router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
||||
if (req.session.user) return next()
|
||||
if (!req.body.email) {
|
||||
return formError(req, res, 'You need to enter your email address.')
|
||||
}
|
||||
|
||||
let email = req.body.email
|
||||
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)
|
||||
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)
|
||||
|
||||
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()
|
||||
@ -448,18 +524,19 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
|
||||
|
||||
// Hash the password
|
||||
let hash = await API.User.Register.hashPassword(password)
|
||||
let newUser
|
||||
|
||||
// Attempt to create the user
|
||||
let newUser = await API.User.Register.newAccount({
|
||||
username: username,
|
||||
display_name: cleanString(displayName),
|
||||
password: hash,
|
||||
email: email,
|
||||
ip_address: req.realIP
|
||||
})
|
||||
|
||||
if (!newUser || newUser.error != null) {
|
||||
return formError(req, res, newUser.error)
|
||||
try {
|
||||
newUser = await API.User.Register.newAccount({
|
||||
username: username,
|
||||
display_name: cleanString(displayName),
|
||||
password: hash,
|
||||
email: email,
|
||||
ip_address: req.realIP
|
||||
})
|
||||
} catch (e) {
|
||||
return formError(req, res, e.message)
|
||||
}
|
||||
|
||||
// Do not include activation link message when the user is already activated
|
||||
@ -492,12 +569,12 @@ router.post('/user/manage', csrfValidation, wrap(async (req, res, next) => {
|
||||
return res.redirect('/user/manage')
|
||||
}
|
||||
|
||||
let success = await API.User.update(req.session.user, {
|
||||
display_name: displayName
|
||||
})
|
||||
|
||||
if (success.error) {
|
||||
return formError(req, res, success.error)
|
||||
try {
|
||||
await API.User.update(req.session.user, {
|
||||
display_name: displayName
|
||||
})
|
||||
} catch (e) {
|
||||
return formError(req, res, e.message)
|
||||
}
|
||||
|
||||
req.session.user.display_name = displayName
|
||||
@ -532,12 +609,12 @@ router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async
|
||||
|
||||
password = await API.User.Register.hashPassword(password)
|
||||
|
||||
let success = await API.User.update(user, {
|
||||
password: password
|
||||
})
|
||||
|
||||
if (success.error) {
|
||||
return formError(req, res, success.error)
|
||||
try {
|
||||
await API.User.update(user, {
|
||||
password: password
|
||||
})
|
||||
} catch (e) {
|
||||
return formError(req, res, e.message)
|
||||
}
|
||||
|
||||
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
|
||||
@ -591,12 +668,12 @@ router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (re
|
||||
return formError(req, res, 'This email is already taken.')
|
||||
}
|
||||
|
||||
let success = await API.User.update(user, {
|
||||
email: newEmail
|
||||
})
|
||||
|
||||
if (success.error) {
|
||||
return formError(req, res, success.error)
|
||||
try {
|
||||
await API.User.update(user, {
|
||||
email: newEmail
|
||||
})
|
||||
} catch (e) {
|
||||
return formError(req, res, e.message)
|
||||
}
|
||||
|
||||
// TODO: Send necessary emails
|
||||
@ -618,7 +695,7 @@ router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (re
|
||||
const docsDir = path.join(__dirname, '../../documents')
|
||||
router.get('/docs/:name', wrap(async (req, res, next) => {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -650,9 +727,11 @@ router.post('/news/compose', newsPrivilege, csrfValidation, wrap(async (req, res
|
||||
return formError(req, res, 'Required fields missing!')
|
||||
}
|
||||
|
||||
let result = await News.compose(req.session.user, req.body)
|
||||
if (result.error) {
|
||||
return formError(req, res, result.error)
|
||||
let result
|
||||
try {
|
||||
result = await News.compose(req.session.user, req.body)
|
||||
} catch (e) {
|
||||
return formError(req, res, e.message)
|
||||
}
|
||||
|
||||
res.redirect('/news/' + result.id + '-' + result.slug)
|
||||
|
@ -12,15 +12,20 @@ function buildTemplateScript (id, ctx) {
|
||||
function paginationButton (pages) {
|
||||
var html = '<div class="pgn">'
|
||||
html += '<span class="pagenum">Page ' + pages.page + ' of ' + pages.pages + '</span>'
|
||||
|
||||
if (pages.page > 1) {
|
||||
html += '<div class="button" data-page="' + (pages.page - 1) + '">Previous</div>'
|
||||
}
|
||||
|
||||
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) {
|
||||
html += '<div class="button" data-page="' + (pages.page + 1) + '">Next</div>'
|
||||
}
|
||||
|
||||
html += '<span class="pagenum total">(' + pages.total + ' Total Entries)</span>'
|
||||
html += '</div>'
|
||||
return html
|
||||
}
|
||||
@ -271,11 +276,9 @@ $(document).ready(function () {
|
||||
|
||||
setInterval(function () {
|
||||
$.get({
|
||||
url: '/admin/access',
|
||||
success: function (data) {
|
||||
if (data && data.access) return
|
||||
window.location.reload()
|
||||
}
|
||||
url: '/admin/access'
|
||||
}).fail(function () {
|
||||
window.location.reload()
|
||||
})
|
||||
}, 30000)
|
||||
})
|
||||
|
@ -206,6 +206,8 @@ input:not([type="submit"])
|
||||
font-size: 120%
|
||||
margin: 10px 0
|
||||
cursor: pointer
|
||||
&.active
|
||||
background-color: #ddd
|
||||
|
||||
.button
|
||||
display: inline-block
|
||||
@ -254,6 +256,9 @@ input:not([type="submit"])
|
||||
.pagenum
|
||||
display: inline-block
|
||||
padding: 10px
|
||||
&.total
|
||||
color: #a9a9a9
|
||||
font-style: italic
|
||||
|
||||
.dlbtn
|
||||
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 for 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="submit", value="Log in")
|
||||
a#create(href="/register") Create an account
|
||||
span.divider ·
|
||||
a#create(href="/login/reset") Forgot password?
|
||||
.right
|
||||
include ../includes/external.pug
|
||||
|
@ -19,8 +19,6 @@ block body
|
||||
if !token
|
||||
label(for="password_old") Current Password
|
||||
input(type="password", name="password_old", id="password_old")
|
||||
else
|
||||
input(type="hidden", name="token", value=token)
|
||||
label(for="password") New Password
|
||||
input(type="password", name="password", id="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