Browse Source

first build

tsMigration
Nareshkumar Rao 3 years ago
parent
commit
e881a7b782
  1. 1
      .vscode/settings.json
  2. 25
      built/app.js
  3. 75
      built/db/db.js
  4. 47
      built/db/utils.js
  5. 48
      built/routes/CodeRoute.js
  6. 73
      built/routes/LoginRoute.js
  7. 84
      built/routes/TelegramWebhookRoute.js
  8. 44
      built/routes/VerifyRoute.js
  9. 16
      built/session.js
  10. 40
      built/telegram.js
  11. 7
      built/utils.js
  12. 211
      package-lock.json
  13. 5
      package.json
  14. 17
      src/app.ts
  15. 75
      src/db/db.ts
  16. 44
      src/db/utils.ts
  17. 54
      src/routes/CodeRoute.ts
  18. 51
      src/routes/LoginRoute.ts
  19. 159
      src/routes/TelegramWebhookRoute.ts
  20. 31
      src/routes/VerifyRoute.ts
  21. 12
      src/session.ts
  22. 26
      src/telegram.ts
  23. 6
      src/utils.ts
  24. 5
      tsconfig.json

1
.vscode/settings.json

@ -0,0 +1 @@
{}

25
built/app.js

@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express = require("express");
const session = require("express-session");
const cors = require("cors");
const session_1 = require("./session");
const TelegramWebhookRoute_1 = require("./routes/TelegramWebhookRoute");
const LoginRoute_1 = require("./routes/LoginRoute");
const CodeRoute_1 = require("./routes/CodeRoute");
const VerifyRoute_1 = require("./routes/VerifyRoute");
require("dotenv-flow").config();
console.log(`Node Environment: ${process.env.NODE_ENV}`);
const app = express();
app.set("trust proxy", 1);
app.use(session(session_1.sessionOpts));
app.use(cors(session_1.corsOpts));
app.use(express.json());
app.post(`/${process.env.TELEGRAM_SECRET}`, TelegramWebhookRoute_1.TelegramWebhookRoute);
app.post("/login", LoginRoute_1.LoginRoute);
app.get("/code", CodeRoute_1.CodeRoute);
app.post("/verify", VerifyRoute_1.VerifyRoute);
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});

75
built/db/db.js

