domotz-remote-pawn
Version:
Domotz Agent
325 lines (301 loc) • 11.5 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2024 Domotz Ltd
*
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
*
* Created by Andrea Azzara <a.azzara@domotz.com> on 18/01/2024.
*/
var jayson = require('jayson');
var fs = require('fs');
var lodash = require('lodash');
var async = require('async');
const DEFAULT_ROUTER_IP = '127.0.0.1';
const DOMOTZ_API_PATH = 'socketCommunication';
const TOKEN_PATH = '/tmp/.api_token';
const JSON_RPC_VERSION = '2.0';
function netgearManager(q, log) {
var logger = log.decorateLogs();
var routerIp = DEFAULT_ROUTER_IP;
var token;
var session = 1;
var utils = require('./netgearUtils').utils();
function messageBuilder(method, commandParameters) {
return {
method: method,
params: commandParameters,
};
}
function loadTokenFromFS() {
var content = null;
try {
var row = fs.readFileSync(TOKEN_PATH, 'utf8');
content = row.toString().trim();
} catch (error) {
logger.error('Error reading file %s stack_trace: %s', TOKEN_PATH, JSON.stringify(error));
}
return content;
}
function createSessionToken() {
logger.info('Creating session token');
var message = messageBuilder('createApiToken', { name: 'domotz' });
return netgearRouterRequest(message);
}
function getToken(renewal) {
if (token) {
return q.resolve(token);
}
var deferred = q.defer();
if (!renewal) {
token = loadTokenFromFS();
logger.info('Token: %s', token);
} else {
logger.debug('Renewing token %s', token);
}
if (token) {
logger.info('Token already present');
deferred.resolve(token);
} else {
createSessionToken()
.then(function () {
logger.info('Token created');
token = loadTokenFromFS();
deferred.resolve(token);
})
.catch(function () {
deferred.reject('Unable to create a session token');
});
}
return deferred.promise;
}
function netgearRouterRequest(message, requestToken, promise) {
logger.debug('Calling netgear with message: %s and token %s', JSON.stringify(message), requestToken);
var deferred = promise || q.defer();
var options = {
host: routerIp,
rejectUnauthorized: false,
path: '/' + DOMOTZ_API_PATH,
port: 443,
version: JSON_RPC_VERSION,
generator: function () {
session += 1;
return session;
},
headers: requestToken ? { Authorization: 'Bearer ' + requestToken } : {},
};
var client = jayson.client.https(options);
logger.debug('Headers %s', JSON.stringify(options.headers));
function removeToken() {
try {
fs.unlinkSync(TOKEN_PATH);
} catch (e) {
logger.info('Unable to remove token');
}
token = null;
logger.info('Token deleted');
}
client.request(message.method, message.params, function (error, response) {
if (error) {
deferred.reject('error connecting to netgear: ' + error);
if (error.code === 401 && !promise) {
logger.info('Renewing token');
removeToken();
getToken(true)
.then(function (newToken) {
netgearRouterRequest(message, newToken, deferred);
})
.catch(function () {
logger.error('Unable to renew token');
deferred.reject('Unable to renew token');
});
logger.warn('Token expired');
}
return;
}
logger.debug('RPC Response: %s', JSON.stringify(response));
deferred.resolve(response.result);
});
return deferred.promise;
}
function getBlockedDevices() {
var deferred = q.defer();
getToken()
.then(function (token) {
var message = messageBuilder('getTrafficRules', {});
netgearRouterRequest(message, token)
.then(function (response) {
var filteredResponse = lodash.filter(response, function (element) {
return element.name.startsWith('domotz_') && element.enabled === 1;
});
deferred.resolve(filteredResponse);
})
.catch(function (err) {
logger.error('Error getting blocked devices: ' + JSON.stringify(err));
deferred.reject(err);
});
})
.catch(function (err) {
manageTokenCreationError(err, deferred);
});
return deferred.promise;
}
function manageTokenCreationError(error, deferred) {
logger.error('Error getting token: ' + JSON.stringify(error));
deferred.reject('Error getting token');
}
function blockDevice(macAddress, ipAddress) {
macAddress = macAddress.toUpperCase();
ipAddress = ipAddress.toUpperCase();
var deferred = q.defer();
getToken()
.then(function (token) {
var message = messageBuilder('addTrafficRules', [
{
name: utils.generateFirewallKey(macAddress, ipAddress),
enabled: 1,
action: 'Deny',
service: 'All Traffic',
schedule: 'Always',
sourceInterface: 'Any',
destinationInterface: 'Any',
sourceAddressType: 'Single',
destinationAddressType: 'Any',
sourceIpV4Address: ipAddress,
destinationIpV4Address: 'Any',
},
]);
netgearRouterRequest(message, token)
.then(function () {
deferred.resolve('ok');
})
.catch(function (err) {
deferred.reject(err);
});
})
.catch(function (err) {
manageTokenCreationError(err, deferred);
});
return deferred.promise;
}
function unblockDeviceByKey(key) {
var deferred = q.defer();
getToken()
.then(function (token) {
var message = messageBuilder('deleteTrafficRuleByName', { name: key });
netgearRouterRequest(message, token)
.then(function () {
deferred.resolve();
})
.catch(function () {
deferred.reject();
});
})
.catch(function (err) {
manageTokenCreationError(err, deferred);
});
return deferred.promise;
}
function unblockAllDevices() {
var deferred = q.defer();
var promises = [];
getBlockedDevices()
.then(function (rules) {
lodash.forOwn(rules, function (rule) {
var name = rule.name;
logger.info('Deleting rule named: %s', name);
promises.push(unblockDeviceByKey(name));
});
q.all(promises)
.then(function () {
deferred.resolve('All rules deleted');
})
.catch(function (err) {
deferred.reject(err);
});
})
.catch(function () {
logger.error('Error retrieving blocked devices list');
});
return deferred.promise;
}
function blockDeviceKeys(deviceKeys) {
var prepareParametersForCreateRules = function (keys) {
var ret = [];
lodash.forEach(keys, function (key) {
var split = key.split('_');
var ipAddress = split[2];
ret.push({
name: key,
enabled: 1,
action: 'Deny',
service: 'All Traffic',
schedule: 'Always',
sourceInterface: 'Any',
destinationInterface: 'Any',
sourceAddressType: 'Single',
destinationAddressType: 'Any',
sourceIpV4Address: ipAddress,
destinationIpV4Address: 'Any',
});
});
return ret;
};
var params = prepareParametersForCreateRules(deviceKeys);
var deferred = q.defer();
getToken()
.then(function (token) {
var message = messageBuilder('addTrafficRules', params);
logger.debug('Calling netgear with message: %s and token %s', JSON.stringify(message), token);
netgearRouterRequest(message, token)
.then(function () {
deferred.resolve();
})
.catch(function (err) {
manageTokenCreationError(err, deferred);
});
})
.catch(function () {
logger.error('Error getting token');
});
return deferred.promise;
}
function unblockDeviceKeys(deviceKeys) {
var deferred = q.defer();
async.eachOfSeries(
deviceKeys,
function (key, index, callback) {
logger.info('Unlocking %s', key);
unblockDeviceByKey(key).then(function () {
callback(null, true);
});
},
function (error) {
if (error) {
return deferred.reject();
}
deferred.resolve();
}
);
return deferred.promise;
}
return {
getToken: getToken,
getBlockedDevices: getBlockedDevices,
blockDevice: blockDevice,
unblockDeviceByKey: unblockDeviceByKey,
blockDeviceKeys: blockDeviceKeys,
unblockDeviceKeys: unblockDeviceKeys,
unblockAllDevices: unblockAllDevices,
};
}
module.exports = netgearManager;