2021-07-27 10:47:13 -07:00
const tcpp = require ( "tcp-ping" ) ;
2023-01-11 09:04:58 -08:00
const ping = require ( "@louislam/ping" ) ;
2021-07-27 10:47:13 -07:00
const { R } = require ( "redbean-node" ) ;
2022-04-13 08:33:37 -07:00
const { log , genSecret } = require ( "../src/util" ) ;
2021-08-08 22:34:44 -07:00
const passwordHash = require ( "./password-hash" ) ;
2021-08-23 07:30:11 -07:00
const { Resolver } = require ( "dns" ) ;
2022-04-13 09:30:32 -07:00
const childProcess = require ( "child_process" ) ;
2021-10-13 09:22:49 -07:00
const iconv = require ( "iconv-lite" ) ;
const chardet = require ( "chardet" ) ;
2021-11-03 18:46:43 -07:00
const mqtt = require ( "mqtt" ) ;
2022-01-03 06:48:52 -08:00
const chroma = require ( "chroma-js" ) ;
2022-01-04 03:21:53 -08:00
const { badgeConstants } = require ( "./config" ) ;
2022-05-13 06:40:46 -07:00
const mssql = require ( "mssql" ) ;
2022-06-15 10:12:47 -07:00
const { Client } = require ( "pg" ) ;
2022-06-15 11:00:14 -07:00
const postgresConParse = require ( "pg-connection-string" ) . parse ;
2022-11-17 10:34:02 -08:00
const mysql = require ( "mysql2" ) ;
2022-11-16 17:50:34 -08:00
const { MongoClient } = require ( "mongodb" ) ;
2022-05-13 10:58:23 -07:00
const { NtlmClient } = require ( "axios-ntlm" ) ;
2022-06-28 23:57:40 -07:00
const { Settings } = require ( "./settings" ) ;
2022-08-02 22:00:39 -07:00
const grpc = require ( "@grpc/grpc-js" ) ;
const protojs = require ( "protobufjs" ) ;
2022-05-12 02:48:38 -07:00
const radiusClient = require ( "node-radius-client" ) ;
2023-01-05 06:58:24 -08:00
const redis = require ( "redis" ) ;
2022-05-12 02:48:38 -07:00
const {
dictionaries : {
rfc2865 : { file , attributes } ,
} ,
} = require ( "node-radius-utils" ) ;
2022-09-25 04:38:28 -07:00
const dayjs = require ( "dayjs" ) ;
2021-08-08 22:34:44 -07:00
2023-01-05 03:22:15 -08:00
const isWindows = process . platform === /^win/ . test ( process . platform ) ;
2021-08-08 22:34:44 -07:00
/ * *
* Init or reset JWT secret
* @ returns { Promise < Bean > }
* /
exports . initJWTSecret = async ( ) => {
let jwtSecretBean = await R . findOne ( "setting" , " `key` = ? " , [
"jwtSecret" ,
] ) ;
2021-11-03 18:46:43 -07:00
if ( ! jwtSecretBean ) {
2021-08-08 22:34:44 -07:00
jwtSecretBean = R . dispense ( "setting" ) ;
jwtSecretBean . key = "jwtSecret" ;
}
2022-03-29 02:38:48 -07:00
jwtSecretBean . value = passwordHash . generate ( genSecret ( ) ) ;
2021-08-08 22:34:44 -07:00
await R . store ( jwtSecretBean ) ;
return jwtSecretBean ;
2021-09-20 01:22:18 -07:00
} ;
2021-06-30 23:03:06 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Send TCP request to specified hostname and port
* @ param { string } hostname Hostname / address of machine
* @ param { number } port TCP port to test
* @ returns { Promise < number > } Maximum time in ms rounded to nearest integer
* /
2021-06-30 23:03:06 -07:00
exports . tcping = function ( hostname , port ) {
return new Promise ( ( resolve , reject ) => {
tcpp . ping ( {
address : hostname ,
port : port ,
attempts : 1 ,
2021-08-05 04:04:38 -07:00
} , function ( err , data ) {
2021-06-30 23:03:06 -07:00
if ( err ) {
reject ( err ) ;
}
if ( data . results . length >= 1 && data . results [ 0 ] . err ) {
reject ( data . results [ 0 ] . err ) ;
}
resolve ( Math . round ( data . max ) ) ;
} ) ;
} ) ;
2021-09-20 01:22:18 -07:00
} ;
2021-07-01 02:00:23 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Ping the specified machine
* @ param { string } hostname Hostname / address of machine
* @ returns { Promise < number > } Time for ping in ms rounded to nearest integer
* /
2021-08-10 06:03:14 -07:00
exports . ping = async ( hostname ) => {
try {
2021-08-10 07:00:29 -07:00
return await exports . pingAsync ( hostname ) ;
2021-08-10 06:03:14 -07:00
} catch ( e ) {
// If the host cannot be resolved, try again with ipv6
if ( e . message . includes ( "service not known" ) ) {
2021-08-10 07:00:29 -07:00
return await exports . pingAsync ( hostname , true ) ;
2021-08-10 06:03:14 -07:00
} else {
throw e ;
}
}
2021-09-20 01:22:18 -07:00
} ;
2021-08-10 06:03:14 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Ping the specified machine
* @ param { string } hostname Hostname / address of machine to ping
* @ param { boolean } ipv6 Should IPv6 be used ?
* @ returns { Promise < number > } Time for ping in ms rounded to nearest integer
* /
2021-08-10 06:03:14 -07:00
exports . pingAsync = function ( hostname , ipv6 = false ) {
2021-07-01 02:00:23 -07:00
return new Promise ( ( resolve , reject ) => {
2023-01-03 12:03:36 -08:00
ping . promise . probe ( hostname , {
v6 : ipv6 ,
2023-01-05 03:30:55 -08:00
min _reply : 1 ,
2023-01-05 04:38:37 -08:00
timeout : 10 ,
2023-01-03 12:03:36 -08:00
} ) . then ( ( res ) => {
// If ping failed, it will set field to unknown
2023-01-04 09:32:27 -08:00
if ( res . alive ) {
2023-01-03 12:03:36 -08:00
resolve ( res . time ) ;
2021-07-01 02:00:23 -07:00
} else {
2023-01-05 03:22:15 -08:00
if ( isWindows ) {
reject ( new Error ( exports . convertToUTF8 ( res . output ) ) ) ;
} else {
reject ( new Error ( res . output ) ) ;
}
2021-07-01 02:00:23 -07:00
}
2023-01-03 12:03:36 -08:00
} ) . catch ( ( err ) => {
reject ( err ) ;
2021-07-01 02:00:23 -07:00
} ) ;
} ) ;
2021-09-20 01:22:18 -07:00
} ;
2021-07-08 23:14:03 -07:00
2022-04-21 05:01:22 -07:00
/ * *
* MQTT Monitor
2022-04-21 10:53:07 -07:00
* @ param { string } hostname Hostname / address of machine to test
* @ param { string } topic MQTT topic
* @ param { string } okMessage Expected result
* @ param { Object } [ options = { } ] MQTT options . Contains port , username ,
* password and interval ( interval defaults to 20 )
* @ returns { Promise < string > }
2022-04-21 05:01:22 -07:00
* /
2021-11-22 00:21:53 -08:00
exports . mqttAsync = function ( hostname , topic , okMessage , options = { } ) {
2021-11-03 18:46:43 -07:00
return new Promise ( ( resolve , reject ) => {
2021-11-22 00:21:53 -08:00
const { port , username , password , interval = 20 } = options ;
2022-01-12 20:42:34 -08:00
// Adds MQTT protocol to the hostname if not already present
if ( ! /^(?:http|mqtt)s?:\/\// . test ( hostname ) ) {
hostname = "mqtt://" + hostname ;
2021-11-03 18:46:43 -07:00
}
2022-01-12 20:42:34 -08:00
2022-01-20 10:20:54 -08:00
const timeoutID = setTimeout ( ( ) => {
2022-04-15 22:37:17 -07:00
log . debug ( "mqtt" , "MQTT timeout triggered" ) ;
2022-01-20 10:20:54 -08:00
client . end ( ) ;
2022-04-16 10:06:47 -07:00
reject ( new Error ( "Timeout" ) ) ;
2022-04-16 00:01:53 -07:00
} , interval * 1000 * 0.8 ) ;
2022-01-20 10:20:54 -08:00
2022-04-15 22:37:17 -07:00
log . debug ( "mqtt" , "MQTT connecting" ) ;
2022-01-12 20:42:34 -08:00
let client = mqtt . connect ( hostname , {
port ,
username ,
password
} ) ;
client . on ( "connect" , ( ) => {
2022-04-16 10:06:47 -07:00
log . debug ( "mqtt" , "MQTT connected" ) ;
2022-04-15 23:50:48 -07:00
try {
2022-04-16 10:06:47 -07:00
log . debug ( "mqtt" , "MQTT subscribe topic" ) ;
2022-04-15 23:50:48 -07:00
client . subscribe ( topic ) ;
} catch ( e ) {
2022-04-16 00:01:53 -07:00
client . end ( ) ;
clearTimeout ( timeoutID ) ;
2022-04-15 23:50:48 -07:00
reject ( new Error ( "Cannot subscribe topic" ) ) ;
}
2022-01-12 20:42:34 -08:00
} ) ;
client . on ( "error" , ( error ) => {
client . end ( ) ;
2022-01-20 10:20:54 -08:00
clearTimeout ( timeoutID ) ;
2022-01-12 20:42:34 -08:00
reject ( error ) ;
} ) ;
client . on ( "message" , ( messageTopic , message ) => {
2022-04-25 16:26:57 -07:00
if ( messageTopic === topic ) {
2022-01-20 10:20:54 -08:00
client . end ( ) ;
clearTimeout ( timeoutID ) ;
2022-04-17 22:04:55 -07:00
if ( okMessage != null && okMessage !== "" && message . toString ( ) !== okMessage ) {
reject ( new Error ( ` Message Mismatch - Topic: ${ messageTopic } ; Message: ${ message . toString ( ) } ` ) ) ;
2022-01-12 20:42:34 -08:00
} else {
2022-04-17 22:04:55 -07:00
resolve ( ` Topic: ${ messageTopic } ; Message: ${ message . toString ( ) } ` ) ;
2022-01-12 20:42:34 -08:00
}
}
} ) ;
2021-11-03 18:46:43 -07:00
} ) ;
2022-05-13 10:58:23 -07:00
} ;
/ * *
* Use NTLM Auth for a http request .
* @ param { Object } options The http request options
* @ param { Object } ntlmOptions The auth options
* @ returns { Promise < ( string [ ] | Object [ ] | Object ) > }
* /
exports . httpNtlm = function ( options , ntlmOptions ) {
return new Promise ( ( resolve , reject ) => {
let client = NtlmClient ( ntlmOptions ) ;
client ( options )
. then ( ( resp ) => {
resolve ( resp ) ;
} )
. catch ( ( err ) => {
reject ( err ) ;
} ) ;
} ) ;
2021-11-03 18:46:43 -07:00
} ;
2022-04-20 11:56:40 -07:00
/ * *
* Resolves a given record using the specified DNS server
* @ param { string } hostname The hostname of the record to lookup
2022-04-21 05:01:22 -07:00
* @ param { string } resolverServer The DNS server to use
2022-04-27 10:26:11 -07:00
* @ param { string } resolverPort Port the DNS server is listening on
2022-04-20 11:56:40 -07:00
* @ param { string } rrtype The type of record to request
* @ returns { Promise < ( string [ ] | Object [ ] | Object ) > }
* /
2022-04-23 17:06:45 -07:00
exports . dnsResolve = function ( hostname , resolverServer , resolverPort , rrtype ) {
2021-08-22 15:05:48 -07:00
const resolver = new Resolver ( ) ;
2022-04-15 11:59:32 -07:00
// Remove brackets from IPv6 addresses so we can re-add them to
// prevent issues with ::1:5300 (::1 port 5300)
2022-04-23 17:06:45 -07:00
resolverServer = resolverServer . replace ( "[" , "" ) . replace ( "]" , "" ) ;
2022-05-31 22:05:12 -07:00
resolver . setServers ( [ ` [ ${ resolverServer } ]: ${ resolverPort } ` ] ) ;
2021-08-22 15:05:48 -07:00
return new Promise ( ( resolve , reject ) => {
2022-04-17 00:43:03 -07:00
if ( rrtype === "PTR" ) {
2021-08-22 15:05:48 -07:00
resolver . reverse ( hostname , ( err , records ) => {
if ( err ) {
reject ( err ) ;
} else {
resolve ( records ) ;
}
} ) ;
} else {
resolver . resolve ( hostname , rrtype , ( err , records ) => {
if ( err ) {
reject ( err ) ;
} else {
resolve ( records ) ;
}
} ) ;
}
2021-09-20 01:22:18 -07:00
} ) ;
} ;
2021-08-22 15:05:48 -07:00
2022-05-12 10:48:03 -07:00
/ * *
* Run a query on SQL Server
* @ param { string } connectionString The database connection string
* @ param { string } query The query to validate the database with
* @ returns { Promise < ( string [ ] | Object [ ] | Object ) > }
* /
2023-01-01 06:27:14 -08:00
exports . mssqlQuery = async function ( connectionString , query ) {
let pool ;
try {
pool = new mssql . ConnectionPool ( connectionString ) ;
await pool . connect ( ) ;
await pool . request ( ) . query ( query ) ;
pool . close ( ) ;
} catch ( e ) {
if ( pool ) {
pool . close ( ) ;
}
throw e ;
}
2022-05-12 10:48:03 -07:00
} ;
2022-06-15 10:12:47 -07:00
/ * *
* Run a query on Postgres
* @ param { string } connectionString The database connection string
* @ param { string } query The query to validate the database with
* @ returns { Promise < ( string [ ] | Object [ ] | Object ) > }
* /
exports . postgresQuery = function ( connectionString , query ) {
return new Promise ( ( resolve , reject ) => {
2022-06-15 11:00:14 -07:00
const config = postgresConParse ( connectionString ) ;
if ( config . password === "" ) {
// See https://github.com/brianc/node-postgres/issues/1927
return reject ( new Error ( "Password is undefined." ) ) ;
}
2022-06-15 10:12:47 -07:00
const client = new Client ( { connectionString } ) ;
client . connect ( ) ;
2022-06-15 11:00:14 -07:00
return client . query ( query )
2022-06-15 10:12:47 -07:00
. then ( res => {
resolve ( res ) ;
} )
. catch ( err => {
reject ( err ) ;
} )
. finally ( ( ) => {
client . end ( ) ;
} ) ;
} ) ;
} ;
2022-10-01 17:52:53 -07:00
/ * *
* Run a query on MySQL / MariaDB
* @ param { string } connectionString The database connection string
* @ param { string } query The query to validate the database with
* @ returns { Promise < ( string [ ] | Object [ ] | Object ) > }
* /
exports . mysqlQuery = function ( connectionString , query ) {
return new Promise ( ( resolve , reject ) => {
2022-11-17 10:34:02 -08:00
const connection = mysql . createConnection ( connectionString ) ;
connection . promise ( ) . query ( query )
2022-10-01 17:52:53 -07:00
. then ( res => {
resolve ( res ) ;
} )
. catch ( err => {
reject ( err ) ;
2022-11-17 10:34:02 -08:00
} )
. finally ( ( ) => {
connection . end ( ) ;
2022-10-01 17:52:53 -07:00
} ) ;
} ) ;
} ;
2022-11-16 17:50:34 -08:00
/ * *
* Connect to and Ping a MongoDB database
* @ param { string } connectionString The database connection string
* @ returns { Promise < ( string [ ] | Object [ ] | Object ) > }
* /
exports . mongodbPing = async function ( connectionString ) {
2023-01-14 09:36:49 -08:00
let client = await MongoClient . connect ( connectionString ) ;
let dbPing = await client . db ( ) . command ( { ping : 1 } ) ;
await client . close ( ) ;
if ( dbPing [ "ok" ] === 1 ) {
return "UP" ;
} else {
throw Error ( "failed" ) ;
2022-11-16 17:50:34 -08:00
}
2022-11-16 19:21:15 -08:00
} ;
2022-11-16 17:50:34 -08:00
2022-10-12 09:32:05 -07:00
/ * *
* Query radius server
* @ param { string } hostname Hostname of radius server
* @ param { string } username Username to use
* @ param { string } password Password to use
* @ param { string } calledStationId ID of called station
* @ param { string } callingStationId ID of calling station
* @ param { string } secret Secret to use
* @ param { number } [ port = 1812 ] Port to contact radius server on
* @ returns { Promise < any > }
* /
2022-05-12 02:48:38 -07:00
exports . radius = function (
hostname ,
username ,
password ,
calledStationId ,
callingStationId ,
secret ,
2022-10-12 09:32:05 -07:00
port = 1812 ,
2022-05-12 02:48:38 -07:00
) {
const client = new radiusClient ( {
host : hostname ,
2022-10-12 09:32:05 -07:00
hostPort : port ,
2022-05-12 02:48:38 -07:00
dictionaries : [ file ] ,
} ) ;
return client . accessRequest ( {
secret : secret ,
attributes : [
[ attributes . USER _NAME , username ] ,
[ attributes . USER _PASSWORD , password ] ,
[ attributes . CALLING _STATION _ID , callingStationId ] ,
[ attributes . CALLED _STATION _ID , calledStationId ] ,
] ,
} ) ;
} ;
2023-01-05 06:58:24 -08:00
/ * *
* Redis server ping
* @ param { string } dsn The redis connection string
* /
2023-01-13 03:10:07 -08:00
exports . redisPingAsync = function ( dsn ) {
return new Promise ( ( resolve , reject ) => {
const client = redis . createClient ( {
url : dsn ,
} ) ;
client . on ( "error" , ( err ) => {
reject ( err ) ;
} ) ;
client . connect ( ) . then ( ( ) => {
client . ping ( ) . then ( ( res , err ) => {
if ( err ) {
reject ( err ) ;
} else {
resolve ( res ) ;
}
} ) ;
} ) ;
2023-01-05 06:58:24 -08:00
} ) ;
} ;
2022-05-12 02:48:38 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Retrieve value of setting based on key
* @ param { string } key Key of setting to retrieve
2022-05-05 23:41:34 -07:00
* @ returns { Promise < any > } Value
2022-07-31 08:41:29 -07:00
* @ deprecated Use await Settings . get ( key )
2022-04-20 11:56:40 -07:00
* /
2021-07-08 23:14:03 -07:00
exports . setting = async function ( key ) {
2022-06-28 23:57:40 -07:00
return await Settings . get ( key ) ;
2021-09-20 01:22:18 -07:00
} ;
2021-07-08 23:14:03 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Sets the specified setting to specifed value
* @ param { string } key Key of setting to set
* @ param { any } value Value to set to
* @ param { ? string } type Type of setting
* @ returns { Promise < void > }
* /
2021-10-09 09:16:13 -07:00
exports . setSetting = async function ( key , value , type = null ) {
2022-06-28 23:57:40 -07:00
await Settings . set ( key , value , type ) ;
2021-09-20 01:22:18 -07:00
} ;
2021-07-21 11:02:35 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Get settings based on type
2022-06-28 23:57:40 -07:00
* @ param { string } type The type of setting
2022-04-20 11:56:40 -07:00
* @ returns { Promise < Bean > }
* /
2021-07-08 23:14:03 -07:00
exports . getSettings = async function ( type ) {
2022-06-28 23:57:40 -07:00
return await Settings . getSettings ( type ) ;
2021-09-20 01:22:18 -07:00
} ;
2021-07-20 21:09:09 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Set settings based on type
2022-06-28 23:57:40 -07:00
* @ param { string } type Type of settings to set
2022-04-20 11:56:40 -07:00
* @ param { Object } data Values of settings
* @ returns { Promise < void > }
* /
2021-07-31 06:57:58 -07:00
exports . setSettings = async function ( type , data ) {
2022-06-28 23:57:40 -07:00
await Settings . setSettings ( type , data ) ;
2021-09-20 01:22:18 -07:00
} ;
2021-07-31 06:57:58 -07:00
2021-07-20 21:09:09 -07:00
// ssl-checker by @dyaa
2022-04-20 11:56:40 -07:00
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
2021-07-20 21:09:09 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Get number of days between two dates
* @ param { Date } validFrom Start date
* @ param { Date } validTo End date
* @ returns { number }
* /
2021-07-20 21:09:09 -07:00
const getDaysBetween = ( validFrom , validTo ) =>
Math . round ( Math . abs ( + validFrom - + validTo ) / 8.64 e7 ) ;
2022-04-20 11:56:40 -07:00
/ * *
* Get days remaining from a time range
* @ param { Date } validFrom Start date
* @ param { Date } validTo End date
* @ returns { number }
* /
2021-07-20 21:09:09 -07:00
const getDaysRemaining = ( validFrom , validTo ) => {
const daysRemaining = getDaysBetween ( validFrom , validTo ) ;
if ( new Date ( validTo ) . getTime ( ) < new Date ( ) . getTime ( ) ) {
return - daysRemaining ;
}
return daysRemaining ;
} ;
2022-04-20 11:56:40 -07:00
/ * *
* Fix certificate info for display
* @ param { Object } info The chain obtained from getPeerCertificate ( )
* @ returns { Object } An object representing certificate information
* /
2021-10-01 03:44:32 -07:00
const parseCertificateInfo = function ( info ) {
let link = info ;
2021-11-07 23:39:17 -08:00
let i = 0 ;
const existingList = { } ;
2021-10-01 03:44:32 -07:00
while ( link ) {
2022-04-15 23:50:48 -07:00
log . debug ( "cert" , ` [ ${ i } ] ${ link . fingerprint } ` ) ;
2021-11-07 23:39:17 -08:00
2021-10-01 03:44:32 -07:00
if ( ! link . valid _from || ! link . valid _to ) {
break ;
}
link . validTo = new Date ( link . valid _to ) ;
link . validFor = link . subjectaltname ? . replace ( /DNS:|IP Address:/g , "" ) . split ( ", " ) ;
link . daysRemaining = getDaysRemaining ( new Date ( ) , link . validTo ) ;
2021-11-07 23:39:17 -08:00
existingList [ link . fingerprint ] = true ;
2021-10-01 03:44:32 -07:00
// Move up the chain until loop is encountered
if ( link . issuerCertificate == null ) {
break ;
2021-11-07 23:39:17 -08:00
} else if ( link . issuerCertificate . fingerprint in existingList ) {
2022-04-15 23:50:48 -07:00
log . debug ( "cert" , ` [Last] ${ link . issuerCertificate . fingerprint } ` ) ;
2021-10-01 03:44:32 -07:00
link . issuerCertificate = null ;
break ;
} else {
link = link . issuerCertificate ;
}
2021-11-07 23:39:17 -08:00
// Should be no use, but just in case.
if ( i > 500 ) {
throw new Error ( "Dead loop occurred in parseCertificateInfo" ) ;
}
i ++ ;
2021-07-20 21:09:09 -07:00
}
2021-10-01 03:44:32 -07:00
return info ;
} ;
2021-07-20 21:09:09 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Check if certificate is valid
* @ param { Object } res Response object from axios
* @ returns { Object } Object containing certificate information
* /
2021-10-01 03:44:32 -07:00
exports . checkCertificate = function ( res ) {
2022-12-12 10:21:12 -08:00
if ( ! res . request . res . socket ) {
throw new Error ( "No socket found" ) ;
}
2021-10-01 03:44:32 -07:00
const info = res . request . res . socket . getPeerCertificate ( true ) ;
const valid = res . request . res . socket . authorized || false ;
2021-07-20 21:09:09 -07:00
2022-04-15 23:50:48 -07:00
log . debug ( "cert" , "Parsing Certificate Info" ) ;
2021-10-01 03:44:32 -07:00
const parsedInfo = parseCertificateInfo ( info ) ;
2021-07-20 21:09:09 -07:00
return {
2021-10-01 03:44:32 -07:00
valid : valid ,
certInfo : parsedInfo
2021-07-20 21:09:09 -07:00
} ;
2021-09-20 01:22:18 -07:00
} ;
2021-08-05 04:04:38 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Check if the provided status code is within the accepted ranges
2022-07-18 07:06:25 -07:00
* @ param { number } status The status code to check
2022-04-21 12:02:18 -07:00
* @ param { string [ ] } acceptedCodes An array of accepted status codes
2022-04-20 11:56:40 -07:00
* @ returns { boolean } True if status code within range , false otherwise
* @ throws { Error } Will throw an error if the provided status code is not a valid range string or code string
* /
2022-04-16 10:39:49 -07:00
exports . checkStatusCode = function ( status , acceptedCodes ) {
if ( acceptedCodes == null || acceptedCodes . length === 0 ) {
2021-08-05 04:04:38 -07:00
return false ;
}
2022-04-16 10:39:49 -07:00
for ( const codeRange of acceptedCodes ) {
const codeRangeSplit = codeRange . split ( "-" ) . map ( string => parseInt ( string ) ) ;
if ( codeRangeSplit . length === 1 ) {
if ( status === codeRangeSplit [ 0 ] ) {
2021-08-05 04:04:38 -07:00
return true ;
}
2022-04-16 10:39:49 -07:00
} else if ( codeRangeSplit . length === 2 ) {
if ( status >= codeRangeSplit [ 0 ] && status <= codeRangeSplit [ 1 ] ) {
2021-08-05 04:04:38 -07:00
return true ;
}
} else {
throw new Error ( "Invalid status code range" ) ;
}
}
return false ;
2021-09-20 01:22:18 -07:00
} ;
2021-08-29 23:55:33 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Get total number of clients in room
* @ param { Server } io Socket server instance
* @ param { string } roomName Name of room to check
* @ returns { number }
* /
2021-08-29 23:55:33 -07:00
exports . getTotalClientInRoom = ( io , roomName ) => {
const sockets = io . sockets ;
2021-11-03 18:46:43 -07:00
if ( ! sockets ) {
2021-08-29 23:55:33 -07:00
return 0 ;
}
const adapter = sockets . adapter ;
2021-11-03 18:46:43 -07:00
if ( ! adapter ) {
2021-08-29 23:55:33 -07:00
return 0 ;
}
const room = adapter . rooms . get ( roomName ) ;
if ( room ) {
return room . size ;
} else {
return 0 ;
}
2021-09-20 01:22:18 -07:00
} ;
2021-09-11 04:40:03 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Allow CORS all origins if development
* @ param { Object } res Response object from axios
* /
2021-09-11 04:40:03 -07:00
exports . allowDevAllOrigin = ( res ) => {
if ( process . env . NODE _ENV === "development" ) {
exports . allowAllOrigin ( res ) ;
}
2021-09-20 01:22:18 -07:00
} ;
2021-09-11 04:40:03 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Allow CORS all origins
* @ param { Object } res Response object from axios
* /
2021-09-11 04:40:03 -07:00
exports . allowAllOrigin = ( res ) => {
res . header ( "Access-Control-Allow-Origin" , "*" ) ;
res . header ( "Access-Control-Allow-Headers" , "Origin, X-Requested-With, Content-Type, Accept" ) ;
2021-09-20 01:22:18 -07:00
} ;
2021-09-16 07:48:28 -07:00
2022-04-20 11:56:40 -07:00
/ * *
* Check if a user is logged in
* @ param { Socket } socket Socket instance
* /
2021-09-16 07:48:28 -07:00
exports . checkLogin = ( socket ) => {
2021-11-03 18:46:43 -07:00
if ( ! socket . userID ) {
2021-09-16 07:48:28 -07:00
throw new Error ( "You are not logged in." ) ;
}
2021-09-20 01:22:18 -07:00
} ;
2021-10-05 04:13:57 -07:00
2022-03-29 02:38:48 -07:00
/ * *
* For logged - in users , double - check the password
2022-04-21 05:01:22 -07:00
* @ param { Socket } socket Socket . io instance
* @ param { string } currentPassword
2022-03-29 02:38:48 -07:00
* @ returns { Promise < Bean > }
* /
exports . doubleCheckPassword = async ( socket , currentPassword ) => {
if ( typeof currentPassword !== "string" ) {
throw new Error ( "Wrong data type?" ) ;
}
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
socket . userID ,
] ) ;
if ( ! user || ! passwordHash . verify ( currentPassword , user . password ) ) {
throw new Error ( "Incorrect current password" ) ;
}
return user ;
} ;
2022-04-20 11:56:40 -07:00
/** Start Unit tests */
2021-10-05 04:13:57 -07:00
exports . startUnitTest = async ( ) => {
console . log ( "Starting unit test..." ) ;
const npm = /^win/ . test ( process . platform ) ? "npm.cmd" : "npm" ;
2022-10-04 23:26:30 -07:00
const child = childProcess . spawn ( npm , [ "run" , "jest-backend" ] ) ;
2021-10-05 04:13:57 -07:00
child . stdout . on ( "data" , ( data ) => {
console . log ( data . toString ( ) ) ;
} ) ;
child . stderr . on ( "data" , ( data ) => {
console . log ( data . toString ( ) ) ;
} ) ;
child . on ( "close" , function ( code ) {
console . log ( "Jest exit code: " + code ) ;
2021-10-05 05:40:40 -07:00
process . exit ( code ) ;
2021-10-05 04:13:57 -07:00
} ) ;
} ;
2021-10-13 09:22:49 -07:00
2022-06-16 05:40:42 -07:00
/** Start end-to-end tests */
2022-06-16 02:28:17 -07:00
exports . startE2eTests = async ( ) => {
console . log ( "Starting unit test..." ) ;
const npm = /^win/ . test ( process . platform ) ? "npm.cmd" : "npm" ;
const child = childProcess . spawn ( npm , [ "run" , "cy:run" ] ) ;
2021-10-05 04:13:57 -07:00
child . stdout . on ( "data" , ( data ) => {
console . log ( data . toString ( ) ) ;
} ) ;
child . stderr . on ( "data" , ( data ) => {
console . log ( data . toString ( ) ) ;
} ) ;
child . on ( "close" , function ( code ) {
console . log ( "Jest exit code: " + code ) ;
2021-10-05 05:40:40 -07:00
process . exit ( code ) ;
2021-10-05 04:13:57 -07:00
} ) ;
} ;
2021-10-13 09:22:49 -07:00
/ * *
2022-04-20 11:56:40 -07:00
* Convert unknown string to UTF8
* @ param { Uint8Array } body Buffer
2021-10-13 09:22:49 -07:00
* @ returns { string }
* /
exports . convertToUTF8 = ( body ) => {
const guessEncoding = chardet . detect ( body ) ;
const str = iconv . decode ( body , guessEncoding ) ;
return str . toString ( ) ;
} ;
2021-10-29 03:24:47 -07:00
2022-01-03 07:04:37 -08:00
/ * *
* Returns a color code in hex format based on a given percentage :
* 0 % => hue = 10 => red
* 100 % => hue = 90 => green
*
2022-04-30 06:36:00 -07:00
* @ param { number } percentage float , 0 to 1
2022-04-30 06:36:07 -07:00
* @ param { number } maxHue
2022-01-03 07:04:37 -08:00
* @ param { number } minHue , int
* @ returns { string } , hex value
* /
2022-01-03 06:48:52 -08:00
exports . percentageToColor = ( percentage , maxHue = 90 , minHue = 10 ) => {
const hue = percentage * ( maxHue - minHue ) + minHue ;
try {
return chroma ( ` hsl( ${ hue } , 90%, 40%) ` ) . hex ( ) ;
} catch ( err ) {
2022-01-04 03:21:53 -08:00
return badgeConstants . naColor ;
2022-01-03 06:48:52 -08:00
}
} ;
2022-01-04 07:00:21 -08:00
/ * *
* Joins and array of string to one string after filtering out empty values
*
* @ param { string [ ] } parts
* @ param { string } connector
* @ returns { string }
* /
exports . filterAndJoin = ( parts , connector = "" ) => {
return parts . filter ( ( part ) => ! ! part && part !== "" ) . join ( connector ) ;
} ;
2022-05-31 22:05:12 -07:00
/ * *
* Send a 403 response
* @ param { Object } res Express response object
* @ param { string } [ msg = "" ] Message to send
* /
module . exports . send403 = ( res , msg = "" ) => {
res . status ( 403 ) . json ( {
"status" : "fail" ,
"msg" : msg ,
} ) ;
} ;
2022-08-02 22:00:39 -07:00
2022-09-25 04:38:28 -07:00
function timeObjectConvertTimezone ( obj , timezone , timeObjectToUTC = true ) {
2022-10-10 05:48:11 -07:00
let offsetString ;
if ( timezone ) {
offsetString = dayjs ( ) . tz ( timezone ) . format ( "Z" ) ;
} else {
offsetString = dayjs ( ) . format ( "Z" ) ;
}
2022-09-25 04:38:28 -07:00
let hours = parseInt ( offsetString . substring ( 1 , 3 ) ) ;
let minutes = parseInt ( offsetString . substring ( 4 , 6 ) ) ;
if (
( timeObjectToUTC && offsetString . startsWith ( "+" ) ) ||
( ! timeObjectToUTC && offsetString . startsWith ( "-" ) )
) {
hours *= - 1 ;
minutes *= - 1 ;
}
obj . hours += hours ;
obj . minutes += minutes ;
// Handle out of bound
2022-10-12 02:02:16 -07:00
if ( obj . minutes < 0 ) {
obj . minutes += 60 ;
obj . hours -- ;
} else if ( obj . minutes > 60 ) {
obj . minutes -= 60 ;
obj . hours ++ ;
}
2022-09-25 04:38:28 -07:00
if ( obj . hours < 0 ) {
obj . hours += 24 ;
} else if ( obj . hours > 24 ) {
obj . hours -= 24 ;
}
return obj ;
}
2022-10-10 05:48:11 -07:00
/ * *
*
* @ param { object } obj
* @ param { string } timezone
* @ returns { object }
* /
module . exports . timeObjectToUTC = ( obj , timezone = undefined ) => {
2022-09-25 04:38:28 -07:00
return timeObjectConvertTimezone ( obj , timezone , true ) ;
} ;
2022-10-10 05:48:11 -07:00
/ * *
*
* @ param { object } obj
* @ param { string } timezone
* @ returns { object }
* /
module . exports . timeObjectToLocal = ( obj , timezone = undefined ) => {
2022-09-25 04:38:28 -07:00
return timeObjectConvertTimezone ( obj , timezone , false ) ;
} ;
2022-10-26 06:41:21 -07:00
2022-08-02 22:00:39 -07:00
/ * *
* Create gRPC client stib
* @ param { Object } options from gRPC client
* /
module . exports . grpcQuery = async ( options ) => {
const { grpcUrl , grpcProtobufData , grpcServiceName , grpcEnableTls , grpcMethod , grpcBody } = options ;
const protocObject = protojs . parse ( grpcProtobufData ) ;
const protoServiceObject = protocObject . root . lookupService ( grpcServiceName ) ;
const Client = grpc . makeGenericClientConstructor ( { } ) ;
const credentials = grpcEnableTls ? grpc . credentials . createSsl ( ) : grpc . credentials . createInsecure ( ) ;
const client = new Client (
grpcUrl ,
credentials
) ;
const grpcService = protoServiceObject . create ( function ( method , requestData , cb ) {
const fullServiceName = method . fullName ;
const serviceFQDN = fullServiceName . split ( "." ) ;
const serviceMethod = serviceFQDN . pop ( ) ;
const serviceMethodClientImpl = ` / ${ serviceFQDN . slice ( 1 ) . join ( "." ) } / ${ serviceMethod } ` ;
log . debug ( "monitor" , ` gRPC method ${ serviceMethodClientImpl } ` ) ;
client . makeUnaryRequest (
serviceMethodClientImpl ,
arg => arg ,
arg => arg ,
requestData ,
cb ) ;
} , false , false ) ;
return new Promise ( ( resolve , _ ) => {
2022-12-28 17:10:58 -08:00
try {
return grpcService [ ` ${ grpcMethod } ` ] ( JSON . parse ( grpcBody ) , function ( err , response ) {
const responseData = JSON . stringify ( response ) ;
if ( err ) {
return resolve ( {
code : err . code ,
errorMessage : err . details ,
data : ""
} ) ;
} else {
log . debug ( "monitor:" , ` gRPC response: ${ JSON . stringify ( response ) } ` ) ;
return resolve ( {
code : 1 ,
errorMessage : "" ,
data : responseData
} ) ;
}
} ) ;
} catch ( err ) {
return resolve ( {
code : - 1 ,
errorMessage : ` Error ${ err } . Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format ` ,
data : ""
} ) ;
}
2022-08-02 22:00:39 -07:00
} ) ;
} ;