consolidate on express-handlebars for web + email

Web templates were using express-hbs while email templates used
express-handlebars. Change both to use express-handlebars, which also
lets us share helpers between email + web templates.
This commit is contained in:
Luke Crouch 2018-09-05 11:26:15 -05:00
Родитель 98eb86efd2
Коммит 8a8f87e810
9 изменённых файлов: 118 добавлений и 180 удалений

Просмотреть файл

@ -42,13 +42,6 @@ async function verify(req, res) {
req.app.locals.breaches
);
if(unsafeBreachesForEmail) {
unsafeBreachesForEmail.forEach((breach) => {
breach.BreachDate = HBSHelpers.prettyDate(breach.BreachDate);
breach.DataClasses = HBSHelpers.breachDataClasses(breach.DataClasses);
});
}
const unsubscribeUrl = EmailUtils.unsubscribeUrl(verifiedEmailHash);
const serverUrl = req.app.locals.SERVER_URL;
@ -58,7 +51,7 @@ async function verify(req, res) {
"report",
{
email: verifiedEmailHash.email,
date: new Date().toLocaleString("en-US", {year: "numeric", month: "long", day: "numeric"}),
date: HBSHelpers.prettyDate(new Date()),
unsafeBreachesForEmail,
TIPS,
unsubscribeUrl,

Просмотреть файл

@ -1,18 +1,20 @@
"use strict";
const AppConstants = require("./app-constants");
const nodemailer = require("nodemailer");
const hbs = require("nodemailer-express-handlebars");
const HBSHelpers = require("./hbs-helpers");
const hbsOptions = {
viewEngine: {
extname: ".hbs",
layoutsDir: __dirname + "/views/email/layouts",
defaultLayout: "default_email",
partialsDir: __dirname + "/views/email/email_partials/",
helpers: HBSHelpers,
},
viewPath: __dirname + "/views/email/",
extName: ".hbs",

Просмотреть файл

@ -1,87 +1,89 @@
"use strict";
const HBSHelpers = {
breachDataClasses(dataClasses) {
if (dataClasses.constructor === Array) {
return dataClasses.join(", ");
} else {
return "";
}
},
function breachDataClasses(dataClasses) {
if (dataClasses.constructor === Array) {
return dataClasses.join(", ");
} else {
return dataClasses;
}
}
prettyDate(date) {
const jsDate = new Date(date);
return jsDate.toLocaleDateString("en-US", {year: "numeric", month: "long", day: "numeric"});
},
localeString(input) {
return input.toLocaleString();
},
function prettyDate(date) {
const jsDate = new Date(date);
return jsDate.toLocaleDateString("en-US", {year: "numeric", month: "long", day: "numeric"});
}
eachFromTo(ary, min, max, options) {
if(!ary || ary.length === 0)
return options.inverse(this);
const result = [];
function localeString(input) {
return input.toLocaleString();
}
for (let i = min; i < max && i < ary.length; i++) {
result.push(options.fn(ary[i], { data : { itemIndex : i } } ));
}
return result.join("");
},
ifCompare(v1, operator, v2, options) {
//https://stackoverflow.com/questions/28978759/length-check-in-a-handlebars-js-if-conditional
const operators = {
">": v1 > v2 ? true : false,
">=": v1 >= v2 ? true : false,
"<": v1 < v2 ? true : false,
"<=": v1 <= v2 ? true : false,
"===": v1 === v2 ? true : false,
};
if (operators.hasOwnProperty(operator)) {
if (operators[operator]) {
return options.fn(this);
}
function eachFromTo(ary, min, max, options) {
if(!ary || ary.length === 0)
return options.inverse(this);
}
return console.error(`Error: ${operator} not found`);
},
breachMath(lValue, operator, rValue) {
lValue = parseFloat(lValue);
rValue = parseFloat(rValue);
const returnValue = {
"+": lValue + rValue,
"-": lValue - rValue,
"*": lValue * rValue,
"/": lValue / rValue,
"%": lValue % rValue,
}[operator];
if (returnValue < 10) {
return {
1: "one",
2: "two",
3: "three",
4: "four",
5: "five",
6: "six",
7: "seven",
8: "eight",
9: "nine",
}[returnValue];
}
return returnValue;
},
const result = [];
register(hbs) {
hbs.registerHelper("prettyDate", this.prettyDate);
hbs.registerHelper("breachDataClasses", this.breachDataClasses);
hbs.registerHelper("localeString", this.localeString);
hbs.registerHelper("each_from_to", this.eachFromTo);
hbs.registerHelper("if_compare", this.ifCompare);
hbs.registerHelper("breachMath", this.breachMath);
},
for (let i = min; i < max && i < ary.length; i++) {
result.push(options.fn(ary[i], { data : { itemIndex : i } } ));
}
return result.join("");
}
function ifCompare(v1, operator, v2, options) {
//https://stackoverflow.com/questions/28978759/length-check-in-a-handlebars-js-if-conditional
const operators = {
">": v1 > v2 ? true : false,
">=": v1 >= v2 ? true : false,
"<": v1 < v2 ? true : false,
"<=": v1 <= v2 ? true : false,
"===": v1 === v2 ? true : false,
};
if (operators.hasOwnProperty(operator)) {
if (operators[operator]) {
return options.fn(this);
}
return options.inverse(this);
}
return console.error(`Error: ${operator} not found`);
}
function breachMath(lValue, operator, rValue) {
lValue = parseFloat(lValue);
rValue = parseFloat(rValue);
const returnValue = {
"+": lValue + rValue,
"-": lValue - rValue,
"*": lValue * rValue,
"/": lValue / rValue,
"%": lValue % rValue,
}[operator];
if (returnValue < 10) {
return {
1: "one",
2: "two",
3: "three",
4: "four",
5: "five",
6: "six",
7: "seven",
8: "eight",
9: "nine",
}[returnValue];
}
return returnValue;
}
module.exports = {
breachDataClasses,
prettyDate,
localeString,
eachFromTo,
ifCompare,
breachMath,
};
module.exports = HBSHelpers;

81
package-lock.json сгенерированный
Просмотреть файл

@ -53,7 +53,8 @@
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"accepts": {
"version": "1.3.5",
@ -1261,15 +1262,6 @@
"typedarray": "^0.0.6"
}
},
"config-chain": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz",
"integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=",
"requires": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"configstore": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
@ -1813,18 +1805,6 @@
"jsbn": "~0.1.0"
}
},
"editorconfig": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz",
"integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==",
"requires": {
"bluebird": "^3.0.5",
"commander": "^2.9.0",
"lru-cache": "^3.2.0",
"semver": "^5.1.0",
"sigmund": "^1.0.1"
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -2345,16 +2325,6 @@
}
}
},
"express-hbs": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/express-hbs/-/express-hbs-1.0.4.tgz",
"integrity": "sha1-xEgNboqfjCNQDTsaE5Txfq5FF4Y=",
"requires": {
"handlebars": "4.0.6",
"js-beautify": "1.6.8",
"readdirp": "2.1.0"
}
},
"extend": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
@ -5569,17 +5539,6 @@
"integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==",
"dev": true
},
"js-beautify": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.6.8.tgz",
"integrity": "sha1-2hFG00QxFFMJyJvn9p7Rbo4P8H4=",
"requires": {
"config-chain": "~1.1.5",
"editorconfig": "^0.13.2",
"mkdirp": "~0.5.0",
"nopt": "~3.0.1"
}
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@ -5961,14 +5920,6 @@
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
},
"lru-cache": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
"integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=",
"requires": {
"pseudomap": "^1.0.1"
}
},
"make-dir": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
@ -6489,14 +6440,6 @@
}
}
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"requires": {
"abbrev": "1"
}
},
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@ -7526,11 +7469,6 @@
"sisteransi": "^0.1.1"
}
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
"integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
},
"proxy-addr": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz",
@ -7552,7 +7490,8 @@
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"pstree.remy": {
"version": "1.1.0",
@ -7689,6 +7628,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
"integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"minimatch": "^3.0.2",
@ -8105,7 +8045,8 @@
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true
},
"semver-diff": {
"version": "2.1.0",
@ -8163,7 +8104,8 @@
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
"dev": true
},
"set-value": {
"version": "2.0.0",
@ -8234,11 +8176,6 @@
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
"dev": true
},
"sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",

