Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prod #9

Merged
merged 29 commits into from
Jun 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e99c734
Merge pull request #2 from andrewjford/master
andrewjford May 5, 2019
8f13755
add verification token sql
andrewjford May 11, 2019
7a972fd
fix verification token sql
andrewjford May 11, 2019
7ca160d
update verification sql
andrewjford May 11, 2019
20e303b
initial setup of verification tokens model
andrewjford May 11, 2019
4bd1738
add sendgrid dependency
andrewjford May 11, 2019
112a848
add send grid mailer functionality, currently a unique endpoint that …
andrewjford May 11, 2019
9f37943
base of email verification endpoint setup
andrewjford May 11, 2019
182b123
comment out validation routing for now
andrewjford May 11, 2019
befc2bf
Merge pull request #3 from andrewjford/dev
andrewjford May 11, 2019
b3ffd8c
creates verif token and sends email on account create
andrewjford May 12, 2019
07ce4e0
add sequelize and update packages
andrewjford May 12, 2019
590dd87
WIP sequelize'ing - verification token insert appears working on mail…
andrewjford May 12, 2019
e2175ed
update response on register rate limit error
andrewjford May 25, 2019
5f2a02d
disable wip endpoints
andrewjford May 25, 2019
f0a38dc
disable all wip endpoints
andrewjford May 25, 2019
720331d
Merge pull request #4 from andrewjford/dev
andrewjford May 25, 2019
07772d5
fix email verification link
andrewjford May 25, 2019
29b434c
move verification tokens to sequelize model
andrewjford May 26, 2019
f9260ba
move account creation to sequelize model
andrewjford May 26, 2019
cc22056
update email template
andrewjford May 26, 2019
676073d
Merge pull request #5 from andrewjford/dev
andrewjford May 26, 2019
9ae0342
fix url env variable
andrewjford May 26, 2019
5acd72e
Merge pull request #6 from andrewjford/dev
andrewjford May 26, 2019
64b5d80
fix mail template email address
andrewjford May 26, 2019
948bd38
Merge pull request #7 from andrewjford/dev
andrewjford May 26, 2019
59448a6
email verif link updates account and deletes token
andrewjford May 27, 2019
fc81f7d
create 1 category on account creation
andrewjford Jun 1, 2019
7afca15
Merge pull request #8 from andrewjford/dev
andrewjford Jun 1, 2019
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
11 changes: 10 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
{
"presets": ["@babel/preset-env"]
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": true
}
}
]
]
}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# misc
.env
dump
.dump

npm-debug.log*
yarn-debug.log*
Expand Down
3 changes: 3 additions & 0 deletions default_env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DATABASE_URL
SECRET_KEY
EMAIL_VERIFICATION_TOKEN
1,351 changes: 1,042 additions & 309 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,29 @@
"author": "Andrew Ford",
"license": "ISC",
"dependencies": {
"@babel/polyfill": "^7.4.0",
"@babel/polyfill": "^7.4.4",
"@sendgrid/mail": "^6.4.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"dotenv": "^6.2.0",
"dotenv": "^8.0.0",
"email-validator": "^2.0.4",
"express": "^4.16.4",
"express-rate-limit": "^3.5.0",
"jsonwebtoken": "^8.5.0",
"moment": "^2.22.2",
"pg": "^7.7.1",
"moment": "^2.24.0",
"pg": "^7.11.0",
"pg-hstore": "^2.3.2",
"sequelize": "^5.8.6",
"uuid": "^3.3.2"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.4.0",
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/node": "^7.2.2",
"@babel/preset-env": "^7.4.2",
"@babel/preset-env": "^7.4.4",
"jest": "^24.5.0",
"nodemon": "^1.18.10",
"nodemon": "^1.19.0",
"supertest": "^4.0.2"
}
}
5 changes: 5 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import "@babel/polyfill";
import express from 'express';
import routes from './routes/index';
import cors from 'cors';
import bodyParser from 'body-parser';
import { sequelize } from './models/models';

const app = express();

sequelize.sync();

