User can change password

main
Ashelyn Dawn 4 years ago
parent 0d1f1d82b7
commit c777651ce0

@ -11,6 +11,12 @@ validators.bothPasswordsMatch = body('password').custom((pass, {req})=>{
return true return true
}) })
validators.oldPasswordNotSame = body('oldPassword').custom((pass, {req}) => {
if(pass === req.body.password)
throw new Error('New password is the same as old password')
return true
})
validators.validEmail = field => body(field).isString().isEmail() validators.validEmail = field => body(field).isString().isEmail()
.withMessage('Email invalid') .withMessage('Email invalid')

@ -2,6 +2,7 @@ const router = require('express-promise-router')()
const parseJSON = require('body-parser').json() const parseJSON = require('body-parser').json()
const db = require('../db') const db = require('../db')
const ensureAdmin = require('./middleware/ensureAdmin') const ensureAdmin = require('./middleware/ensureAdmin')
const ensureUser = require('./middleware/ensureUser')
const sendgrid = require('@sendgrid/mail') const sendgrid = require('@sendgrid/mail')
sendgrid.setApiKey(process.env.SENDGRID_KEY) sendgrid.setApiKey(process.env.SENDGRID_KEY)
@ -77,4 +78,28 @@ router.delete('/:uuid/admin', ensureAdmin, async (req, res) => {
res.json(user) res.json(user)
}) })
const changePasswordValidation = [
validate.validPassword('password'),
validate.bothPasswordsMatch,
validate.oldPasswordNotSame,
validate.handleApiError
]
router.put('/current/password', parseJSON, changePasswordValidation, ensureUser, async (req, res) => {
const user = await db.user.changePassword(
req.user.uuid,
req.body.oldPassword,
req.body.password
)
if(!user){
return res.status(422).json({errors: [{
param: 'oldPassword',
msg: 'Incorrect password'
}]})
}
res.json(user)
})
module.exports = router; module.exports = router;

@ -73,6 +73,36 @@ user.login = async (email, password) => {
return _user return _user
} }
user.changePassword = async (user_uuid, oldPassword, newPassword) => {
const _user = await user.findById(user_uuid)
if(!_user){
// Avoid early exit timing difference
await bcrypt.hash(oldPassword, saltRounds)
return null
}
const passwordCorrect = await bcrypt.compare(oldPassword, _user.password_hash)
if(!passwordCorrect)
return null
const newHash = await bcrypt.hash(newPassword, saltRounds)
const query = {
text: 'select * from sos.change_password($1, $2)',
values: [
user_uuid,
newHash
]
}
debug(query);
const {rows} = await pg.query(query)
return joinjs.map(rows, mappings, 'userMap', 'user_')[0];
}
user.getOpenEmailLinks = (user_uuid) => user.getOpenEmailLinks = (user_uuid) =>
dbUtil.executeFunction({ dbUtil.executeFunction({
name: 'get_open_email_links_for_user', name: 'get_open_email_links_for_user',

@ -16,6 +16,18 @@ begin
return query select * from sos."user" where user_uuid = _user_uuid; return query select * from sos."user" where user_uuid = _user_uuid;
end; $function$; end; $function$;
create or replace function sos.change_password(_user_uuid uuid, _new_hash text)
returns setof sos.user
language plpgsql
as $function$
begin
update sos."user"
set user_password_hash = _new_hash
where user_uuid = _user_uuid;
return query select * from sos."user" where user_uuid = _user_uuid;
end; $function$;
create or replace function sos.validate_session(_session_uuid uuid) create or replace function sos.validate_session(_session_uuid uuid)
returns setof sos.v_session returns setof sos.v_session
language plpgsql language plpgsql

@ -0,0 +1,40 @@
import {useSetUser} from '~/hooks/useUser'
import Head from 'next/head'
import Router from 'next/router'
import redirect from '~/utils/redirectGetInitialProps'
import {FormController, Input, Button} from '~/components/form'
ChangePassword.getInitialProps = async function({ctx, user}) {
if(!user)
return redirect(ctx, 302, '/login')
if(!user.email_confirmed)
return redirect(ctx, 302, '/account/email/confirm')
return {}
}
export default function ChangePassword() {
const setUser = useSetUser()
function afterChange(user) {
setUser(user);
Router.push('/account')
}
return (
<>
<Head>
<title>Change Password | Society of Socks</title>
</Head>
<h2>Change Password</h2>
<FormController method="put" url="/api/users/current/password" afterSubmit={afterChange}>
<Input label="Current Password" type="password" name="oldPassword" validate={value=>(value.length >= 8)} hint="Password must be at least 8 characters long" />
<Input label="New Password" type="password" name="password" validate={value=>(value.length >= 8)} hint="Password must be at least 8 characters long" />
<Input label="Repeat password" type="password" name="password2" validate={(value, fields)=>(value === fields.password.value)} hint="Passwords must match" />
<Button type="submit">Change Password</Button>
</FormController>
</>
)
}

@ -1,6 +1,7 @@
import {DateTime} from 'luxon' import {DateTime} from 'luxon'
import Head from 'next/head' import Head from 'next/head'
import Router from 'next/router' import Router from 'next/router'
import Link from 'next/link'
import Table from '~/components/table' import Table from '~/components/table'
import useUser from '~/hooks/useUser' import useUser from '~/hooks/useUser'
@ -43,7 +44,7 @@ export default function AccountPage({orders}) {
<p><strong>Email:</strong> {user.email} <button className="buttonLink">Change</button></p> <p><strong>Email:</strong> {user.email} <button className="buttonLink">Change</button></p>
{/* TODO: Store date password was set so we can show "Set on [date]"? */} {/* TODO: Store date password was set so we can show "Set on [date]"? */}
<p><strong>Password:</strong> {!user.password_hash ? 'Unset' : <>Set. <button className="buttonLink">Change</button></>}</p> <p><strong>Password:</strong> {!user.password_hash ? 'Unset' : <>Set. <Link href="/account/change-password"><a>Change</a></Link></>}</p>
</div> </div>
<h3>Your Orders</h3> <h3>Your Orders</h3>

Loading…
Cancel
Save