additions

This commit is contained in:
Evert Prants 2021-10-18 17:47:40 +03:00
parent dbb03e6325
commit 4a1b8348b0
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
3 changed files with 77 additions and 46 deletions

View File

@ -1,5 +1,5 @@
{ {
"name": "webface", "name": "webface-server",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",

View File

@ -1,5 +1,5 @@
{ {
"name": "webface", "name": "webface-server",
"plugins": [ "plugins": [
{ {
"name": "webface", "name": "webface",

View File

@ -45,13 +45,11 @@ class WebSocketServer extends EventEmitter {
constructor( constructor(
public port: number, public port: number,
public host: string, public host: string,
trustProxy = false, private authorizedURL: string,
private trustProxy = false,
) { ) {
super(); super();
this.app.disable('x-powered-by'); this.setupApp();
if (trustProxy) {
this.app.set('trust proxy', 1);
}
} }
public init(): void { public init(): void {
@ -66,6 +64,21 @@ class WebSocketServer extends EventEmitter {
this.wss.close(); this.wss.close();
this.server.close(); this.server.close();
} }
private setupApp(): void {
this.app.disable('x-powered-by');
if (this.trustProxy) {
this.app.set('trust proxy', 1);
}
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();
});
}
} }
class WebfaceServer extends WebSocketServer { class WebfaceServer extends WebSocketServer {
@ -74,13 +87,13 @@ class WebfaceServer extends WebSocketServer {
private issuedTokens = new Map<string, string>(); private issuedTokens = new Map<string, string>();
constructor( constructor(
public port: number, port: number,
public host: string, host: string,
public authorizedURL: string, authorizedURL: string,
private users: RegisteredUser[],
trustProxy = false, trustProxy = false,
private users: RegisteredUser[],
) { ) {
super(port, host, trustProxy); super(port, host, authorizedURL, trustProxy);
this.initializeWebSocket(); this.initializeWebSocket();
this.initializeAPI(); this.initializeAPI();
} }
@ -196,10 +209,11 @@ class WebfaceServer extends WebSocketServer {
* @param ws WebSocket client * @param ws WebSocket client
* @param username Client's username * @param username Client's username
*/ */
private sendStatus(ws: WebSocket, username: string): void { private sendStatus(ws: WebSocket, username: string, state?: string): void {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
status: this.controlPlugin ? 'OK' : 'MISSING_CONTROL', status: this.controlPlugin ? 'OK' : 'MISSING_CONTROL',
commands: this.controlPlugin?.listControlCommands() || [], commands: this.controlPlugin?.listControlCommands() || [],
state,
user: { user: {
username, username,
} }
@ -230,7 +244,7 @@ class WebfaceServer extends WebSocketServer {
status: 'ERROR', status: 'ERROR',
message: 'Unknown or missing command', message: 'Unknown or missing command',
command: '', command: '',
state: jCmd.state, state,
})); }));
return; return;
} }
@ -241,7 +255,7 @@ class WebfaceServer extends WebSocketServer {
} }
if (command === 'help' || command === 'status') { if (command === 'help' || command === 'status') {
this.sendStatus(ws, user); this.sendStatus(ws, user, state);
return; return;
} }
@ -312,37 +326,10 @@ class WebfaceServer extends WebSocketServer {
* Set up the HTTP API * Set up the HTTP API
*/ */
private initializeAPI(): void { 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 router = express.Router();
const authMiddleware = this.tokenOwnerMiddleware(); const authMiddleware = this.tokenOwnerMiddleware();
router.get('/', (req, res) => { router.get('/', (req, res) => res.json({ status: 'OK'}));
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) => { router.post('/authorize', (req, res) => {
const { body: { username, password } } = req; const { body: { username, password } } = req;
@ -372,8 +359,39 @@ class WebfaceServer extends WebSocketServer {
}); });
}); });
router.post('/introspect', authMiddleware, (req, res) => { router.get('/squeebot', authMiddleware, (req, res) => {
res.json({ status: 'OK', message: 'Token valid', user: { username: res.locals.user } }); 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('/verify', authMiddleware,
(req, res) => res.json({
status: 'OK',
message: 'Token valid',
user: {
username: res.locals.user
}
}));
router.post('/password', authMiddleware, (req, res) => {
const { password } = req.body;
if (!password || password.length < 8) {
return res.status(400).json({
error: 'invalid_password',
error_message: 'Password must be at least 8 characters long.',
});
}
this.emit('chpwd', { password, user: res.locals.user });
res.json({
status: 'OK'
});
}); });
router.delete('/token', authMiddleware, (req, res) => { router.delete('/token', authMiddleware, (req, res) => {
@ -521,10 +539,23 @@ class WebfacePlugin extends Plugin {
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, this.config.get('trustedRemote', '*') as string,
users,
this.config.get('trustProxy', false), this.config.get('trustProxy', false),
users,
); );
this.srv.on('chpwd', ({ user, password }) => {
const currentUsers = this.config.get('users');
const currentUser = currentUsers.find(({ username }: RegisteredUser) => username === user);
if (currentUsers) {
bcrypt.hash(password, 10).then((hashed) => {
currentUser.password = hashed;
logger.warn('[webface] User %s password changed.', user);
this.config.set('users', currentUsers);
this.config.save().catch((e) => logger.error('[webface] Failed to save users:', e.message));
});
}
});
this.srv.init(); this.srv.init();
} }
} }