Skip to content

Commit 500b49e

Browse files
committed
first commit
0 parents  commit 500b49e

6 files changed

+302
-0
lines changed

.env-example

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
MOSQUITTO_SERVER=XXXXX.stackhero-network.com
2+
MOSQUITTO_USERNAME=xxxxxxxxx
3+
MOSQUITTO_PASSWORD=xxxxxxxxx

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
node_modules/
3+
git-crypt.key
4+
yarn.lock
5+
package-lock.json
6+
*.log
7+
.env

README.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Getting started with Mosquitto
2+
3+
You'll find in this repository two applications.
4+
5+
The `client.js` is a MQTT client that will show you how to connect, publish and subscribe to a MQTT server.
6+
7+
The `authenticationServer.js` is an API written with Express that will allow you to handle MQTT users authentication in a simple and powerful way.
8+
You'll get more informations about the API authentication on [Stackhero's documentation](https://www.stackhero.io/documentations/).
9+
10+
## The client
11+
12+
To use the client, you have to fill the file `.env-example` and then rename the file `.env`.
13+
Then simply run `node client.js` (or `npm run client`).
14+
15+
16+
## The authentication server
17+
18+
The authentication server has to be accessible from your Mosquitto instance. It means that you can't run it directly on your computer.
19+
The simplest way to run it is to create a Node.js service on Stackhero and push the example to it.
20+
21+
Here are the detailed procedure to do that in only 5 minutes.
22+
23+
### 1. Create a Node.js service
24+
25+
In stackhero, create a Node.js service. You can select LTS or CURRENT version, both will work, but we recommend the LTS one.
26+
Add your SSH public key in configuration and validate the configuration.
27+
28+
29+
### 2. Clone the app
30+
31+
On your computer, clone this project:
32+
33+
```
34+
git clone https://github.com/stackhero-io/mosquittoApiAuthentication.git
35+
cd mosquittoApiAuthentication
36+
```
37+
38+
It's a good idea to check the `app.js` file content and change the default passwords, to avoid that someone connects to your Mosquitto server.
39+
40+
41+
### 3. Deploy the app
42+
43+
From your Node.js service, in Stackhero's console, copy the `git remote command` and paste it to the cloned app directory (you'll have to do that only the first time).
44+
45+
Then, deploy the app: `git push -u stackhero`
46+
47+
If you want to change the `app.js` content, you'll have to commit the changes (`git add -A . && git commit`) then redeploy your app with `git push -u stackhero`.
48+
49+
50+
### 4. Configure your Mosquitto service
51+
52+
In your Mosquitto's service configuration, enable `API authentication` and copy this configuration:
53+
- Host: put your Node.js endpoint domain (XXXXXX.stackhero-network.com)
54+
- Protocol: `HTTPS`
55+
- Port: `443`
56+
- User route: `/user`
57+
- Super user route: `/superUser`
58+
- ACLs route: `/acls`
59+
60+
Validate the configuration and voila! Your Mosquitto is now using this Node.js API to validate devices authentication and ACLs!
61+
Note that if you defined users in your Mosquitto's service configuration, those users are still authorized too (but without ACLs rules).
62+
63+
64+
You now have a way to handle devices authentication directly with an app.
65+
You can now modify this code to check permissions, for example, in a database, letting you handle tons of devices in a dynamic way!
66+
67+
Enjoy your code!

