From e78b3e2e12195c486f168e435667b31f5d2fd89b Mon Sep 17 00:00:00 2001 From: Nihanth Subramanya Date: Wed, 18 Apr 2018 16:30:59 +0200 Subject: [PATCH] Update routes to use new DB api --- db/utils.js | 30 ++++++++++++++++++++++- routes/home.js | 13 +++------- routes/oauth.js | 5 ++-- routes/user.js | 29 +++++++++++++--------- tests/fixtures/make-breach-with-emails.js | 7 +++--- 5 files changed, 54 insertions(+), 30 deletions(-) diff --git a/db/utils.js b/db/utils.js index bdc815128..18de8fbe7 100644 --- a/db/utils.js +++ b/db/utils.js @@ -48,6 +48,26 @@ const DBUtils = { return await this._addEmailHash(sha1, email); }, + async removeSubscriber(email) { + const sha1 = getSha1(email); + + // Check if an entry exists. + const existingEntries = await EmailHash + .query() + .where("sha1", sha1); + + // If not, nothing to be done, return. + if (!existingEntries.length) { + return; + } + + // Patch out the email from the entry. + await existingEntries[0] + .$query() + .patch({ email: null }) + .returning('*'); // Postgres trick to return the updated row as model. + }, + async addBreachedHash(breachName, sha1) { console.log(`Adding ${sha1} to ${breachName}`); const addedEmailHash = await this._addEmailHash(sha1); @@ -61,6 +81,10 @@ const DBUtils = { .relate(addedEmailHash.id); }, + addBreachedEmail(breachName, email) { + return this.addBreachedHash(breachName, getSha1(email)); + }, + async getBreachesForHash(sha1) { console.log(`Finding EmailHash entry for ${sha1}`); const emailHashesBySha1 = await EmailHash @@ -70,7 +94,11 @@ const DBUtils = { return await emailHashesBySha1[0] .$relatedQuery("breaches") .orderBy("name"); - } + }, + + getBreachesForEmail(email) { + return this.getBreachesForHash(getSha1(email)); + }, }; module.exports = DBUtils; diff --git a/routes/home.js b/routes/home.js index 3fe6ca18a..fc8edca8b 100644 --- a/routes/home.js +++ b/routes/home.js @@ -1,10 +1,9 @@ "use strict"; -const crypto = require("crypto"); const express = require("express"); const bodyParser = require("body-parser"); -const models = require("../db/models"); +const DBUtils = require("../db/utils"); const router = express.Router(); const urlEncodedParser = bodyParser.urlencoded({ extended: false }); @@ -19,11 +18,9 @@ router.post("/scan", urlEncodedParser, async (req, res) => { const email = req.body.email; let foundBreaches; if (email) { - const emailHash = await models.EmailHash.findOne({ where: { sha1: getSha1(email) }}); - if (emailHash) { - foundBreaches = (await emailHash.getBreaches()).map(aBreach => aBreach.dataValues); - } + foundBreaches = await DBUtils.getBreachesForEmail(email); } + res.render("scan", { title: "Firefox Breach Alerts: Scan Results", email: email, @@ -31,8 +28,4 @@ router.post("/scan", urlEncodedParser, async (req, res) => { }); }); -function getSha1(email) { - return crypto.createHash("sha1").update(email).digest("hex"); -} - module.exports = router; diff --git a/routes/oauth.js b/routes/oauth.js index 2ae60a464..f3fc8ee74 100644 --- a/routes/oauth.js +++ b/routes/oauth.js @@ -8,7 +8,7 @@ const express = require("express"); const bodyParser = require("body-parser"); const popsicle = require("popsicle"); -const models = require("../db/models"); +const DBUtils = require("../db/utils"); // This object exists instead of inlining the env vars to make it easy // to abstract fetching API endpoints from the OAuth server (instead @@ -58,8 +58,7 @@ router.get("/confirmed", jsonParser, async (req, res) => { }, }); const email = JSON.parse(data.body).email; - const user = await models.Subscriber.create({ email: email }); - user.saveSha1(); + const user = await DBUtils.addSubscriber(email); res.render("confirm", { title: "Firefox Breach Alerts: Subscribed", diff --git a/routes/user.js b/routes/user.js index c8674e068..46e390969 100644 --- a/routes/user.js +++ b/routes/user.js @@ -4,8 +4,9 @@ const AppConstants = require("../app-constants"); const express = require("express"); const bodyParser = require("body-parser"); +const crypto = require("crypto"); -const models = require("../db/models"); +const DBUtils = require("../db/utils"); const EmailUtils = require("../email-utils"); const ResponseCodes = Object.freeze({ @@ -19,17 +20,22 @@ const router = express.Router(); const jsonParser = bodyParser.json(); const urlEncodedParser = bodyParser.urlencoded({ extended: false }); +const tempUserStore = new Map(); + router.post("/add", urlEncodedParser, async (req, res) => { - const user = await models.Subscriber.create({ email: req.body.email }); - const url = `${AppConstants.SERVER_URL}/user/verify?state=${encodeURIComponent(user.verificationToken)}&email=${encodeURIComponent(user.email)}`; + const email = req.body.email; + const verificationToken = crypto.randomBytes(40).toString("hex"); + tempUserStore.set(verificationToken, email); + const url = `${AppConstants.SERVER_URL}/user/verify?state=${encodeURIComponent(verificationToken)}&email=${encodeURIComponent(email)}`; + console.log(url); // Temporary for debugging. try { - await EmailUtils.sendEmail(user.email, "Firefox Breach Alert", + await EmailUtils.sendEmail(email, "Firefox Breach Alert", `Visit this link to subscribe: ${url}`); res.render("add", { title: "Verify email", - email: user.email, + email: email, }); } catch (e) { console.log(e); @@ -41,24 +47,23 @@ router.post("/add", urlEncodedParser, async (req, res) => { }); router.get("/verify", jsonParser, async (req, res) => { - const user = await models.Subscriber.findOne({ where: { email: req.query.email, verificationToken: req.query.state } }); - if (user === null) { + const email = tempUserStore.get(req.query.state); + if (!email || email !== req.query.email) { res.status(400).json({ error_code: ResponseCodes.EmailNotFound, info: "Email not found or verification token does not match.", }); return; } - // TODO: make a better user "verified" status than implicit presence of - // SHA1 hash value - user.saveSha1(); + + await DBUtils.addSubscriber(email); res.status(201).json({ - info: `Successfully verified ${user.email}`, + info: `Successfully verified ${email}`, }); }); router.post("/remove", jsonParser, async (req, res) => { - models.Subscriber.destroy({ where: { email: req.query.email } }); + await DBUtils.removeSubscriber(req.body.email); res.status(200).json({ info: "Deleted user.", }); diff --git a/tests/fixtures/make-breach-with-emails.js b/tests/fixtures/make-breach-with-emails.js index 67ea9bcad..6d11ad906 100644 --- a/tests/fixtures/make-breach-with-emails.js +++ b/tests/fixtures/make-breach-with-emails.js @@ -5,8 +5,7 @@ const Knex = require("knex"); const knexConfig = require('../../db/knexfile'); const { Model } = require("objection"); -const DBUtils = require("../../db/utils.js") -const getSha1 = require("../../sha1-utils"); +const DBUtils = require("../../db/utils") const knex = Knex(knexConfig.development); Model.knex(knex); @@ -33,12 +32,12 @@ const sampleBreaches = [ for (const sB of sampleBreaches) { await DBUtils.createBreach(sB.name, sB.meta); for (const e of sB.emails) { - await DBUtils.addBreachedHash(sB.name, getSha1(e)); + await DBUtils.addBreachedEmail(sB.name, e); } } const testEmail = "test1@test.com"; - const foundBreaches = await DBUtils.getBreachesForHash(getSha1(testEmail)); + const foundBreaches = await DBUtils.getBreachesForEmail(testEmail); console.log(`\n\n${testEmail} was found in the following breaches:\n`); console.log(foundBreaches.map(b => b.name)); // eslint-disable-next-line no-process-exit