@ -0,0 +1,75 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.User = exports.Contact = exports.store = exports.storeDB = exports.sequelize = void 0;
const connect_session_sequelize_1 = __importDefault(require("connect-session-sequelize"));
const express_session_1 = __importDefault(require("express-session"));
const sequelize_1 = require("sequelize");
const SequelizeStore = connect_session_sequelize_1.default(express_session_1.default.Store);
const isProduction = process.env.NODE_ENV == "production";
exports.sequelize = (() => {
if (isProduction) {
return new sequelize_1.Sequelize(process.env.DB_DATA_NAME || "DATABASE", process.env.DB_USER || "USERNAME", process.env.DB_PASS || "PASSWORD", {
host: process.env.DB_PATH || "localhost",
dialect: "postgres",
});
}
else {
return new sequelize_1.Sequelize("sqlite::memory:");
}
})();
exports.storeDB = (() => {
if (isProduction) {
return new sequelize_1.Sequelize(process.env.DB_STORE_NAME || "DATABASE", process.env.DB_USER || "USERNAME", process.env.DB_PASS || "PASSWORD", {
host: process.env.DB_PATH,
dialect: "postgres",
});
}
else {
return new sequelize_1.Sequelize("sqlite::memory:");
}
})();
exports.store = new SequelizeStore({
db: exports.storeDB,
});
exports.Contact = exports.sequelize.define("Contact", {
user: {
type: sequelize_1.DataTypes.INTEGER,
allowNull: false,
},
with: {
type: sequelize_1.DataTypes.INTEGER,
allowNull: false,
},
});
exports.User = exports.sequelize.define("User", {
id: {
type: sequelize_1.DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
telegram: {
type: sequelize_1.DataTypes.INTEGER,
allowNull: false,
unique: true,
},
verification: {
type: sequelize_1.DataTypes.STRING,
},
isInfected: {
type: sequelize_1.DataTypes.BOOLEAN,
},
});
exports.Contact.sync();
exports.User.sync().then(() => {
if (process.env.ADMIN_USERNAME && process.env.ADMIN_PASSWORD) {
exports.User.create({
telegram: 12345,
}).catch(() => {
console.log("Couldn't create admin account. Probably exists.");
});
}
});
exports.store.sync();

47
built/db/utils.js

@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createUser = exports.addContact = void 0;
const telegram_1 = require("../telegram");
const db_1 = require("./db");
function addContact(userATelegram, userBRowID, done) {
db_1.User.findOne({ where: { telegram: userATelegram } }).then((userA) => {
db_1.User.findOne({ where: { id: userBRowID } }).then((userB) => {
if (!!userA || !!userB) {
done(false, "Could not find user.");
return;
}
db_1.Contact.create({ user: userA.id, with: userBRowID })
.then(() => {
console.log(`Registering contact between ${userA.id} and ${userBRowID}`);
telegram_1.sendTelegramMessage(userB.telegram, "Someone scanned your QR code. You will be notified if they are tested positive with Covid. If you are tested positive, please tell this bot /COVIDPOSITIVE");
done(true, "Successfully added contact");
})
.catch((e) => {
done(false, e);
});
});
});
}
exports.addContact = addContact;
function createUser(telegram, callback) {
db_1.User.create({
telegram: telegram,
})
.then((user) => {
if (!user) {
callback(false, "Could not create user");
}
else {
callback(true, "Success");
}
})
.catch((reason) => {
if (reason.name == "SequelizeUniqueConstraintError") {
callback(false, "User already exists");
}
else {
callback(false, "Unknown error");
}
});
}
exports.createUser = createUser;

48
built/routes/CodeRoute.js

@ -0,0 +1,48 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodeRoute = void 0;
const db_1 = require("../db/db");
const bcrypt_1 = __importDefault(require("bcrypt"));
const qrcode_1 = __importDefault(require("qrcode"));
function CodeRoute(req, res) {
if (!req.session.user) {
res.status(401).send("Not logged in");
return;
}
createQRCode(req.session.user, (err, url) => {
res.status(url ? 200 : 401).send({ error: err, data: url });
});
}
exports.CodeRoute = CodeRoute;
function createQRCode(telegram, callback) {
db_1.User.findOne({
where: {
telegram: telegram,
},
})
.then((user) => {
user &&
refreshVerification(user, (result) => {
const verifyURL = `${process.env.WEBSITE_URL}/#/verify/${encodeURIComponent(result.verification)}`;
qrcode_1.default.toDataURL(verifyURL, { width: 300, height: 300 }, (error, url) => {
callback(error.message, url);
});
});
})
.catch((error) => {
callback(error);
});
}
function refreshVerification(user, callback) {
let newVerification = bcrypt_1.default
.hashSync(`${new Date().getTime()}-${user.telegram}`, 5)
.replace(/[^a-zA-Z0-9]+/g, "");
newVerification = newVerification.substr(0, newVerification.length / 2);
user.verification = newVerification;
user.save().then((result) => {
callback(result);
});
}

73
built/routes/LoginRoute.js

@ -0,0 +1,73 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoginRoute = void 0;
const db_1 = require("../db/db");
const crypto_1 = __importDefault(require("crypto"));
const utils_1 = require("../db/utils");
function LoginRoute(req, res) {
const telegramResponse = req.body.telegramResponse;
authUser(telegramResponse, (authObject) => {
if (authObject) {
const verified = req.session.verified;
const verifiedBy = req.session.verifiedBy;
req.session.regenerate(() => {
req.session.user = telegramResponse.id;
if (verified) {
utils_1.addContact(telegramResponse.id, verifiedBy, (contactSuccess) => {
res.send({
authorized: authObject.authorized,
contactSuccess: contactSuccess,
});
});
}
else {
res.send(authObject);
}
});
}
else {
res.status(401).send(authObject);
}
});
}
exports.LoginRoute = LoginRoute;
function authUser(telegramResponse, callback) {
let dataCheckArray = [];
for (const [key, value] of Object.entries(telegramResponse)) {
if (key == "hash")
continue;
dataCheckArray.push(`${key}=${value}`);
}
dataCheckArray.sort();
const dataCheckString = dataCheckArray.join("\n");
const secretKey = crypto_1.default
.createHash("sha256")
.update(process.env.TELEGRAM_TOKEN)
.digest();
const confirmationHash = crypto_1.default
.createHmac("sha256", secretKey)
.update(dataCheckString)
.digest("hex");
const authorized = confirmationHash == telegramResponse.hash;
if (!authorized) {
callback({ authorized: false });
}
db_1.User.findOne({
where: {
telegram: telegramResponse.id,
},
}).then((user) => {
if (!user) {
utils_1.createUser(telegramResponse.id, (success) => {
callback({ authorized: success });
});
}
else {
callback({ authorized: true });
}
});
}
exports.LoginRoute = LoginRoute;

84
built/routes/TelegramWebhookRoute.js

@ -0,0 +1,84 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelegramWebhookRoute = void 0;
const types_1 = require("sequelize/types");
const db_1 = require("../db/db");
const telegram_1 = require("../telegram");
function TelegramWebhookRoute(req, res) {
try {
const messageText = req.body.message.text;
const telegramID = req.body.message.from.id;
if (messageText.toLowerCase() == "/covidpositive") {
userInfected(telegramID, (result) => {
if (result.saved) {
telegram_1.sendTelegramMessage(telegramID, "Thanks for informing us. We will notify the people you were in contact with!");
informContacts(telegramID);
}
else {
telegram_1.sendTelegramMessage(telegramID, "Sorry, something went wrong.");
}
});
}
}
catch (e) {
console.log("Could not get Telegram Message");
}
res.send();
}
exports.TelegramWebhookRoute = TelegramWebhookRoute;
function informContacts(telegramID) {
db_1.User.findOne({
where: {
telegram: telegramID,
},
}).then((user) => {
if (user) {
const userRowID = user.id;
db_1.Contact.findAll({
where: {
[types_1.Op.or]: [{ user: userRowID }, { with: userRowID }],
},
}).then((result) => {
result.forEach((contact) => {
const otherPersonID = contact.user == userRowID ? contact.with : contact.user;
db_1.User.findOne({
where: {
id: otherPersonID,
},
}).then((otherPerson) => {
otherPerson &&
telegram_1.sendTelegramMessage(otherPerson.telegram, "You're infected.");
});
});
});
}
});
}
function userInfected(telegramID, callback) {
db_1.User.findOne({
where: {
telegram: telegramID,
},
})
.then((user) => {
if (!user) {
callback({ saved: false });
}
else {
user.isInfected = true;
user
.save()
.then((result) => {
if (result) {
callback({ saved: true });
}
})
.catch((err) => {
callback({ saved: false });
});
}
})
.catch((err) => {
callback({ saved: false });
});
}

44
built/routes/VerifyRoute.js

@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerifyRoute = void 0;
const db_1 = require("../db/db");
const utils_1 = require("../db/utils");
function VerifyRoute(req, res) {
checkVerification(req.body.id, (success, msg, withUserID) => {
if (success) {
req.session.verified = success;
req.session.verifiedBy = withUserID;
if (req.session.user) {
// If Logged In
utils_1.addContact(req.session.user, withUserID, (success, msg) => {
res
.status(success ? 200 : 400)
.send({ success: success, message: msg, loggedIn: true });
});
}
else {
// If Not Logged In
res.send({ success: success, message: msg, loggedIn: false });
}
}
else {
res.status(400).send({ success: success, message: msg });
}
});
}
exports.VerifyRoute = VerifyRoute;
function checkVerification(verification, callback) {
db_1.User.findOne({
where: {
verification: decodeURIComponent(verification),
},
}).then((user) => {
if (user) {
callback(true, "User verified", user.id);
}
else {
callback(false, "No such verification");
}
});
}
exports.VerifyRoute = VerifyRoute;

16
built/session.js

@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.corsOpts = exports.sessionOpts = void 0;
const db_1 = require("./db/db");
exports.sessionOpts = {
secret: process.env.SERVER_SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
sameSite: "none",
maxAge: Number(process.env.SESSION_LENGTH),
},
store: db_1.store,
};
exports.corsOpts = { credentials: true, origin: true, secure: true };