app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.enable("trust proxy");
app.use('/', routes);
Expand Down
79 changes: 72 additions & 7 deletions src/controllers/AccountController.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,86 @@
import AccountModel from '../models/AccountModel';
import AccountValidator from '../validators/AccountValidator';
import VerificationTokenController from './VerificationTokenController';
import db from '../services/dbService';
import models from '../models/models';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import mailer from '../services/mailer';
import CategoryModel from '../models/CategoryModel';

const AccountController = {
async validateAccount(req, res) {
const query = `SELECT v.*, a.*
FROM verification_tokens v
LEFT JOIN accounts a ON v.account_id = a.id
WHERE v.token = $1`;
const { rows } = await db.query(query, [req.query.token]);

if (rows && rows.length > 0) {
try {
const updatedAccount = await models.Account.update(
{isVerified: true},
{where: {id: rows[0].id}}
);

models.VerificationToken.destroy({
where: {account_id: rows[0].id}
});
return res.status(200).send({message: "email successfully verified"});
} catch(error) {
console.log('error updating account... '+error);
return res.status(500).send({message: "internal server error"});
}
} else {
return res.status(400).send({message: "Unable to resolve email verification."});
}
},

async create(req, res) {
const errors = AccountValidator.onCreate(req);
if (errors.length > 0) {
return res.status(422).send({ message: errors });
}

try {
const modelResponse = await AccountModel.create(req);
return res.status(201).send(modelResponse);
const password = await bcrypt.hash(req.body.password, 10);
const account = await models.Account.create({
name: req.body.name,
email: req.body.email,
password,
})
.catch(error => {
if (error.parent.code === '23505') {
return res.status(422).send({message: ["A user already exists for this email."]});
} else {
throw Error(error);
}
});

const verificationTokenModel = await VerificationTokenController.create(req, res, account);
mailer.sendVerificationMessage(account.email, verificationTokenModel.token)
.catch(error => {
console.log(`error sending email: ${error}`);
return res.status(500).send({message: "Error sending email."});
});

CategoryModel.create({
body: {
name: "Groceries",
},
accountId: account.id
});

const tokenExpiration = 24*60*60;
const token = jwt.sign({id: account.id}, process.env.SECRET_KEY, {expiresIn: tokenExpiration});

return res.status(201).send({
user: account, // prob need to strip this down
token,
tokenExpiration
});
} catch(error) {
if (error.code === '23505') {
return res.status(422).send({message: ["A user already exists for this email."]});
}
return res.status(400).send("error creating user");
return res.status(400).send({message: ["error creating user"]});
}
},

Expand All @@ -41,7 +106,7 @@ const AccountController = {
try {
const result = await AccountModel.delete(req);
if(result.rowCount === 0) {
return res.status(404).send({'message': 'category not found'});
return res.status(404).send({'message': 'account not found'});
}
return res.status(204).send({ 'message': 'deleted' });
} catch(error) {
Expand Down
44 changes: 44 additions & 0 deletions src/controllers/VerificationTokenController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import models from '../models/models';
import mailer from '../services/mailer';
import bcrypt from 'bcryptjs';

const VerificationTokenController = {
async resendEmailVerification(req, res) {
const account = await models.Account.findOne({
where: {email: req.query.email},
include: [{
model: models.VerificationToken,
as: "VerificationToken",
}],
});

if (account && account.VerificationToken) {
mailer.sendVerificationMessage(account.email, account.VerificationToken.token)
.then(() => {
return res.status(200).send({message: "Verification email sent."});
});
} else if (account) {
const newVerificationToken = await this.create(req, res, account);
mailer.sendVerificationMessage(account.email, newVerificationToken.token)
.then(() => {
return res.status(200).send({message: "Verification email sent."});
});
} else {
return res.status(404).send({message: "Account not found for given email."});
}
},

async create(req, res, account) {
const newToken = await bcrypt.hash(account.email, 10);
return models.VerificationToken.create({
token: newToken,
account_id: account.id
})
.catch(error => {
console.log(`error creating verification token: ${error}`);
return res.status(500).send({message: "Internal Server error."});
});
}
}

export default VerificationTokenController;
4 changes: 2 additions & 2 deletions src/db/db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ CREATE TABLE IF NOT EXISTS users (
email text UNIQUE,
password text,
created_date timestamptz NOT NULL DEFAULT NOW(),
modified_date timestamptz NOT NULL DEFAULT NOW()
updated_at timestamptz NOT NULL DEFAULT NOW()
);

CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified_date = NOW();
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Expand Down
15 changes: 15 additions & 0 deletions src/db/user_email_verification.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ALTER TABLE accounts ADD COLUMN is_verified BOOLEAN;

CREATE TABLE IF NOT EXISTS verification_tokens (
id SERIAL PRIMARY KEY,
token text NOT NULL,
account_id INTEGER REFERENCES accounts(id) NOT NULL,
created_date timestamptz NOT NULL DEFAULT NOW()
);

ALTER TABLE accounts ALTER COLUMN is_verified SET DEFAULT FALSE;


UPDATE accounts
SET is_verified = DEFAULT
WHERE is_verified IS NULL;
23 changes: 23 additions & 0 deletions src/models/Account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Sequelize from 'sequelize';

const account = (sequelize, DataTypes) => {
class Account extends Sequelize.Model {}
Account.init({
name: DataTypes.TEXT,
email: DataTypes.TEXT,
password: DataTypes.TEXT,
isVerified: {type: DataTypes.BOOLEAN, defaultValue: false},
}, {
underscored: true,
sequelize,
modelName: "account",
});

Account.associations = models => {
Account.hasOne(models.VerificationToken);
}

return Account;
}

export default account;
26 changes: 26 additions & 0 deletions src/models/AccountModel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import db from '../services/dbService';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import models from '../models/models';
import mailer from '../services/mailer';

const AccountModel = {
async create(req) {
Expand All @@ -17,6 +19,14 @@ const AccountModel = {
];

const { rows } = await db.query(sqlString, values);
const verificationToken = await bcrypt.hash(rows[0].email, 10);
const verificationTokenModel = await models.VerificationToken.create({
token: verificationToken,
account_id: rows[0].id,
});

mailer.sendVerificationMessage(rows[0].email, verificationTokenModel.token);

const tokenExpiration = 24*60*60;
const token = jwt.sign({id: rows[0].id}, process.env.SECRET_KEY, {expiresIn: tokenExpiration});

Expand All @@ -37,6 +47,9 @@ const AccountModel = {
return db.query(queryText, [email]);
},

async update(req) {
},

async delete(req) {
const deleteQuery = 'DELETE FROM accounts WHERE id = $1';
return db.query(deleteQuery, [req.accountId]);
Expand All @@ -63,6 +76,19 @@ const AccountModel = {
token,
tokenExpiration
};
},

async validateAccount(req) {
const query = `SELECT v.*, a.*
FROM verification_tokens v
LEFT JOIN accounts a ON v.account_id = a.id
WHERE v.token = $1`;
const { rows } = await db.query(query, [req.query.token]);
if (rows && rows.length > 0) {
// update account
// delete verification token
console.log(rows[0]);
}
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/models/VerificationToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Sequelize from 'sequelize';

const verificationToken = (sequelize, DataTypes) => {
class VerificationToken extends Sequelize.Model {}
VerificationToken.init({
token: DataTypes.TEXT,
}, {
underscored: true,
sequelize,
modelName: "verification_token",
});

VerificationToken.associate = models => {
VerificationToken.belongsTo(models.Account, {
foreignKey: {
name: 'account_id',
allowNull: false,
},
});
}

return VerificationToken;
}

export default verificationToken;
22 changes: 22 additions & 0 deletions src/models/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Sequelize from 'sequelize';

const sequelize = new Sequelize(
process.env.DATABASE_URL,
{
dialect: 'postgres',
},
);

const models = {
Account: sequelize.import('./Account'),
VerificationToken: sequelize.import('./VerificationToken'),
};

Object.keys(models).forEach(key => {
if ('associate' in models[key]) {
models[key].associate(models);
}
});

export { sequelize };
export default models;
Loading