Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nioc committed Oct 13, 2019
0 parents commit 0fd85a9
Show file tree
Hide file tree
Showing 19 changed files with 6,857 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": [
"standard"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/node_modules/
/config.json
/*.log
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
dist: xenial

language: node_js

node_js:
- node
- lts/*

before_script:
- cp test/config.json config.json
660 changes: 660 additions & 0 deletions LICENSE.md

Large diffs are not rendered by default.

156 changes: 156 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# XMPP Bot

[![license: AGPLv3](https://img.shields.io/badge/license-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![GitHub release](https://img.shields.io/github/release/nioc/xmpp-bot.svg)](https://github.com/nioc/xmpp-bot/releases/latest)
[![Build Status](https://travis-ci.org/nioc/xmpp-bot.svg?branch=master)](https://travis-ci.org/nioc/xmpp-bot)

XMPP Bot is a tiny little bot making the link between XMPP conversations and webhooks.

User ⇄ XMPP client ⇄ XMPP Server ⇄ **XMPP Bot** ⇄ REST API

## Key features

- Call outgoing webhook on XMPP incoming messages from user chat or group chat (Multi-user chat "MUC"),

- Send message templates (with values to apply to variables in that template) to user or room (MUC) on incoming authorized (basic or bearer) webhook.

## Installation

- Install [Node.js](https://nodejs.org/):
```shell
curl -sL https://deb.nodesource.com/setup_10.x | bash -
apt-get install -y nodejs
```

- Install npm:
```shell
npm install npm@latest -g
```

- Clone repository:
```shell
git clone https://github.com/nioc/xmpp-bot.git /usr/local/bin/xmpp-bot/
```

- Install dependency:
```shell
cd /usr/local/bin/xmpp-bot/ && npm install --production
```

- Create run user (optionnal):
```
useradd -r -s /bin/false xmpp-bot
chown xmpp-bot:xmpp-bot /usr/local/bin/xmpp-bot -R
```

- Set [configuration](#configuration) in `config.json` (you can copy `config.json.dist`)

- Add systemd service from [model](/docs/xmpp-bot.service):
```shell
cp docs/xmpp-bot.service /etc/systemd/system/xmpp-bot.service
```

- Update systemd:
```shell
systemctl daemon-reload
```

- Start service:
```shell
systemctl start xmpp-bot
```

- Start service at boot:
```shell
systemctl enable xmpp-bot
```

- Add fail2ban filter from [model](/docs/xmpp-bot.conf) (optionnal):
```shell
cp docs/xmpp-bot.conf /etc/fail2ban/filter.d/xmpp-bot.conf
```
Add the jail (`/etc/fail2ban/jail.local`):
```properties
[xmpp-bot]
enabled = true
port = http,https
filter = xmpp-bot
logpath = /var/log/xmpp-bot/webhook.log
maxretry = 3
bantime = 21600 ; 6 hours
```

## Configuration

### Logger

- `level` log4js level (all < trace < debug < info < warn < error < fatal < mark < off)

- `file`, `console` and `stdout` define log appenders (see [log4js doc](https://log4js-node.github.io/log4js-node/appenders.html))

### Webhooks listener

- `path` and `port` define the listening endpoint

- `ssl` define key and certificat location and port used for exposing in https, make sure that user of the process is allowed to read cert

- `users` is an array of user/password for basic authentication

- `accessLog` define the listener logger

### XMPP Server

- `host` and `port` define XMPP server
- `jid` and `password` define XMPP "bot" user credentials
- `rooms` list rooms (and optionnal password) where bot will listen

### Incoming webhooks (list)

- `path` is the webhook key:a POST request on this path will trigger corresponding `action`

- `action` among enumeration:
- `send_xmpp_message` will send message (`message` in request body) to `destination` (from request body) ; if `destination` is found in `config.xmppServer.rooms` array, message will send as a groupchat). Request exemple:

```http
POST /webhooks/w1 HTTP/1.1
Host: domain.ltd:8000
Content-Type: application/json
Authorization: Basic dXNlcjE6cGFzczE=
Content-Length: 70
{
"destination":"[email protected]",
"message":"Hi, there something wrong."
}
```

- `send_xmpp_template` will send template with merged variables (using JMESPath) to `destination` (user or room if `sendToGroup` set to true)

### XMPP hooks (list)

- `room` is the XMPP hook key: an incoming groupchat (or chat) from this room (or this user) will trigger corresponding `action`

- `action` among enumeration:
- `outgoing_webhook` will execute a request to corresponding webhook with `args` as webhook code

## Credits

- **[Nioc](https://github.com/nioc/)** - _Initial work_

See also the list of [contributors](https://github.com/nioc/xmpp-bot/contributors) to this project.

This project is powered by the following components:

- [node-simple-xmpp](https://github.com/simple-xmpp/node-simple-xmpp) (MIT)
- [express](https://github.com/expressjs/express) (MIT)
- [body-parser](https://github.com/expressjs/body-parser) (MIT)
- [express-basic-auth](https://github.com/LionC/express-basic-auth) (MIT)
- [morgan](https://github.com/expressjs/morgan) (MIT)
- [jmespath.js](https://github.com/jmespath/jmespath.js) (Apache-2.0)
- [request](https://github.com/request/request) (Apache-2.0)
- [node-cleanup](https://github.com/jtlapp/node-cleanup) (MIT)
- [log4js-node](https://github.com/log4js-node/log4js-node) (Apache-2.0)

## License

This project is licensed under the GNU Affero General Public License v3.0 - see the [LICENSE](LICENSE.md) file for details
50 changes: 50 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Configuration
*
* Handle configuration file
*
* @file This files defines the configuration
* @author nioc
* @since 1.0.0
* @license AGPL-3.0+
*/