40
built/telegram.js

@ -0,0 +1,40 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendTelegramMessage = exports.setTelegramWebHook = void 0;
const axios_1 = __importDefault(require("axios"));
function setTelegramWebHook(callback = () => { }) {
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/setWebhook`;
axios_1.default
.post(url, {
url: `${process.env.SERVER_API_URL}/${process.env.TELEGRAM_SECRET}`,
allowed_updates: [],
drop_pending_updates: true,
})
.then((res) => {
callback(!!res);
})
.catch((err) => {
callback(!!err);
});
}
exports.setTelegramWebHook = setTelegramWebHook;
function sendTelegramMessage(telegramID, message, callback = () => { }) {
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`;
axios_1.default
.post(url, {
chat_id: telegramID,
text: message,
})
.then((res) => {
callback(!!res);
})
.catch((err) => {
console.error("Problem sending Telegram message.");
callback(!!err);
});
}
exports.sendTelegramMessage = sendTelegramMessage;
setTelegramWebHook();

7
built/utils.js

@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCookieExpiry = void 0;
function getCookieExpiry() {
return new Date(Date.now() + Number(process.env.COOKIE_EXPIRY_DURATION));
}
exports.getCookieExpiry = getCookieExpiry;

211
package-lock.json

@ -22,6 +22,11 @@
},
"devDependencies": {
"@tsconfig/node14": "^1.0.1",
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.4",
"@types/qrcode": "^1.4.1",
"eslint": "^7.31.0",
"prettier": "2.3.2"
}
@ -225,11 +230,114 @@
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
"dev": true
},
"node_modules/@types/bcrypt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz",
"integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/body-parser": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz",
"integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==",
"dev": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/cors": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
"dev": true
},
"node_modules/@types/express": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
"integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.18",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.24",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz",
"integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*"
}
},
"node_modules/@types/express-session": {
"version": "1.17.4",
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz",
"integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
"dev": true
},
"node_modules/@types/node": {
"version": "16.4.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.1.tgz",
"integrity": "sha512-UW7cbLqf/Wu5XH2RKKY1cHwUNLicIDRLMraYKz+HHAerJ0ZffUEk+fMnd8qU2JaS6cAy0r8tsaf7yqHASf/Y0Q=="
},
"node_modules/@types/qrcode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.4.1.tgz",
"integrity": "sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"node_modules/@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
"node_modules/@types/serve-static": {
"version": "1.13.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
"integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==",
"dev": true,
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -4613,11 +4721,114 @@
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
"dev": true
},
"@types/bcrypt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz",
"integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/body-parser": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz",
"integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/cors": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
"dev": true
},
"@types/express": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
"integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==",
"dev": true,
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.18",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.24",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz",
"integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*"
}
},
"@types/express-session": {
"version": "1.17.4",
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz",
"integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
"dev": true
},
"@types/node": {
"version": "16.4.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.1.tgz",
"integrity": "sha512-UW7cbLqf/Wu5XH2RKKY1cHwUNLicIDRLMraYKz+HHAerJ0ZffUEk+fMnd8qU2JaS6cAy0r8tsaf7yqHASf/Y0Q=="
},
"@types/qrcode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.4.1.tgz",
"integrity": "sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
"@types/serve-static": {
"version": "1.13.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
"integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==",
"dev": true,
"requires": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",

5
package.json

@ -24,6 +24,11 @@
},
"devDependencies": {
"@tsconfig/node14": "^1.0.1",
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.4",
"@types/qrcode": "^1.4.1",
"eslint": "^7.31.0",
"prettier": "2.3.2"
}

