Nareshkumar Rao
3 years ago
committed by
GitHub
33 changed files with 843 additions and 502 deletions
@ -1,12 +0,0 @@ |
|||
module.exports = { |
|||
env: { |
|||
node: true, |
|||
commonjs: true, |
|||
es2021: true, |
|||
}, |
|||
extends: "eslint:recommended", |
|||
parserOptions: { |
|||
ecmaVersion: 12, |
|||
}, |
|||
rules: {}, |
|||
}; |
@ -0,0 +1 @@ |
|||
{} |
@ -1,28 +0,0 @@ |
|||
const express = require("express"); |
|||
const session = require("express-session"); |
|||
const cors = require("cors"); |
|||
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(); |
|||
app.set("trust proxy", 1); |
|||
app.use(session(sessionOpts)); |
|||
app.use(cors(corsOpts)); |
|||
app.use(express.json()); |
|||
|
|||
app.post(`/${process.env.TELEGRAM_SECRET}`, TelegramWebhookRoute); |
|||
app.post("/login", LoginRoute); |
|||
app.get("/code", CodeRoute); |
|||
app.post("/verify", VerifyRoute); |
|||
|
|||
const port = process.env.PORT || 8080; |
|||
app.listen(port, () => { |
|||
console.log(`Listening on port ${port}`); |
|||
}); |
@ -0,0 +1,31 @@ |
|||
import * as dotenvFlow from "dotenv-flow"; |
|||
dotenvFlow.config(); |
|||
|
|||
import express from "express"; |
|||
import session from "express-session"; |
|||
import cors from "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"; |
|||
import { CovidRoute } from "./routes/CovidRoute"; |
|||
|
|||
console.log(`Node Environment: ${process.env.NODE_ENV}`); |
|||
|
|||
const app = express(); |
|||
app.set("trust proxy", 1); |
|||
app.use(session(sessionOpts)); |
|||
app.use(cors(corsOpts)); |
|||
app.use(express.json()); |
|||
|
|||
app.post(`/${process.env.TELEGRAM_SECRET}`, TelegramWebhookRoute); |
|||
app.post("/login", LoginRoute); |
|||
app.get("/code", CodeRoute); |
|||
app.post("/verify", VerifyRoute); |
|||
app.post("/covid", CovidRoute); |
|||
|
|||
const port = process.env.PORT || 8080; |
|||
app.listen(port, () => { |
|||
console.log(`Listening on port ${port}`); |
|||
}); |
@ -1,86 +0,0 @@ |
|||
const session = require("express-session"); |
|||
const { Sequelize, DataTypes } = require("sequelize"); |
|||
var SequelizeStore = require("connect-session-sequelize")(session.Store); |
|||
|
|||
const isProduction = process.env.NODE_ENV == "production"; |
|||
|
|||
const sequelize = (() => { |
|||
if (isProduction) { |
|||
return new Sequelize( |
|||
process.env.DB_DATA_NAME, |
|||
process.env.DB_USER, |
|||
process.env.DB_PASS, |
|||
{ |
|||
host: process.env.DB_PATH, |
|||
dialect: process.env.DB_DATA_DIALECT, |
|||
} |
|||
); |
|||
} else { |
|||
return new Sequelize("sqlite::memory:"); |
|||
} |
|||
})(); |
|||
|
|||
const storeDB = (() => { |
|||
if (isProduction) { |
|||
return new Sequelize( |
|||
process.env.DB_STORE_NAME, |
|||
process.env.DB_USER, |
|||
process.env.DB_PASS, |
|||
{ |
|||
host: process.env.DB_PATH, |
|||
dialect: process.env.DB_DATA_DIALECT, |
|||
} |
|||
); |
|||
} else { |
|||
return new Sequelize("sqlite::memory:"); |
|||
} |
|||
})(); |
|||
|
|||
const store = new SequelizeStore({ |
|||
db: storeDB, |
|||
}); |
|||
|
|||
const Contact = sequelize.define("Contact", { |
|||
user: { |
|||
type: DataTypes.INTEGER, |
|||
allowNull: false, |
|||
}, |
|||
with: { |
|||
type: DataTypes.INTEGER, |
|||
allowNull: false, |
|||
}, |
|||
}); |
|||
|
|||
const User = sequelize.define("User", { |
|||
telegram: { |
|||
type: DataTypes.INTEGER, |
|||
allowNull: false, |
|||
unique: true, |
|||
}, |
|||
verification: { |
|||
type: DataTypes.STRING, |
|||
}, |
|||
isInfected: { |
|||
type: DataTypes.BOOLEAN, |
|||
}, |
|||
}); |
|||
|
|||
Contact.sync(); |
|||
|
|||
User.sync().then(() => { |
|||
if (process.env.ADMIN_USERNAME && process.env.ADMIN_PASSWORD) { |
|||
User.create({ |
|||
telegram: process.env.ADMIN_USERNAME, |
|||
}).catch(() => { |
|||
console.log("Couldn't create admin account. Probably exists."); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
store.sync(); |
|||
|
|||
exports.User = User; |
|||
exports.Contact = Contact; |
|||
exports.sequelize = sequelize; |
|||
exports.storeDB = storeDB; |
|||
exports.store = store; |
@ -0,0 +1,44 @@ |
|||
import ConnectSessionSequelize from "connect-session-sequelize"; |
|||
import session from "express-session"; |
|||
import { Sequelize } from "sequelize"; |
|||
|
|||
const SequelizeStore = ConnectSessionSequelize(session.Store); |
|||
|
|||
const isProduction: boolean = process.env.NODE_ENV == "production"; |
|||
|
|||
export const sequelize: Sequelize = (() => { |
|||
if (isProduction) { |
|||
return new 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("sqlite::memory:"); |
|||
} |
|||
})(); |
|||
|
|||
export const storeDB: Sequelize = (() => { |
|||
if (isProduction) { |
|||
return new 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("sqlite::memory:"); |
|||
} |
|||
})(); |
|||
|
|||
export const store = new SequelizeStore({ |
|||
db: storeDB, |
|||
}); |
|||
store.sync(); |
@ -0,0 +1,23 @@ |
|||
import { DataTypes, Model } from "sequelize"; |
|||
import { UserRowID } from "../../types"; |
|||
import { sequelize } from "../db"; |
|||
|
|||
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, |
|||
}, |
|||
with: { |
|||
type: DataTypes.INTEGER, |
|||
allowNull: false, |
|||
}, |
|||
}); |
|||
|
|||
Contact.sync(); |
@ -0,0 +1,60 @@ |
|||
import { TelegramID, UserRowID, VerificationString } from "../../types"; |
|||
import { User, UserInstance } from "./User"; |
|||
|
|||
export async function getUserByTelegramID( |
|||
telegramID: TelegramID |
|||
): Promise<UserInstance | null> { |
|||
const user = await User.findOne({ |
|||
where: { |
|||
telegram: telegramID, |
|||
}, |
|||
}); |
|||
return user; |
|||
} |
|||
|
|||
export async function getUserByRowID( |
|||
rowID: UserRowID |
|||
): Promise<UserInstance | null> { |
|||
const user = await User.findOne({ |
|||
where: { |
|||
id: rowID, |
|||
}, |
|||
}); |
|||
return user; |
|||
} |
|||
|
|||
export async function getUserByVerification( |
|||
verification: VerificationString |
|||
): Promise<UserInstance | null> { |
|||
const user = await User.findOne({ |
|||
where: { |
|||
verification: verification, |
|||
}, |
|||
}); |
|||
return user; |
|||
} |
|||
|
|||
export async function getUserCovidPositivity( |
|||
telegramID: TelegramID |
|||
): Promise<boolean> { |
|||
const user = await getUserByTelegramID(telegramID); |
|||
if (!user) throw new Error("User not found"); |
|||
const infectionDuration = new Date().getTime() - user.infectionDate.getTime(); |
|||
if (infectionDuration > 60 * 60 * 24 * 14 * 1000) { |
|||
await setUserCovidPositivity(telegramID, false); |
|||
return false; |
|||
} else { |
|||
return user.isInfected; |
|||
} |
|||
} |
|||
|
|||
export async function setUserCovidPositivity( |
|||
telegramID: TelegramID, |
|||
infectionState: boolean |
|||
): Promise<void> { |
|||
const user = await getUserByTelegramID(telegramID); |
|||
if (!user) throw new Error("User not found"); |
|||
user.isInfected = infectionState; |
|||
user.infectionDate = new Date(); |
|||
if (!(await user.save())) throw new Error("Could not save user state"); |
|||
} |
@ -0,0 +1,50 @@ |
|||
import { DataTypes, Model } from "sequelize"; |
|||
import { TelegramID, UserRowID, VerificationString } from "../../types"; |
|||
import { sequelize } from "../db"; |
|||
|
|||
interface UserAttributes { |
|||
id: UserRowID; |
|||
telegram: TelegramID; |
|||
verification: VerificationString; |
|||
isInfected: boolean; |
|||
infectionDate: Date; |
|||
} |
|||
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, |
|||
unique: true, |
|||
}, |
|||
verification: { |
|||
type: DataTypes.STRING, |
|||
}, |
|||
isInfected: { |
|||
type: DataTypes.BOOLEAN, |
|||
defaultValue: false, |
|||
}, |
|||
infectionDate: { |
|||
type: DataTypes.DATE, |
|||
}, |
|||
}); |
|||
|
|||
User.sync().then(() => { |
|||
if (process.env.ADMIN_USERNAME && process.env.ADMIN_PASSWORD) { |
|||
User.create({ |
|||
telegram: 12345 as TelegramID, |
|||
}).catch(() => { |
|||
console.log("Couldn't create admin account. Probably exists."); |
|||
}); |
|||
} |
|||
}); |
@ -1,48 +0,0 @@ |
|||
const { strings_en } = require("../strings"); |
|||
const { sendTelegramMessage } = require("../telegram"); |
|||
const { User, Contact } = require("./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 }) |
|||
.then(() => { |
|||
console.log( |
|||
`Registering contact between ${user.id} and ${withUserID}` |
|||
); |
|||
sendTelegramMessage( |
|||
withUser.telegram, |
|||
strings_en.telegram_qr_scanned, |
|||
() => {} |
|||
); |
|||
done(true, "Successfully added contact"); |
|||
}) |
|||
.catch((e) => { |
|||
done(false, e); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function createUser(telegram, done) { |
|||
User.create({ |
|||
telegram: telegram, |
|||
}) |
|||
.then((user) => { |
|||
if (!user) { |
|||
done(false, "Could not create user"); |
|||
} else { |
|||
done(true, "Success"); |
|||
} |
|||
}) |
|||
.catch((reason) => { |
|||
if (reason.name == "SequelizeUniqueConstraintError") { |
|||
done(false, "User already exists"); |
|||
} else { |
|||
done(false, "Unknown error"); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
exports.addContact = addContact; |
|||
exports.createUser = createUser; |
@ -0,0 +1,30 @@ |
|||
import { strings_en } from "../strings"; |
|||
import { sendTelegramMessage } from "../telegram"; |
|||
import { TelegramID } from "../types"; |
|||
import { Contact } from "./models/Contact"; |
|||
import { User, UserInstance } from "./models/User"; |
|||
import { getUserByTelegramID } from "./models/User.helper"; |
|||
|
|||
export async function addContact( |
|||
userATelegram: TelegramID, |
|||
userBTelegram: TelegramID |
|||
): Promise<void> { |
|||
const userA = await getUserByTelegramID(userATelegram); |
|||
const userB = await getUserByTelegramID(userBTelegram); |
|||
|
|||
if (!userA || !userB) { |
|||
throw new Error("Could not found users"); |
|||
} |
|||
|
|||
await Contact.create({ user: userA.id, with: userB.id }); |
|||
await sendTelegramMessage(userB.telegram, strings_en.telegram_qr_scanned); |
|||
} |
|||
|
|||
export async function createUser( |
|||
telegram: TelegramID |
|||
): Promise<UserInstance | null> { |
|||
const user = await User.create({ |
|||
telegram: telegram, |
|||
}); |
|||
return user; |
|||
} |
@ -1,47 +0,0 @@ |
|||
const bcrypt = require("bcrypt"); |
|||
const QRCode = require("qrcode"); |
|||
const { User } = require("../db/db"); |
|||
|
|||
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 }); |
|||
}); |
|||
} |
|||
|
|||
function createQRCode(telegram, done) { |
|||
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); |
|||
}); |
|||
}); |
|||
}) |
|||
.catch((err) => { |
|||
done(err); |
|||
}); |
|||
} |
|||
|
|||
function refreshVerification(user, done) { |
|||
let newVerification = bcrypt |
|||
.hashSync(`${new Date().getTime()}-${user.hash}`, 5) |
|||
.replace(/[^a-zA-Z0-9]+/g, ""); |
|||
newVerification = newVerification.substr(0, newVerification.length / 2); |
|||
user.verification = newVerification; |
|||
user.save().then((result) => { |
|||
done(result); |
|||
}); |
|||
} |
|||
|
|||
exports.CodeRoute = CodeRoute; |
@ -0,0 +1,49 @@ |
|||
import { Request, Response } from "express"; |
|||
import bcrypt from "bcrypt"; |
|||
import QRCode, { QRCodeToDataURLOptions } from "qrcode"; |
|||
import { TelegramID, VerificationString } from "../types"; |
|||
import { User, UserInstance } from "../db/models/User"; |
|||
import { getUserByTelegramID } from "../db/models/User.helper"; |
|||
|
|||
export async function CodeRoute(req: Request, res: Response) { |
|||
if (!req.session.userTelegramID) { |
|||
res.status(401).send("Not logged in"); |
|||
return; |
|||
} |
|||
try { |
|||
const url = await createQRCode(req.session.userTelegramID); |
|||
res.send({ data: url }); |
|||
} catch (error) { |
|||
res |
|||
.status(500) |
|||
.send({ error: error instanceof Error ? error.message : "Error" }); |
|||
} |
|||
} |
|||
|
|||
async function createQRCode(telegram: TelegramID): Promise<string> { |
|||
const user = await getUserByTelegramID(telegram); |
|||
if (!user) throw new Error("User not found"); |
|||
const newVerification = await refreshVerification(user); |
|||
const verifyURL = `${process.env.WEBSITE_URL}/#/verify/${encodeURIComponent( |
|||
newVerification |
|||
)}`;
|
|||
return await QRCode.toDataURL(verifyURL, { |
|||
width: 300, |
|||
height: 300, |
|||
} as QRCodeToDataURLOptions); |
|||
} |
|||
|
|||
async function refreshVerification( |
|||
user: UserInstance |
|||
): Promise<VerificationString> { |
|||
let newVerification = bcrypt |
|||
.hashSync(`${new Date().getTime()}-${user.telegram}`, 5) |
|||
.replace(/[^a-zA-Z0-9]+/g, "") as VerificationString; |
|||
newVerification = newVerification.substr( |
|||
0, |
|||
newVerification.length / 2 |
|||
) as VerificationString; |
|||
user.verification = newVerification; |
|||
await user.save(); |
|||
return newVerification; |
|||
} |
@ -0,0 +1,33 @@ |
|||
import { Request, Response } from "express"; |
|||
import { |
|||
getUserCovidPositivity, |
|||
setUserCovidPositivity, |
|||
} from "../db/models/User.helper"; |
|||
|
|||
interface CovidRouteRequest extends Request { |
|||
body: { |
|||
setPositive: boolean; |
|||
}; |
|||
} |
|||
|
|||
export async function CovidRoute(req: CovidRouteRequest, res: Response) { |
|||
if (!req.session.userTelegramID) { |
|||
res.status(401).send("Not logged in"); |
|||
return; |
|||
} |
|||
try { |
|||
if (req.body.setPositive) { |
|||
await setUserCovidPositivity(req.session.userTelegramID, true); |
|||
res.send({ covidPositive: true }); |
|||
} else { |
|||
const isInfected = await getUserCovidPositivity( |
|||
req.session.userTelegramID |
|||
); |
|||
res.send({ covidPositive: isInfected }); |
|||
} |
|||
} catch (error) { |
|||
res |
|||
.send(500) |
|||
.send({ error: error instanceof Error ? error.message : "Error" }); |
|||
} |
|||
} |
@ -1,78 +0,0 @@ |
|||
const crypto = require("crypto"); |
|||
const { User } = require("../db/db"); |
|||
const { addContact, createUser } = require("../db/utils"); |
|||
|
|||
function LoginRoute(req, res) { |
|||
const telegramResponse = req.body.telegramResponse; |
|||
|
|||
authUser(telegramResponse, (success, msg) => { |
|||
if (success) { |
|||
// User is already logged in
|
|||
if (req.session.user == telegramResponse.id) { |
|||
res.send({ authorized: success }); |
|||
return; |
|||
} |
|||
|
|||
const verified = req.session.verified; |
|||
const verifiedBy = req.session.verifiedBy; |
|||
req.session.regenerate(() => { |
|||
req.session.user = telegramResponse.id; |
|||
if (verified) { |
|||
addContact(telegramResponse.id, verifiedBy, (contactSuccess) => { |
|||
res.send({ |
|||
authorized: success, |
|||
message: msg, |
|||
contactSuccess: contactSuccess, |
|||
}); |
|||
}); |
|||
} else { |
|||
res.send({ authorized: success, message: msg }); |
|||
} |
|||
}); |
|||
} else { |
|||
res.status(401).send({ authorized: success, message: msg }); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function authUser(telegramResponse, done) { |
|||
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 |
|||
.createHash("sha256") |
|||
.update(process.env.TELEGRAM_TOKEN) |
|||
.digest(); |
|||
const confirmationHash = crypto |
|||
.createHmac("sha256", secretKey) |
|||
.update(dataCheckString) |
|||
.digest("hex"); |
|||
|
|||
const authorized = confirmationHash == telegramResponse.hash; |
|||
|
|||
if (!authorized) { |
|||
done({ authorized: false }); |
|||
} |
|||
|
|||
User.findOne({ |
|||
where: { |
|||
telegram: telegramResponse.id, |
|||
}, |
|||
}).then((user) => { |
|||
if (!user) { |
|||
createUser(telegramResponse.id, (success) => { |
|||
done({ authorized: success }); |
|||
}); |
|||
} else { |
|||
done({ authorized: true }); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
exports.LoginRoute = LoginRoute; |
@ -0,0 +1,87 @@ |
|||
import { Request, Response } from "express"; |
|||
import crypto from "crypto"; |
|||
import { addContact, createUser } from "../db/utils"; |
|||
import { TelegramID, UserRowID } from "../types"; |
|||
import { User } from "../db/models/User"; |
|||
import { getUserByTelegramID } from "../db/models/User.helper"; |
|||
|
|||
type TelegramLoginResponse = { |
|||
id: TelegramID; |
|||
hash: string; |
|||
}; |
|||
|
|||
interface LoginRequest extends Request { |
|||
body: { |
|||
telegramResponse: TelegramLoginResponse; |
|||
}; |
|||
} |
|||
|
|||
export async function LoginRoute(req: LoginRequest, res: Response) { |
|||
const telegramResponse = req.body.telegramResponse; |
|||
try { |
|||
const authorized = await authUser(telegramResponse); |
|||
if (authorized) { |
|||
// User is already logged in
|
|||
if (req.session.userTelegramID == telegramResponse.id) { |
|||
res.send({ authorized: authorized }); |
|||
return; |
|||
} |
|||
// User not logged in
|
|||
const verified = req.session.isVerified; |
|||
const verifiedBy = req.session.verifiedByTelegramID; |
|||
req.session.regenerate(async () => { |
|||
req.session.userTelegramID = telegramResponse.id; |
|||
if (verified) { |
|||
await addContact(telegramResponse.id, verifiedBy); |
|||
res.send({ |
|||
authorized: true, |
|||
contactSuccess: true, |
|||
}); |
|||
} else { |
|||
res.send({ authorized: authorized }); |
|||
} |
|||
}); |
|||
} else { |
|||
res.status(401).send({ error: "Unauthorized" }); |
|||
} |
|||
} catch (error) { |
|||
res |
|||
.status(500) |
|||
.send({ error: error instanceof Error ? error.message : "Error" }); |
|||
} |
|||
} |
|||
|
|||
async function authUser( |
|||
telegramResponse: TelegramLoginResponse |
|||
): Promise<boolean> { |
|||
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 |
|||
.createHash("sha256") |
|||
.update(process.env.TELEGRAM_TOKEN!) |
|||
.digest(); |
|||
const confirmationHash = crypto |
|||
.createHmac("sha256", secretKey) |
|||
.update(dataCheckString) |
|||
.digest("hex"); |
|||
|
|||
const authorized = confirmationHash == telegramResponse.hash; |
|||
|
|||
if (!authorized) { |
|||
return false; |
|||
} |
|||
|
|||
const user = await getUserByTelegramID(telegramResponse.id); |
|||
if (!!user) { |
|||
return true; |
|||
} else { |
|||
return !!(await createUser(telegramResponse.id)); |
|||
} |
|||
} |
@ -1,97 +0,0 @@ |
|||
const { Op } = require("sequelize"); |
|||
const { User, Contact } = require("../db/db"); |
|||
const { strings_en } = require("../strings"); |
|||
const { sendTelegramMessage } = require("../telegram"); |
|||
|
|||
function TelegramWebhookRoute(req, res) { |
|||
try { |
|||
if (req.body.message.connected_website) { |
|||
sendTelegramMessage( |
|||
req.body.message.from.id, |
|||
"Thanks for using OurSejahtera! Let's stay safer together <3" |
|||
); |
|||
} else { |
|||
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, |
|||
strings_en.telegram_inform_positive |
|||
); |
|||
informContacts(telegramID); |
|||
} else { |
|||
sendTelegramMessage(telegramID, "Sorry, something went wrong."); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} catch (e) { |
|||
console.log("Could not get Telegram Message"); |
|||
} |
|||
|
|||
res.send(); |
|||
} |
|||
|
|||
function informContacts(telegramID, doneCallback = () => {}) { |
|||
User.findOne({ |
|||
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, |
|||
strings_en.telegram_inform_infect |
|||
); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
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 }); |
|||
}); |
|||
} |
|||
}) |
|||
.catch((err) => { |
|||
doneCallback({ saved: false }); |
|||
}); |
|||
} |
|||
|
|||
exports.TelegramWebhookRoute = TelegramWebhookRoute; |
@ -0,0 +1,78 @@ |
|||
import { Request, Response } from "express"; |
|||
import { Op } from "sequelize"; |
|||
import { Contact } from "../db/models/Contact"; |
|||
import { User } from "../db/models/User"; |
|||
import { getUserByRowID, getUserByTelegramID } from "../db/models/User.helper"; |
|||
import { strings_en } from "../strings"; |
|||
import { sendTelegramMessage } from "../telegram"; |
|||
import { TelegramID } from "../types"; |
|||
|
|||
interface TelegramWebhookRequest extends Request { |
|||
body: { |
|||
message: { |
|||
text: string; |
|||
from: { |
|||
id: TelegramID; |
|||
}; |
|||
connected_website: string; |
|||
}; |
|||
}; |
|||
} |
|||
|
|||
export async function TelegramWebhookRoute( |
|||
req: TelegramWebhookRequest, |
|||
res: Response |
|||
) { |
|||
try { |
|||
if (req.body.message.connected_website) { |
|||
await sendTelegramMessage( |
|||
req.body.message.from.id, |
|||
"Thanks for using OurSejahtera! Let's stay safer together <3" |
|||
); |
|||
} else { |
|||
const messageText = req.body.message.text; |
|||
const telegramID = req.body.message.from.id; |
|||
if (messageText.toLowerCase() == "/covidpositive") { |
|||
await userInfected(telegramID); |
|||
await sendTelegramMessage( |
|||
telegramID, |
|||
strings_en.telegram_inform_positive |
|||
); |
|||
await informContacts(telegramID); |
|||
} |
|||
} |
|||
} catch (e) { |
|||
console.log( |
|||
e instanceof Error ? e.message : "Could not get Telegram Message" |
|||
); |
|||
} |
|||
|
|||
res.send(); |
|||
} |
|||
|
|||
async function informContacts(telegramID: TelegramID): Promise<void> { |
|||
const user = await getUserByTelegramID(telegramID); |
|||
if (!user) throw new Error("User not found"); |
|||
const contacts = await Contact.findAll({ |
|||
where: { |
|||
[Op.or]: [{ user: user.id }, { with: user.id }], |
|||
}, |
|||
}); |
|||
|
|||
contacts.forEach(async (contact) => { |
|||
const otherPersonID = contact.user == user.id ? contact.with : contact.user; |
|||
const otherUser = await getUserByRowID(otherPersonID); |
|||
if (!otherUser) throw new Error("Other user does not exist"); |
|||
await sendTelegramMessage( |
|||
otherUser.telegram, |
|||
strings_en.telegram_inform_infect |
|||
); |
|||
}); |
|||
} |
|||
|
|||
async function userInfected(telegramID: TelegramID): Promise<void> { |
|||
const user = await getUserByTelegramID(telegramID); |
|||
if (!user) throw new Error("User not found"); |
|||
user.isInfected = true; |
|||
await user.save(); |
|||
} |
@ -1,41 +0,0 @@ |
|||
const { User } = require("../db/db"); |
|||
const { addContact } = require("../db/utils"); |
|||
|
|||
function VerifyRoute(req, res) { |
|||
checkVerification(req.body.id, (success, msg, withUserID) => { |
|||
req.session.verified = success; |
|||
req.session.verifiedBy = withUserID; |
|||
|
|||
if (success) { |
|||
if (req.session.user) { |
|||
// If Logged In
|
|||
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 }); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function checkVerification(id, done) { |
|||
User.findOne({ |
|||
where: { |
|||
verification: decodeURIComponent(id), |
|||
}, |
|||
}).then((user) => { |
|||
if (user) { |
|||
done(true, "User verified", user.id); |
|||
} else { |
|||
done(false, "No such verification"); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
exports.VerifyRoute = VerifyRoute; |
@ -0,0 +1,38 @@ |
|||
import { Request, Response } from "express"; |
|||
import { User } from "../db/models/User"; |
|||
import { getUserByVerification } from "../db/models/User.helper"; |
|||
import { addContact } from "../db/utils"; |
|||
import { UserRowID, VerificationString } from "../types"; |
|||
|
|||
interface VerifyRequest extends Request { |
|||
body: { |
|||
id: VerificationString; |
|||
}; |
|||
} |
|||
|
|||
export async function VerifyRoute(req: VerifyRequest, res: Response) { |
|||
const verifiedByUser = await getUserByVerification( |
|||
decodeURIComponent(req.body.id) as VerificationString |
|||
); |
|||
try{ |
|||
if (!!verifiedByUser) { |
|||
req.session.isVerified = !!verifiedByUser; |
|||
req.session.verifiedByTelegramID = verifiedByUser.telegram; |
|||
if (req.session.userTelegramID) { |
|||
// If Logged In
|
|||
await addContact(req.session.userTelegramID, verifiedByUser.telegram); |
|||
res.send({ success: true, loggedIn: true }); |
|||
} else { |
|||
// If Not Logged In
|
|||
res.send({ |
|||
success: false, |
|||
loggedIn: false, |
|||
}); |
|||
} |
|||
} else { |
|||
res.status(400).send({ success: false }); |
|||
} |
|||
}catch(e){ |
|||
res.status(500).send({error: e instanceof Error ? e.message : "Error"}); |
|||
} |
|||
} |
@ -1,18 +0,0 @@ |
|||
const { store } = require("./db/db"); |
|||
|
|||
const sessionOpts = { |
|||
secret: process.env.SERVER_SESSION_SECRET, |
|||
resave: false, |
|||
saveUninitialized: false, |
|||
cookie: { |
|||
secure: true, |
|||
sameSite: "none", |
|||
maxAge: Number(process.env.SESSION_LENGTH), |
|||
}, |
|||
store: store, |
|||
}; |
|||
|
|||
const corsOpts = { credentials: true, origin: true, secure: true }; |
|||
|
|||
exports.sessionOpts = sessionOpts; |
|||
exports.corsOpts = corsOpts; |
@ -0,0 +1,16 @@ |
|||
import { SessionOptions } from "express-session"; |
|||
import { store } from "./db/db"; |
|||
|
|||
export const sessionOpts: SessionOptions = { |
|||
secret: process.env.SERVER_SESSION_SECRET!, |
|||
resave: false, |
|||
saveUninitialized: false, |
|||
cookie: { |
|||
secure: true, |
|||
sameSite: "none", |
|||
maxAge: Number(process.env.SESSION_LENGTH), |
|||
}, |
|||
store: store, |
|||
}; |
|||
|
|||
export const corsOpts = { credentials: true, origin: true, secure: true }; |
@ -1,38 +0,0 @@ |
|||
const { default: axios } = require("axios"); |
|||
|
|||
function setTelegramWebHook(done) { |
|||
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/setWebhook`; |
|||
axios |
|||
.post(url, { |
|||
url: `${process.env.SERVER_API_URL}/${process.env.TELEGRAM_SECRET}`, |
|||
allowed_updates: [], |
|||
drop_pending_updates: true, |
|||
}) |
|||
.then((res) => { |
|||
done(res); |
|||
}) |
|||
.catch((err) => { |
|||
done(err); |
|||
}); |
|||
} |
|||
|
|||
function sendTelegramMessage(telegramID, message, done = () => {}) { |
|||
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`; |
|||
axios |
|||
.post(url, { |
|||
chat_id: telegramID, |
|||
text: message, |
|||
}) |
|||
.then((res) => { |
|||
done(res); |
|||
}) |
|||
.catch((err) => { |
|||
console.error("Problem sending Telegram message."); |
|||
done(err); |
|||
}); |
|||
} |
|||
|
|||
setTelegramWebHook(() => {}); |
|||
|
|||
exports.sendTelegramMessage = sendTelegramMessage; |
|||
exports.setTelegramWebHook = setTelegramWebHook; |
@ -0,0 +1,30 @@ |
|||
import axios from "axios"; |
|||
import { TelegramID } from "./types"; |
|||
|
|||
export async function setTelegramWebHook(): Promise<void> { |
|||
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/setWebhook`; |
|||
await axios.post(url, { |
|||
url: `${process.env.SERVER_API_URL}/${process.env.TELEGRAM_SECRET}`, |
|||
allowed_updates: [], |
|||
drop_pending_updates: true, |
|||
}); |
|||
} |
|||
|
|||
export async function sendTelegramMessage( |
|||
telegramID: TelegramID, |
|||
message: string |
|||
): Promise<void> { |
|||
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`; |
|||
const response = await axios.post(url, { |
|||
chat_id: telegramID, |
|||
text: message, |
|||
}); |
|||
} |
|||
|
|||
|
|||
setTelegramWebHook() |
|||
.catch(error=>{ |
|||
console.error("Error setting Telegram Webhook"); |
|||
error instanceof Error && console.error(error.message); |
|||
}); |
|||
|
@ -0,0 +1,14 @@ |
|||
/* |
|||
* Branding allows to use nominal typing and avoid errors |
|||
*/ |
|||
export type UserRowID = number & { __userRowIDBrand: any }; |
|||
export type TelegramID = number & { __telegramIDBrand: any }; |
|||
export type VerificationString = string & { __verificationStringBrand: any }; |
|||
|
|||
declare module "express-session" { |
|||
interface Session { |
|||
isVerified: boolean; |
|||
verifiedByTelegramID: TelegramID; |
|||
userTelegramID: TelegramID; |
|||
} |
|||
} |
@ -1,5 +0,0 @@ |
|||
function getCookieExpiry() { |
|||
return new Date(Date.now() + process.env.COOKIE_EXPIRY_DURATION); |
|||
} |
|||
|
|||
exports.getCookieExpiry = getCookieExpiry; |
@ -0,0 +1,3 @@ |
|||
export function getCookieExpiry(): Date { |
|||
return new Date(Date.now() + Number(process.env.COOKIE_EXPIRY_DURATION)); |
|||
} |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"extends": "@tsconfig/node14/tsconfig.json", |
|||
"include": ["./src/**/*"], |
|||
"compilerOptions": { |
|||
"outDir": "./dist" |
|||
} |
|||
} |
Loading…
Reference in new issue