module.exports = function Configuration (logger) {
let config
try {
let data = require('fs').readFileSync('./config.json')
config = JSON.parse(data)
} catch (error) {
logger.fatal(`Invalid configuration file: ${error.message}`)
process.exit(99)
}
return {
listener: {
path: config.webhooksListener.path,
port: config.webhooksListener.port,
ssl: config.webhooksListener.ssl,
log: config.webhooksListener.accessLog,
users: config.webhooksListener.users.reduce((acc, user) => {
acc[user.login] = user.password
return acc
}, {})
},
xmpp: config.xmppServer,
logger: config.logger,
getWebhookAction: (path) => {
return config.incomingWebhooks.find((webhook) => {
return (webhook.path === path)
})
},
getOutgoingWebhook: (code) => {
return config.outgoingWebhooks.find((webhook) => {
return (webhook.code === code)
})
},
getXmppHookAction: (room) => {
return config.xmppHooks.find((xmppHook) => {
return (xmppHook.room === room)
})
}
}
}
94 changes: 94 additions & 0 deletions config.json.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"logger": {
"level": "debug",
"file": {
"active": false,
"pattern": "%d %p %m",
"path": "/var/log/xmpp-bot/",
"filename": "xmpp-bot.log"
},
"console": {
"active": false,
"coloured": true
},
"stdout": {
"active": true,
"pattern": "%p %m"
}
},
"webhooksListener": {
"path": "/webhooks",
"port": 8000,
"ssl": {
"port": 8001,
"certPath": "/etc/ssl/certs/ssl-cert-snakeoil.pem",
"keyPath": "/etc/ssl/private/ssl-cert-snakeoil.key"
},
"users": [
{
"login": "login1",
"password": "1pass"
},
{
"login": "login2",
"password": "2pass"
}
],
"accessLog": {
"active": true,
"path": "/var/log/xmpp-bot/",
"filename": "webhook.log"
}
},
"xmppServer": {
"host": "domain-xmpp.ltd",
"port": 5222,
"jid": "[email protected]",
"password": "botPass",
"rooms": [
{
"id": "[email protected]",
"password": null
}
]
},
"incomingWebhooks": [
{
"path": "/webhooks/w1",
"action": "send_xmpp_message"
},
{
"path": "/webhooks/grafana",
"action": "send_xmpp_template",
"args": {
"destination": "[email protected]",
"sendToGroup": true
},
"template": "${title}\r\n${message}\r\n${evalMatches[].metric}: ${evalMatches[].value}\r\n${imageUrl}"
}
],
"xmppHooks": [
{
"room": "bot",
"action": "outgoing_webhook",
"args": ["w1"]
},
{
"room": "[email protected]",
"action": "outgoing_webhook",
"args": ["w1"]
}
],
"outgoingWebhooks": [
{
"code": "w1",
"url": "https://domain.ltd:port/path/resource?parameter1=value1",
"strictSSL": true,
"contentType": "application/json",
"authMethod": "basic",
"user": "user3",
"password": "3pass",
"bearer": null
}
]
}
14 changes: 14 additions & 0 deletions docs/xmpp-bot.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Fail2Ban configuration file for webhook log

# Option: failregex
# Notes.: regex to match the Unauthorized log entrys in webhook log (defined in config.json: webhooksListener.accessLog).
# Values: TEXT
#
[Definition]
failregex=^<HOST> - .* ".*" 401 \d* ".*" ".*"$

# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =
16 changes: 16 additions & 0 deletions docs/xmpp-bot.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Unit]
Description=XMPP Bot
Documentation=https://github.com/nioc/xmpp-bot
After=network.target

[Service]
User=xmpp-bot
WorkingDirectory=/usr/local/bin/xmpp-bot/
ExecStart=/usr/bin/node /usr/local/bin/xmpp-bot/server.js
Restart=on-failure
RestartSec=1000ms
Environment=NODE_ENV=production
SyslogIdentifier=xmpp-bot

[Install]
WantedBy=multi-user.target
24 changes: 24 additions & 0 deletions error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Close handler
*
* Disconnect bot from XMPP server before app closes
*
* @file This files defines the closing handler
* @author nioc
* @since 1.0.0
* @license AGPL-3.0+
*/

module.exports = (logger, xmpp) => {
let nodeCleanup = require('node-cleanup')
nodeCleanup(function (exitCode, signal) {
logger.warn(`Received ${exitCode}/${signal} (application is closing), disconnect from XMPP server`)
try {
xmpp.disconnect()
} catch (error) {
logger.error('Error during XMPP disconnection: ' + error.message)
}
logger.debug('Synchronize logs file')
logger.shutdown()
})
}
Loading

0 comments on commit 0fd85a9

Please sign in to comment.