2019-11-13 11:03:30 -04:00
|
|
|
const Events = require("events");
|
2019-11-13 20:22:01 -04:00
|
|
|
const stripHexPrefix = require("strip-hex-prefix");
|
2019-11-13 11:03:30 -04:00
|
|
|
const { isSignatureValid } = require("./utils");
|
|
|
|
const express = require("express");
|
|
|
|
const { check, validationResult } = require("express-validator");
|
|
|
|
const cors = require("cors");
|
|
|
|
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");
|
2019-11-13 20:22:01 -04:00
|
|
|
const Mailer = require("../mail/sendgrid");
|
|
|
|
const DappConfig = require("../config/dapps");
|
2019-11-14 14:33:30 -04:00
|
|
|
const cryptoRandomString = require("crypto-random-string");
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-13 20:22:01 -04:00
|
|
|
const dappConfig = new DappConfig();
|
2019-11-13 11:03:30 -04:00
|
|
|
const mailer = new Mailer(config);
|
|
|
|
const db = new Database(events, config);
|
|
|
|
|
|
|
|
db.init();
|
|
|
|
|
2019-11-14 14:33:30 -04:00
|
|
|
const getToken = () => {
|
|
|
|
const expirationTime = new Date();
|
|
|
|
expirationTime.setUTCHours(expirationTime.getUTCHours() + 2);
|
|
|
|
return {
|
|
|
|
token: cryptoRandomString({ length: 150, type: "url-safe" }),
|
|
|
|
expirationTime
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2019-11-13 11:03:30 -04:00
|
|
|
const hexValidator = value => {
|
|
|
|
const regex = /^[0-9A-Fa-f]*$/g;
|
2019-11-13 20:22:01 -04:00
|
|
|
if (regex.test(stripHexPrefix(value))) {
|
2019-11-13 11:03:30 -04:00
|
|
|
return true;
|
|
|
|
}
|
2019-11-13 20:22:01 -04:00
|
|
|
throw new Error("Invalid hex string");
|
2019-11-13 11:03:30 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
events.on("db:connected", () => {
|
|
|
|
const app = express();
|
|
|
|
|
|
|
|
app.use(rateLimit());
|
|
|
|
app.use(cors());
|
|
|
|
app.use(express.json());
|
|
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
app.use(helmet.expectCt({ enforce: true, maxAge: 60 }));
|
|
|
|
app.use(helmet());
|
|
|
|
|
|
|
|
app.post(
|
|
|
|
"/:dappId/subscribe",
|
|
|
|
[
|
|
|
|
check("signature")
|
|
|
|
.exists()
|
2019-11-13 20:22:01 -04:00
|
|
|
.isLength({ min: 132, max: 132 })
|
2019-11-13 11:03:30 -04:00
|
|
|
.custom(hexValidator),
|
|
|
|
check("address")
|
|
|
|
.exists()
|
2019-11-13 20:22:01 -04:00
|
|
|
.isLength({ min: 42, max: 42 })
|
2019-11-13 11:03:30 -04:00
|
|
|
.custom(hexValidator),
|
|
|
|
check("email")
|
|
|
|
.exists()
|
|
|
|
.isEmail(),
|
|
|
|
check("dappId").exists()
|
|
|
|
],
|
|
|
|
async (req, res) => {
|
|
|
|
const {
|
|
|
|
params: { dappId },
|
|
|
|
body: { address, email, signature }
|
|
|
|
} = req;
|
|
|
|
|
|
|
|
const errors = validationResult(req);
|
|
|
|
if (!errors.isEmpty()) {
|
|
|
|
return res.status(404).json({ errors: errors.array() });
|
|
|
|
}
|
|
|
|
|
2019-11-13 20:22:01 -04:00
|
|
|
if (!dappConfig.isDapp(dappId)) {
|
2019-11-13 11:03:30 -04:00
|
|
|
return res.status(404).send("Invalid dapp");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isSignatureValid(address, email, signature)) {
|
|
|
|
return res.status(404).send("Invalid signature");
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle subscriptions to particular events
|
|
|
|
|
|
|
|
try {
|
|
|
|
const subscriber = await Subscriber.findOne({
|
|
|
|
dappId,
|
|
|
|
address
|
|
|
|
});
|
|
|
|
|
2019-11-14 14:33:30 -04:00
|
|
|
const t = getToken();
|
|
|
|
|
2019-11-13 11:03:30 -04:00
|
|
|
if (!subscriber) {
|
|
|
|
await Subscriber.create({
|
|
|
|
dappId,
|
|
|
|
email,
|
|
|
|
address,
|
2019-11-14 14:33:30 -04:00
|
|
|
verificationTokens: [t]
|
2019-11-13 11:03:30 -04:00
|
|
|
});
|
2019-11-14 14:33:30 -04:00
|
|
|
} else if (!subscriber.isVerified) {
|
|
|
|
const d = new Date(subscriber.lastSignUpAttempt);
|
|
|
|
d.setMinutes(d.getMinutes() + 5);
|
|
|
|
if (d > new Date()) {
|
|
|
|
return res
|
|
|
|
.status(400)
|
|
|
|
.send(
|
|
|
|
"You need to wait at least 5 minutes between sign up attempts"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
subscriber.lastSignUpAttempt = d;
|
|
|
|
subscriber.verificationTokens.push(t);
|
|
|
|
subscriber.save();
|
|
|
|
}
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-14 14:33:30 -04:00
|
|
|
if (!subscriber || !subscriber.isVerified) {
|
2019-11-13 20:22:01 -04:00
|
|
|
const template = dappConfig.template(dappId, "sign-up");
|
|
|
|
mailer.send(
|
|
|
|
dappConfig.getEmailTemplate(dappId, template),
|
|
|
|
dappConfig.config(dappId).from,
|
|
|
|
{
|
2019-11-14 14:33:30 -04:00
|
|
|
email,
|
|
|
|
token: t.token
|
2019-11-13 20:22:01 -04:00
|
|
|
}
|
|
|
|
);
|
2019-11-13 11:03:30 -04:00
|
|
|
}
|
2019-11-14 14:33:30 -04:00
|
|
|
|
2019-11-13 11:03:30 -04:00
|
|
|
} catch (err) {
|
|
|
|
// TODO: add global error handler
|
|
|
|
return res.status(400).send(err.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
return res.status(200).send("OK");
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2019-11-14 10:28:40 -04:00
|
|
|
app.post(
|
|
|
|
"/:dappId/unsubscribe",
|
|
|
|
[
|
|
|
|
check("signature")
|
|
|
|
.exists()
|
|
|
|
.isLength({ min: 132, max: 132 })
|
|
|
|
.custom(hexValidator),
|
|
|
|
check("address")
|
|
|
|
.exists()
|
|
|
|
.isLength({ min: 42, max: 42 })
|
|
|
|
.custom(hexValidator),
|
|
|
|
check("dappId").exists()
|
|
|
|
],
|
|
|
|
async (req, res) => {
|
|
|
|
// TODO:
|
|
|
|
const {
|
|
|
|
params: { dappId },
|
|
|
|
body: { address, signature }
|
|
|
|
} = req;
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-14 10:28:40 -04:00
|
|
|
if (!dappConfig.isDapp(dappId)) {
|
|
|
|
return res.status(404).send("Invalid dapp");
|
|
|
|
}
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-14 10:28:40 -04:00
|
|
|
if (!isSignatureValid(address, dappId, signature)) {
|
|
|
|
return res.status(404).send("Invalid signature");
|
|
|
|
}
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-14 10:28:40 -04:00
|
|
|
// TODO: handle unsubscribe to particular events
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-14 10:28:40 -04:00
|
|
|
try {
|
|
|
|
await Subscriber.deleteOne({
|
|
|
|
dappId,
|
|
|
|
address
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
// TODO: add global error handler
|
|
|
|
return res.status(400).send(err.message);
|
|
|
|
}
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-14 10:28:40 -04:00
|
|
|
return res.status(200).send("OK");
|
|
|
|
}
|
|
|
|
);
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-13 20:22:01 -04:00
|
|
|
app.get("/confirm/:token", (req, res) => {
|
|
|
|
// TODO:
|
|
|
|
});
|
2019-11-13 11:03:30 -04:00
|
|
|
|
2019-11-13 20:22:01 -04:00
|
|
|
app.get("/", (req, res) => res.status(200).json({ ok: true }));
|
2019-11-13 11:03:30 -04:00
|
|
|
|
|
|
|
app.listen(config.PORT, () =>
|
|
|
|
console.log(`App listening on port ${config.PORT}!`)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
// MVP
|
|
|
|
// ====
|
|
|
|
|
|
|
|
// TODO: register DAPP and content
|
|
|
|
// TODO: handle errors sending email
|