Made mosquitto auth work
This commit is contained in:
parent
4f7a6202a4
commit
dbb314aaad
11
Dockerfile.mosquitto
Normal file
11
Dockerfile.mosquitto
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM debian:stretch
|
||||||
|
|
||||||
|
LABEL Author="Alex Mikhalev"
|
||||||
|
LABEL Description="Eclipse Mosquitto MQTT Broker"
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y mosquitto mosquitto-auth-plugin
|
||||||
|
|
||||||
|
COPY sprinklers3.mosquitto.conf /etc/mosquitto/conf.d/
|
||||||
|
|
||||||
|
CMD ["/usr/sbin/mosquitto", "-c", "/etc/mosquitto/mosquitto.conf"]
|
@ -28,9 +28,9 @@ export default class AppState extends TypedEventEmitter<AppEvents> {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.sprinklersRpc.on("newUserData", this.userStore.receiveUserData);
|
this.sprinklersRpc.on("newUserData", this.userStore.receiveUserData);
|
||||||
this.sprinklersRpc.on("tokenError", this.checkToken);
|
this.sprinklersRpc.on("tokenError", this.clearToken);
|
||||||
this.httpApi.on("tokenGranted", () => this.emit("hasToken"));
|
this.httpApi.on("tokenGranted", () => this.emit("hasToken"));
|
||||||
this.httpApi.on("tokenError", this.checkToken);
|
this.httpApi.on("tokenError", this.clearToken);
|
||||||
|
|
||||||
this.on("checkToken", this.doCheckToken);
|
this.on("checkToken", this.doCheckToken);
|
||||||
|
|
||||||
@ -55,6 +55,11 @@ export default class AppState extends TypedEventEmitter<AppEvents> {
|
|||||||
await this.checkToken();
|
await this.checkToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearToken = (err?: any) => {
|
||||||
|
this.tokenStore.clearAccessToken();
|
||||||
|
this.checkToken();
|
||||||
|
}
|
||||||
|
|
||||||
checkToken = () => {
|
checkToken = () => {
|
||||||
this.emit("checkToken");
|
this.emit("checkToken");
|
||||||
}
|
}
|
||||||
@ -76,7 +81,7 @@ export default class AppState extends TypedEventEmitter<AppEvents> {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError && err.code === ErrorCode.BadToken) {
|
if (err instanceof ApiError && err.code === ErrorCode.BadToken) {
|
||||||
log.warn({ err }, "refresh is bad for some reason, erasing");
|
log.warn({ err }, "refresh is bad for some reason, erasing");
|
||||||
this.tokenStore.clear();
|
this.tokenStore.clearAll();
|
||||||
this.history.push("/login");
|
this.history.push("/login");
|
||||||
} else {
|
} else {
|
||||||
log.error({ err }, "could not refresh access token");
|
log.error({ err }, "could not refresh access token");
|
||||||
|
@ -10,7 +10,13 @@ export class TokenStore {
|
|||||||
@observable refreshToken: Token<RefreshToken> = new Token();
|
@observable refreshToken: Token<RefreshToken> = new Token();
|
||||||
|
|
||||||
@action
|
@action
|
||||||
clear() {
|
clearAccessToken() {
|
||||||
|
this.accessToken.token = null;
|
||||||
|
this.saveLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
clearAll() {
|
||||||
this.accessToken.token = null;
|
this.accessToken.token = null;
|
||||||
this.refreshToken.token = null;
|
this.refreshToken.token = null;
|
||||||
this.saveLocalStorage();
|
this.saveLocalStorage();
|
||||||
|
@ -22,6 +22,11 @@ export interface DeviceRegistrationToken extends BaseClaims {
|
|||||||
export interface DeviceToken extends BaseClaims {
|
export interface DeviceToken extends BaseClaims {
|
||||||
type: "device";
|
type: "device";
|
||||||
aud: string;
|
aud: string;
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TokenClaims = AccessToken | RefreshToken | DeviceRegistrationToken | DeviceToken;
|
export interface SuperuserToken extends BaseClaims {
|
||||||
|
type: "superuser";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TokenClaims = AccessToken | RefreshToken | DeviceRegistrationToken | DeviceToken | SuperuserToken;
|
||||||
|
@ -17,7 +17,15 @@ interface WithRid {
|
|||||||
rid: number;
|
rid: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MqttRpcClient implements s.SprinklersRPC {
|
export const DEVICE_PREFIX = "devices";
|
||||||
|
|
||||||
|
export interface MqttRpcClientOptions {
|
||||||
|
mqttUri: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MqttRpcClient implements s.SprinklersRPC, MqttRpcClientOptions {
|
||||||
get connected(): boolean {
|
get connected(): boolean {
|
||||||
return this.connectionState.isServerConnected || false;
|
return this.connectionState.isServerConnected || false;
|
||||||
}
|
}
|
||||||
@ -26,21 +34,26 @@ export class MqttRpcClient implements s.SprinklersRPC {
|
|||||||
return "sprinklers3-MqttApiClient-" + getRandomId();
|
return "sprinklers3-MqttApiClient-" + getRandomId();
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly mqttUri: string;
|
mqttUri!: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
|
||||||
client!: mqtt.Client;
|
client!: mqtt.Client;
|
||||||
@observable connectionState: s.ConnectionState = new s.ConnectionState();
|
@observable connectionState: s.ConnectionState = new s.ConnectionState();
|
||||||
devices: Map<string, MqttSprinklersDevice> = new Map();
|
devices: Map<string, MqttSprinklersDevice> = new Map();
|
||||||
|
|
||||||
constructor(mqttUri: string) {
|
constructor(opts: MqttRpcClientOptions) {
|
||||||
this.mqttUri = mqttUri;
|
Object.assign(this, opts);
|
||||||
this.connectionState.serverToBroker = false;
|
this.connectionState.serverToBroker = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
const clientId = MqttRpcClient.newClientId();
|
const clientId = MqttRpcClient.newClientId();
|
||||||
log.info({ mqttUri: this.mqttUri, clientId }, "connecting to mqtt broker with client id");
|
const mqttUri = this.mqttUri;
|
||||||
this.client = mqtt.connect(this.mqttUri, {
|
log.info({ mqttUri, clientId }, "connecting to mqtt broker with client id");
|
||||||
|
this.client = mqtt.connect(mqttUri, {
|
||||||
clientId, connectTimeout: 5000, reconnectPeriod: 5000,
|
clientId, connectTimeout: 5000, reconnectPeriod: 5000,
|
||||||
|
username: this.username, password: this.password,
|
||||||
});
|
});
|
||||||
this.client.on("message", this.onMessageArrived.bind(this));
|
this.client.on("message", this.onMessageArrived.bind(this));
|
||||||
this.client.on("close", () => {
|
this.client.on("close", () => {
|
||||||
@ -90,12 +103,16 @@ export class MqttRpcClient implements s.SprinklersRPC {
|
|||||||
private processMessage(topic: string, payloadBuf: Buffer, packet: mqtt.Packet) {
|
private processMessage(topic: string, payloadBuf: Buffer, packet: mqtt.Packet) {
|
||||||
const payload = payloadBuf.toString("utf8");
|
const payload = payloadBuf.toString("utf8");
|
||||||
log.trace({ topic, payload }, "message arrived: ");
|
log.trace({ topic, payload }, "message arrived: ");
|
||||||
const topicIdx = topic.indexOf("/"); // find the first /
|
const regexp = new RegExp(`^${DEVICE_PREFIX}\\/([^\\/]+)\\/?(.*)$`);
|
||||||
const prefix = topic.substr(0, topicIdx); // assume prefix does not contain a /
|
const matches = regexp.exec(topic);
|
||||||
const topicSuffix = topic.substr(topicIdx + 1);
|
if (!matches) {
|
||||||
const device = this.devices.get(prefix);
|
return log.warn({ topic }, "received message on invalid topic");
|
||||||
|
}
|
||||||
|
const id = matches[1];
|
||||||
|
const topicSuffix = matches[2];
|
||||||
|
const device = this.devices.get(id);
|
||||||
if (!device) {
|
if (!device) {
|
||||||
log.debug({ prefix }, "received message for unknown device");
|
log.debug({ id }, "received message for unknown device");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
device.onMessage(topicSuffix, payload);
|
device.onMessage(topicSuffix, payload);
|
||||||
@ -131,20 +148,22 @@ const handler = (test: RegExp) =>
|
|||||||
|
|
||||||
class MqttSprinklersDevice extends s.SprinklersDevice {
|
class MqttSprinklersDevice extends s.SprinklersDevice {
|
||||||
readonly apiClient: MqttRpcClient;
|
readonly apiClient: MqttRpcClient;
|
||||||
readonly prefix: string;
|
readonly id: string;
|
||||||
|
|
||||||
handlers!: IHandlerEntry[];
|
handlers!: IHandlerEntry[];
|
||||||
|
private subscriptions: string[];
|
||||||
private nextRequestId: number = Math.floor(Math.random() * 1000000000);
|
private nextRequestId: number = Math.floor(Math.random() * 1000000000);
|
||||||
private responseCallbacks: Map<number, ResponseCallback> = new Map();
|
private responseCallbacks: Map<number, ResponseCallback> = new Map();
|
||||||
|
|
||||||
constructor(apiClient: MqttRpcClient, prefix: string) {
|
constructor(apiClient: MqttRpcClient, id: string) {
|
||||||
super();
|
super();
|
||||||
this.sectionConstructor = MqttSection;
|
this.sectionConstructor = MqttSection;
|
||||||
this.sectionRunnerConstructor = MqttSectionRunner;
|
this.sectionRunnerConstructor = MqttSectionRunner;
|
||||||
this.programConstructor = MqttProgram;
|
this.programConstructor = MqttProgram;
|
||||||
this.apiClient = apiClient;
|
this.apiClient = apiClient;
|
||||||
this.prefix = prefix;
|
this.id = id;
|
||||||
this.sectionRunner = new MqttSectionRunner(this);
|
this.sectionRunner = new MqttSectionRunner(this);
|
||||||
|
this.subscriptions = subscriptions.map((filter) => this.prefix + filter);
|
||||||
|
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
const brokerConnected = apiClient.connected;
|
const brokerConnected = apiClient.connected;
|
||||||
@ -160,14 +179,13 @@ class MqttSprinklersDevice extends s.SprinklersDevice {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get id(): string {
|
get prefix(): string {
|
||||||
return this.prefix;
|
return DEVICE_PREFIX + "/" + this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
doSubscribe(): Promise<void> {
|
doSubscribe(): Promise<void> {
|
||||||
const topics = subscriptions.map((filter) => this.prefix + filter);
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.apiClient.client.subscribe(topics, { qos: 1 }, (err) => {
|
this.apiClient.client.subscribe(this.subscriptions, { qos: 1 }, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@ -178,9 +196,8 @@ class MqttSprinklersDevice extends s.SprinklersDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
doUnsubscribe(): Promise<void> {
|
doUnsubscribe(): Promise<void> {
|
||||||
const topics = subscriptions.map((filter) => this.prefix + filter);
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.apiClient.client.unsubscribe(topics, (err) => {
|
this.apiClient.client.unsubscribe(this.subscriptions, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,6 +7,7 @@ services:
|
|||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
|
- mosquitto
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
- "8081:8081"
|
- "8081:8081"
|
||||||
@ -23,9 +24,19 @@ services:
|
|||||||
- TYPEORM_DATABASE=postgres
|
- TYPEORM_DATABASE=postgres
|
||||||
- TYPEORM_USERNAME=postgres
|
- TYPEORM_USERNAME=postgres
|
||||||
- TYPEORM_PASSWORD=8JN4w0UsN5dbjMjNvPe452P2yYOqg5PV
|
- TYPEORM_PASSWORD=8JN4w0UsN5dbjMjNvPe452P2yYOqg5PV
|
||||||
|
- MQTT_URL=tcp://mosquitto:1883
|
||||||
# Must specify JWT_SECRET and MQTT_URL
|
# Must specify JWT_SECRET and MQTT_URL
|
||||||
|
|
||||||
|
mosquitto:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.mosquitto
|
||||||
|
ports:
|
||||||
|
- "1883:1883"
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: "postgres:11-alpine"
|
image: "postgres:11-alpine"
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=8JN4w0UsN5dbjMjNvPe452P2yYOqg5PV
|
- POSTGRES_PASSWORD=8JN4w0UsN5dbjMjNvPe452P2yYOqg5PV
|
@ -49,7 +49,7 @@ export function devices(state: ServerState) {
|
|||||||
name: "Sprinklers Device", deviceId,
|
name: "Sprinklers Device", deviceId,
|
||||||
});
|
});
|
||||||
await state.database.sprinklersDevices.save(newDevice);
|
await state.database.sprinklersDevices.save(newDevice);
|
||||||
const token = await generateDeviceToken(deviceId);
|
const token = await generateDeviceToken(newDevice.id, deviceId);
|
||||||
res.send({
|
res.send({
|
||||||
data: newDevice, token,
|
data: newDevice, token,
|
||||||
});
|
});
|
||||||
|
@ -1,19 +1,56 @@
|
|||||||
import PromiseRouter from "express-promise-router";
|
import PromiseRouter from "express-promise-router";
|
||||||
|
|
||||||
|
import ApiError from "@common/ApiError";
|
||||||
|
import { ErrorCode } from "@common/ErrorCode";
|
||||||
|
import { DEVICE_PREFIX } from "@common/sprinklersRpc/mqtt";
|
||||||
|
import { DeviceToken, SuperuserToken } from "@common/TokenClaims";
|
||||||
|
import { verifyToken } from "@server/express/authentication";
|
||||||
import { ServerState } from "@server/state";
|
import { ServerState } from "@server/state";
|
||||||
|
|
||||||
|
export const SUPERUSER = "sprinklers3";
|
||||||
|
|
||||||
export function mosquitto(state: ServerState) {
|
export function mosquitto(state: ServerState) {
|
||||||
const router = PromiseRouter();
|
const router = PromiseRouter();
|
||||||
|
|
||||||
router.post("/auth", async (req, res) => {
|
router.post("/auth", async (req, res) => {
|
||||||
res.status(200).send();
|
const body = req.body;
|
||||||
|
const { username, password, topic, acc } = body;
|
||||||
|
if (typeof username !== "string" || typeof password !== "string") {
|
||||||
|
throw new ApiError("Must specify a username and password", ErrorCode.BadRequest);
|
||||||
|
}
|
||||||
|
if (username === SUPERUSER) {
|
||||||
|
await verifyToken<SuperuserToken>(password, "superuser");
|
||||||
|
return res.status(200).send({ username });
|
||||||
|
}
|
||||||
|
const claims = await verifyToken<DeviceToken>(password, "device");
|
||||||
|
if (claims.aud !== username) {
|
||||||
|
throw new ApiError("Username does not match token", ErrorCode.BadRequest);
|
||||||
|
}
|
||||||
|
res.status(200).send({
|
||||||
|
username, id: claims.id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/superuser", async (req, res) => {
|
router.post("/superuser", async (req, res) => {
|
||||||
|
const { username } = req.body;
|
||||||
|
if (typeof username !== "string") {
|
||||||
|
throw new ApiError("Must specify a username", ErrorCode.BadRequest);
|
||||||
|
}
|
||||||
|
if (username !== SUPERUSER) {
|
||||||
|
return res.status(403).send();
|
||||||
|
}
|
||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/acl", async (req, res) => {
|
router.post("/acl", async (req, res) => {
|
||||||
|
const { username, topic, clientid, acc } = req.body;
|
||||||
|
if (typeof username !== "string" || typeof topic !== "string") {
|
||||||
|
throw new ApiError("username and topic must be specified as strings", ErrorCode.BadRequest);
|
||||||
|
}
|
||||||
|
const prefix = DEVICE_PREFIX + "/" + username;
|
||||||
|
if (!topic.startsWith(prefix)) {
|
||||||
|
throw new ApiError(`device ${username} cannot access topic ${topic}`);
|
||||||
|
}
|
||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
TokenGrantRequest,
|
TokenGrantRequest,
|
||||||
TokenGrantResponse,
|
TokenGrantResponse,
|
||||||
} from "@common/httpApi";
|
} from "@common/httpApi";
|
||||||
import { AccessToken, DeviceRegistrationToken, DeviceToken, RefreshToken, TokenClaims } from "@common/TokenClaims";
|
import { AccessToken, DeviceRegistrationToken, DeviceToken, RefreshToken, TokenClaims, SuperuserToken } from "@common/TokenClaims";
|
||||||
import { User } from "../entities";
|
import { User } from "../entities";
|
||||||
import { ServerState } from "../state";
|
import { ServerState } from "../state";
|
||||||
|
|
||||||
@ -110,15 +110,24 @@ function generateDeviceRegistrationToken(secret: string): Promise<string> {
|
|||||||
return signToken(device_reg_token_claims);
|
return signToken(device_reg_token_claims);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateDeviceToken(deviceId: string): Promise<string> {
|
export function generateDeviceToken(id: number, deviceId: string): Promise<string> {
|
||||||
const device_token_claims: DeviceToken = {
|
const device_token_claims: DeviceToken = {
|
||||||
iss: ISSUER,
|
iss: ISSUER,
|
||||||
type: "device",
|
type: "device",
|
||||||
aud: deviceId,
|
aud: deviceId,
|
||||||
|
id,
|
||||||
};
|
};
|
||||||
return signToken(device_token_claims);
|
return signToken(device_token_claims);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateSuperuserToken(): Promise<string> {
|
||||||
|
const superuser_claims: SuperuserToken = {
|
||||||
|
iss: ISSUER,
|
||||||
|
type: "superuser",
|
||||||
|
};
|
||||||
|
return signToken(superuser_claims);
|
||||||
|
}
|
||||||
|
|
||||||
export function authentication(state: ServerState) {
|
export function authentication(state: ServerState) {
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@ -143,15 +152,15 @@ export function authentication(state: ServerState) {
|
|||||||
async function refreshGrant(body: TokenGrantRefreshRequest, res: Express.Response): Promise<User> {
|
async function refreshGrant(body: TokenGrantRefreshRequest, res: Express.Response): Promise<User> {
|
||||||
const { refresh_token } = body;
|
const { refresh_token } = body;
|
||||||
if (!body || !refresh_token) {
|
if (!body || !refresh_token) {
|
||||||
throw new ApiError("Must specify a refresh_token");
|
throw new ApiError("Must specify a refresh_token", ErrorCode.BadToken);
|
||||||
}
|
}
|
||||||
const claims = await verifyToken(refresh_token);
|
const claims = await verifyToken(refresh_token);
|
||||||
if (claims.type !== "refresh") {
|
if (claims.type !== "refresh") {
|
||||||
throw new ApiError("Not a refresh token");
|
throw new ApiError("Not a refresh token", ErrorCode.BadToken);
|
||||||
}
|
}
|
||||||
const user = await state.database.users.findOne(claims.aud);
|
const user = await state.database.users.findOne(claims.aud);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ApiError("User no longer exists");
|
throw new ApiError("User no longer exists", ErrorCode.BadToken);
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
@ -88,11 +88,14 @@ export class WebSocketClient {
|
|||||||
this.userId = claims.aud;
|
this.userId = claims.aud;
|
||||||
this.user = await this.state.database.users.
|
this.user = await this.state.database.users.
|
||||||
findById(this.userId, { devices: true }) || null;
|
findById(this.userId, { devices: true }) || null;
|
||||||
|
if (!this.user) {
|
||||||
|
throw new ws.RpcError("user no longer exists", ErrorCode.BadToken);
|
||||||
|
}
|
||||||
log.info({ userId: claims.aud, name: claims.name }, "authenticated websocket client");
|
log.info({ userId: claims.aud, name: claims.name }, "authenticated websocket client");
|
||||||
this.subscribeBrokerConnection();
|
this.subscribeBrokerConnection();
|
||||||
return {
|
return {
|
||||||
result: "success",
|
result: "success",
|
||||||
data: { authenticated: true, message: "authenticated", user: this.user!.toJSON() },
|
data: { authenticated: true, message: "authenticated", user: this.user.toJSON() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
deviceSubscribe: async (data: ws.IDeviceSubscribeRequest) => {
|
deviceSubscribe: async (data: ws.IDeviceSubscribeRequest) => {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import logger from "@common/logger";
|
import logger from "@common/logger";
|
||||||
import * as mqtt from "@common/sprinklersRpc/mqtt";
|
import * as mqtt from "@common/sprinklersRpc/mqtt";
|
||||||
|
import { SUPERUSER } from "@server/express/api/mosquitto";
|
||||||
|
import { generateSuperuserToken } from "@server/express/authentication";
|
||||||
import { Database } from "./Database";
|
import { Database } from "./Database";
|
||||||
|
|
||||||
export class ServerState {
|
export class ServerState {
|
||||||
@ -13,7 +15,9 @@ export class ServerState {
|
|||||||
throw new Error("Must specify a MQTT_URL to connect to");
|
throw new Error("Must specify a MQTT_URL to connect to");
|
||||||
}
|
}
|
||||||
this.mqttUrl = mqttUrl;
|
this.mqttUrl = mqttUrl;
|
||||||
this.mqttClient = new mqtt.MqttRpcClient(mqttUrl);
|
this.mqttClient = new mqtt.MqttRpcClient({
|
||||||
|
mqttUri: mqttUrl,
|
||||||
|
});
|
||||||
this.database = new Database();
|
this.database = new Database();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +26,8 @@ export class ServerState {
|
|||||||
await this.database.createAll();
|
await this.database.createAll();
|
||||||
logger.info("created database and tables");
|
logger.info("created database and tables");
|
||||||
|
|
||||||
|
this.mqttClient.username = SUPERUSER;
|
||||||
|
this.mqttClient.password = await generateSuperuserToken();
|
||||||
this.mqttClient.start();
|
this.mqttClient.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
sprinklers3.mosquitto.conf
Normal file
8
sprinklers3.mosquitto.conf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
allow_anonymous false
|
||||||
|
auth_plugin /usr/lib/mosquitto-auth-plugin/auth-plugin.so
|
||||||
|
auth_opt_backends http
|
||||||
|
auth_opt_http_ip web
|
||||||
|
auth_opt_http_port 8080
|
||||||
|
auth_opt_http_getuser_uri /api/mosquitto/auth
|
||||||
|
auth_opt_http_superuser_uri /api/mosquitto/superuser
|
||||||
|
auth_opt_http_aclcheck_uri /api/mosquitto/acl
|
Loading…
x
Reference in New Issue
Block a user