2020-05-04 01:16:54 +00:00
|
|
|
const { Controller } = require('libflitter')
|
|
|
|
const zxcvbn = require('zxcvbn')
|
|
|
|
|
|
|
|
class PasswordController extends Controller {
|
|
|
|
static get services() {
|
2020-05-25 20:45:26 +00:00
|
|
|
return [...super.services, 'auth', 'jobs', 'models']
|
2020-05-04 01:16:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async get_resets(req, res, next) {
|
|
|
|
return res.api(req.user.password_resets.map(x => {
|
|
|
|
return {
|
|
|
|
reset_on: x.reset_on,
|
|
|
|
reason: x.reason,
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
async get_app_passwords(req, res, next) {
|
|
|
|
return res.api(req.user.app_passwords.map(x => {
|
|
|
|
return {
|
|
|
|
created: x.created,
|
|
|
|
expires: x.expires,
|
|
|
|
active: x.active,
|
|
|
|
name: x.name ?? '(unnamed)',
|
|
|
|
uuid: x.uuid,
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
async create_app_password(req, res, next) {
|
|
|
|
if ( !req.body.name )
|
|
|
|
return res.status(400)
|
|
|
|
.message('Missing required field: name')
|
|
|
|
.api()
|
|
|
|
|
|
|
|
const { password, record } = await req.user.app_password(req.body.name)
|
|
|
|
await req.user.save()
|
|
|
|
|
|
|
|
return res.api({
|
|
|
|
password,
|
|
|
|
name: req.body.name,
|
|
|
|
uuid: record.uuid,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async delete_app_password(req, res, next) {
|
|
|
|
if ( !req.params.uuid )
|
|
|
|
return res.status(400)
|
|
|
|
.message('Missing required parameter: uuid')
|
|
|
|
.api()
|
|
|
|
|
|
|
|
const match = req.user.app_passwords.filter(x => x.uuid === req.params.uuid)[0]
|
|
|
|
if ( !match )
|
|
|
|
return res.status(400)
|
|
|
|
.message('App password not found with that UUID.')
|
|
|
|
.api()
|
|
|
|
|
|
|
|
req.user.app_passwords = req.user.app_passwords.filter(x => x.uuid !== req.params.uuid)
|
|
|
|
await req.user.save()
|
|
|
|
return res.api()
|
|
|
|
}
|
|
|
|
|
|
|
|
async reset_password(req, res, next) {
|
|
|
|
if ( !req.body.password )
|
|
|
|
return res.status(400)
|
|
|
|
.message('Missing required field: password')
|
|
|
|
.api()
|
|
|
|
|
|
|
|
// Verify password complexity
|
|
|
|
const min_score = 3
|
|
|
|
const result = zxcvbn(req.body.password)
|
|
|
|
if ( result.score < min_score )
|
|
|
|
return res.status(400)
|
|
|
|
.message(`Password does not meet the minimum complexity score of ${min_score}.`)
|
|
|
|
.api()
|
|
|
|
|
|
|
|
// Make sure it's not a re-do
|
|
|
|
for ( const old_pw of req.user.password_resets ) {
|
|
|
|
if ( await old_pw.check(req.body.password) ) {
|
|
|
|
return res.status(400)
|
|
|
|
.message(`This password is a duplicate of one of your previous passwords.`)
|
|
|
|
.api()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the password reset
|
|
|
|
const reset = await req.user.reset_password(req.body.password)
|
|
|
|
await req.user.save()
|
2020-05-20 14:56:03 +00:00
|
|
|
if ( req.trap.has_trap() && req.trap.get_trap() === 'password_reset' ) await req.trap.end()
|
2020-05-04 01:16:54 +00:00
|
|
|
|
|
|
|
// invalidate existing tokens and other logins
|
|
|
|
const flitter = await this.auth.get_provider('flitter')
|
|
|
|
await flitter.logout(req)
|
|
|
|
await req.user.kickout()
|
2020-05-25 20:45:26 +00:00
|
|
|
req.trust.unassume()
|
2020-05-04 01:16:54 +00:00
|
|
|
return res.api()
|
|
|
|
}
|
2020-05-25 20:45:26 +00:00
|
|
|
|
|
|
|
async request_reset(req, res, next) {
|
|
|
|
if ( !req.body.email )
|
|
|
|
return res.status(400)
|
|
|
|
.message('Missing required field: email')
|
|
|
|
.api()
|
|
|
|
|
|
|
|
const User = this.models.get('auth:User')
|
|
|
|
const user = await User.findOne({ email: req.body.email })
|
|
|
|
|
|
|
|
if ( user ) {
|
|
|
|
const reset_queue = this.jobs.queue('password_resets')
|
|
|
|
await reset_queue.add('PasswordReset', { user_id: user.id })
|
|
|
|
}
|
|
|
|
|
|
|
|
return res.api({ success: true })
|
|
|
|
}
|
2020-05-04 01:16:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = exports = PasswordController
|