Просмотреть файл

@ -16,7 +16,7 @@
"dompurify": "^1.0.4",
"dotenv": "^5.0.1",
"express": "^4.16.2",
"express-hbs": "^1.0.4",
"express-handlebars": "^3.0.0",
"git-rev-sync": "^1.12.0",
"got": "^8.3.1",
"hbs": "^4.0.1",

Просмотреть файл

@ -3,7 +3,7 @@
const AppConstants = require("./app-constants");
const express = require("express");
const hbs = require("express-hbs");
const exphbs = require("express-handlebars");
const helmet = require("helmet");
const sessions = require("client-sessions");
@ -63,13 +63,14 @@ app.use(helmet.contentSecurityPolicy({
}));
app.use(express.static("public"));
app.engine("hbs", hbs.express4({
app.engine("hbs", exphbs({
extname: ".hbs",
layoutsDir: __dirname + "/views/layouts",
defaultLayout: "default",
partialsDir: __dirname + "/views/partials",
helpers: HBSHelpers,
}));
app.set("view engine", "hbs");
app.set("views", __dirname + "/views");
HBSHelpers.register(hbs);
const cookie = {httpOnly: true, secureProxy: true};

Просмотреть файл

@ -4,9 +4,11 @@
</td>
<td valign="top" style="vertical-align: top; padding-top:10px;">
<p style="padding: 0px 0px 5px 0px; margin: 0px; color: #333333; margin: 0px; font-weight: 600; font-size: 16px;">{{ Title }} Breach</p>
<p style="padding: 0px 0px 5px 0px; margin: 0px; color: #333333; font-weight: 600;">Breach Date:<span style="font-weight:300; padding-left: 5px; color: #5d5d5d">{{ BreachDate }}</span></p>
<p style="padding: 0px 0px 5px 0px; margin: 0px; color: #333333; font-weight: 600;">Breach Date:<span style="font-weight:300; padding-left: 5px; color: #5d5d5d">{{prettyDate BreachDate }}</span></p>
<p style="padding: 0px 0px 5px 0px; margin: 0px; color: #333333; font-weight: 600;">Compromised Accounts:<span style="font-weight:300; padding-left: 5px; color: #5d5d5d">{{ PwnCount }}</span></p>
<p style="padding: 0px 0px 5px 0px; margin: 0px; color: #333333; font-weight: 600;">Compromised Data:<span style="font-weight:300; padding-left: 5px; color: #5d5d5d">{{ DataClasses }}</span></p>
{{#if DataClasses }}
<p style="padding: 0px 0px 5px 0px; margin: 0px; color: #333333; font-weight: 600;">Compromised Data:<span style="font-weight:300; padding-left: 5px; color: #5d5d5d">{{breachDataClasses DataClasses }}</span></p>
{{/if}}
<p style="padding: 0px 0px 5px 0px; margin: 0px; font-weight: 300; color: #5d5d5d;">{{ Description }}</p>
<br />
</td>

Просмотреть файл

@ -1,24 +1,24 @@
{{#if_compare foundBreaches.length ">=" 1}}
{{#ifCompare foundBreaches.length ">=" 1}}
<div class="compromised-accounts">
<div class="section-wrapper">
<!--show first 4 breaches-->
{{#each_from_to foundBreaches 0 4 }}
{{#eachFromTo foundBreaches 0 4 }}
<div class="half listings">
{{> breach_listing }}
</div>
{{/each_from_to}}
{{/eachFromTo}}
<!--display "Show More" button and show remaining breaches onclick if foundBreaches.length > 4-->
{{#if_compare foundBreaches.length ">" 4}}
{{#ifCompare foundBreaches.length ">" 4}}
<div id="additional-breaches" class="section-wrapper">
{{#each_from_to foundBreaches 4 foundBreaches.length }}
{{#eachFromTo foundBreaches 4 foundBreaches.length }}
<div class="half listings">
{{> breach_listing }}
</div>
{{/each_from_to}}
{{/eachFromTo}}
</div>
<button id="show-additional-breaches" class="button transparent-button">Show More</button>
{{> hibp_attribution }}
{{/if_compare}}
{{/ifCompare}}
</div>
</div>
{{/if_compare}}
{{/ifCompare}}

Просмотреть файл

@ -7,25 +7,26 @@
<h3 id="found-breaches" class="section-headline">This might be a problem</h3>
{{#if featuredBreach}}
{{#if userAccountCompromised}}
{{#if_compare foundBreaches.length ">" 1}} <!-- checking for additional "non-featured" breaches (is foundBreaches.length > 1) -->
<p>Your accounts appear in the <span class="bold">{{ featuredBreach.Title }}</span> breach, as well as <span class="bold">{{ breachMath foundBreaches.length "-" 1 }}</span> other breach{{#if_compare foundBreaches.length ">" 2}}es{{/if_compare}}.</p>
{{#ifCompare foundBreaches.length ">" 1}} <!-- checking for additional "non-featured" breaches (is foundBreaches.length > 1) -->
<p>Your accounts appear in the <span class="bold">{{ featuredBreach.Title }}</span> breach, as well as <span class="bold">{{ breachMath foundBreaches.length "-" 1 }}</span> other breach{{#ifCompare foundBreaches.length ">" 2}}es{{/ifCompare}}.</p>
{{else}}
<p>Your account appears in the <span class="bold">{{ featuredBreach.Title }}</span> breach, but does not appear in any other known breaches.</p>
</section><!--closes half.scan-results-content-left-->
<section class="half order-last">
{{> featured_breach}}
{{/if_compare}}
</div>
{{/ifCompare}}
{{else}} <!--#if !userAccountCompromised -->
{{#if_compare foundBreaches.length ">=" 1}} <!-- user has breached accounts (foundBreaches.length > 0) but wasn't compromised in the 'featuredBreach' -->
<p>Your account did not appear in the <span class="bold">{{ featuredBreach.Title }}</span> breach, but it did appear in <span class="bold">{{ breachMath foundBreaches.length "-" 0 }}</span> other breach{{#if_compare foundBreaches.length ">" 1}}es{{/if_compare}}.</p>
{{/if_compare}}
{{#ifCompare foundBreaches.length ">=" 1}} <!-- user has breached accounts (foundBreaches.length > 0) but wasn't compromised in the 'featuredBreach' -->
<p>Your account did not appear in the <span class="bold">{{ featuredBreach.Title }}</span> breach, but it did appear in <span class="bold">{{ breachMath foundBreaches.length "-" 0 }}</span> other breach{{#ifCompare foundBreaches.length ">" 1}}es{{/ifCompare}}.</p>
{{/ifCompare}}
{{/if}}
{{else}} <!--#if !featuredBreach-->
{{#if_compare foundBreaches.length "===" 1}}
{{#ifCompare foundBreaches.length "===" 1}}
<p>Your account was compromised in the following breach.</p>
{{else}}
<p>Your accounts were compromised in the following breaches.</p>
{{/if_compare}}
{{/ifCompare}}
{{/if}}<!--/#if featuredBreach-->
</section><!--.half.scan-results-content-left // or closes .half.order-last (Line 15)-->
{{> compromised_accounts }}