From caf6f01fd97ad2d26b5224a29ce624d0afaa4166 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Wed, 7 Aug 2024 13:12:24 -0400 Subject: [PATCH 01/11] feature: integration with venus os security --- src/tokensecurity.js | 2 +- src/venussecurity.js | 76 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/venussecurity.js diff --git a/src/tokensecurity.js b/src/tokensecurity.js index 5220d941d..6678302a2 100644 --- a/src/tokensecurity.js +++ b/src/tokensecurity.js @@ -184,7 +184,7 @@ module.exports = function (app, config) { const remember = req.body.rememberMe const configuration = getConfiguration() - login(name, password) + strategy.login(name, password) .then((reply) => { const requestType = req.get('Content-Type') diff --git a/src/venussecurity.js b/src/venussecurity.js new file mode 100644 index 000000000..e4691060c --- /dev/null +++ b/src/venussecurity.js @@ -0,0 +1,76 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* + * Copyright 2017 Teppo Kurki + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +import fs from 'fs' +import { createDebug } from './debug' +const debug = createDebug('signalk-server:tokensecurity') +import dummysecurity from './dummysecurity' +import { + saveSecurityConfig, +} from './security' + +//const passwordFile = '/data/conf/vncpassword.txt' +const passwordFile = './vncpassword.txt' + +module.exports = function (app, config) { + let security + + if ( fs.existsSync(passwordFile) && fs.readFileSync(passwordFile).length ) { + if ( !config.users || config.users.length == 0 ) { + const user = { + username: 'admin', + type: 'admin', + password: fs.readFileSync(passwordFile).toString().trim() + } + + security = require('./tokensecurity')(app, config) + config = security.getConfiguration() + config.users = [ user ] + app.securityStrategy = security + saveSecurityConfig(app, config, (theError) => { + if (theError) { + console.error(theError) + } + }) + } else { + security = require('./tokensecurity')(app, config) + } + const tslogin = security.login + + security.login = (username, password) => { + if ( username === 'admin' ) { + + const user = security.getConfiguration().users.find((aUser) => aUser.username === username) + + const password = fs.readFileSync(passwordFile).toString().trim() + + if ( password !== user.password ) { + user.password = password + saveSecurityConfig(app, config, (theError) => { + if (theError) { + console.error(theError) + } + }) + } + } + return tslogin(username, password) + } + } else { + security = dummysecurity() + } + return security +} From 7f8e3c83163e9374b2fc994d5eeddf8482750f43 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Wed, 7 Aug 2024 14:28:09 -0400 Subject: [PATCH 02/11] add ws auth via venus os cookie --- src/venussecurity.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/venussecurity.js b/src/venussecurity.js index e4691060c..ff9fc4927 100644 --- a/src/venussecurity.js +++ b/src/venussecurity.js @@ -69,6 +69,20 @@ module.exports = function (app, config) { } return tslogin(username, password) } + + const tsAuthorizeWS = security.authorizeWS + security.tsAuthorizeWS = (req) => { + tsAuthorizeWS(req) + if ( !req.skIsAuthenticated && + req.headers.venus_os_authenticated === 'true' ) { + req.skIsAuthenticated = true + req.skPrincipal = { + identifier: 'admin', + permissions: 'admin' + } + } + } + } else { security = dummysecurity() } From 4d222266a58c0be93a0bd360f88a43cda0974c2d Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 8 Aug 2024 15:02:17 -0400 Subject: [PATCH 03/11] add http header authorization --- src/tokensecurity.js | 104 ++++++++++++++++++++++--------------------- src/venussecurity.js | 24 +++++++++- 2 files changed, 76 insertions(+), 52 deletions(-) diff --git a/src/tokensecurity.js b/src/tokensecurity.js index 6678302a2..b808b0a22 100644 --- a/src/tokensecurity.js +++ b/src/tokensecurity.js @@ -674,7 +674,7 @@ module.exports = function (app, config) { } } - function getAuthorizationFromHeaders(req) { + strategy.getAuthorizationFromHeaders = (req) => { if (req.headers) { let header = req.headers.authorization if (!header) { @@ -705,7 +705,7 @@ module.exports = function (app, config) { if (req.query && req.query.token) { token = req.query.token } else { - token = getAuthorizationFromHeaders(req) + token = strategy.getAuthorizationFromHeaders(req) } } @@ -869,65 +869,69 @@ module.exports = function (app, config) { function http_authorize(redirect, forLoginStatus) { // debug('http_authorize: ' + redirect) return function (req, res, next) { - let token = req.cookies.JAUTHENTICATION - - debug(`http_authorize: ${req.path} (forLogin: ${forLoginStatus})`) + strategy.httpAuthorize(redirect, forLoginStatus, req, res, next) + } + } - if (!getIsEnabled()) { - return next() - } + strategy.httpAuthorize = (redirect, forLoginStatus, req, res, next) => { + debug(`http_authorize: ${req.path} (forLogin: ${forLoginStatus})`) - const configuration = getConfiguration() + if (!getIsEnabled()) { + return next() + } - if (!token) { - token = getAuthorizationFromHeaders(req) - } + const configuration = getConfiguration() - if (token) { - jwt.verify(token, configuration.secretKey, function (err, decoded) { - debug('verify') - if (!err) { - const principal = getPrincipal(decoded) - if (principal) { - debug('authorized') - req.skPrincipal = principal - req.skIsAuthenticated = true - req.userLoggedIn = true - next() - return - } else { - debug('unknown user: ' + (decoded.id || decoded.device)) - } - } else { - debug(`bad token: ${err.message} ${req.path}`) - res.clearCookie('JAUTHENTICATION') - } + let token = req.cookies.JAUTHENTICATION + + if (!token) { + token = strategy.getAuthorizationFromHeaders(req) + } - if (configuration.allow_readonly) { - req.skIsAuthenticated = false + if (token) { + jwt.verify(token, configuration.secretKey, function (err, decoded) { + debug('verify') + if (!err) { + const principal = getPrincipal(decoded) + if (principal) { + debug('authorized') + req.skPrincipal = principal + req.skIsAuthenticated = true + req.userLoggedIn = true next() + return } else { - res.status(401).send('bad auth token') + debug('unknown user: ' + (decoded.id || decoded.device)) } - }) - } else { - debug('no token') - - if (configuration.allow_readonly && !forLoginStatus) { - req.skPrincipal = { identifier: 'AUTO', permissions: 'readonly' } - req.skIsAuthenticated = true - return next() } else { + debug(`bad token: ${err.message} ${req.path}`) + res.clearCookie('JAUTHENTICATION') + } + + if (configuration.allow_readonly) { req.skIsAuthenticated = false + next() + } else { + res.status(401).send('bad auth token') + } + }) + } else { + debug('no token') - if (forLoginStatus) { - next() - } else if (redirect) { - debug('redirecting to login') - res.redirect('/@signalk/server-admin-ui/#/login') - } else { - res.status(401).send('Unauthorized') - } + if (configuration.allow_readonly && !forLoginStatus) { + req.skPrincipal = { identifier: 'AUTO', permissions: 'readonly' } + req.skIsAuthenticated = true + return next() + } else { + req.skIsAuthenticated = false + + if (forLoginStatus) { + next() + } else if (redirect) { + debug('redirecting to login') + res.redirect('/@signalk/server-admin-ui/#/login') + } else { + res.status(401).send('Unauthorized') } } } diff --git a/src/venussecurity.js b/src/venussecurity.js index ff9fc4927..a51d46535 100644 --- a/src/venussecurity.js +++ b/src/venussecurity.js @@ -17,7 +17,7 @@ import fs from 'fs' import { createDebug } from './debug' -const debug = createDebug('signalk-server:tokensecurity') +//const debug = createDebug('signalk-server:venussecurity') import dummysecurity from './dummysecurity' import { saveSecurityConfig, @@ -71,7 +71,7 @@ module.exports = function (app, config) { } const tsAuthorizeWS = security.authorizeWS - security.tsAuthorizeWS = (req) => { + security.authorizeWS = (req) => { tsAuthorizeWS(req) if ( !req.skIsAuthenticated && req.headers.venus_os_authenticated === 'true' ) { @@ -82,6 +82,26 @@ module.exports = function (app, config) { } } } + + const tsHttpAuthorize = security.httpAuthorize + security.httpAuthorize = (redirect, forLoginStatus, req, res, next) => { + if ( req.cookies.JAUTHENTICATION || + security.getAuthorizationFromHeaders(req) ) { + return tsHttpAuthorize(redirect, forLoginStatus, req, res, next) + } + + if ( req.headers.venus_os_authenticated === 'true' ) { + req.skIsAuthenticated = true + req.userLoggedIn = true + req.skPrincipal = { + identifier: 'admin', + permissions: 'admin' + } + return next() + } else { + return tsHttpAuthorize(redirect, forLoginStatus, req, res, next) + } + } } else { security = dummysecurity() From bfceeb2eda541f3e030564abf29cd83481f09646 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 8 Aug 2024 15:03:16 -0400 Subject: [PATCH 04/11] chore: run prettier and lint --- src/tokensecurity.js | 5 +++-- src/venussecurity.js | 46 +++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/tokensecurity.js b/src/tokensecurity.js index b808b0a22..753fe5446 100644 --- a/src/tokensecurity.js +++ b/src/tokensecurity.js @@ -184,7 +184,8 @@ module.exports = function (app, config) { const remember = req.body.rememberMe const configuration = getConfiguration() - strategy.login(name, password) + strategy + .login(name, password) .then((reply) => { const requestType = req.get('Content-Type') @@ -883,7 +884,7 @@ module.exports = function (app, config) { const configuration = getConfiguration() let token = req.cookies.JAUTHENTICATION - + if (!token) { token = strategy.getAuthorizationFromHeaders(req) } diff --git a/src/venussecurity.js b/src/venussecurity.js index a51d46535..bf2ee2072 100644 --- a/src/venussecurity.js +++ b/src/venussecurity.js @@ -16,21 +16,19 @@ */ import fs from 'fs' -import { createDebug } from './debug' +//import { createDebug } from './debug' //const debug = createDebug('signalk-server:venussecurity') import dummysecurity from './dummysecurity' -import { - saveSecurityConfig, -} from './security' +import { saveSecurityConfig } from './security' //const passwordFile = '/data/conf/vncpassword.txt' const passwordFile = './vncpassword.txt' module.exports = function (app, config) { let security - - if ( fs.existsSync(passwordFile) && fs.readFileSync(passwordFile).length ) { - if ( !config.users || config.users.length == 0 ) { + + if (fs.existsSync(passwordFile) && fs.readFileSync(passwordFile).length) { + if (!config.users || config.users.length == 0) { const user = { username: 'admin', type: 'admin', @@ -39,7 +37,7 @@ module.exports = function (app, config) { security = require('./tokensecurity')(app, config) config = security.getConfiguration() - config.users = [ user ] + config.users = [user] app.securityStrategy = security saveSecurityConfig(app, config, (theError) => { if (theError) { @@ -50,15 +48,16 @@ module.exports = function (app, config) { security = require('./tokensecurity')(app, config) } const tslogin = security.login - + security.login = (username, password) => { - if ( username === 'admin' ) { - - const user = security.getConfiguration().users.find((aUser) => aUser.username === username) - + if (username === 'admin') { + const user = security + .getConfiguration() + .users.find((aUser) => aUser.username === username) + const password = fs.readFileSync(passwordFile).toString().trim() - if ( password !== user.password ) { + if (password !== user.password) { user.password = password saveSecurityConfig(app, config, (theError) => { if (theError) { @@ -73,8 +72,10 @@ module.exports = function (app, config) { const tsAuthorizeWS = security.authorizeWS security.authorizeWS = (req) => { tsAuthorizeWS(req) - if ( !req.skIsAuthenticated && - req.headers.venus_os_authenticated === 'true' ) { + if ( + !req.skIsAuthenticated && + req.headers.venus_os_authenticated === 'true' + ) { req.skIsAuthenticated = true req.skPrincipal = { identifier: 'admin', @@ -85,12 +86,14 @@ module.exports = function (app, config) { const tsHttpAuthorize = security.httpAuthorize security.httpAuthorize = (redirect, forLoginStatus, req, res, next) => { - if ( req.cookies.JAUTHENTICATION || - security.getAuthorizationFromHeaders(req) ) { + if ( + req.cookies.JAUTHENTICATION || + security.getAuthorizationFromHeaders(req) + ) { return tsHttpAuthorize(redirect, forLoginStatus, req, res, next) } - - if ( req.headers.venus_os_authenticated === 'true' ) { + + if (req.headers.venus_os_authenticated === 'true') { req.skIsAuthenticated = true req.userLoggedIn = true req.skPrincipal = { @@ -102,8 +105,7 @@ module.exports = function (app, config) { return tsHttpAuthorize(redirect, forLoginStatus, req, res, next) } } - - } else { + } else { security = dummysecurity() } return security From c10808c94cc800354477943c36277b1f4ac5c20c Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 8 Aug 2024 16:01:58 -0400 Subject: [PATCH 05/11] feature: add ability to specify the network interface to listen on --- src/config/config.ts | 1 + src/index.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 0b9d587e4..0287180fb 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -75,6 +75,7 @@ export interface Config { enablePluginLogging?: boolean loggingDirectory?: string sourcePriorities?: any + interface?: string } defaults: object } diff --git a/src/index.ts b/src/index.ts index 574a7d519..97873e6da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -448,9 +448,9 @@ class Server { const primaryPort = getPrimaryPort(app) debug(`primary port:${primaryPort}`) - server.listen(primaryPort, () => { + server.listen(primaryPort, app.config.settings.interface, () => { console.log( - 'signalk-server running at 0.0.0.0:' + primaryPort.toString() + '\n' + 'signalk-server running at ' + JSON.stringify(server.address()) + '\n' ) app.started = true resolve(self) @@ -465,7 +465,8 @@ class Server { if (!anErr) { app.redirectServer = aServer } - } + }, + app.config.settings.interface ) } }) @@ -584,7 +585,8 @@ function createServer(app: any, cb: (err: any, server?: any) => void) { function startRedirectToSsl( port: number, redirectPort: number, - cb: (e: unknown, server: any) => void + cb: (e: unknown, server: any) => void, + networkInterface?: string, ) { const redirectApp = express() redirectApp.use((req: Request, res: Response) => { @@ -592,7 +594,7 @@ function startRedirectToSsl( res.redirect(`https://${host}:${redirectPort}${req.path}`) }) const server = http.createServer(redirectApp) - server.listen(port, () => { + server.listen(port, networkInterface, () => { console.log(`Redirect server running on port ${port.toString()}`) cb(null, server) }) From 2b9906b5ff87e62803b42d3c9cc6ea14f2a2774b Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Thu, 8 Aug 2024 16:10:10 -0400 Subject: [PATCH 06/11] fix: only store changed venus os password if the user was auto generated --- src/venussecurity.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/venussecurity.js b/src/venussecurity.js index bf2ee2072..7dee05273 100644 --- a/src/venussecurity.js +++ b/src/venussecurity.js @@ -32,7 +32,8 @@ module.exports = function (app, config) { const user = { username: 'admin', type: 'admin', - password: fs.readFileSync(passwordFile).toString().trim() + password: fs.readFileSync(passwordFile).toString().trim(), + venusAdminUser: true } security = require('./tokensecurity')(app, config) @@ -55,15 +56,17 @@ module.exports = function (app, config) { .getConfiguration() .users.find((aUser) => aUser.username === username) - const password = fs.readFileSync(passwordFile).toString().trim() - - if (password !== user.password) { - user.password = password - saveSecurityConfig(app, config, (theError) => { - if (theError) { - console.error(theError) - } - }) + if ( user.venusAdminUser ) { + const password = fs.readFileSync(passwordFile).toString().trim() + + if (password !== user.password) { + user.password = password + saveSecurityConfig(app, config, (theError) => { + if (theError) { + console.error(theError) + } + }) + } } } return tslogin(username, password) From 391d2d841db918a80cdd5f4d5b13767d3f078a9b Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Fri, 9 Aug 2024 15:02:26 -0400 Subject: [PATCH 07/11] feature: support listening on multiple interfaces --- src/app.ts | 4 +- src/config/config.ts | 2 +- src/index.ts | 138 ++++++++++++++++++++++++++++++------------- src/interfaces/ws.js | 8 ++- 4 files changed, 105 insertions(+), 47 deletions(-) diff --git a/src/app.ts b/src/app.ts index 1e775d40d..de34657e0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,8 +11,8 @@ export interface ServerApp { interfaces: { [key: string]: any } intervals: NodeJS.Timeout[] providers: any[] - server: any - redirectServer?: any + servers: any[] + redirectServers?: any[] deltaCache: DeltaCache getProviderStatus: () => any lastServerEvents: { [key: string]: any } diff --git a/src/config/config.ts b/src/config/config.ts index 0287180fb..83c773bec 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -75,7 +75,7 @@ export interface Config { enablePluginLogging?: boolean loggingDirectory?: string sourcePriorities?: any - interface?: string + networkInterfaces?: string[] } defaults: object } diff --git a/src/index.ts b/src/index.ts index 97873e6da..ff2792190 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,7 +70,6 @@ import { pipedProviders } from './pipedproviders' import { EventsActorId, WithWrappedEmitter, wrapEmitter } from './events' import { Zones } from './zones' const debug = createDebug('signalk-server') - const { StreamBundle } = require('./streambundle') interface ServerOptions { @@ -427,12 +426,12 @@ class Server { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { - createServer(app, async (err, server) => { - if (err) { + createServer(app, async (err, servers) => { + if (err || _.isUndefined(servers) ) { reject(err) return } - app.server = server + app.servers = servers app.interfaces = {} app.clients = 0 @@ -448,12 +447,14 @@ class Server { const primaryPort = getPrimaryPort(app) debug(`primary port:${primaryPort}`) - server.listen(primaryPort, app.config.settings.interface, () => { - console.log( - 'signalk-server running at ' + JSON.stringify(server.address()) + '\n' - ) - app.started = true - resolve(self) + + serverListen('signalk-server running at', app.config.settings.networkInterfaces, servers, primaryPort, (err:any) => { + if ( !err ) { + app.started = true + resolve(self) + } else { + reject(err) + } }) const secondaryPort = getSecondaryPort(app) debug(`secondary port:${primaryPort}`) @@ -461,12 +462,12 @@ class Server { startRedirectToSsl( secondaryPort, getExternalPort(app), - (anErr: any, aServer: any) => { + (anErr: any, aServers: any[]) => { if (!anErr) { - app.redirectServer = aServer + app.redirectServers = aServers } }, - app.config.settings.interface + app.config.settings.networkInterfaces ) } }) @@ -529,26 +530,17 @@ class Server { debug('Closing server...') const that = this - this.app.server.close(() => { - debug('Server closed') - if (that.app.redirectServer) { - try { - that.app.redirectServer.close(() => { - debug('Redirect server closed') - delete that.app.redirectServer - that.app.started = false - cb && cb() - resolve(that) - }) - } catch (err) { - reject(err) - } - } else { + Promise.all([closeServers(this.app.servers), + closeServers(this.app.redirectServers)]) + .then(() => { + debug('Servers closed') that.app.started = false cb && cb() resolve(that) - } - }) + }) + .catch(err => { + reject(err) + }) } catch (err) { reject(err) } @@ -559,44 +551,108 @@ class Server { module.exports = Server -function createServer(app: any, cb: (err: any, server?: any) => void) { +function closeServers(servers: any[] | undefined) +{ + if ( !servers ) { + return null + } else { + return Promise.all(servers.map((server) => { + return new Promise((resolve, reject) => { + try { + server.close(() => { + resolve(server) + }) + } catch ( err ) { + reject(err) + } + }) + })) + } +} + +function createServer(app: any, cb: (err: any, servers?: any[]) => void) { + const serverCount = app.config.settings.networkInterfaces ? app.config.settings.networkInterfaces.length : 1 + if (app.config.settings.ssl) { getCertificateOptions(app, (err: any, options: any) => { if (err) { cb(err) } else { debug('Starting server to serve both http and https') - cb(null, https.createServer(options, app)) + + const servers = [] + for ( let i = 0; i < serverCount; i++ ) { + servers.push(https.createServer(options, app)) + } + + cb(null, servers) } }) return } - let server + const servers = [] try { debug('Starting server to serve only http') - server = http.createServer(app) + for ( let i = 0; i < serverCount; i++ ) { + servers.push(http.createServer(app)) + } } catch (e) { cb(e) return } - cb(null, server) + cb(null, servers) +} + +function serverListen(msg:string, networkInterfaces: string[] | undefined, servers: any[], port: number | { fd: number; }, cb: (err: any) => void) +{ + let interfaces : any + + interfaces = networkInterfaces + + if ( !interfaces ) + { + interfaces = [undefined] + } + + const promises:any[] = [] + + servers.forEach((server:any, idx:number) => { + promises.push(new Promise((resolve, reject) => { + try + { + server.listen(port, interfaces[idx], () => { + console.log(`${msg} ${JSON.stringify(server.address())}`) + resolve(null) + }) + } + catch ( err ) + { + reject(err) + } + })) + }) + Promise.all(promises) + .then( () => { cb(null) } ) + .catch( cb ) } function startRedirectToSsl( port: number, redirectPort: number, - cb: (e: unknown, server: any) => void, - networkInterface?: string, + cb: (e: unknown, servers: any[]) => void, + networkInterfaces: string[] | undefined, ) { const redirectApp = express() redirectApp.use((req: Request, res: Response) => { const host = req.headers.host?.split(':')[0] res.redirect(`https://${host}:${redirectPort}${req.path}`) }) - const server = http.createServer(redirectApp) - server.listen(port, networkInterface, () => { - console.log(`Redirect server running on port ${port.toString()}`) - cb(null, server) + const servers = (networkInterfaces || [undefined]).map(() => { + return http.createServer(redirectApp) + }) + + serverListen('Redirect server running at', networkInterfaces, servers, port, (err:any) => { + cb(err, servers) }) } diff --git a/src/interfaces/ws.js b/src/interfaces/ws.js index ed02b3c40..a3c94577d 100644 --- a/src/interfaces/ws.js +++ b/src/interfaces/ws.js @@ -154,7 +154,8 @@ module.exports = function (app) { const assertBufferSize = getAssertBufferSize(app.config) primuses = allWsOptions.map((primusOptions) => { - const primus = new Primus(app.server, primusOptions) + return app.servers.map((server) => { + const primus = new Primus(server, primusOptions) if (app.securityStrategy.canAuthorizeWS()) { primus.authorize( @@ -302,8 +303,9 @@ module.exports = function (app) { debug(spark.id + ' disconnected') }) - return primus - }) + return primus + }) + }).reduce((prev, current) => [...prev, ...current]) } api.stop = function () { From b0bea4ba48f3872f357c98cf1e902601c3753246 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Fri, 9 Aug 2024 16:48:42 -0400 Subject: [PATCH 08/11] chore: cleanup async http server stuff --- src/index.ts | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/index.ts b/src/index.ts index ff2792190..d544d75a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -448,28 +448,20 @@ class Server { const primaryPort = getPrimaryPort(app) debug(`primary port:${primaryPort}`) - serverListen('signalk-server running at', app.config.settings.networkInterfaces, servers, primaryPort, (err:any) => { - if ( !err ) { - app.started = true - resolve(self) - } else { - reject(err) - } - }) + await serverListen('signalk-server running at', app.config.settings.networkInterfaces, servers, primaryPort) + const secondaryPort = getSecondaryPort(app) debug(`secondary port:${primaryPort}`) if (app.config.settings.ssl && secondaryPort) { - startRedirectToSsl( + app.redirectServers = await startRedirectToSsl( secondaryPort, getExternalPort(app), - (anErr: any, aServers: any[]) => { - if (!anErr) { - app.redirectServers = aServers - } - }, app.config.settings.networkInterfaces ) } + + app.started = true + resolve(self) }) }) } @@ -603,7 +595,7 @@ function createServer(app: any, cb: (err: any, servers?: any[]) => void) { cb(null, servers) } -function serverListen(msg:string, networkInterfaces: string[] | undefined, servers: any[], port: number | { fd: number; }, cb: (err: any) => void) +function serverListen(msg:string, networkInterfaces: string[] | undefined, servers: any[], port: number | { fd: number; }) { let interfaces : any @@ -631,15 +623,12 @@ function serverListen(msg:string, networkInterfaces: string[] | undefined, serve } })) }) - Promise.all(promises) - .then( () => { cb(null) } ) - .catch( cb ) + return Promise.all(promises) } -function startRedirectToSsl( +async function startRedirectToSsl( port: number, redirectPort: number, - cb: (e: unknown, servers: any[]) => void, networkInterfaces: string[] | undefined, ) { const redirectApp = express() @@ -651,9 +640,12 @@ function startRedirectToSsl( return http.createServer(redirectApp) }) - serverListen('Redirect server running at', networkInterfaces, servers, port, (err:any) => { - cb(err, servers) - }) + await serverListen('signalk-server redirect server running at', networkInterfaces, servers, port) + + throw new Error('Configuration is immutable') + + + return servers } function startMdns(app: ServerApp & WithConfig) { From 109b66d56072764638fe045740a79529f3057ec5 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Fri, 9 Aug 2024 16:49:29 -0400 Subject: [PATCH 09/11] chore: run prettier --- src/index.ts | 112 ++++++++++--------- src/interfaces/ws.js | 250 ++++++++++++++++++++++--------------------- src/venussecurity.js | 4 +- 3 files changed, 192 insertions(+), 174 deletions(-) diff --git a/src/index.ts b/src/index.ts index d544d75a8..a80001538 100644 --- a/src/index.ts +++ b/src/index.ts @@ -427,7 +427,7 @@ class Server { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { createServer(app, async (err, servers) => { - if (err || _.isUndefined(servers) ) { + if (err || _.isUndefined(servers)) { reject(err) return } @@ -448,8 +448,13 @@ class Server { const primaryPort = getPrimaryPort(app) debug(`primary port:${primaryPort}`) - await serverListen('signalk-server running at', app.config.settings.networkInterfaces, servers, primaryPort) - + await serverListen( + 'signalk-server running at', + app.config.settings.networkInterfaces, + servers, + primaryPort + ) + const secondaryPort = getSecondaryPort(app) debug(`secondary port:${primaryPort}`) if (app.config.settings.ssl && secondaryPort) { @@ -522,15 +527,17 @@ class Server { debug('Closing server...') const that = this - Promise.all([closeServers(this.app.servers), - closeServers(this.app.redirectServers)]) + Promise.all([ + closeServers(this.app.servers), + closeServers(this.app.redirectServers) + ]) .then(() => { debug('Servers closed') that.app.started = false cb && cb() resolve(that) }) - .catch(err => { + .catch((err) => { reject(err) }) } catch (err) { @@ -543,28 +550,31 @@ class Server { module.exports = Server -function closeServers(servers: any[] | undefined) -{ - if ( !servers ) { +function closeServers(servers: any[] | undefined) { + if (!servers) { return null } else { - return Promise.all(servers.map((server) => { - return new Promise((resolve, reject) => { - try { - server.close(() => { - resolve(server) - }) - } catch ( err ) { - reject(err) - } + return Promise.all( + servers.map((server) => { + return new Promise((resolve, reject) => { + try { + server.close(() => { + resolve(server) + }) + } catch (err) { + reject(err) + } + }) }) - })) + ) } } function createServer(app: any, cb: (err: any, servers?: any[]) => void) { - const serverCount = app.config.settings.networkInterfaces ? app.config.settings.networkInterfaces.length : 1 - + const serverCount = app.config.settings.networkInterfaces + ? app.config.settings.networkInterfaces.length + : 1 + if (app.config.settings.ssl) { getCertificateOptions(app, (err: any, options: any) => { if (err) { @@ -573,10 +583,10 @@ function createServer(app: any, cb: (err: any, servers?: any[]) => void) { debug('Starting server to serve both http and https') const servers = [] - for ( let i = 0; i < serverCount; i++ ) { + for (let i = 0; i < serverCount; i++) { servers.push(https.createServer(options, app)) } - + cb(null, servers) } }) @@ -585,7 +595,7 @@ function createServer(app: any, cb: (err: any, servers?: any[]) => void) { const servers = [] try { debug('Starting server to serve only http') - for ( let i = 0; i < serverCount; i++ ) { + for (let i = 0; i < serverCount; i++) { servers.push(http.createServer(app)) } } catch (e) { @@ -595,33 +605,35 @@ function createServer(app: any, cb: (err: any, servers?: any[]) => void) { cb(null, servers) } -function serverListen(msg:string, networkInterfaces: string[] | undefined, servers: any[], port: number | { fd: number; }) -{ - let interfaces : any +function serverListen( + msg: string, + networkInterfaces: string[] | undefined, + servers: any[], + port: number | { fd: number } +) { + let interfaces: any interfaces = networkInterfaces - if ( !interfaces ) - { + if (!interfaces) { interfaces = [undefined] } - const promises:any[] = [] + const promises: any[] = [] - servers.forEach((server:any, idx:number) => { - promises.push(new Promise((resolve, reject) => { - try - { - server.listen(port, interfaces[idx], () => { - console.log(`${msg} ${JSON.stringify(server.address())}`) - resolve(null) - }) - } - catch ( err ) - { - reject(err) - } - })) + servers.forEach((server: any, idx: number) => { + promises.push( + new Promise((resolve, reject) => { + try { + server.listen(port, interfaces[idx], () => { + console.log(`${msg} ${JSON.stringify(server.address())}`) + resolve(null) + }) + } catch (err) { + reject(err) + } + }) + ) }) return Promise.all(promises) } @@ -629,7 +641,7 @@ function serverListen(msg:string, networkInterfaces: string[] | undefined, serve async function startRedirectToSsl( port: number, redirectPort: number, - networkInterfaces: string[] | undefined, + networkInterfaces: string[] | undefined ) { const redirectApp = express() redirectApp.use((req: Request, res: Response) => { @@ -639,12 +651,16 @@ async function startRedirectToSsl( const servers = (networkInterfaces || [undefined]).map(() => { return http.createServer(redirectApp) }) - - await serverListen('signalk-server redirect server running at', networkInterfaces, servers, port) + + await serverListen( + 'signalk-server redirect server running at', + networkInterfaces, + servers, + port + ) throw new Error('Configuration is immutable') - return servers } diff --git a/src/interfaces/ws.js b/src/interfaces/ws.js index a3c94577d..fae07ce57 100644 --- a/src/interfaces/ws.js +++ b/src/interfaces/ws.js @@ -153,159 +153,161 @@ module.exports = function (app) { const assertBufferSize = getAssertBufferSize(app.config) - primuses = allWsOptions.map((primusOptions) => { - return app.servers.map((server) => { - const primus = new Primus(server, primusOptions) + primuses = allWsOptions + .map((primusOptions) => { + return app.servers.map((server) => { + const primus = new Primus(server, primusOptions) + + if (app.securityStrategy.canAuthorizeWS()) { + primus.authorize( + createPrimusAuthorize(app.securityStrategy.authorizeWS) + ) + } - if (app.securityStrategy.canAuthorizeWS()) { - primus.authorize( - createPrimusAuthorize(app.securityStrategy.authorizeWS) - ) - } + primus.on('connection', function (spark) { + let principalId + if (spark.request.skPrincipal) { + principalId = spark.request.skPrincipal.identifier + } - primus.on('connection', function (spark) { - let principalId - if (spark.request.skPrincipal) { - principalId = spark.request.skPrincipal.identifier - } + debugConnection( + `${spark.id} connected ${JSON.stringify(spark.query)} ${ + spark.request.connection.remoteAddress + }:${principalId}` + ) - debugConnection( - `${spark.id} connected ${JSON.stringify(spark.query)} ${ - spark.request.connection.remoteAddress - }:${principalId}` - ) + spark.sendMetaDeltas = spark.query.sendMeta === 'all' + spark.sentMetaData = {} - spark.sendMetaDeltas = spark.query.sendMeta === 'all' - spark.sentMetaData = {} - - let onChange = (delta) => { - const filtered = app.securityStrategy.filterReadDelta( - spark.request.skPrincipal, - delta - ) - if (filtered) { - sendMetaData(app, spark, filtered) - spark.write(filtered) - assertBufferSize(spark) - } - } + let onChange = (delta) => { + const filtered = app.securityStrategy.filterReadDelta( + spark.request.skPrincipal, + delta + ) + if (filtered) { + sendMetaData(app, spark, filtered) + spark.write(filtered) + assertBufferSize(spark) + } + } - const unsubscribes = [] + const unsubscribes = [] - if (primusOptions.isPlayback) { - spark.on('data', () => { - console.error('Playback does not support ws upstream messages') - spark.end('Playback does not support ws upstream messages') - }) - } else { - spark.on('data', function (msg) { - debug('<' + JSON.stringify(msg)) + if (primusOptions.isPlayback) { + spark.on('data', () => { + console.error('Playback does not support ws upstream messages') + spark.end('Playback does not support ws upstream messages') + }) + } else { + spark.on('data', function (msg) { + debug('<' + JSON.stringify(msg)) - try { - if (msg.token) { - spark.request.token = msg.token - } + try { + if (msg.token) { + spark.request.token = msg.token + } - if (msg.updates) { - processUpdates(app, pathSources, spark, msg) - } + if (msg.updates) { + processUpdates(app, pathSources, spark, msg) + } - if (msg.subscribe) { - processSubscribe( - app, - unsubscribes, - spark, - assertBufferSize, - msg - ) - } + if (msg.subscribe) { + processSubscribe( + app, + unsubscribes, + spark, + assertBufferSize, + msg + ) + } - if (msg.unsubscribe) { - processUnsubscribe(app, unsubscribes, msg, onChange, spark) - } + if (msg.unsubscribe) { + processUnsubscribe(app, unsubscribes, msg, onChange, spark) + } - if (msg.accessRequest) { - processAccessRequest(spark, msg) - } + if (msg.accessRequest) { + processAccessRequest(spark, msg) + } - if (msg.login && app.securityStrategy.supportsLogin()) { - processLoginRequest(spark, msg) - } + if (msg.login && app.securityStrategy.supportsLogin()) { + processLoginRequest(spark, msg) + } - if (msg.put) { - processPutRequest(spark, msg) - } + if (msg.put) { + processPutRequest(spark, msg) + } - if (msg.delete) { - processDeleteRequest(spark, msg) - } + if (msg.delete) { + processDeleteRequest(spark, msg) + } - if (msg.requestId && msg.query) { - processReuestQuery(spark, msg) - } - } catch (e) { - console.error(e) + if (msg.requestId && msg.query) { + processReuestQuery(spark, msg) + } + } catch (e) { + console.error(e) + } + }) } - }) - } - spark.on('end', function () { - debugConnection( - `${spark.id} end ${JSON.stringify(spark.query)} ${ - spark.request.connection.remoteAddress - }:${principalId}` - ) + spark.on('end', function () { + debugConnection( + `${spark.id} end ${JSON.stringify(spark.query)} ${ + spark.request.connection.remoteAddress + }:${principalId}` + ) - unsubscribes.forEach((unsubscribe) => unsubscribe()) + unsubscribes.forEach((unsubscribe) => unsubscribe()) - _.keys(pathSources).forEach((path) => { - _.keys(pathSources[path]).forEach((source) => { - if (pathSources[path][source] === spark) { - debug('removing source for %s', path) - delete pathSources[path][source] - } + _.keys(pathSources).forEach((path) => { + _.keys(pathSources[path]).forEach((source) => { + if (pathSources[path][source] === spark) { + debug('removing source for %s', path) + delete pathSources[path][source] + } + }) + }) }) - }) - }) - if (isSelfSubscription(spark.query)) { - const realOnChange = onChange - onChange = function (msg) { - if (!msg.context || msg.context === app.selfContext) { - realOnChange(msg) + if (isSelfSubscription(spark.query)) { + const realOnChange = onChange + onChange = function (msg) { + if (!msg.context || msg.context === app.selfContext) { + realOnChange(msg) + } + } } - } - } - if (spark.query.subscribe === 'none') { - onChange = () => undefined - } + if (spark.query.subscribe === 'none') { + onChange = () => undefined + } - onChange = wrapWithverifyWS(app.securityStrategy, spark, onChange) + onChange = wrapWithverifyWS(app.securityStrategy, spark, onChange) - spark.onDisconnects = [] + spark.onDisconnects = [] - if (primusOptions.isPlayback) { - if (!spark.query.startTime) { - spark.end( - 'startTime is a required query parameter for playback connections' - ) - } else { - handlePlaybackConnection(app, spark, onChange) - } - } else { - handleRealtimeConnection(app, spark, onChange) - } - }) + if (primusOptions.isPlayback) { + if (!spark.query.startTime) { + spark.end( + 'startTime is a required query parameter for playback connections' + ) + } else { + handlePlaybackConnection(app, spark, onChange) + } + } else { + handleRealtimeConnection(app, spark, onChange) + } + }) - primus.on('disconnection', function (spark) { - spark.onDisconnects.forEach((f) => f()) - debug(spark.id + ' disconnected') - }) + primus.on('disconnection', function (spark) { + spark.onDisconnects.forEach((f) => f()) + debug(spark.id + ' disconnected') + }) - return primus + return primus + }) }) - }).reduce((prev, current) => [...prev, ...current]) + .reduce((prev, current) => [...prev, ...current]) } api.stop = function () { diff --git a/src/venussecurity.js b/src/venussecurity.js index 7dee05273..4f53475c5 100644 --- a/src/venussecurity.js +++ b/src/venussecurity.js @@ -56,9 +56,9 @@ module.exports = function (app, config) { .getConfiguration() .users.find((aUser) => aUser.username === username) - if ( user.venusAdminUser ) { + if (user.venusAdminUser) { const password = fs.readFileSync(passwordFile).toString().trim() - + if (password !== user.password) { user.password = password saveSecurityConfig(app, config, (theError) => { From 0f80c539876794962542a3f11d58cef4d872bc61 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Fri, 9 Aug 2024 16:50:05 -0400 Subject: [PATCH 10/11] chore: remove testing code --- src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index a80001538..0a507cc77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -659,8 +659,6 @@ async function startRedirectToSsl( port ) - throw new Error('Configuration is immutable') - return servers } From bb40f030a1d43c463cb7d25ee61b566b9b42e047 Mon Sep 17 00:00:00 2001 From: Scott Bender Date: Fri, 9 Aug 2024 16:55:52 -0400 Subject: [PATCH 11/11] chore: cleanup listener logging --- src/index.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0a507cc77..79361649a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -449,12 +449,17 @@ class Server { debug(`primary port:${primaryPort}`) await serverListen( - 'signalk-server running at', app.config.settings.networkInterfaces, servers, primaryPort ) + servers.forEach((server) => { + console.log( + `signalk-server running at ${JSON.stringify(server.address())}` + ) + }) + const secondaryPort = getSecondaryPort(app) debug(`secondary port:${primaryPort}`) if (app.config.settings.ssl && secondaryPort) { @@ -606,7 +611,6 @@ function createServer(app: any, cb: (err: any, servers?: any[]) => void) { } function serverListen( - msg: string, networkInterfaces: string[] | undefined, servers: any[], port: number | { fd: number } @@ -626,7 +630,6 @@ function serverListen( new Promise((resolve, reject) => { try { server.listen(port, interfaces[idx], () => { - console.log(`${msg} ${JSON.stringify(server.address())}`) resolve(null) }) } catch (err) { @@ -652,12 +655,15 @@ async function startRedirectToSsl( return http.createServer(redirectApp) }) - await serverListen( - 'signalk-server redirect server running at', - networkInterfaces, - servers, - port - ) + await serverListen(networkInterfaces, servers, port) + + servers.forEach((server) => { + console.log( + `signalk-server redirect server running at ${JSON.stringify( + server.address() + )}` + ) + }) return servers }