Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
395 changes: 392 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"homepage": "https://github.com/codydaig/911vm#readme",
"dependencies": {
"@material-ui/core": "^4.4.2",
"@sendgrid/mail": "^6.4.0",
"axios": "^0.19.0",
"babel-cli": "^6.6.5",
"babel-core": "6.24.1",
Expand Down
38 changes: 38 additions & 0 deletions server/controllers/person.controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,42 @@ const login = (req, res) => {
}
}

// reset account
const forgotPassword = (req, res) => {
const emailAddress = req.body.emailAddress;
if(!emailAddress) {
res.status(400).json({error_message: 'Missing email address'})
} else {
Persons.forgotPassword(emailAddress)
.then((data) => {
res.status(201).json({data: data})
})
.catch((err) => {
res.status(400).json({error_message: err.message})
});
}
}

const resetPassword = (req, res) => {
const token = req.body.token;
const emailAddress = token.split('|')[0];
const accessToken = token.split('|')[1];

const password = req.body.password;

if(!token || !emailAddress || !accessToken) {
res.status(400).json({error_message: 'Invalid token'})
} else {
Persons.resetPassword(emailAddress, accessToken, password)
.then((data) => {
res.status(201).json({data: data})
})
.catch((err) => {
res.status(400).json({error_message: err.message})
});
}
}

// GET all volunteers
const get = (req, res) => {
Persons.getAll()
Expand Down Expand Up @@ -200,5 +236,7 @@ module.exports = {
signUp: signUp,
login: login,
protect: protect,
forgotPassword: forgotPassword,
resetPassword: resetPassword,
search: search,
}
4 changes: 4 additions & 0 deletions server/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ app.use(bodyParser.json());

require('./routes.js')(app);

app.get('/auth/*', (req, res) => {
res.status(404).send({data: "Not Found"});
});

app.get('/api/*', (req, res) => {
res.status(404).send({data: "Not Found"});
});
Expand Down
92 changes: 90 additions & 2 deletions server/models/person.model.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const moment = require('moment');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const EmailApp = require('../utils/email')
const neode = require('../schema/index');
const auth = require('../utils/auth');
const bcrypt = require('bcrypt');

const saltRounds = 10;

let Persons = {};
Expand Down Expand Up @@ -83,6 +86,89 @@ Persons.login = (params) => {
})
}

Persons.forgotPassword = (emailAddress) => {
// 1. Look up user with email address
// 2. Generate access token
// 3. send user reset password email with access token
return neode.first('Person', 'email_address', emailAddress)
.then((person) => {
if(person) {
const buf = crypto.randomBytes(24);
const token = buf.toString('hex')
return person.update({
id: person.get('id'),
first_name: person.get('first_name'),
last_name: person.get('last_name'),
email_address: person.get('email_address'),
reset_password_token: token,
reset_password_expires: (new Date()).getTime() + 30 * 60000
})
}
else {
throw new Error('User does not exist.');
}
})
.then((person) => {

const to = person.get('email_address')
const subject = '911 vm password reset'
const html = `Reset link ${process.env.SERVER_ENDPOINT}/reset_password_token/${person.get('email_address')}|${person.get('reset_password_token')}`
const email = new EmailApp(to, subject, html);
email.send()
return 'Reset password email sent.'
})
}

// reset password
// find user with email and reset_password_token
// compare reset_password_expires time vs now
// update user password
// return user auth token
Persons.resetPassword = (emailAddress, token, password) => {
const now = (new Date()).getTime();
const buf = crypto.randomBytes(24);
const randomToken = buf.toString('hex')

return neode.first('Person', {email_address: emailAddress, reset_password_token: token})
.then((person) => {
if(!person) {
throw new Error('User does not exist.');
} else if(person.get('reset_password_expires') < now) {
throw new Error('Invalid token');
} else {
// hash password
const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(password, salt);
return person.update({
id: person.get('id'),
first_name: person.get('first_name'),
last_name: person.get('last_name'),
email_address: person.get('email_address'),
password: hash,
reset_password_token: randomToken,
reset_password_expires: 100
})
}
})
.then((person) => {
const user = {
id: person.get('id'),
first_name: person.get('first_name'),
last_name: person.get('last_name'),
email_address: person.get('email_address'),
phone_number: person.get('phone_number'),
is_admin: person.get('is_admin'),
is_volunteer: person.get('is_volunteer')
}
return user;
})
.then((user) => {
const token = auth.newToken(user);
user.token = token;
return user;
})
}

Persons.getAll = () => {
const query = 'match (p:Person) return p';
return neode.cypher(query, {})
Expand Down Expand Up @@ -146,6 +232,7 @@ Persons.search = (keyword) => {
where p.first_name =~ '(?i)${keyword}.*'
or p.last_name =~ '(?i)${keyword}.*'
or p.email_address =~ '(?i)${keyword}.*'
or p.phone_number =~ '(?i)${keyword}.*'
return p
`;

Expand All @@ -161,7 +248,8 @@ Persons.search = (keyword) => {
'last_name': person.last_name,
'first_name': person.first_name,
'class': person.class,
'start_date': person.start_date
'start_date': person.start_date,
'phone_number': person.phone_number
}
})

Expand Down
7 changes: 5 additions & 2 deletions server/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ module.exports = function(app) {
app.get('/helloworld', controllers.helloWorld);

// Authentication
app.post('/signup', Person.signUp)
app.post('/login', Person.login)
app.post('/auth/signup', Person.signUp)
app.post('/auth/login', Person.login)
app.post('/auth/forgot_password', Person.forgotPassword)
app.post('/auth/reset_password', Person.resetPassword)
// app.get('/auth/forgot_password/:token')

// Middleware to check JWT on all api transactions
// app.use('/api', Person.protect);
Expand Down
9 changes: 9 additions & 0 deletions server/schema/person.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,22 @@ const schema = {
type: 'boolean',
default: true,
},
reset_password_token: {
type: 'string',
empty: true,
},
reset_password_expires: {
type: 'number',
empty: true,
},
has_certification: {
type: 'relationship',
relationship: 'HAS_CERTIFICATION',
direction: 'out',
'cascade': 'detach',
properties: {
expriation_date: {
type: 'number',
empty: true
},
signature_person_id: {
Expand Down
1 change: 0 additions & 1 deletion server/utils/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const config = {
}
}


const newToken = user => {
return jwt.sign({ id: user.id }, config.secrets.jwt, {
expiresIn: config.secrets.jwtExp
Expand Down
33 changes: 33 additions & 0 deletions server/utils/email.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const sgMail = require('@sendgrid/mail');
const RESET_PASSWORD_EMAIL_FROM = process.env.RESET_PASSWORD_EMAIL_FROM
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

class Email {
constructor(to, subject, html) {
this.to = to;
this.from = RESET_PASSWORD_EMAIL_FROM;
this.subject = subject;
this.html = html;
}

send() {
const msg = {
to: this.to,
from: this.from,
subject: this.subject,
text: this.html,
html: this.html
}
// console.log(msg)
sgMail.send(msg).then((res) => {
console.log('sent')
//console.log(res)
})
.catch((err) => {
console.log('err')
//console.log(err)
})
}
}

module.exports = Email