Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions libraries/mobile/mobileShutdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const EXPECTED_SECRET_LENGTH = 44; // string length of base-64 encoding a 32-byte secret
const BASE64_32BYTE_REGEX = /^[A-Za-z0-9+/]{43}=$/;

/**
* To be able to shut down the server with POST /shutdown/ (e.g. in the mobile app),
* the server can be run with a secret generated by `openssl rand -base64 32`.
* This function validates that the given string is a base64-encoded 32-byte secret.
*/
function isValidShutdownSecret(secret) {
if (typeof secret !== 'string' || secret.length !== EXPECTED_SECRET_LENGTH) {
return false;
}

if (!BASE64_32BYTE_REGEX.test(secret)) {
return false;
}

try {
const decoded = Buffer.from(secret, 'base64');
return decoded.length === 32;
} catch {
return false;
}
}

/**
* Attempts to load the shutdown secret from process.argv[2].
* Returns null if invalid or missing. It's ok to omit it, but the /shutdown/ route will be disabled.
*/
function getShutdownSecretFromArgs() {
const rawSecret = process.argv[2];

if (!isValidShutdownSecret(rawSecret)) {
return null;
}

return rawSecret;
}

/**
* Compares the incoming request token with the expected shutdown secret.
*/
function verifyShutdownRequest(req, expectedSecret) {
const token = req.headers['x-shutdown-token'];
return token === expectedSecret;
}

module.exports = {
getShutdownSecretFromArgs,
verifyShutdownRequest,
};
22 changes: 22 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ try {
const _logger = require('./logger');
const {objectsPath, beatPort, serverPort, allowSecureMode, persistToCloud} = require('./config');
const {providedServices} = require('./services');
const {
getShutdownSecretFromArgs,
verifyShutdownRequest,
} = require('./libraries/mobile/mobileShutdown');

const os = require('os');
const {isLightweightMobile, isStandaloneMobile} = require('./isMobile.js');
Expand Down Expand Up @@ -2104,6 +2108,24 @@ function objectWebServer() {
}
});

// If the server args includes a 32-byte base-64 encoded secret, then enable a special /shutdown/ route
const SHUTDOWN_SECRET = getShutdownSecretFromArgs();

if (SHUTDOWN_SECRET) {
console.info('POST /shutdown/ route is enabled, with secret', SHUTDOWN_SECRET);
webServer.post('/shutdown/', function (req, res) {
if (!verifyShutdownRequest(req, SHUTDOWN_SECRET)) {
return res.status(403).send('Forbidden: Invalid shutdown token');
}

console.info('Authorized shutdown request received');
res.send('Shutting down server...');
exit();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may turn out that you want to use process.exit(0) here to circumvent the usual safe shutdown process

});
} else {
console.info('POST /shutdown/ route is disabled');
}

webServer.get('/server/networkInterface/:activeInterface/', function (req, res) {
services.ips.activeInterface = req.params.activeInterface;
res.json(services.ips);
Expand Down