194 lines
No EOL
6.2 KiB
JavaScript
194 lines
No EOL
6.2 KiB
JavaScript
/**
|
|
* Webhooks listener
|
|
*
|
|
* Create webhooks listener
|
|
*
|
|
* @file This files defines the webhooks listener
|
|
* @author nioc
|
|
* @since 1.0.0
|
|
* @license AGPL-3.0+
|
|
*/
|
|
|
|
module.exports = (logger, config, xmpp) => {
|
|
const http = require('http')
|
|
const express = require('express')
|
|
const bodyParser = require('body-parser')
|
|
const basicAuth = require('express-basic-auth')
|
|
const jmespath = require('jmespath')
|
|
const port = config.listener.port || 8000
|
|
const portSsl = config.listener.ssl.port || 8001
|
|
|
|
const webhook = express()
|
|
|
|
// handle connection from proxy (get IP in 'X-Forwarded-For' header)
|
|
webhook.set('trust proxy', true)
|
|
|
|
// logs request
|
|
if (config.listener.log.active) {
|
|
const morgan = require('morgan')
|
|
const fs = require('fs')
|
|
// create path if not exists
|
|
if (!fs.existsSync(config.listener.log.path)) {
|
|
try {
|
|
fs.mkdirSync(config.listener.log.path)
|
|
} catch (error) {
|
|
logger.fatal(`Can not create webhooks log folder: ${error.message}`)
|
|
process.exit(99)
|
|
}
|
|
}
|
|
// create log
|
|
const accessLogStream = fs.createWriteStream(config.listener.log.path + config.listener.log.filename, { flags: 'a' })
|
|
accessLogStream.on('error', (err) => {
|
|
logger.fatal('Can not create webhooks log file: ' + err.message)
|
|
process.exit(99)
|
|
})
|
|
webhook.use(morgan('combined', { stream: accessLogStream }))
|
|
}
|
|
|
|
// parse request
|
|
webhook.use(bodyParser.json())
|
|
|
|
// add basic authentification
|
|
webhook.use(basicAuth({
|
|
users: config.listener.users,
|
|
unauthorizedResponse: 'Invalid authorization'
|
|
}))
|
|
|
|
// handle post request
|
|
webhook.post(config.listener.path + '/*', (req, res) => {
|
|
logger.info(`Incoming webhook from ${req.auth.user}`)
|
|
const webhook = config.getWebhookAction(req.path)
|
|
if (!webhook) {
|
|
logger.error(`Webhook received: ${req.path}, not found`)
|
|
return res.status(404).send('Webhook not found')
|
|
}
|
|
logger.debug(`Webhook received: ${webhook.path}, start action: ${webhook.action}`)
|
|
logger.trace(JSON.stringify(req.body))
|
|
switch (webhook.action) {
|
|
case 'send_xmpp_message': {
|
|
// get destination
|
|
if ('destination' in req.body === false) {
|
|
logger.error('Destination not found')
|
|
return res.status(400).send('Destination not found')
|
|
}
|
|
const destination = req.body.destination
|
|
|
|
// get message
|
|
if ('message' in req.body === false) {
|
|
logger.error('Message not found')
|
|
return res.status(400).send('Message not found')
|
|
}
|
|
const message = req.body.message
|
|
|
|
// check if destination is a group chat
|
|
const type = config.xmpp.rooms.some((room) => room.id === destination) ? 'groupchat' : 'chat'
|
|
|
|
// send message
|
|
logger.trace(`Send to ${destination} (group:${type}) following message :\r\n${message}`)
|
|
xmpp.send(destination, message, type)
|
|
.then(() => {
|
|
return res.status(200).send({ status: 'ok', destination })
|
|
})
|
|
.catch(() => {
|
|
return res.status(500).send('Could not send message')
|
|
})
|
|
break
|
|
}
|
|
|
|
case 'send_xmpp_template': {
|
|
// bind data in template
|
|
const msg = webhook.template.replace(/\$\{(.+?)\}/g, (match, $1) => {
|
|
return jmespath.search(req.body, $1) || ''
|
|
})
|
|
logger.trace(`Message:\r\n${msg}`)
|
|
logger.trace(`Arguments: ${webhook.args}`)
|
|
|
|
// send message
|
|
logger.trace(`Send to ${webhook.args.destination} (group:${webhook.args.type}) following message :\r\n${msg}`)
|
|
xmpp.send(webhook.args.destination, msg, webhook.args.type)
|
|
.then(() => {
|
|
return res.status(200).send('ok')
|
|
})
|
|
.catch(() => {
|
|
return res.status(500).send('Could not send message')
|
|
})
|
|
break
|
|
}
|
|
|
|
default:
|
|
return res.status(204).send()
|
|
}
|
|
})
|
|
|
|
// handle non post requests
|
|
webhook.all('*', (req, res) => {
|
|
return res.status(405).send('Method not allowed')
|
|
})
|
|
|
|
// handle server error
|
|
webhook.on('error', (error) => {
|
|
logger.error('Error', error)
|
|
})
|
|
|
|
// get IP v4 addresses and prepare endpoints for output
|
|
let addresses = []
|
|
const networkInterfaces = require('os').networkInterfaces()
|
|
for (const ifaceName in networkInterfaces) {
|
|
addresses = addresses.concat(networkInterfaces[ifaceName].reduce((add, iface) => {
|
|
if (iface.family === 'IPv4') {
|
|
add.push(iface.address)
|
|
}
|
|
return add
|
|
}, []))
|
|
}
|
|
|
|
// start HTTP listener
|
|
const httpServer = http.createServer(webhook).listen(port, () => {
|
|
let endpoints = `http://localhost:${port}${config.listener.path}`
|
|
addresses.forEach(address => {
|
|
endpoints += ` http://${address}:${port}${config.listener.path}`
|
|
})
|
|
logger.info(`Listening webhooks on ${endpoints}`)
|
|
})
|
|
|
|
// start HTTPS listener
|
|
if (config.listener.ssl.port !== null) {
|
|
if (process.getuid) {
|
|
logger.debug(`App is started with uid: ${process.getuid()}`)
|
|
}
|
|
logger.debug(`Start HTTPS on port ${portSsl}, private key: ${config.listener.ssl.keyPath}, cert: ${config.listener.ssl.certPath}`)
|
|
const https = require('https')
|
|
const fs = require('fs')
|
|
// check if cert is readable
|
|
try {
|
|
fs.accessSync(config.listener.ssl.keyPath, fs.constants.R_OK)
|
|
logger.debug('Can read private key')
|
|
try {
|
|
fs.accessSync(config.listener.ssl.certPath, fs.constants.R_OK)
|
|
logger.debug('Can read certificate')
|
|
const credentials = {
|
|
key: fs.readFileSync(config.listener.ssl.keyPath),
|
|
cert: fs.readFileSync(config.listener.ssl.certPath)
|
|
}
|
|
https.createServer(credentials, webhook).listen(portSsl, () => {
|
|
let endpoints = `https://localhost:${portSsl}${config.listener.path}`
|
|
addresses.forEach(address => {
|
|
endpoints += ` https://${address}:${portSsl}${config.listener.path}`
|
|
})
|
|
logger.info(`Listening webhooks on ${endpoints}`)
|
|
})
|
|
} catch (err) {
|
|
logger.error(`Can not read certificate: ${err.message}`)
|
|
}
|
|
} catch (err) {
|
|
logger.error(`Can not read private key: ${err.message}`)
|
|
}
|
|
}
|
|
|
|
// Closing HTTP listener (for test)
|
|
webhook.close = () => {
|
|
httpServer.close()
|
|
}
|
|
|
|
return webhook
|
|
} |