mirror of
https://github.com/strapi/strapi.git
synced 2025-07-15 13:02:42 +00:00
529 lines
15 KiB
JavaScript
529 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Module dependencies
|
|
*/
|
|
|
|
// Node.js core.
|
|
const crypto = require('crypto');
|
|
const path = require('path');
|
|
const exec = require('child_process').exec;
|
|
|
|
// Public node modules.
|
|
const _ = require('lodash');
|
|
const fs = require('fs-extra');
|
|
const io = require('socket.io-client');
|
|
const request = require('request');
|
|
const RSA = require('node-rsa');
|
|
const stringify = require('json-stringify-safe');
|
|
const unzip = require('unzip');
|
|
|
|
/**
|
|
* SaaS hook
|
|
*/
|
|
|
|
module.exports = function(strapi) {
|
|
const hook = {
|
|
|
|
/**
|
|
* Default options
|
|
*/
|
|
|
|
defaults: {
|
|
saas: {
|
|
enabled: true,
|
|
secretKey: ''
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initialize the hook
|
|
*/
|
|
|
|
initialize: function(cb) {
|
|
const _self = this;
|
|
let firstConnectionAttempt = true;
|
|
|
|
if (_.isPlainObject(strapi.config.saas) && !_.isEmpty(strapi.config.saas)) {
|
|
const manager = io.Manager(strapi.config.saas.url, {
|
|
'reconnectionDelay': 2000,
|
|
'reconnectionDelayMax': 5000,
|
|
'reconnectionAttempts': 5
|
|
});
|
|
|
|
const socket = io.connect(strapi.config.saas.url, {
|
|
'reconnection': true,
|
|
'force new connection': true,
|
|
'transports': [
|
|
'polling',
|
|
'websocket',
|
|
'htmlfile',
|
|
'xhr-polling',
|
|
'jsonp-polling'
|
|
]
|
|
});
|
|
|
|
manager.on('connect_failed', function(err) {
|
|
if (firstConnectionAttempt) {
|
|
strapi.log.warn('Connection to the SaaS server failed!');
|
|
}
|
|
});
|
|
|
|
manager.on('reconnect_failed', function() {
|
|
strapi.log.warn('Reconnection to the SaaS server failed!');
|
|
});
|
|
|
|
manager.on('reconnect', function() {
|
|
strapi.log.info('Connection to the SaaS server found, please wait a few seconds...');
|
|
});
|
|
|
|
manager.on('reconnecting', function(number) {
|
|
strapi.log.warn('Connection error with the SaaS server, new attempt in progress... (' + number + ')');
|
|
});
|
|
|
|
socket.on('connect', function() {
|
|
strapi.log.info('Connection with the SaaS server found, please wait a few seconds...');
|
|
firstConnectionAttempt = false;
|
|
_self.connectWithSaaS(socket);
|
|
});
|
|
|
|
socket.on('error', function(err) {
|
|
strapi.log.warn(err);
|
|
});
|
|
|
|
socket.on('disconnect', function() {
|
|
strapi.log.info('Disconnected from the SaaS server.');
|
|
});
|
|
|
|
socket.on('authorized', function(data) {
|
|
const decryptedData = strapi.rsa.decryptPublic(data, 'json');
|
|
|
|
if (decryptedData.status === 'ok') {
|
|
if (strapi.config.environment === 'development') {
|
|
socket.emit('testEncryption', {
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
secretKey: strapi.config.saas.secretKey,
|
|
data: 'ok'
|
|
})
|
|
}, function(err, data) {
|
|
if (err) {
|
|
strapi.log.warn(err);
|
|
}
|
|
|
|
strapi.log.info('Connected with the SaaS server.');
|
|
});
|
|
} else {
|
|
socket.emit('testEncryption', {
|
|
appId: strapi.config.saas.appId,
|
|
env: strapi.config.environment,
|
|
encrypted: strapi.rsa.encrypt({
|
|
secretKey: strapi.config.saas.secretKey,
|
|
data: 'ok'
|
|
})
|
|
}, function(err, data) {
|
|
if (err) {
|
|
strapi.log.warn(err);
|
|
}
|
|
|
|
strapi.log.info('Connected with the SaaS server.');
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
socket.on('todo', function(data, fn) {
|
|
if (!data.hasOwnProperty('from') || !data.hasOwnProperty('to')) {
|
|
fn(stringify('Some required attributes are missing', null, 2), null);
|
|
} else {
|
|
if (data.from === strapi.token) {
|
|
if (data.hasOwnProperty('files')) {
|
|
let arrayOfPromises = [];
|
|
|
|
_.forEach(data.files, function(file) {
|
|
arrayOfPromises.push(_self.unzipper(file));
|
|
});
|
|
|
|
Promise.all(arrayOfPromises)
|
|
.then(function() {
|
|
if (data.hasOwnProperty('action') && _.isFunction(_self[data.action])) {
|
|
_self[data.action](data, function(err, obj) {
|
|
if (err) {
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: stringify(err, null, 2),
|
|
data: null
|
|
})
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: null,
|
|
data: stringify(obj, null, 2)
|
|
})
|
|
});
|
|
});
|
|
} else {
|
|
if (!data.hasOwnProperty('action')) {
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: null,
|
|
data: stringify(true, null, 2)
|
|
})
|
|
});
|
|
} else {
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: stringify('Unknow action', null, 2),
|
|
data: null
|
|
})
|
|
});
|
|
}
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: err,
|
|
data: null
|
|
})
|
|
});
|
|
});
|
|
} else {
|
|
if (!data.hasOwnProperty('action')) {
|
|
fn(strapi.rsa.encrypt(stringify('`action` attribute is missing', null, 2)), strapi.rsa.encryptPrivate(null));
|
|
} else {
|
|
if (_.isFunction(_self[data.action])) {
|
|
_self[data.action](data, function(err, obj) {
|
|
if (err) {
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: err,
|
|
data: null
|
|
})
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: null,
|
|
data: stringify(obj, null, 2)
|
|
})
|
|
});
|
|
});
|
|
} else {
|
|
fn({
|
|
appId: strapi.config.saas.appId,
|
|
token: strapi.token,
|
|
encrypted: strapi.rsa.encrypt({
|
|
err: stringify('Unknow action', null, 2),
|
|
data: null
|
|
})
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
socket.on('err', function(data) {
|
|
strapi.log.warn(data.text);
|
|
});
|
|
|
|
cb();
|
|
}
|
|
},
|
|
|
|
connectWithSaaS: function(socket) {
|
|
strapi.rsa = new RSA({
|
|
b: 2048
|
|
});
|
|
|
|
fs.readFile(path.resolve(process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'], '.strapirc'), {
|
|
encoding: 'utf8'
|
|
}, function(err, config) {
|
|
if (err) {
|
|
strapi.log.warn('Continuing without credentials.');
|
|
} else {
|
|
config = JSON.parse(config);
|
|
strapi.token = config.token;
|
|
|
|
socket.emit('getPublicKey', null, function(publicKey) {
|
|
if (publicKey) {
|
|
const key = new RSA();
|
|
let object;
|
|
key.importKey(publicKey, 'public');
|
|
|
|
if (strapi.config.environment === 'development') {
|
|
object = {
|
|
appId: strapi.config.saas.appId,
|
|
appName: strapi.config.name,
|
|
publicKey: strapi.rsa.exportKey('private'),
|
|
secretKey: strapi.config.saas.secretKey,
|
|
token: strapi.token,
|
|
env: strapi.config.environment
|
|
};
|
|
} else {
|
|
object = {
|
|
appId: strapi.config.saas.appId,
|
|
appName: strapi.config.name,
|
|
publicKey: strapi.rsa.exportKey('private'),
|
|
secretKey: strapi.config.saas.secretKey,
|
|
env: strapi.config.environment
|
|
};
|
|
}
|
|
|
|
socket.emit('check', key.encrypt(object));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Subaction for config
|
|
*
|
|
* @param {Object} data
|
|
*
|
|
* @return {Function} cb
|
|
*/
|
|
|
|
handleConfig: function(data, cb) {
|
|
const _self = this;
|
|
|
|
strapi.log.warn('We need to flush server.');
|
|
strapi.log.warn('Install dependencies if we have to.');
|
|
|
|
cb(null, true);
|
|
},
|
|
|
|
/**
|
|
* Pull global strapi variable from local server
|
|
*
|
|
* @param {Object} data
|
|
*
|
|
* @return {Function} cb
|
|
*/
|
|
|
|
pullServer: function(data, cb) {
|
|
let obj = {};
|
|
obj.token = strapi.token;
|
|
obj.config = strapi.config;
|
|
obj.models = strapi.models;
|
|
obj.modules = strapi.modules;
|
|
obj.templates = {};
|
|
|
|
cb(null, obj);
|
|
},
|
|
|
|
/**
|
|
* Rebuild dictionary
|
|
*
|
|
* @param {Object} data
|
|
*
|
|
* @return {Function} cb
|
|
*/
|
|
|
|
rebuild: function(data, cb) {
|
|
strapi.rebuild();
|
|
|
|
cb(null, true);
|
|
},
|
|
|
|
/**
|
|
* Remove file or folder
|
|
*
|
|
* @param {Object} data
|
|
*
|
|
* @return {Function} cb
|
|
*/
|
|
|
|
removeFileOrFolder: function(data, cb) {
|
|
const _self = this;
|
|
let arrayOfPromises = [];
|
|
|
|
if (data.hasOwnProperty('toRemove') && _.isArray(data.toRemove)) {
|
|
var toRemove = function(path) {
|
|
var deferred = Promise.defer();
|
|
|
|
fs.exists(path, function(exists) {
|
|
if (exists) {
|
|
fs.remove(path, function(err) {
|
|
if (err) {
|
|
deferred.reject(err);
|
|
} else {
|
|
deferred.resolve();
|
|
}
|
|
});
|
|
} else {
|
|
deferred.reject('Unknow path `' + path + '`');
|
|
}
|
|
});
|
|
|
|
return deferred.promise;
|
|
};
|
|
|
|
_.forEach(data.toRemove, function(fileOrFolder) {
|
|
arrayOfPromises.push(toRemove(fileOrFolder.path));
|
|
});
|
|
|
|
Promise.all(arrayOfPromises)
|
|
.then(function() {
|
|
cb(null, true);
|
|
})
|
|
.catch(function(err) {
|
|
cb(err, null);
|
|
});
|
|
} else {
|
|
cb('Attribute `toRemove` is missing or is not an array', null);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Rename file or folder
|
|
*
|
|
* @param {Object} data
|
|
*
|
|
* @return {Function} cb
|
|
*/
|
|
|
|
renameFileOrFolder: function(data, cb) {
|
|
const _self = this;
|
|
let arrayOfPromises = [];
|
|
|
|
if (data.hasOwnProperty('toRename') && _.isArray(data.toRename)) {
|
|
var toRename = function(oldPath, newPath) {
|
|
var deferred = Promise.defer();
|
|
|
|
fs.exists(oldPath, function(exists) {
|
|
if (exists) {
|
|
fs.copy(oldPath, newPath, function(err) {
|
|
if (err) {
|
|
deferred.reject(err);
|
|
} else {
|
|
fs.remove(oldPath, function(err) {
|
|
if (err) {
|
|
deferred.reject(err);
|
|
}
|
|
|
|
deferred.resolve();
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
deferred.reject('Unknow path `' + path + '`');
|
|
}
|
|
});
|
|
|
|
return deferred.promise;
|
|
};
|
|
|
|
_.forEach(data.toRename, function(fileOrFolder) {
|
|
arrayOfPromises.push(toRename(fileOrFolder.oldPath, fileOrFolder.newPath));
|
|
});
|
|
|
|
Promise.all(arrayOfPromises)
|
|
.then(function() {
|
|
cb(null, true);
|
|
})
|
|
.catch(function(err) {
|
|
cb(err, null);
|
|
});
|
|
} else {
|
|
cb('Attribute `toRename` is missing or is not an array', null);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Function to unzip file or folder to specific folder
|
|
*
|
|
* @param {Object} data
|
|
*
|
|
* @return {Function} cb
|
|
*/
|
|
|
|
unzipper: function(data) {
|
|
const deferred = Promise.defer();
|
|
|
|
|
|
crypto.pbkdf2(data.token, strapi.token, 4096, 32, 'sha256', function(err, derivedKey) {
|
|
const fileToUnzip = path.resolve(strapi.config.appPath, '.tmp', derivedKey.toString('hex') + '.zip');
|
|
|
|
request({
|
|
method: 'POST',
|
|
preambleCRLF: true,
|
|
postambleCRLF: true,
|
|
json: true,
|
|
uri: strapi.config.saas.url + '/socket/download',
|
|
encoding: null,
|
|
body: {
|
|
token: strapi.token,
|
|
fileId: data.token,
|
|
src: data.src
|
|
}
|
|
})
|
|
.pipe(fs.createWriteStream(fileToUnzip))
|
|
.on('close', function() {
|
|
|
|
let folderDest;
|
|
const folderOrFiletoRemove = path.resolve(data.dest);
|
|
|
|
if (data.src === 'modules') {
|
|
folderDest = folderOrFiletoRemove;
|
|
} else {
|
|
folderDest = path.resolve(data.dest, '..');
|
|
}
|
|
|
|
fs.remove(folderOrFiletoRemove, function(err) {
|
|
if (err) {
|
|
deferred.reject(err);
|
|
}
|
|
fs.createReadStream(fileToUnzip).pipe(unzip.Extract({
|
|
path: folderDest
|
|
}))
|
|
.on('close', function() {
|
|
fs.remove(fileToUnzip, function(err) {
|
|
if (err) {
|
|
deferred.reject(err);
|
|
}
|
|
|
|
deferred.resolve();
|
|
});
|
|
}).on('error', function(err) {
|
|
deferred.reject(err);
|
|
});
|
|
});
|
|
})
|
|
.on('error', function(err) {
|
|
cb('Download ZIP or unzip not worked fine', null);
|
|
});
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
};
|
|
|
|
return hook;
|
|
};
|