mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 23:14:53 +09:00
Token auth for all destructive endpoints
This commit is contained in:
1 parent
4b7ad9b8b8
commit
0b0b2bc1a9
3 files changed
+53
-33
No files matched your search
@@ -42,6 +42,19 @@ const command = process.argv[2];
|
||||
// and as the process name in ps/top on linux.
|
||||
process.title = "clusterioClient "+instance;
|
||||
|
||||
// make sure we have the master access token (can't write to master without it since clusterio 2.0)
|
||||
if(!config.masterAuthToken || typeof config.masterAuthToken !== "string"){
|
||||
console.log("ERROR invalid config!");
|
||||
console.log("Master server now needs an access token for write operations. As clusterio slaves depends \
|
||||
upon this, please add your token to config.js in the field named masterAuthToken. \
|
||||
You can retrieve your auth token from the master in secret-api-token.txt after running it once.");
|
||||
}
|
||||
const needleOptionsWithTokenAuthHeader = {
|
||||
headers: {
|
||||
'x-access-token': config.masterAuthToken
|
||||
},
|
||||
};
|
||||
|
||||
var instanceInfo = {};
|
||||
|
||||
// function to handle sending commands into the game
|
||||
@@ -604,7 +617,7 @@ function instanceManagement() {
|
||||
} else {
|
||||
payload.mac = mac
|
||||
console.log("Registered our precense with master "+config.masterIP+" at " + payload.time);
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/getID', payload, function (err, response, body) {
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/getID', payload, needleOptionsWithTokenAuthHeader, function (err, response, body) {
|
||||
if (err && err.code != "ECONNRESET"){
|
||||
console.error("We got problems, something went wrong when contacting master");
|
||||
console.error(err);
|
||||
@@ -637,7 +650,7 @@ function instanceManagement() {
|
||||
modName: modHashes[i].modName,
|
||||
hash: modHashes[i].hash,
|
||||
}
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/checkMod', payload, function (err, response, body) {
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/checkMod', payload, needleOptionsWithTokenAuthHeader, function (err, response, body) {
|
||||
if(err) console.log(new Error("Unable to contact master server! Please check your config.json."));
|
||||
if(response && body && body == "found") {
|
||||
console.log("master has mod");
|
||||
@@ -647,7 +660,11 @@ function instanceManagement() {
|
||||
console.log("Sending mod: " + mod);
|
||||
// Send mods master says it wants
|
||||
// response.body is a string which is a modName.zip
|
||||
var req = request.post("http://"+config.masterIP + ":" + config.masterPort + '/api/uploadMod', function (err, resp, body) {
|
||||
var req = request.post({url: "http://"+config.masterIP + ":" + config.masterPort + '/api/uploadMod',
|
||||
headers: {
|
||||
"x-access-token": config.masterAuthToken,
|
||||
},
|
||||
}, function (err, resp, body) {
|
||||
if (err) {
|
||||
console.log(new Error("Unable to contact master server! Please check your config.json."));
|
||||
} else {
|
||||
@@ -705,7 +722,7 @@ function instanceManagement() {
|
||||
}
|
||||
}
|
||||
console.log("Recorded flows, copper plate since last time: " + payload["copper-plate"]);
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/logStats', {timestamp: timestamp, instanceID: instanceconfig.unique,data: payload}, function (err, response, body) {
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/logStats', {timestamp: timestamp, instanceID: instanceconfig.unique,data: payload}, needleOptionsWithTokenAuthHeader, function (err, response, body) {
|
||||
// we did it, keep going
|
||||
});
|
||||
}
|
||||
@@ -737,7 +754,7 @@ function instanceManagement() {
|
||||
count: g[1],
|
||||
instanceName: instance, // name of instance
|
||||
instanceID: instanceconfig.unique, // a hash computed from the randomly generated rcon password
|
||||
}, function (err, resp, body) {
|
||||
}, needleOptionsWithTokenAuthHeader, function (err, resp, body) {
|
||||
if(body == "failure") console.error("#### Export failed! Lost: "+g[1]+" "+g[0]);
|
||||
if(config.logItemTransfers){
|
||||
if(body == "success") console.log(`Exported ${g[1]} ${g[0]} to master`);
|
||||
@@ -794,7 +811,7 @@ function instanceManagement() {
|
||||
// request our items, one item at a time
|
||||
for (let i = 0; i < Object.keys(preparedPackage).length; i++) {
|
||||
// console.log(preparedPackage[Object.keys(preparedPackage)[i]]);
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', preparedPackage[Object.keys(preparedPackage)[i]], function (err, response, body) {
|
||||
needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', preparedPackage[Object.keys(preparedPackage)[i]], needleOptionsWithTokenAuthHeader, function (err, response, body) {
|
||||
if (response && response.body && typeof response.body == "object") {
|
||||
// buffer confirmed orders
|
||||
confirmedOrders[confirmedOrders.length] = {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const config = require("./../config");
|
||||
|
||||
var middleware = function (req, res, next) {
|
||||
var token = req.headers['x-access-token'];
|
||||
if(!token) return res.status(401).send({ auth: false, message: 'No token provided.', endpoint: req.route.path});
|
||||
|
||||
jwt.verify(token, config.masterAuthSecret, function(err, decoded) {
|
||||
if(err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.', endpoint: req.route.path});
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {middleware};
|
||||
@@ -28,6 +28,9 @@ var config = require('./config');
|
||||
const getFactorioLocale = require("./lib/getFactorioLocale");
|
||||
const stringUtils = require("./lib/stringUtils");
|
||||
|
||||
// homemade express middleware for token auth
|
||||
const authenticate = require("./lib/authenticate");
|
||||
|
||||
// Library for create folder recursively if it does not exist
|
||||
const mkdirp = require("mkdirp");
|
||||
mkdirp.sync("./database");
|
||||
@@ -37,8 +40,13 @@ const deepmerge = require("deepmerge");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const nedb = require("nedb");
|
||||
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcryptjs');
|
||||
// write an auth token to file
|
||||
fs.writeFileSync("secret-api-token.txt", jwt.sign({ id: "api" }, config.masterAuthSecret, {
|
||||
expiresIn: 86400*365 // expires in 1 year
|
||||
}));
|
||||
|
||||
const express = require("express");
|
||||
const ejs = require("ejs");
|
||||
@@ -224,7 +232,7 @@ POST World ID management. Slaves post here to tell the server they exist
|
||||
@instance
|
||||
@alias /api/getID
|
||||
*/
|
||||
app.post("/api/getID", function(req,res) {
|
||||
app.post("/api/getID", authenticate.middleware, function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
// request.body should be an object
|
||||
@@ -253,7 +261,7 @@ POST Allows you to add metadata related to slaves for other tools, like owner da
|
||||
@instance
|
||||
@alias /api/editSlaveMeta
|
||||
*/
|
||||
app.post("/api/editSlaveMeta", function(req,res) {
|
||||
app.post("/api/editSlaveMeta", authenticate.middleware, function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
// request.body should be an object
|
||||
@@ -292,22 +300,6 @@ app.post("/api/getSlaveMeta", function (req, res) {
|
||||
res.send('{"INVALID REQUEST":1}');
|
||||
}
|
||||
});
|
||||
// TODO: Remove deprecated function, also update researchSync to not use it
|
||||
/**
|
||||
POST DEPRECATED Get metadata from all slaves. This is already covered by /api/slaves, please use that one instead.
|
||||
@memberof clusterioMaster
|
||||
@instance
|
||||
@alias /api/getSlavesMeta
|
||||
*/
|
||||
app.post("/api/getSlavesMeta", function (req, res) {
|
||||
if(req.body.password){
|
||||
let metas = {};
|
||||
res.send(JSON.stringify(Object.keys(slaves).map(key => metas[key] = slaves[key].meta)));
|
||||
} else {
|
||||
res.status(400);
|
||||
res.send('{"INVALID REQUEST":1}');
|
||||
}
|
||||
});
|
||||
// mod management
|
||||
// should handle uploading and checking if mods are uploaded
|
||||
/**
|
||||
@@ -341,7 +333,7 @@ POST endpoint for uploading mods to the master server. Required for automatic mo
|
||||
@instance
|
||||
@alias /api/uploadMod
|
||||
*/
|
||||
app.post("/api/uploadMod", function(req,res) {
|
||||
app.post("/api/uploadMod", authenticate.middleware, function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
if (!req.files) {
|
||||
@@ -398,7 +390,7 @@ POST endpoint for storing items in master's inventory.
|
||||
@param {string} [itemStack.instanceName="unknown"] the name of an instance for identification in statistics, as provided when launching it. ex node client.js start [name]
|
||||
@returns {string} status either "success" or "failure"
|
||||
*/
|
||||
app.post("/api/place", function(req, res) {
|
||||
app.post("/api/place", authenticate.middleware, function(req, res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
@@ -465,7 +457,7 @@ POST endpoint to remove items from DB when client orders items.
|
||||
@returns {itemStack} the number of items actually removed, may be lower than what was asked for due to shortages.
|
||||
*/
|
||||
_doleDivisionFactor = {}; //If the server regularly can't fulfill requests, this number grows until it can. Then it slowly shrinks back down.
|
||||
app.post("/api/remove", function(req, res) {
|
||||
app.post("/api/remove", authenticate.middleware, function(req, res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
const doleDivisionRetardation = 10; //lower rates will equal more dramatic swings
|
||||
@@ -540,7 +532,7 @@ Gives no response
|
||||
@param {object} circuitFrame
|
||||
@param {number} circuitFrame.time
|
||||
*/
|
||||
app.post("/api/setSignal", function(req,res) {
|
||||
app.post("/api/setSignal", authenticate.middleware, function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
if(typeof req.body == "object" && req.body.time){
|
||||
@@ -625,7 +617,7 @@ course the timeSeries data.
|
||||
@param {object} JSON {timestamp: Date.now(), instanceID: "string", data: {"item": number}}
|
||||
@returns {string} failure
|
||||
*/
|
||||
app.post("/api/logStats", function(req,res) {
|
||||
app.post("/api/logStats", authenticate.middleware, function(req,res) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
let reqStartTime = Date.now();
|
||||
if(typeof req.body == "object" && req.body.instanceID && req.body.timestamp && req.body.data) {
|
||||
@@ -793,10 +785,6 @@ Requires x-access-token header to be set. Find you api token in secret-api-token
|
||||
@param {object} JSON {instanceID:19412312, command:"/c game.print('hello')"}
|
||||
@returns {object} Status {auth: bool, message: "Informative error", data:{}}
|
||||
*/
|
||||
// write an auth token to file
|
||||
fs.writeFileSync("secret-api-token.txt", jwt.sign({ id: "api" }, config.masterAuthSecret, {
|
||||
expiresIn: 86400*365 // expires in 1 year
|
||||
}));
|
||||
app.post("/api/runCommand", (req,res) => {
|
||||
var token = req.headers['x-access-token'];
|
||||
if(!token) return res.status(401).send({ auth: false, message: 'No token provided.' });
|
||||
|
||||
Reference in new issue
Block a user