17
src/app.ts

@ -1,14 +1,13 @@
const express = require("express");
const session = require("express-session");
const cors = require("cors");
import express = require("express");
import session = require("express-session");
import cors = require("cors");
import { corsOpts, sessionOpts } from "./session";
import { TelegramWebhookRoute } from "./routes/TelegramWebhookRoute";
import { LoginRoute } from "./routes/LoginRoute";
import { CodeRoute } from "./routes/CodeRoute";
import { VerifyRoute } from "./routes/VerifyRoute";
require("dotenv-flow").config();
const { LoginRoute } = require("./routes/LoginRoute");
const { CodeRoute } = require("./routes/CodeRoute");
const { VerifyRoute } = require("./routes/VerifyRoute");
const { corsOpts, sessionOpts } = require("./session");
const { TelegramWebhookRoute } = require("./routes/TelegramWebhookRoute");
console.log(`Node Environment: ${process.env.NODE_ENV}`);
const app = express();

75
src/db/db.ts

@ -1,18 +1,20 @@
const session = require("express-session");
const { Sequelize, DataTypes } = require("sequelize");
var SequelizeStore = require("connect-session-sequelize")(session.Store);
import ConnectSessionSequelize from "connect-session-sequelize";
import session from "express-session";
import { DataTypes, Model, Optional, Sequelize } from "sequelize";
const isProduction = process.env.NODE_ENV == "production";
const SequelizeStore = ConnectSessionSequelize(session.Store);
const sequelize = (() => {
const isProduction: boolean = process.env.NODE_ENV == "production";
export const sequelize: Sequelize = (() => {
if (isProduction) {
return new Sequelize(
process.env.DB_DATA_NAME,
process.env.DB_USER,
process.env.DB_PASS,
process.env.DB_DATA_NAME || "DATABASE",
process.env.DB_USER || "USERNAME",
process.env.DB_PASS || "PASSWORD",
{
host: process.env.DB_PATH,
dialect: process.env.DB_DATA_DIALECT,
host: process.env.DB_PATH || "localhost",
dialect: "postgres",
}
);
} else {
@ -20,15 +22,15 @@ const sequelize = (() => {
}
})();
const storeDB = (() => {
export const storeDB: Sequelize = (() => {
if (isProduction) {
return new Sequelize(
process.env.DB_STORE_NAME,
process.env.DB_USER,
process.env.DB_PASS,
process.env.DB_STORE_NAME || "DATABASE",
process.env.DB_USER || "USERNAME",
process.env.DB_PASS || "PASSWORD",
{
host: process.env.DB_PATH,
dialect: process.env.DB_DATA_DIALECT,
dialect: "postgres",
}
);
} else {
@ -36,11 +38,22 @@ const storeDB = (() => {
}
})();
const store = new SequelizeStore({
export const store = new SequelizeStore({
db: storeDB,
});
const Contact = sequelize.define("Contact", {
export type UserRowID = number;
export type TelegramID = number;
export type VerificationString = string;
interface ContactAttributes {
user: UserRowID;
with: UserRowID;
}
export interface ContactInterface
extends Model<ContactAttributes, ContactAttributes>,
ContactAttributes {}
export const Contact = sequelize.define<ContactInterface>("Contact", {
user: {
type: DataTypes.INTEGER,
allowNull: false,
@ -51,7 +64,25 @@ const Contact = sequelize.define("Contact", {
},
});
const User = sequelize.define("User", {
interface UserAttributes {
id: UserRowID;
telegram: TelegramID;
verification: VerificationString;
isInfected: boolean;
}
interface UserCreationAttributes {
telegram: TelegramID;
}
export interface UserInstance
extends Model<UserAttributes, UserCreationAttributes>,
UserAttributes {}
export const User = sequelize.define<UserInstance>("User", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
telegram: {
type: DataTypes.INTEGER,
allowNull: false,
@ -70,7 +101,7 @@ Contact.sync();
User.sync().then(() => {
if (process.env.ADMIN_USERNAME && process.env.ADMIN_PASSWORD) {
User.create({
telegram: process.env.ADMIN_USERNAME,
telegram: 12345,
}).catch(() => {
console.log("Couldn't create admin account. Probably exists.");
});
@ -78,9 +109,3 @@ User.sync().then(() => {
});
store.sync();
exports.User = User;
exports.Contact = Contact;
exports.sequelize = sequelize;
exports.storeDB = storeDB;
exports.store = store;

44
src/db/utils.ts

@ -1,18 +1,26 @@
const { sendTelegramMessage } = require("../telegram");
const { User, Contact } = require("./db");
import { sendTelegramMessage } from "../telegram";
import { User, Contact, TelegramID, UserRowID } from "./db";
function addContact(telegram, withUserID, done) {
User.findOne({ where: { telegram: telegram } }).then((user) => {
User.findOne({ where: { id: withUserID } }).then((withUser) => {
Contact.create({ user: user.id, with: withUserID })
export function addContact(
userATelegram: TelegramID,
userBRowID: UserRowID,
done: (success: boolean, message: string) => void
): void {
User.findOne({ where: { telegram: userATelegram } }).then((userA) => {
User.findOne({ where: { id: userBRowID } }).then((userB) => {
if (!!userA || !!userB) {
done(false, "Could not find user.");
return;
}
Contact.create({ user: userA!.id, with: userBRowID })
.then(() => {
console.log(
`Registering contact between ${user.id} and ${withUserID}`
`Registering contact between ${userA!.id} and ${userBRowID}`
);
sendTelegramMessage(
withUser.telegram,
"Someone scanned your QR code. You will be notified if they are tested positive with Covid. If you are tested positive, please tell this bot /COVIDPOSITIVE",
() => {}
userB!.telegram,
"Someone scanned your QR code. You will be notified if they are tested positive with Covid. If you are tested positive, please tell this bot /COVIDPOSITIVE"
);
done(true, "Successfully added contact");
})
@ -23,25 +31,25 @@ function addContact(telegram, withUserID, done) {
});
}
function createUser(telegram, done) {
export function createUser(
telegram: TelegramID,
callback: (success: boolean, message: string) => void
): void {
User.create({
telegram: telegram,
})
.then((user) => {
if (!user) {
done(false, "Could not create user");
callback(false, "Could not create user");
} else {
done(true, "Success");
callback(true, "Success");
}
})
.catch((reason) => {
if (reason.name == "SequelizeUniqueConstraintError") {
done(false, "User already exists");
callback(false, "User already exists");
} else {
done(false, "Unknown error");
callback(false, "Unknown error");
}
});
}
exports.addContact = addContact;
exports.createUser = createUser;

54
src/routes/CodeRoute.ts

@ -1,8 +1,15 @@
const bcrypt = require("bcrypt");
const QRCode = require("qrcode");
const { User } = require("../db/db");
import { Request, Response } from "express";
import { TelegramID, User, UserInstance } from "../db/db";
import bcrypt from "bcrypt";
import QRCode, { QRCodeToDataURLOptions } from "qrcode";
function CodeRoute(req, res) {
declare module "express-session" {
interface Session {
user: TelegramID;
}
}
export function CodeRoute(req: Request, res: Response) {
if (!req.session.user) {
res.status(401).send("Not logged in");
return;
@ -12,36 +19,45 @@ function CodeRoute(req, res) {
});
}
function createQRCode(telegram, done) {
function createQRCode(
telegram: TelegramID,
callback: (errorMessage: string, url?: string) => void
): void {
User.findOne({
where: {
telegram: telegram,
},
})
.then((user) => {
refreshVerification(user, (result) => {
const verifyURL = `${
process.env.WEBSITE_URL
}/#/verify/${encodeURIComponent(result.verification)}`;
QRCode.toDataURL(verifyURL, { width: 300, height: 300 }, (err, url) => {
done(err, url);
user &&
refreshVerification(user, (result) => {
const verifyURL = `${
process.env.WEBSITE_URL
}/#/verify/${encodeURIComponent(result.verification)}`;
QRCode.toDataURL(
verifyURL,
{ width: 300, height: 300 } as QRCodeToDataURLOptions,
(error, url) => {
callback(error.message, url);
}
);
});
});
})
.catch((err) => {
done(err);
.catch((error) => {
callback(error);
});
}
function refreshVerification(user, done) {
function refreshVerification(
user: UserInstance,
callback: (success: UserInstance) => void
): void {
let newVerification = bcrypt
.hashSync(`${new Date().getTime()}-${user.hash}`, 5)
.hashSync(`${new Date().getTime()}-${user.telegram}`, 5)
.replace(/[^a-zA-Z0-9]+/g, "");
newVerification = newVerification.substr(0, newVerification.length / 2);
user.verification = newVerification;
user.save().then((result) => {
done(result);
callback(result);
});
}
exports.CodeRoute = CodeRoute;

51
src/routes/LoginRoute.ts

@ -1,11 +1,30 @@
const crypto = require("crypto");
const { User } = require("../db/db");
const { addContact, createUser } = require("../db/utils");
import { Request, Response } from "express";
import { TelegramID, User, UserRowID } from "../db/db";
import crypto from "crypto";
import { addContact, createUser } from "../db/utils";
function LoginRoute(req, res) {
declare module "express-session" {
interface Session {
verified: boolean;
verifiedBy: UserRowID;
}
}
type TelegramLoginResponse = {
id: TelegramID;
hash: string;
};
interface LoginRequest extends Request {
body: {
telegramResponse: TelegramLoginResponse;
};
}
export function LoginRoute(req: LoginRequest, res: Response) {
const telegramResponse = req.body.telegramResponse;
authUser(telegramResponse, (success, msg) => {
if (success) {
authUser(telegramResponse, (authObject) => {
if (authObject) {
const verified = req.session.verified;
const verifiedBy = req.session.verifiedBy;
req.session.regenerate(() => {
@ -13,22 +32,24 @@ function LoginRoute(req, res) {
if (verified) {
addContact(telegramResponse.id, verifiedBy, (contactSuccess) => {
res.send({
authorized: success,
message: msg,
authorized: authObject.authorized,
contactSuccess: contactSuccess,
});
});
} else {
res.send({ authorized: success, message: msg });
res.send(authObject);
}
});
} else {
res.status(401).send({ authorized: success, message: msg });
res.status(401).send(authObject);
}
});
}
function authUser(telegramResponse, done) {
function authUser(
telegramResponse: TelegramLoginResponse,
callback: (callbackObject: { authorized: boolean }) => void
): void {
let dataCheckArray = [];
for (const [key, value] of Object.entries(telegramResponse)) {
@ -40,7 +61,7 @@ function authUser(telegramResponse, done) {
const secretKey = crypto
.createHash("sha256")
.update(process.env.TELEGRAM_TOKEN)
.update(process.env.TELEGRAM_TOKEN!)
.digest();
const confirmationHash = crypto
.createHmac("sha256", secretKey)
@ -50,7 +71,7 @@ function authUser(telegramResponse, done) {
const authorized = confirmationHash == telegramResponse.hash;
if (!authorized) {
done({ authorized: false });
callback({ authorized: false });
}
User.findOne({
@ -60,10 +81,10 @@ function authUser(telegramResponse, done) {
}).then((user) => {
if (!user) {
createUser(telegramResponse.id, (success) => {
done({ authorized: success });
callback({ authorized: success });
});
} else {
done({ authorized: true });
callback({ authorized: true });
}
});
}

159
src/routes/TelegramWebhookRoute.ts

@ -1,82 +1,103 @@
const { Op } = require("sequelize");
const { User, Contact } = require("../db/db");
const { sendTelegramMessage } = require("../telegram");
import { Request, Response } from "express";
import { Op } from "sequelize/types";
import { Contact, TelegramID, User } from "../db/db";
import { sendTelegramMessage } from "../telegram";
function TelegramWebhookRoute(req, res) {
interface TelegramWebhookRequest extends Request {
body: {
message: {
text: string;
from: {
id: TelegramID;
};
};
};
}
try{
const messageText = req.body.message.text;
const telegramID = req.body.message.from.id;
if (messageText.toLowerCase() == "/covidpositive") {
userInfected(telegramID, (result) => {
if(result.saved){
sendTelegramMessage(telegramID, "Thanks for informing us. We will notify the people you were in contact with!", ()=>{});
informContacts(telegramID, ()=>{});
}else{
sendTelegramMessage(telegramID, "Sorry, something went wrong.", ()=>{});
}
});
export function TelegramWebhookRoute(
req: TelegramWebhookRequest,
res: Response
) {
try {
const messageText = req.body.message.text;
const telegramID = req.body.message.from.id;
if (messageText.toLowerCase() == "/covidpositive") {
userInfected(telegramID, (result) => {
if (result.saved) {
sendTelegramMessage(
telegramID,
"Thanks for informing us. We will notify the people you were in contact with!"
);
informContacts(telegramID);
} else {
sendTelegramMessage(telegramID, "Sorry, something went wrong.");
}
});
}
catch(e){
console.log("Could not get Telegram Message");
}
} catch (e) {
console.log("Could not get Telegram Message");
}
res.send();
res.send();
}
function informContacts(telegramID, doneCallback){
User.findOne({
function informContacts(telegramID: TelegramID) {
User.findOne({
where: {
telegram: telegramID,
},
}).then((user) => {
if (user) {
const userRowID = user.id;
Contact.findAll({
where: {
telegram: telegramID,
}
}).then(user => {
if(user){
const userRowID = user.id;
Contact.findAll({
where: {
[Op.or]: [{user: userRowID}, {with: userRowID}],
}
})
.then(result => {
result.forEach(contact => {
const otherPersonID = contact.user == userRowID ? contact.with : contact.user;
User.findOne({
where: {
id: otherPersonID,
}
}).then(otherPerson => {
sendTelegramMessage(otherPerson.telegram, "You're infected.", ()=>{});
});
});
});
}
});
[Op.or]: [{ user: userRowID }, { with: userRowID }],
},
}).then((result) => {
result.forEach((contact) => {
const otherPersonID =
contact.user == userRowID ? contact.with : contact.user;
User.findOne({
where: {
id: otherPersonID,
},
}).then((otherPerson) => {
otherPerson &&
sendTelegramMessage(otherPerson.telegram, "You're infected.");
});
});
});
}
});
}
function userInfected(telegramID, doneCallback) {
User.findOne({
where: {
telegram: telegramID,
},
}).then((user) => {
if (!user) {
done({saved: false});
} else {
user.isInfected = true;
user.save().then(result => {
if(result){
doneCallback({saved: true});
}
}).catch(err=>{doneCallback({saved: false})});
}
function userInfected(
telegramID: TelegramID,
callback: (callbackObject: { saved: boolean }) => void
): void {
User.findOne({
where: {
telegram: telegramID,
},
})
.then((user) => {
if (!user) {
callback({ saved: false });
} else {
user.isInfected = true;
user
.save()
.then((result) => {
if (result) {
callback({ saved: true });
}
})
.catch((err) => {
callback({ saved: false });
});
}
})
.catch(err=>{
doneCallback({saved: false});
.catch((err) => {
callback({ saved: false });
});
}
exports.TelegramWebhookRoute = TelegramWebhookRoute;

31
src/routes/VerifyRoute.ts

@ -1,15 +1,21 @@
const { User } = require("../db/db");
const { addContact } = require("../db/utils");
import { Request, Response } from "express";
import { User, UserRowID, VerificationString } from "../db/db";
import { addContact } from "../db/utils";
function VerifyRoute(req, res) {
checkVerification(req.body.id, (success, msg, withUserID) => {
req.session.verified = success;
req.session.verifiedBy = withUserID;
interface VerifyRequest extends Request {
body: {
id: VerificationString;
};
}
export function VerifyRoute(req: VerifyRequest, res: Response) {
checkVerification(req.body.id, (success, msg, withUserID) => {
if (success) {
req.session.verified = success;
req.session.verifiedBy = withUserID!;
if (req.session.user) {
// If Logged In
addContact(req.session.user, withUserID, (success, msg) => {
addContact(req.session.user, withUserID!, (success, msg) => {
res
.status(success ? 200 : 400)
.send({ success: success, message: msg, loggedIn: true });
@ -24,16 +30,19 @@ function VerifyRoute(req, res) {
});
}
function checkVerification(id, done) {
function checkVerification(
verification: VerificationString,
callback: (success: boolean, msg: string, userID?: UserRowID) => void
): void {
User.findOne({
where: {
verification: decodeURIComponent(id),
verification: decodeURIComponent(verification),
},
}).then((user) => {
if (user) {
done(true, "User verified", user.id);
callback(true, "User verified", user.id);
} else {
done(false, "No such verification");
callback(false, "No such verification");
}
});
}

12
src/session.ts

@ -1,7 +1,8 @@
const { store } = require("./db/db");
import { SessionOptions } from "express-session";
import { store } from "./db/db";
const sessionOpts = {
secret: process.env.SERVER_SESSION_SECRET,
export const sessionOpts: SessionOptions = {
secret: process.env.SERVER_SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
@ -12,7 +13,4 @@ const sessionOpts = {
store: store,
};
const corsOpts = { credentials: true, origin: true, secure: true };
exports.sessionOpts = sessionOpts;
exports.corsOpts = corsOpts;
export const corsOpts = { credentials: true, origin: true, secure: true };

26
src/telegram.ts

@ -1,6 +1,9 @@
const { default: axios } = require("axios");
import axios from "axios";
import { TelegramID } from "./db/db";
function setTelegramWebHook(done) {
export function setTelegramWebHook(
callback: (success: boolean) => void = () => {}
): void {
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/setWebhook`;
axios
.post(url, {
@ -9,14 +12,18 @@ function setTelegramWebHook(done) {
drop_pending_updates: true,
})
.then((res) => {
done(res);
callback(!!res);
})
.catch((err) => {
done(err);
callback(!!err);
});
}
function sendTelegramMessage(telegramID, message, done) {
export function sendTelegramMessage(
telegramID: TelegramID,
message: string,
callback: (success: boolean) => void = () => {}
): void {
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`;
axios
.post(url, {
@ -24,15 +31,12 @@ function sendTelegramMessage(telegramID, message, done) {
text: message,
})
.then((res) => {
done(res);
callback(!!res);
})
.catch((err) => {
console.error("Problem sending Telegram message.");
done(err);
callback(!!err);
});
}
setTelegramWebHook(() => {});
exports.sendTelegramMessage = sendTelegramMessage;
exports.setTelegramWebHook = setTelegramWebHook;
setTelegramWebHook();

6
src/utils.ts

@ -1,5 +1,3 @@
function getCookieExpiry() {
return new Date(Date.now() + process.env.COOKIE_EXPIRY_DURATION);
export function getCookieExpiry(): Date {
return new Date(Date.now() + Number(process.env.COOKIE_EXPIRY_DURATION));
}
exports.getCookieExpiry = getCookieExpiry;

5
tsconfig.json

@ -1,4 +1,7 @@
{
"extends": "@tsconfig/node14/tsconfig.json",
"include": ["./src/**/*"],
}
"compilerOptions": {
"outDir": "./built"
}
}

Loading…
Cancel
Save