const express = require ( 'express' ) ;
const { Sequelize , DataTypes , STRING } = require ( 'sequelize' ) ;
const session = require ( 'express-session' ) ;
const bcrypt = require ( 'bcrypt' ) ;
const QRCode = require ( 'qrcode' ) ;
const cors = require ( 'cors' ) ;
const { createSecureServer } = require ( 'http2' ) ;
const { default : axios } = require ( 'axios' ) ;
require ( "dotenv-flow" ) . config ( ) ;
const crypto = require ( 'crypto' ) ;
var SequelizeStore = require ( "connect-session-sequelize" ) ( session . Store ) ;
const COOKIE_EXPIRY_DURATION = 60 * 60 * 3 * 1000 ; // 3 hours in milliseconds
const isProduction = process . env . NODE_ENV == "production" ;
const isDev = process . env . NODE_ENV == "development" ;
console . log ( ` Node Environment: ${ process . env . NODE_ENV } ` ) ;
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 ,
expiration : COOKIE_EXPIRY_DURATION ,
} ) ;
store . sync ( ) ;
const Contact = sequelize . define ( 'Contact' , {
user : {
type : DataTypes . INTEGER ,
allowNull : false ,
} ,
with : {
type : DataTypes . INTEGER ,
allowNull : false ,
} ,
} ) ;
Contact . sync ( ) ;
const User = sequelize . define ( 'User' , {
telegram : {
type : DataTypes . INTEGER ,
allowNull : false ,
unique : true ,
} ,
verification : {
type : DataTypes . STRING ,
} ,
} ) ;
User . sync ( ) . then ( ( ) => {
if ( process . env . ADMIN_USERNAME && process . env . ADMIN_PASSWORD ) {
User . create ( {
telegram : process . env . ADMIN_USERNAME ,
hash : bcrypt . hashSync ( process . env . ADMIN_PASSWORD , 10 ) ,
} ) . catch ( e => {
console . log ( "Couldn't create admin account. Probably exists." ) ;
} ) ;
}
} ) ;
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" ) ;
secretKey = crypto . createHash ( "sha256" ) . update ( process . env . TELEGRAM_TOKEN ) . digest ( ) ;
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 } ) ;
}
} ) ;
}
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 )
} ) ;
}
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 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" ) ;
}
} ) ;
}
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" ) ;
}
} ) ;
}
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 ( res => {
console . log ( ` Registering contact between ${ user . id } and ${ withUserID } ` ) ;
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" ,
( ) => { }
) ;
done ( true , "Successfully added contact" ) ;
} )
. catch ( e => {
done ( false , e ) ;
} ) ;
} )
} )
}
function getCookieExpiry ( ) {
return new Date ( Date . now ( ) + COOKIE_EXPIRY_DURATION ) ;
}
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 )
} ) ;
}
const app = express ( ) ;
app . set ( 'trust proxy' , 1 )
app . use ( session ( {
secret : process . env . SERVER_SESSION_SECRET ,
resave : false ,
saveUninitialized : false ,
cookie : {
secure : true ,
sameSite : "none" ,
} ,
store : store ,
} ) )
app . use ( cors ( { credentials : true , origin : true , secure : true } ) ) ;
app . use ( express . json ( ) )
setTelegramWebHook ( ( ) => { } ) ;
app . post ( ` / ${ process . env . TELEGRAM_SECRET } ` , ( req , res ) => {
res . send ( ) ;
} ) ;
app . post ( '/login' , ( req , res ) => {
telegramResponse = req . body . telegramResponse ;
const auth = authUser ( telegramResponse , ( success , msg ) => {
if ( success ) {
const verified = req . session . verified ;
const verifiedBy = req . session . verifiedBy ;
req . session . regenerate ( ( ) => {
cookieExpiry = getCookieExpiry ( ) ;
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 } ) ;
}
} ) ;
} ) ;
app . get ( '/code' , ( 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 } ) ;
} ) ;
} )
app . post ( "/verify" , ( req , res ) => {
checkVerification ( req . body . id , ( success , msg , withUserID ) => {
cookieExpiry = getCookieExpiry ( ) ;
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 } ) ;
}
} ) ;
} ) ;
const port = process . env . PORT || 8080 ;
app . listen ( port , ( ) => {
console . log ( ` Listening on port ${ port } ` ) ;
} )