Email confirmation and templates
This commit is contained in:
parent
0bf6b9d487
commit
62515ce825
80
api/index.js
80
api/index.js
|
@ -1,6 +1,5 @@
|
|||
const Events = require("events");
|
||||
const stripHexPrefix = require("strip-hex-prefix");
|
||||
const { isSignatureValid } = require("./utils");
|
||||
const { isSignatureValid, getToken, hexValidator } = require("./utils");
|
||||
const express = require("express");
|
||||
const { check, validationResult } = require("express-validator");
|
||||
const cors = require("cors");
|
||||
|
@ -8,11 +7,13 @@ const helmet = require("helmet");
|
|||
const rateLimit = require("../middleware/rate-limit");
|
||||
const config = require("../config");
|
||||
const Database = require("../database");
|
||||
const events = new Events();
|
||||
const Subscriber = require("../models/subscriber");
|
||||
const Subscribers = require("../models/subscribers");
|
||||
const Verifications = require("../models/verifications");
|
||||
const Mailer = require("../mail/sendgrid");
|
||||
const DappConfig = require("../config/dapps");
|
||||
const cryptoRandomString = require("crypto-random-string");
|
||||
|
||||
|
||||
const events = new Events();
|
||||
|
||||
const dappConfig = new DappConfig();
|
||||
const mailer = new Mailer(config);
|
||||
|
@ -20,23 +21,6 @@ const db = new Database(events, config);
|
|||
|
||||
db.init();
|
||||
|
||||
const getToken = () => {
|
||||
const expirationTime = new Date();
|
||||
expirationTime.setUTCHours(expirationTime.getUTCHours() + 2);
|
||||
return {
|
||||
token: cryptoRandomString({ length: 150, type: "url-safe" }),
|
||||
expirationTime
|
||||
};
|
||||
};
|
||||
|
||||
const hexValidator = value => {
|
||||
const regex = /^[0-9A-Fa-f]*$/g;
|
||||
if (regex.test(stripHexPrefix(value))) {
|
||||
return true;
|
||||
}
|
||||
throw new Error("Invalid hex string");
|
||||
};
|
||||
|
||||
events.on("db:connected", () => {
|
||||
const app = express();
|
||||
|
||||
|
@ -85,7 +69,7 @@ events.on("db:connected", () => {
|
|||
// TODO: handle subscriptions to particular events
|
||||
|
||||
try {
|
||||
const subscriber = await Subscriber.findOne({
|
||||
const subscriber = await Subscribers.findOne({
|
||||
dappId,
|
||||
address
|
||||
});
|
||||
|
@ -93,11 +77,15 @@ events.on("db:connected", () => {
|
|||
const t = getToken();
|
||||
|
||||
if (!subscriber) {
|
||||
await Subscriber.create({
|
||||
const s = await Subscribers.create({
|
||||
dappId,
|
||||
email,
|
||||
address,
|
||||
verificationTokens: [t]
|
||||
address
|
||||
});
|
||||
|
||||
await Verifications.create({
|
||||
...t,
|
||||
subscriber: s._id
|
||||
});
|
||||
} else if (!subscriber.isVerified) {
|
||||
const d = new Date(subscriber.lastSignUpAttempt);
|
||||
|
@ -111,8 +99,12 @@ events.on("db:connected", () => {
|
|||
}
|
||||
|
||||
subscriber.lastSignUpAttempt = d;
|
||||
subscriber.verificationTokens.push(t);
|
||||
subscriber.save();
|
||||
await subscriber.save();
|
||||
|
||||
await Verification.create({
|
||||
...t,
|
||||
subscriber: subscriber._id
|
||||
});
|
||||
}
|
||||
|
||||
if (!subscriber || !subscriber.isVerified) {
|
||||
|
@ -126,7 +118,6 @@ events.on("db:connected", () => {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
// TODO: add global error handler
|
||||
return res.status(400).send(err.message);
|
||||
|
@ -167,7 +158,7 @@ events.on("db:connected", () => {
|
|||
// TODO: handle unsubscribe to particular events
|
||||
|
||||
try {
|
||||
await Subscriber.deleteOne({
|
||||
await Subscribers.deleteOne({
|
||||
dappId,
|
||||
address
|
||||
});
|
||||
|
@ -180,8 +171,33 @@ events.on("db:connected", () => {
|
|||
}
|
||||
);
|
||||
|
||||
app.get("/confirm/:token", (req, res) => {
|
||||
// TODO:
|
||||
app.get("/confirm/:token", [check("token").exists()], async (req, res) => {
|
||||
const {
|
||||
params: { token }
|
||||
} = req;
|
||||
|
||||
const verification = await Verifications.findOne({
|
||||
token
|
||||
}).populate("subscriber");
|
||||
|
||||
if (verification) {
|
||||
if (verification.expirationTime < new Date()) {
|
||||
return res.status(400).send("Verification token already expired");
|
||||
}
|
||||
|
||||
if (!verification.subscriber.isVerified) {
|
||||
verification.subscriber.isVerified = true;
|
||||
await verification.subscriber.save();
|
||||
}
|
||||
|
||||
await Verifications.deleteMany({
|
||||
subscriber: verification.subscriber._id
|
||||
});
|
||||
} else {
|
||||
return res.status(400).send("Invalid verification token");
|
||||
}
|
||||
|
||||
return res.status(200).send("OK");
|
||||
});
|
||||
|
||||
app.get("/", (req, res) => res.status(200).json({ ok: true }));
|
||||
|
|
25
api/utils.js
25
api/utils.js
|
@ -1,5 +1,7 @@
|
|||
const web3EthAccounts = require("web3-eth-accounts");
|
||||
const web3Utils = require("web3-utils");
|
||||
const cryptoRandomString = require("crypto-random-string");
|
||||
const stripHexPrefix = require("strip-hex-prefix");
|
||||
|
||||
const isSignatureValid = (address, message, signature) => {
|
||||
const accounts = new web3EthAccounts();
|
||||
|
@ -9,6 +11,25 @@ const isSignatureValid = (address, message, signature) => {
|
|||
return address === recoverAddress;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isSignatureValid
|
||||
const getToken = () => {
|
||||
const expirationTime = new Date();
|
||||
expirationTime.setUTCHours(expirationTime.getUTCHours() + 2);
|
||||
return {
|
||||
token: cryptoRandomString({ length: 200, type: "url-safe" }),
|
||||
expirationTime
|
||||
};
|
||||
};
|
||||
|
||||
const hexValidator = value => {
|
||||
const regex = /^[0-9A-Fa-f]*$/g;
|
||||
if (regex.test(stripHexPrefix(value))) {
|
||||
return true;
|
||||
}
|
||||
throw new Error("Invalid hex string");
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isSignatureValid,
|
||||
getToken,
|
||||
hexValidator
|
||||
};
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
<p>Welcome to teller network!</p>
|
||||
<h3>Welcome to teller network!</h3>
|
||||
<p>Verify your email at TODO: {{ token }}</p>
|
|
@ -1 +1,2 @@
|
|||
Welcome to teller network!
|
||||
Welcome to teller network!
|
||||
Verify your email at TODO: {{ token }}
|
|
@ -1,6 +1,5 @@
|
|||
const sgMail = require("@sendgrid/mail");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const Handlebars = require("handlebars");
|
||||
|
||||
class SendGridMailer {
|
||||
constructor(config) {
|
||||
|
@ -8,12 +7,15 @@ class SendGridMailer {
|
|||
}
|
||||
|
||||
send(template, from, data) {
|
||||
// TODO: data should be used for templating
|
||||
const tplText = Handlebars.compile(template.text);
|
||||
const tplHtml = Handlebars.compile(template.html);
|
||||
|
||||
const msg = {
|
||||
to: data.email,
|
||||
from,
|
||||
...template
|
||||
...template,
|
||||
text: tplText(data),
|
||||
html: tplHtml(data)
|
||||
};
|
||||
|
||||
sgMail.send(msg);
|
||||
|
|
|
@ -3,12 +3,6 @@ const Schema = mongoose.Schema;
|
|||
|
||||
const validator = require("validator");
|
||||
|
||||
const VerificationSchema = new Schema({
|
||||
token: String,
|
||||
expirationTime: Date
|
||||
});
|
||||
|
||||
|
||||
const SubscriberSchema = new Schema({
|
||||
id: Schema.Types.ObjectId,
|
||||
dappId: {
|
||||
|
@ -50,13 +44,11 @@ const SubscriberSchema = new Schema({
|
|||
isVerified: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
verificationTokens: [VerificationSchema]
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
SubscriberSchema.statics.findVerifiedUsersByDapp = function(dappId) {
|
||||
return this.find({ dappId, isVerified: true });
|
||||
};
|
||||
|
||||
module.exports = mongoose.model("Subscribers", SubscriberSchema);
|
||||
module.exports = mongoose.model("Subscribers", SubscriberSchema);
|
|
@ -0,0 +1,13 @@
|
|||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const VerificationSchema = new Schema({
|
||||
token: String,
|
||||
expirationTime: Date,
|
||||
subscriber: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Subscribers"
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Verifications", VerificationSchema);
|
|
@ -17,6 +17,7 @@
|
|||
"express": "^4.17.1",
|
||||
"express-rate-limit": "^5.0.0",
|
||||
"express-validator": "^6.2.0",
|
||||
"handlebars": "^4.5.2",
|
||||
"helmet": "^3.21.2",
|
||||
"mongoose": "5.7.5",
|
||||
"strip-hex-prefix": "^1.0.0",
|
||||
|
|
|
@ -5,7 +5,7 @@ const Ethereum = require("./ethereum");
|
|||
const { addressCompare } = require("./utils");
|
||||
const Mailer = require("../mail/sendgrid");
|
||||
const DappConfig = require("../config/dapps");
|
||||
const Subscribers = require("../models/subscriber");
|
||||
const Subscribers = require("../models/subscribers");
|
||||
|
||||
const events = new Events();
|
||||
const dappConfig = new DappConfig();
|
||||
|
|
52
yarn.lock
52
yarn.lock
|
@ -419,6 +419,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@~2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||
|
@ -1233,6 +1238,17 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
|||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
||||
|
||||
handlebars@^4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.2.tgz#5a4eb92ab5962ca3415ac188c86dc7f784f76a0f"
|
||||
integrity sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==
|
||||
dependencies:
|
||||
neo-async "^2.6.0"
|
||||
optimist "^0.6.1"
|
||||
source-map "^0.6.1"
|
||||
optionalDependencies:
|
||||
uglify-js "^3.1.4"
|
||||
|
||||
har-schema@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
|
@ -1720,6 +1736,11 @@ minimist@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
|
||||
|
||||
minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
|
||||
|
@ -1836,6 +1857,11 @@ negotiator@0.6.2:
|
|||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||
|
||||
neo-async@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||
|
||||
next-tick@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
||||
|
@ -1900,6 +1926,14 @@ once@^1.3.1, once@^1.4.0:
|
|||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
optimist@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
||||
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
|
||||
dependencies:
|
||||
minimist "~0.0.1"
|
||||
wordwrap "~0.0.2"
|
||||
|
||||
p-cancelable@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
|
||||
|
@ -2353,6 +2387,11 @@ sliced@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
|
||||
integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
|
||||
|
||||
source-map@^0.6.1, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
|
@ -2550,6 +2589,14 @@ typedarray-to-buffer@^3.1.5:
|
|||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.6.9"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.9.tgz#85d353edb6ddfb62a9d798f36e91792249320611"
|
||||
integrity sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==
|
||||
dependencies:
|
||||
commander "~2.20.3"
|
||||
source-map "~0.6.1"
|
||||
|
||||
ultron@~1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
|
||||
|
@ -2911,6 +2958,11 @@ web3@^1.2.2:
|
|||
typedarray-to-buffer "^3.1.5"
|
||||
yaeti "^0.0.6"
|
||||
|
||||
wordwrap@~0.0.2:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
||||
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
|
Loading…
Reference in New Issue