Nareshkumar Rao
3 years ago
committed by
GitHub
32 changed files with 879 additions and 521 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,14 +1,15 @@ |
|||||
const express = require("express"); |
|
||||
const session = require("express-session"); |
|
||||
const cors = require("cors"); |
|
||||
require("dotenv-flow").config(); |
|
||||
|
import * as dotenvFlow from "dotenv-flow"; |
||||
|
dotenvFlow.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"); |
|
||||
const { CovidRoute } = require("./routes/CovidRoute"); |
|
||||
|
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}`); |
console.log(`Node Environment: ${process.env.NODE_ENV}`); |
||||
|
|
@ -1,89 +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, |
|
||||
}, |
|
||||
infectionDate: { |
|
||||
type: DataTypes.DATE, |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
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,80 @@ |
|||||
|
import { TelegramID, UserRowID, VerificationString } from "../../types"; |
||||
|
import { User, UserInstance } from "./User"; |
||||
|
|
||||
|
export function getUserByTelegramID( |
||||
|
telegramID: TelegramID, |
||||
|
callback: (user?: UserInstance, message?: string) => void |
||||
|
): void { |
||||
|
User.findOne({ |
||||
|
where: { |
||||
|
telegram: telegramID, |
||||
|
}, |
||||
|
}) |
||||
|
.then((result) => { |
||||
|
callback(!!result ? result : undefined); |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
callback(undefined); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getUserByRowID( |
||||
|
rowID: UserRowID, |
||||
|
callback: (user?: UserInstance, message?: string) => void |
||||
|
): void { |
||||
|
User.findOne({ |
||||
|
where: { |
||||
|
id: rowID, |
||||
|
}, |
||||
|
}) |
||||
|
.then((result) => { |
||||
|
callback(!!result ? result : undefined); |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
callback(undefined); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getUserByVerification( |
||||
|
verification: VerificationString, |
||||
|
callback: (user?: UserInstance, message?: string) => void |
||||
|
): void { |
||||
|
User.findOne({ |
||||
|
where: { |
||||
|
verification: verification, |
||||
|
}, |
||||
|
}) |
||||
|
.then((result) => { |
||||
|
callback(!!result ? result : undefined); |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
callback(undefined); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getUserCovidPositivity(telegramID: TelegramID, callback: (isInfected?: boolean) => void): void { |
||||
|
getUserByTelegramID(telegramID, user => { |
||||
|
if (!!user) { |
||||
|
const infectionDuration = +user.infectionDate - Date.now(); |
||||
|
if (infectionDuration > 60 * 60 * 24 * 14) { |
||||
|
setUserCovidPositivity(telegramID, false, success => { |
||||
|
callback(success ? false : undefined); |
||||
|
}); |
||||
|
} else { |
||||
|
callback(user.isInfected); |
||||
|
} |
||||
|
} else { |
||||
|
callback(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function setUserCovidPositivity(telegramID: TelegramID, infectionState: boolean, callback: (success: boolean) => void): void { |
||||
|
getUserByTelegramID(telegramID, user => { |
||||
|
if (!!user) { |
||||
|
user.isInfected = infectionState; |
||||
|
user.infectionDate = new Date(); |
||||
|
user.save().then(() => callback(true)).catch(() => callback(false)); |
||||
|
} else { callback(false) } |
||||
|
}); |
||||
|
} |
@ -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,56 @@ |
|||||
|
import { strings_en } from "../strings"; |
||||
|
import { sendTelegramMessage } from "../telegram"; |
||||
|
import { TelegramID, UserRowID } from "../types"; |
||||
|
import { Contact } from "./models/Contact"; |
||||
|
import { User } from "./models/User"; |
||||
|
import { getUserByRowID, getUserByTelegramID } from "./models/User.helper"; |
||||
|
|
||||
|
export function addContact( |
||||
|
userATelegram: TelegramID, |
||||
|
userBTelegram: TelegramID, |
||||
|
callback: (success: boolean, message?: string) => void |
||||
|
): void { |
||||
|
getUserByTelegramID(userATelegram, (userA) => { |
||||
|
getUserByTelegramID(userBTelegram, (userB) => { |
||||
|
if (!userA || !userB) { |
||||
|
callback(false, "Could not find user."); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Contact.create({ user: userA.id, with: userB.id }) |
||||
|
.then(() => { |
||||
|
console.log( |
||||
|
`Registering contact between ${userA.id} and ${userB.id}` |
||||
|
); |
||||
|
sendTelegramMessage(userB.telegram, strings_en.telegram_qr_scanned); |
||||
|
callback(true, "Successfully added contact"); |
||||
|
}) |
||||
|
.catch((e) => { |
||||
|
callback(false, e); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function createUser( |
||||
|
telegram: TelegramID, |
||||
|
callback: (success: boolean, message: string) => void |
||||
|
): void { |
||||
|
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"); |
||||
|
} |
||||
|
}); |
||||
|
} |
@ -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,54 @@ |
|||||
|
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 function CodeRoute(req: Request, res: Response) { |
||||
|
if (!req.session.userTelegramID) { |
||||
|
res.status(401).send("Not logged in"); |
||||
|
return; |
||||
|
} |
||||
|
createQRCode(req.session.userTelegramID, (err, url) => { |
||||
|
res.status(url ? 200 : 401).send({ error: err, data: url }); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function createQRCode( |
||||
|
telegram: TelegramID, |
||||
|
callback: (errorMessage: string | Error, url?: string) => void |
||||
|
): void { |
||||
|
getUserByTelegramID(telegram, (user) => { |
||||
|
!!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, url); |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function refreshVerification( |
||||
|
user: UserInstance, |
||||
|
callback: (success: UserInstance) => void |
||||
|
): void { |
||||
|
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; |
||||
|
user.save().then((result) => { |
||||
|
callback(result); |
||||
|
}); |
||||
|
} |
@ -1,61 +0,0 @@ |
|||||
const { User } = require("../db/db"); |
|
||||
|
|
||||
function CovidRoute(req, res){ |
|
||||
if(!req.session.user){ |
|
||||
res.status(401).send("Not logged in"); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
console.log(`SetPositive: ${req.body.setPositive}`); |
|
||||
|
|
||||
if(req.body.setPositive){ |
|
||||
setUserCovidPositive(req.session.user, true, response=>{ |
|
||||
res.send({covidPositive: response}); |
|
||||
}); |
|
||||
}else{ |
|
||||
getUserCovidPositivity(req.session.user, (success, positivity)=>{ |
|
||||
res.status(success ? 200 : 400).send({covidPositive: positivity}); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function getUserCovidPositivity(telegramID, callback){ |
|
||||
User.findOne({ |
|
||||
where: {telegram: telegramID}, |
|
||||
}) |
|
||||
.then(user=>{ |
|
||||
if(user){ |
|
||||
const infectionDuration = user.infectionDate - Date.now(); |
|
||||
if(infectionDuration > 60 * 60 * 24 * 14){ |
|
||||
setUserCovidPositive(telegramID, false, res=>{ |
|
||||
callback(res, res ? false : null); |
|
||||
}); |
|
||||
}else{ |
|
||||
callback(true, user.isInfected); |
|
||||
} |
|
||||
}else{ |
|
||||
callback(false, null); |
|
||||
} |
|
||||
}) |
|
||||
.catch(()=>{ |
|
||||
callback(false, null); |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
function setUserCovidPositive(telegramID, infectionState, callback){ |
|
||||
User.findOne({ |
|
||||
where: {telegram: telegramID}, |
|
||||
}) |
|
||||
.then(user=>{ |
|
||||
if(user){ |
|
||||
user.isInfected = infectionState; |
|
||||
user.infectionDate = Date.now(); |
|
||||
user.save().then(()=>callback(true)).catch(()=>callback(false)); |
|
||||
}else{ |
|
||||
callback(false); |
|
||||
} |
|
||||
}) |
|
||||
.catch(()=>callback(false)); |
|
||||
} |
|
||||
|
|
||||
exports.CovidRoute = CovidRoute; |
|
@ -0,0 +1,26 @@ |
|||||
|
import { Request, Response } from "express"; |
||||
|
import { getUserCovidPositivity, setUserCovidPositivity } from "../db/models/User.helper"; |
||||
|
|
||||
|
interface CovidRouteRequest extends Request { |
||||
|
body:{ |
||||
|
setPositive: boolean; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function CovidRoute(req: CovidRouteRequest, res:Response){ |
||||
|
if(!req.session.userTelegramID){ |
||||
|
res.status(401).send("Not logged in"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if(req.body.setPositive){ |
||||
|
setUserCovidPositivity(req.session.userTelegramID, true, success=>{ |
||||
|
res.send({covidPositive: true}); |
||||
|
}); |
||||
|
}else{ |
||||
|
getUserCovidPositivity(req.session.userTelegramID, isInfected=>{ |
||||
|
res.send({covidPositive: isInfected}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
@ -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 function LoginRoute(req: LoginRequest, res: Response) { |
||||
|
const telegramResponse = req.body.telegramResponse; |
||||
|
authUser(telegramResponse, (authorized) => { |
||||
|
if (authorized) { |
||||
|
// User is already logged in
|
||||
|
if (req.session.userTelegramID == telegramResponse.id) { |
||||
|
res.send({authorized: authorized}); |
||||
|
return; |
||||
|
} |
||||
|
const verified = req.session.isVerified; |
||||
|
const verifiedBy = req.session.verifiedByTelegramID; |
||||
|
req.session.regenerate(() => { |
||||
|
req.session.userTelegramID = telegramResponse.id; |
||||
|
if (verified) { |
||||
|
addContact(telegramResponse.id, verifiedBy, (success) => { |
||||
|
res.send({ |
||||
|
authorized: authorized, |
||||
|
contactSuccess: success, |
||||
|
}); |
||||
|
}); |
||||
|
} else { |
||||
|
res.send({authorized: authorized}); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
res.status(401).send(authorized); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function authUser( |
||||
|
telegramResponse: TelegramLoginResponse, |
||||
|
callback: (authorized: boolean, message?: string) => void |
||||
|
): void { |
||||
|
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) { |
||||
|
callback(false); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
getUserByTelegramID(telegramResponse.id, (user) => { |
||||
|
if (!!user) { |
||||
|
callback(true); |
||||
|
} else { |
||||
|
createUser(telegramResponse.id, (success, message) => { |
||||
|
callback(success, message); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
@ -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,99 @@ |
|||||
|
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 function TelegramWebhookRoute( |
||||
|
req: TelegramWebhookRequest, |
||||
|
res: Response |
||||
|
) { |
||||
|
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, (success) => { |
||||
|
if (success) { |
||||
|
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: TelegramID) { |
||||
|
getUserByTelegramID(telegramID, (user) => { |
||||
|
if (user) { |
||||
|
Contact.findAll({ |
||||
|
where: { |
||||
|
[Op.or]: [{ user: user.id }, { with: user.id }], |
||||
|
}, |
||||
|
}).then((result) => { |
||||
|
result.forEach((contact) => { |
||||
|
const otherPersonID = |
||||
|
contact.user == user.id ? contact.with : contact.user; |
||||
|
getUserByRowID(otherPersonID, (otherUser) => { |
||||
|
otherUser && |
||||
|
sendTelegramMessage( |
||||
|
otherUser.telegram, |
||||
|
strings_en.telegram_inform_infect |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function userInfected( |
||||
|
telegramID: TelegramID, |
||||
|
callback: (success: boolean) => void |
||||
|
): void { |
||||
|
getUserByTelegramID(telegramID, (user) => { |
||||
|
if (!!user) { |
||||
|
user.isInfected = true; |
||||
|
user |
||||
|
.save() |
||||
|
.then((result) => { |
||||
|
callback(!!result); |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
callback(false); |
||||
|
}); |
||||
|
} else { |
||||
|
callback(false); |
||||
|
} |
||||
|
}); |
||||
|
} |
@ -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,44 @@ |
|||||
|
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 function VerifyRoute(req: VerifyRequest, res: Response) { |
||||
|
getUserByVerification( |
||||
|
decodeURIComponent(req.body.id) as VerificationString, |
||||
|
(verifiedByUser, message) => { |
||||
|
if (!!verifiedByUser) { |
||||
|
req.session.isVerified = !!verifiedByUser; |
||||
|
req.session.verifiedByTelegramID = verifiedByUser.telegram; |
||||
|
if (req.session.userTelegramID) { |
||||
|
// If Logged In
|
||||
|
addContact( |
||||
|
req.session.userTelegramID, |
||||
|
verifiedByUser.telegram, |
||||
|
(success, message) => { |
||||
|
res |
||||
|
.status(success ? 200 : 400) |
||||
|
.send({ success: success, message: message, loggedIn: true }); |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
// If Not Logged In
|
||||
|
res.send({ |
||||
|
success: !!verifiedByUser, |
||||
|
message: message, |
||||
|
loggedIn: false, |
||||
|
}); |
||||
|
} |
||||
|
} else { |
||||
|
res.status(400).send({ success: !!verifiedByUser, message: message }); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
@ -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 }; |
@ -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