authenticationServer.js

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
const express = require('express');
2+
const bodyParser = require('body-parser');
3+
const mqttWildcard = require('mqtt-wildcard');
4+
const app = express();
5+
app.use(bodyParser());
6+
const port = 8080;
7+
8+
const users = [
9+
{
10+
login: 'testUser',
11+
password: 'testPassword',
12+
isSuper: false,
13+
acls: [
14+
{
15+
topic: 'users/testUser/#',
16+
permissions: [ 'read', 'write', 'subscribe' ] // Can be "read", "write" or "subscribe"
17+
},
18+
{
19+
topic: 'global',
20+
permissions: [ 'write', 'read', 'subscribe' ]
21+
}
22+
]
23+
},
24+
{
25+
login: 'testUser2',
26+
password: 'testPassword2',
27+
isSuper: false,
28+
acls: [
29+
{
30+
topic: 'users/testUser2/#',
31+
permissions: [ 'read', 'write', 'subscribe' ] // Can be "read", "write" or "subscribe"
32+
},
33+
{
34+
topic: 'global',
35+
permissions: [ 'write', 'read', 'subscribe' ]
36+
}
37+
]
38+
}
39+
];
40+
41+
42+
// Define POST route "/user"
43+
// This route will be used to check the user login and password
44+
app.post(
45+
'/user',
46+
(req, res) => {
47+
// Mosquitto sends us the username and the password
48+
const { username, password } = req.body;
49+
50+
// We try to find the user
51+
const userFound = users.find(user => user.login === username && user.password === password);
52+
53+
// We send a 200 if the authentication succeed or 401 else
54+
if (userFound) {
55+
return res.status(200).send();
56+
}
57+
else {
58+
console.warn(`⛔️ User ${username} doesn't exist or password is incorrect`);
59+
return res.status(401).send();
60+
}
61+
}
62+
);
63+
64+
65+
// Define POST route "/superUser"
66+
// This route will be used to check if the user is a super user or not
67+
app.post(
68+
'/superUser',
69+
(req, res) => {
70+
// Mosquitto sends us the username
71+
const { username } = req.body;
72+
73+
// We try to find the user and check if he's a super user
74+
const userFound = users.find(user => user.login === username);
75+
76+
// We send a 200 if he is a super user or 401 else
77+
if (userFound && userFound.isSuper) {
78+
return res.status(200).send();
79+
}
80+
else {
81+
return res.status(401).send();
82+
}
83+
}
84+
);
85+
86+
87+
// Define POST route "/acls"
88+
// This route will be used to check the topic ACL
89+
app.post(
90+
'/acls',
91+
(req, res) => {
92+
const { username, topic, clientId, acc } = req.body;
93+
94+
// "acc" represents the type of access required by the client to this topic
95+
// - 1: read
96+
// - 2: write
97+
// - 3: read and write
98+
// - 4: subscribe
99+
100+
const accToPermissions = {
101+
1: [ 'read' ],
102+
2: [ 'write' ],
103+
3: [ 'read', 'write' ],
104+
4: [ 'subscribe' ]
105+
}
106+
107+
const allowed = users.find(user => {
108+
if (user.login !== username) {
109+
return false;
110+
}
111+
112+
const aclValidated = user.acls.find(
113+
acl => mqttWildcard(topic, acl.topic) !== null
114+
&& accToPermissions[acc].every(v => acl.permissions.includes(v))
115+
);
116+
return aclValidated;
117+
});
118+
119+
if (allowed) {
120+
return res.status(200).send();
121+
}
122+
else {
123+
console.warn(`⛔️ Error when checking ACL for user ${username} on topic ${topic} with permission "${acc}"`);
124+
return res.status(401).send();
125+
}
126+
}
127+
);
128+
129+
130+
131+
// Start Express server
132+
const server = app.listen(port);
133+
134+
// You'll see this log directly on your Stackhero's console
135+
console.log('🎉 The app has just start!');
136+
137+
// Handle termination signal
138+
// When you'll push your code to Stackhero, we'll send a termination signal (SIGTERM).
139+
// The goal is to let you close cleanly connections from Express, connections to databases etc...
140+
process.on('SIGTERM', () => {
141+
// You'll see this log directly on your Stackhero's console
142+
console.info('😯 SIGTERM signal received.');
143+
144+
// Close the server and all connections
145+
server.close(
146+
err => {
147+
if (err) {
148+
// You'll see this log directly on your Stackhero's console
149+
console.error(err);
150+
process.exit(1);
151+
}
152+
else {
153+
// You'll see this log directly on your Stackhero's console
154+
console.log('👋 Exit the app with status code 0');
155+
process.exit(0);
156+
}
157+
}
158+
);
159+
});

client.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require('dotenv').config()
2+
const mqtt = require('mqtt');
3+
4+
if (!process.env.MOSQUITTO_SERVER) {
5+
throw Error('You should first fill the .env-example file and rename it to .env');
6+
}
7+
8+
// Connection to MQTT server
9+
const client = mqtt.connect(
10+
'mqtts://' + process.env.MOSQUITTO_SERVER,
11+
{
12+
username: process.env.MOSQUITTO_USERNAME,
13+
password: process.env.MOSQUITTO_PASSWORD,
14+
clean: true
15+
}
16+
);
17+
18+
19+
// This callback will be executed when a message is received on topics you subscribed (see client.subscribe below)
20+
client.on('message', (topic, message) => {
21+
// message is Buffer
22+
console.log(`[${topic}]: ${message.toString()}`);
23+
})
24+
25+
26+
// Fired when connection to MQTT is done
27+
client.on('connect', () => {
28+
29+
// We subscribe to the topic "global"
30+
client.subscribe('global', (err, granted) => {
31+
if (err) { throw err; }
32+
33+
if (granted.find(({ qos }) => qos === 128)) {
34+
throw Error(`Permission error when subscribing: ${JSON.stringify(granted)}`);
35+
}
36+
});
37+
38+
39+
// We publish to the topic "global"
40+
client.publish('global', 'Hello everyone!', { qos: 2 }, err => {
41+
if (err) { throw err; }
42+
});
43+
44+
45+
// We publish to as user topic
46+
client.publish('users/testUser/sensor1', '123', { qos: 2 }, err => {
47+
if (err) { throw err; }
48+
});
49+
50+
// Note: we can publish to a topic without "write" rights. MQTT will ignore the message without informing us.
51+
})

package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "mosquitto-api-authentication",
3+
"version": "1.0.0",
4+
"main": "app.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"start": "node authenticationServer.js",
8+
"client": "node client.js"
9+
},
10+
"dependencies": {
11+
"body-parser": "^1.18.3",
12+
"express": "^4.16.4",
13+
"mqtt-wildcard": "^3.0.9"
14+
}
15+
}

0 commit comments

Comments
 (0)