v1.13.0: Reset Password
This commit is contained in:
parent
10c585d230
commit
22ebde8a0b
7 changed files with 134 additions and 11 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "christmas-community",
|
"name": "christmas-community",
|
||||||
"version": "1.12.0",
|
"version": "1.13.0",
|
||||||
"description": "Christmas lists for communities",
|
"description": "Christmas lists for communities",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -3,8 +3,8 @@ const bcrypt = require('bcrypt-nodejs');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { nanoid } = require('nanoid')
|
const { nanoid } = require('nanoid')
|
||||||
|
|
||||||
const SIGNUP_TOKEN_LENGTH = 32
|
const SECRET_TOKEN_LENGTH = 32
|
||||||
const SIGNUP_TOKEN_LIFETIME =
|
const SECRET_TOKEN_LIFETIME =
|
||||||
// One week, approximately. Doesn't need to be perfect.
|
// One week, approximately. Doesn't need to be perfect.
|
||||||
1000 // milliseconds
|
1000 // milliseconds
|
||||||
* 60 // seconds
|
* 60 // seconds
|
||||||
|
@ -31,8 +31,8 @@ module.exports = (db) => {
|
||||||
admin: false,
|
admin: false,
|
||||||
wishlist: [],
|
wishlist: [],
|
||||||
|
|
||||||
signupToken: nanoid(SIGNUP_TOKEN_LENGTH),
|
signupToken: nanoid(SECRET_TOKEN_LENGTH),
|
||||||
expiry: new Date().getTime() + SIGNUP_TOKEN_LIFETIME
|
expiry: new Date().getTime() + SECRET_TOKEN_LIFETIME
|
||||||
|
|
||||||
});
|
});
|
||||||
res.redirect(`/admin-settings/edit/${req.body.newUserUsername.trim()}`)
|
res.redirect(`/admin-settings/edit/${req.body.newUserUsername.trim()}`)
|
||||||
|
@ -48,8 +48,26 @@ module.exports = (db) => {
|
||||||
router.post('/edit/refresh-signup-token/:userToEdit', verifyAuth(), async (req, res) => {
|
router.post('/edit/refresh-signup-token/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
if (!req.user.admin) return res.redirect('/');
|
if (!req.user.admin) return res.redirect('/');
|
||||||
const doc = await db.get(req.params.userToEdit)
|
const doc = await db.get(req.params.userToEdit)
|
||||||
doc.signupToken = nanoid(SIGNUP_TOKEN_LENGTH)
|
doc.signupToken = nanoid(SECRET_TOKEN_LENGTH)
|
||||||
doc.expiry = new Date().getTime() + SIGNUP_TOKEN_LIFETIME
|
doc.expiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
|
||||||
|
await db.put(doc)
|
||||||
|
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/edit/resetpw/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
|
if (!req.user.admin) return res.redirect('/');
|
||||||
|
const doc = await db.get(req.params.userToEdit)
|
||||||
|
doc.pwToken = nanoid(SECRET_TOKEN_LENGTH)
|
||||||
|
doc.pwExpiry = new Date().getTime() + SECRET_TOKEN_LIFETIME
|
||||||
|
await db.put(doc)
|
||||||
|
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/edit/cancelresetpw/:userToEdit', verifyAuth(), async (req, res) => {
|
||||||
|
if (!req.user.admin) return res.redirect('/');
|
||||||
|
const doc = await db.get(req.params.userToEdit)
|
||||||
|
delete doc.pwToken
|
||||||
|
delete doc.pwExpiry
|
||||||
await db.put(doc)
|
await db.put(doc)
|
||||||
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
return res.redirect(`/admin-settings/edit/${req.params.userToEdit}`)
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,11 +5,11 @@ module.exports = (db) => {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get('/:code', async (req, res) => {
|
router.get('/:code', async (req, res) => {
|
||||||
const { doc } = (await db.allDocs({ include_docs: true }))
|
const row = (await db.allDocs({ include_docs: true }))
|
||||||
.rows
|
.rows
|
||||||
.find(({ doc }) => doc.signupToken === req.params.code)
|
.find(({ doc }) => doc.signupToken === req.params.code)
|
||||||
|
|
||||||
res.render('confirm-account', { doc })
|
res.render('confirm-account', { doc: row ? row.doc : undefined })
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:code', async (req, res) => {
|
router.post('/:code', async (req, res) => {
|
||||||
|
|
|
@ -28,6 +28,7 @@ module.exports = ({ db, config }) => {
|
||||||
|
|
||||||
router.use('/login', require('./login')());
|
router.use('/login', require('./login')());
|
||||||
router.use('/logout', require('./logout')());
|
router.use('/logout', require('./logout')());
|
||||||
|
router.use('/resetpw', require('./resetpw')(db));
|
||||||
router.use('/confirm-account', require('./confirm-account')(db));
|
router.use('/confirm-account', require('./confirm-account')(db));
|
||||||
|
|
||||||
router.use('/wishlist', require('./wishlist')(db));
|
router.use('/wishlist', require('./wishlist')(db));
|
||||||
|
|
45
routes/resetpw/index.js
Normal file
45
routes/resetpw/index.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
const bcrypt = require('bcrypt-nodejs');
|
||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
module.exports = (db) => {
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get('/:code', async (req, res) => {
|
||||||
|
const row = (await db.allDocs({ include_docs: true }))
|
||||||
|
.rows
|
||||||
|
.find(({ doc }) => doc.pwToken === req.params.code)
|
||||||
|
|
||||||
|
|
||||||
|
res.render('resetpw', { doc: row ? row.doc : undefined })
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/:code', async (req, res) => {
|
||||||
|
const { doc } = (await db.allDocs({ include_docs: true }))
|
||||||
|
.rows
|
||||||
|
.find(({ doc }) => doc.pwToken === req.params.code)
|
||||||
|
|
||||||
|
if (doc.expiry < new Date().getTime()) return res.redirect(`/resetpw/${req.params.code}`)
|
||||||
|
|
||||||
|
bcrypt.hash(req.body.password, null, null, async (err, passwordHash) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
doc.password = passwordHash
|
||||||
|
delete doc.pwToken
|
||||||
|
delete doc.pwExpiry
|
||||||
|
|
||||||
|
await db.put(doc)
|
||||||
|
|
||||||
|
req.login({ _id: doc._id }, err => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err)
|
||||||
|
req.flash('error', err.message)
|
||||||
|
return res.redirect('/')
|
||||||
|
}
|
||||||
|
req.flash('success', `Welcome to ${_CC.config.siteTitle}!`);
|
||||||
|
res.redirect('/');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return router;
|
||||||
|
};
|
|
@ -10,7 +10,7 @@ block content
|
||||||
.columns
|
.columns
|
||||||
.column
|
.column
|
||||||
if user.signupToken
|
if user.signupToken
|
||||||
- const link = `${_CC.config.base}confirm-account/${user.signupToken}`
|
- const signupLink = `${_CC.config.base}confirm-account/${user.signupToken}`
|
||||||
.box(style='overflow: hidden;')
|
.box(style='overflow: hidden;')
|
||||||
.columns(style='margin-bottom: 0;')
|
.columns(style='margin-bottom: 0;')
|
||||||
.column.is-narrow(style='padding-bottom: 0;')
|
.column.is-narrow(style='padding-bottom: 0;')
|
||||||
|
@ -30,7 +30,7 @@ block content
|
||||||
form(method='POST', action=`${_CC.config.base}admin-settings/edit/refresh-signup-token/${user._id}`)
|
form(method='POST', action=`${_CC.config.base}admin-settings/edit/refresh-signup-token/${user._id}`)
|
||||||
input.button.is-rounded(type='submit', value='Generate New Link')
|
input.button.is-rounded(type='submit', value='Generate New Link')
|
||||||
.level-item
|
.level-item
|
||||||
a(href=link, style='font-family: monospaced; word-break: break-all;')= link
|
a(href=signupLink, style='font-family: monospaced; word-break: break-all;')= signupLink
|
||||||
h2 Change Name
|
h2 Change Name
|
||||||
form(action=`${_CC.config.base}admin-settings/edit/rename/${user._id}`, method='POST')
|
form(action=`${_CC.config.base}admin-settings/edit/rename/${user._id}`, method='POST')
|
||||||
.field
|
.field
|
||||||
|
@ -42,6 +42,31 @@ block content
|
||||||
.field
|
.field
|
||||||
.control
|
.control
|
||||||
input.button.is-primary(type='submit' value='Change Username')
|
input.button.is-primary(type='submit' value='Change Username')
|
||||||
|
h2(style='margin-bottom: 1em;') Reset Password
|
||||||
|
if user.pwToken
|
||||||
|
- const resetLink = `${_CC.config.base}resetpw/${user.pwToken}`
|
||||||
|
p There is a reset password link for this user.
|
||||||
|
if user.pwExpiry > new Date().getTime()
|
||||||
|
span It expires #{_CC.require('moment')(user.pwExpiry).fromNow()}
|
||||||
|
else
|
||||||
|
span.has-text-weight-bold.has-text-danger It expired #{_CC.require('moment')(user.pwExpiry).fromNow()}
|
||||||
|
a(href=resetLink)= resetLink
|
||||||
|
.columns
|
||||||
|
.column.is-narrow
|
||||||
|
form(method='POST', action=`${_CC.config.base}admin-settings/edit/resetpw/${user._id}`)
|
||||||
|
.field
|
||||||
|
.control
|
||||||
|
input.button.is-primary(type='submit' value='Refresh Password Reset Link')
|
||||||
|
.column.is-narrow
|
||||||
|
form(method='POST', action=`${_CC.config.base}admin-settings/edit/cancelresetpw/${user._id}`)
|
||||||
|
.field
|
||||||
|
.control
|
||||||
|
input.button.is-info(type='submit' value='Cancel Password Reset Link')
|
||||||
|
else
|
||||||
|
form(method='POST', action=`${_CC.config.base}admin-settings/edit/resetpw/${user._id}`)
|
||||||
|
.field
|
||||||
|
.control
|
||||||
|
input.button.is-danger(type='submit' value='Create Password Reset Link')
|
||||||
.column.is-narrow
|
.column.is-narrow
|
||||||
h2 Irreversible Deletion
|
h2 Irreversible Deletion
|
||||||
form(method='POST', action=`${_CC.config.base}admin-settings/edit/remove/${user._id}`)
|
form(method='POST', action=`${_CC.config.base}admin-settings/edit/remove/${user._id}`)
|
||||||
|
|
34
views/resetpw.pug
Normal file
34
views/resetpw.pug
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
extends layout.pug
|
||||||
|
|
||||||
|
mixin icon(c, text)
|
||||||
|
.columns.is-vcentered.is-mobile
|
||||||
|
.column.is-narrow
|
||||||
|
span.icon.is-large
|
||||||
|
i.fa-3x(class=c)
|
||||||
|
.column #{text}
|
||||||
|
|
||||||
|
block title
|
||||||
|
if doc
|
||||||
|
h1 #{config.siteTitle} | Reset Password
|
||||||
|
else
|
||||||
|
h1 #{config.siteTitle} | Reset Link Invalid
|
||||||
|
|
||||||
|
block content
|
||||||
|
if doc
|
||||||
|
if doc.pwExpiry > new Date().getTime()
|
||||||
|
+icon('fas fa-smile-beam', `Hello ${doc._id}! Please set your password here.`)
|
||||||
|
form(method='POST')
|
||||||
|
.field
|
||||||
|
label.label Password
|
||||||
|
.control.has-icons-left
|
||||||
|
input.input(type='password', name='password', placeholder='pa$$word!')
|
||||||
|
span.icon.is-small.is-left
|
||||||
|
i.fas.fa-lock
|
||||||
|
.field
|
||||||
|
.control
|
||||||
|
input.button.is-primary(type='submit' value=`Reset Password`)
|
||||||
|
else
|
||||||
|
+icon('fas fa-frown-open', 'Your reset link has expired. Please ask for a new one.')
|
||||||
|
else
|
||||||
|
+icon('fas fa-frown-open', "This reset link isn't valid, perhaps the link was canceled or some characters at the end got cut off?")
|
||||||
|
|
Loading…
Reference in a new issue