first api and websocket server
This commit is contained in:
parent
bd02a496d9
commit
dbb03e6325
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,3 +4,5 @@ deployment.json
|
|||||||
*.js
|
*.js
|
||||||
*.d.ts
|
*.d.ts
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
!webpack.config.js
|
||||||
|
/webface/public/
|
||||||
|
5359
package-lock.json
generated
5359
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@ -11,14 +11,22 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@squeebot/core": "^3.2.0",
|
"@squeebot/core": "^3.3.4",
|
||||||
|
"bcrypt": "^5.0.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"typescript": "^4.1.5",
|
"ws": "^8.2.2"
|
||||||
"ws": "^7.4.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.11",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/node": "^14.14.27",
|
"@types/express": "^4.17.13",
|
||||||
"@types/ws": "^7.4.0"
|
"@types/node": "^16.10.2",
|
||||||
|
"@types/ws": "^8.2.0",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"html-webpack-plugin": "^5.3.2",
|
||||||
|
"sass": "^1.42.1",
|
||||||
|
"sass-loader": "^12.1.0",
|
||||||
|
"ts-loader": "^9.2.6",
|
||||||
|
"typescript": "^4.4.3",
|
||||||
|
"webpack-cli": "^4.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"main": "plugin.js",
|
"main": "plugin.js",
|
||||||
"name": "webface",
|
"name": "webface",
|
||||||
"description": "Web interface plugin",
|
"description": "Web interface WebSocket server plugin",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"dependencies": [],
|
"dependencies": ["control"],
|
||||||
"npmDependencies": ["ws@7.4.3", "express@4.17.1"]
|
"npmDependencies": ["ws@7.4.3", "express@4.17.1", "bcrypt@5.0.1"]
|
||||||
}
|
}
|
||||||
|
@ -2,34 +2,63 @@ import {
|
|||||||
Plugin,
|
Plugin,
|
||||||
Configurable,
|
Configurable,
|
||||||
EventListener,
|
EventListener,
|
||||||
DependencyLoad
|
DependencyLoad,
|
||||||
|
DependencyUnload
|
||||||
} from '@squeebot/core/lib/plugin';
|
} from '@squeebot/core/lib/plugin';
|
||||||
|
|
||||||
import express from 'express';
|
import express, { RequestHandler } from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import path from 'path';
|
import crypto from 'crypto';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
import * as WebSocket from 'ws';
|
import * as WebSocket from 'ws';
|
||||||
|
|
||||||
import { logger } from '@squeebot/core/lib/core';
|
import { logger } from '@squeebot/core/lib/core';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import { Socket } from 'net';
|
||||||
|
|
||||||
|
interface RegisteredUser {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserRequest {
|
||||||
|
command: string;
|
||||||
|
arguments?: string[];
|
||||||
|
state?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserResponse {
|
||||||
|
status: 'OK' | 'ERROR';
|
||||||
|
list?: any[];
|
||||||
|
data?: any;
|
||||||
|
command: string;
|
||||||
|
state?: string;
|
||||||
|
time?: number;
|
||||||
|
}
|
||||||
|
|
||||||
class WebSocketServer extends EventEmitter {
|
class WebSocketServer extends EventEmitter {
|
||||||
public app = express();
|
public app = express();
|
||||||
public server = http.createServer(this.app);
|
public server = http.createServer(this.app);
|
||||||
public wss = new WebSocket.Server({ server: this.server });
|
public wss = new WebSocket.Server({ noServer: true });
|
||||||
public running = false;
|
public running = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public port: number,
|
public port: number,
|
||||||
public host: string)
|
public host: string,
|
||||||
{
|
trustProxy = false,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
this.app.disable('x-powered-by');
|
||||||
|
if (trustProxy) {
|
||||||
|
this.app.set('trust proxy', 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(): void {
|
public init(): void {
|
||||||
|
this.server.listen(this.port, this.host, () => {
|
||||||
|
logger.log('[webface] WebSocket server listening on %s:%s', this.host, this.port);
|
||||||
this.running = true;
|
this.running = true;
|
||||||
this.server.listen(this.port, this.host,
|
});
|
||||||
() => logger.log('Web server listening on %s:%s', this.host, this.port));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
@ -40,22 +69,392 @@ class WebSocketServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class WebfaceServer extends WebSocketServer {
|
class WebfaceServer extends WebSocketServer {
|
||||||
|
private controlPlugin: any;
|
||||||
|
private connections = new Map<string, WebSocket>();
|
||||||
|
private issuedTokens = new Map<string, string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public port: number,
|
public port: number,
|
||||||
public host: string)
|
public host: string,
|
||||||
{
|
public authorizedURL: string,
|
||||||
super(port, host);
|
private users: RegisteredUser[],
|
||||||
|
trustProxy = false,
|
||||||
|
) {
|
||||||
|
super(port, host, trustProxy);
|
||||||
|
this.initializeWebSocket();
|
||||||
|
this.initializeAPI();
|
||||||
|
}
|
||||||
|
|
||||||
this.app.use(express.static(path.join(__dirname, 'public')));
|
/**
|
||||||
this.wss.on('connection', (ws) => {
|
* Set the loaded control plugin and announce that it has been loaded
|
||||||
|
* @param control Control plugin
|
||||||
|
*/
|
||||||
|
public setControlPlugin(control: any): void {
|
||||||
|
this.controlPlugin = control;
|
||||||
|
this.connections.forEach((ws) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: 'OK_CONTROL',
|
||||||
|
message: 'Control plugin has loaded again!'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify websocket connections that the control plugin disappeared
|
||||||
|
*/
|
||||||
|
public controlPluginUnloaded(): void {
|
||||||
|
this.controlPlugin = null;
|
||||||
|
this.connections.forEach((ws) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: 'MISSING_CONTROL',
|
||||||
|
message: 'Control plugin has unloaded!'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the username of a token or throw an error if missing or invalid
|
||||||
|
* @param req HTTP connection
|
||||||
|
* @returns Token owner
|
||||||
|
*/
|
||||||
|
public async authorizeAccessToken(req: http.IncomingMessage): Promise<string> {
|
||||||
|
const authHead = req.headers.authorization;
|
||||||
|
if (!authHead || !authHead.startsWith('Bearer')) {
|
||||||
|
throw new Error('Authorization header missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const owner = this.getTokenOwner(authHead.substring(7));
|
||||||
|
|
||||||
|
if (!owner) {
|
||||||
|
throw new Error('Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shut down
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
this.connections.forEach((ws, user) => {
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connections.clear();
|
||||||
|
this.issuedTokens.clear();
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorize a token
|
||||||
|
* @returns Middleware
|
||||||
|
*/
|
||||||
|
public tokenOwnerMiddleware(): RequestHandler {
|
||||||
|
return (req, res, next) => {
|
||||||
|
const authHeader = req.get('authorization');
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer')) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'unauthorized',
|
||||||
|
error_message: 'Unauthorized',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.substring(7);
|
||||||
|
const tokenOwner = this.getTokenOwner(token);
|
||||||
|
if (!tokenOwner) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'unauthorized',
|
||||||
|
error_message: 'Unauthorized',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.token = token;
|
||||||
|
res.locals.user = tokenOwner;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a token's owner or undefined if invalid or missing
|
||||||
|
* @param token Token from headers
|
||||||
|
* @returns Token owner or undefined
|
||||||
|
*/
|
||||||
|
private getTokenOwner(token: string): string | undefined {
|
||||||
|
const representative = [...this.issuedTokens.entries()]
|
||||||
|
.filter(({ 1: v }) => v === token)
|
||||||
|
.map(([k]) => k);
|
||||||
|
|
||||||
|
if (!representative?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return representative[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a status message with available commands
|
||||||
|
* @param ws WebSocket client
|
||||||
|
* @param username Client's username
|
||||||
|
*/
|
||||||
|
private sendStatus(ws: WebSocket, username: string): void {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: this.controlPlugin ? 'OK' : 'MISSING_CONTROL',
|
||||||
|
commands: this.controlPlugin?.listControlCommands() || [],
|
||||||
|
user: {
|
||||||
|
username,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle input from a WebSocket connection
|
||||||
|
* @param raw WebSocket message
|
||||||
|
* @param ws WebSocket client
|
||||||
|
* @param user Client username
|
||||||
|
*/
|
||||||
|
private handleWSLine(raw: WebSocket.RawData, ws: WebSocket, user: string): void {
|
||||||
|
const line = raw.toString('utf8');
|
||||||
|
let jCmd: UserRequest;
|
||||||
|
try {
|
||||||
|
jCmd = JSON.parse(line);
|
||||||
|
} catch (e: any) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: 'ERROR', message: e.message
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { command, state } = jCmd;
|
||||||
|
if (!command) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: 'ERROR',
|
||||||
|
message: 'Unknown or missing command',
|
||||||
|
command: '',
|
||||||
|
state: jCmd.state,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command === 'quit' || command === 'exit') {
|
||||||
|
ws.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command === 'help' || command === 'status') {
|
||||||
|
this.sendStatus(ws, user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeStart = Date.now();
|
||||||
|
this.controlPlugin.executeControlCommand(command, jCmd.arguments || []).then(
|
||||||
|
(data: any) => {
|
||||||
|
const response: UserResponse = {
|
||||||
|
status: 'OK',
|
||||||
|
command,
|
||||||
|
state,
|
||||||
|
time: Date.now() - timeStart,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
response.list = data;
|
||||||
|
} else {
|
||||||
|
response.data = data;
|
||||||
|
}
|
||||||
|
ws.send(JSON.stringify(response));
|
||||||
|
}, (error: Error) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: 'ERROR',
|
||||||
|
message: error.message,
|
||||||
|
command,
|
||||||
|
state,
|
||||||
|
time: Date.now() - timeStart,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start listening for websocket connections
|
||||||
|
*/
|
||||||
|
private initializeWebSocket(): void {
|
||||||
|
this.wss.on('connection', (ws: WebSocket, request: http.IncomingMessage, user: string) => {
|
||||||
|
if (this.connections.has(user)) {
|
||||||
|
ws.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('[webface] User %s connected via WebSocket', user);
|
||||||
|
|
||||||
|
this.sendStatus(ws, user);
|
||||||
|
this.connections.set(user, ws);
|
||||||
|
|
||||||
|
ws.on('message', (data) => this.handleWSLine(data, ws, user));
|
||||||
|
ws.on('close', () => {
|
||||||
|
if (this.connections.has(user)) {
|
||||||
|
this.connections.delete(user);
|
||||||
|
}
|
||||||
|
logger.log('[webface] User %s WebSocket disconnected', user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.on('upgrade', (req, socket, head) => {
|
||||||
|
this.authorizeAccessToken(req).then((user) => {
|
||||||
|
this.wss.handleUpgrade(req, socket as Socket, head, (ws) => {
|
||||||
|
this.wss.emit('connection', ws, req, user);
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the HTTP API
|
||||||
|
*/
|
||||||
|
private initializeAPI(): void {
|
||||||
|
this.app.use(express.json() as RequestHandler);
|
||||||
|
this.app.use((req, res, next) => {
|
||||||
|
res.header('Access-Control-Allow-Origin', this.authorizedURL);
|
||||||
|
res.header('Access-Control-Allow-Methods', 'POST, GET, DELETE, OPTIONS');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
||||||
|
res.header('Access-Control-Allow-Credentials', 'true');
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const authMiddleware = this.tokenOwnerMiddleware();
|
||||||
|
|
||||||
|
router.get('/', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: 'OK',
|
||||||
|
uptime: process.uptime(),
|
||||||
|
connections: this.connections.size,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/squeebot', authMiddleware, (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: this.controlPlugin ? 'OK' : 'MISSING_CONTROL',
|
||||||
|
uptime: process.uptime(),
|
||||||
|
connections: this.connections.size,
|
||||||
|
user: {
|
||||||
|
username: res.locals.user,
|
||||||
|
},
|
||||||
|
commands: this.controlPlugin?.listControlCommands() || [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/authorize', (req, res) => {
|
||||||
|
const { body: { username, password } } = req;
|
||||||
|
const user = this.users.find(
|
||||||
|
(userEntry) => userEntry.username.toLowerCase() === username.toLowerCase()
|
||||||
|
);
|
||||||
|
if (!user || !username || !password) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'invalid_username_password',
|
||||||
|
error_message: 'Invalid username or password',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bcrypt.compare(password, user.password).then((success) => {
|
||||||
|
if (!success) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'invalid_username_password',
|
||||||
|
error_message: 'Invalid username or password',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = crypto.randomBytes(16).toString('hex');
|
||||||
|
this.issuedTokens.set(username, token);
|
||||||
|
res.json({
|
||||||
|
username, token,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/introspect', authMiddleware, (req, res) => {
|
||||||
|
res.json({ status: 'OK', message: 'Token valid', user: { username: res.locals.user } });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/token', authMiddleware, (req, res) => {
|
||||||
|
const { user } = res.locals;
|
||||||
|
this.issuedTokens.delete(user);
|
||||||
|
const ws = this.connections.get(user);
|
||||||
|
if (ws) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
res.json({ status: 'OK', message: 'Logged out' });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/:command', authMiddleware, (req, res) => {
|
||||||
|
if (!this.controlPlugin) {
|
||||||
|
return res.status(500).json({
|
||||||
|
error: 'control_plugin_unavailable',
|
||||||
|
error_message: 'Control plugin is currently unavailable'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = req.params.command;
|
||||||
|
const { body } = req;
|
||||||
|
if (body?.arguments && !Array.isArray(body.arguments)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'arguments_invalid',
|
||||||
|
error_message: 'Arguments have to be passed as an array',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeStart = Date.now();
|
||||||
|
this.controlPlugin.executeControlCommand(command, body?.arguments || []).then(
|
||||||
|
(data: any) => {
|
||||||
|
res.json({
|
||||||
|
status: 'OK',
|
||||||
|
data,
|
||||||
|
time: Date.now() - timeStart,
|
||||||
|
});
|
||||||
|
}, (error: Error) => {
|
||||||
|
res.status(400).json({
|
||||||
|
error: `${command}_fail`,
|
||||||
|
error_message: error.message,
|
||||||
|
time: Date.now() - timeStart,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.use('/api/v1', router);
|
||||||
|
}
|
||||||
|
|
||||||
|
public announcePluginLoaded(plugin: string): void {
|
||||||
|
this.connections.forEach((ws) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: 'PLUGIN_LOADED',
|
||||||
|
message: `${plugin} has been loaded`,
|
||||||
|
plugin,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public announcePluginUnloaded(plugin: string): void {
|
||||||
|
this.connections.forEach((ws) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
status: 'PLUGIN_UNLOADED',
|
||||||
|
message: `${plugin} has been unloaded`,
|
||||||
|
plugin,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configurable({
|
@Configurable({
|
||||||
port: 3000,
|
port: 5511,
|
||||||
host: 'localhost'
|
host: 'localhost',
|
||||||
|
trustedRemote: '*',
|
||||||
|
trustProxy: false,
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
username: 'squeeadmin',
|
||||||
|
password: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
class WebfacePlugin extends Plugin {
|
class WebfacePlugin extends Plugin {
|
||||||
private srv?: WebfaceServer;
|
private srv?: WebfaceServer;
|
||||||
@ -63,15 +462,69 @@ class WebfacePlugin extends Plugin {
|
|||||||
public unloadEventHandler(plugin: string | Plugin): void {
|
public unloadEventHandler(plugin: string | Plugin): void {
|
||||||
if (plugin === this.name || plugin === this) {
|
if (plugin === this.name || plugin === this) {
|
||||||
this.srv?.destroy();
|
this.srv?.destroy();
|
||||||
this.config.save().then(() =>
|
this.emit('pluginUnloaded', this);
|
||||||
this.emit('pluginUnloaded', this));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener('pluginLoaded')
|
||||||
|
public pluginLoadedEvent(plugin: string | Plugin): void {
|
||||||
|
let pluginName = plugin as string;
|
||||||
|
if (typeof plugin !== 'string') {
|
||||||
|
pluginName = plugin.manifest.name;
|
||||||
|
}
|
||||||
|
this.srv?.announcePluginLoaded(pluginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener('pluginUnloaded')
|
||||||
|
public pluginUnloadedEvent(plugin: string | Plugin): void {
|
||||||
|
let pluginName = plugin as string;
|
||||||
|
if (typeof plugin !== 'string') {
|
||||||
|
pluginName = plugin.manifest.name;
|
||||||
|
}
|
||||||
|
this.srv?.announcePluginUnloaded(pluginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyLoad('control')
|
||||||
|
public controlLoaded(control: any): void {
|
||||||
|
if (this.srv) {
|
||||||
|
this.srv.setControlPlugin(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyUnload('control')
|
||||||
|
public controlUnloaded(): void {
|
||||||
|
if (this.srv) {
|
||||||
|
this.srv.controlPluginUnloaded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(): void {
|
initialize(): void {
|
||||||
|
const users = this.config.get('users', []) as RegisteredUser[];
|
||||||
|
const defaultUser = users.find(
|
||||||
|
(user) => user.username === 'squeeadmin'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set default user password
|
||||||
|
if (defaultUser && defaultUser.password === '') {
|
||||||
|
const defaultPassword = 'squeeadmin13';
|
||||||
|
bcrypt.hash(defaultPassword, 10).then((hashed) => {
|
||||||
|
defaultUser.password = hashed;
|
||||||
|
logger.warn('[webface] !!! The default user is "squeeadmin" and the password has been set to "squeeadmin13" !!!');
|
||||||
|
logger.warn('[webface] !!! Change this password ASAP from the front-end of your choice !!!');
|
||||||
|
this.config.set('users', users);
|
||||||
|
this.config.save().catch((e) => logger.error('[webface] Failed to save users:', e.message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the server
|
||||||
this.srv = new WebfaceServer(
|
this.srv = new WebfaceServer(
|
||||||
this.config.get('port') as number,
|
this.config.get('port') as number,
|
||||||
this.config.get('host'));
|
this.config.get('host'),
|
||||||
|
this.config.get('trustedRemote', '*') as string,
|
||||||
|
users,
|
||||||
|
this.config.get('trustProxy', false),
|
||||||
|
);
|
||||||
|
|
||||||
this.srv.init();
|
this.srv.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Squeebot Webface</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main id="app"></main>
|
|
||||||
<script src="index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue
Block a user