496 lines
14 KiB
JavaScript
Raw Normal View History

2015-10-01 00:30:16 +02:00
'use strict';
/**
* Module dependencies
*/
// Node.js core.
const crypto = require('crypto');
const path = require('path');
// 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('unzip2');
2015-10-01 00:30:16 +02:00
/**
* Studio hook
2015-10-01 00:30:16 +02:00
*/
module.exports = function (strapi) {
2015-10-01 00:30:16 +02:00
const hook = {
/**
* Default options
*/
defaults: {
studio: {
2015-10-01 00:30:16 +02:00
enabled: true,
secretKey: ''
}
},
/**
* Initialize the hook
*/
initialize: function (cb) {
2015-10-01 00:30:16 +02:00
const _self = this;
if (_.isPlainObject(strapi.config.studio) && !_.isEmpty(strapi.config.studio) && strapi.config.studio.enabled === true && strapi.config.environment === 'development') {
const manager = io.Manager('http://studio.strapi.io', {
reconnectionDelay: 2000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 5
2015-10-01 00:30:16 +02:00
});
const socket = io.connect('http://studio.strapi.io', {
2015-10-01 00:30:16 +02:00
'reconnection': true,
'force new connection': true,
'transports': [
'polling',
'websocket',
'htmlfile',
'xhr-polling',
'jsonp-polling'
]
});
// Launch studio connection after received
// bootstrap and socket connect events
const done = _.after(2, function () {
_self.connectWithStudio(socket);
});
// After bootstrap
strapi.once('bootstrap:done', function () {
done();
});
2015-11-13 14:08:18 +01:00
manager.on('connect_failed', function () {
strapi.log.warn('Connection to the Studio server failed!');
2015-11-13 14:08:18 +01:00
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
manager.on('reconnect_failed', function () {
strapi.log.warn('Reconnection to the Studio server failed!');
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
manager.on('reconnect', function () {
strapi.log.info('Connection to the Studio server found, please wait a few seconds...');
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
manager.on('reconnecting', function (number) {
strapi.log.warn('Connection error with the Studio server, new attempt in progress... (' + number + ')');
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
socket.on('connect', function () {
done();
2015-11-13 14:08:18 +01:00
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
socket.on('error', function (err) {
strapi.log.warn(err);
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
socket.on('disconnect', function () {
strapi.log.info('Disconnected from the Studio server.');
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
socket.on('authorized', function (data) {
const decryptedData = strapi.rsa.decryptPublic(data, 'json');
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
if (decryptedData.status === 'ok') {
if (strapi.config.environment === 'development') {
socket.emit('testEncryption', {
appId: strapi.config.studio.appId,
token: strapi.token,
encrypted: strapi.rsa.encrypt({
secretKey: strapi.config.studio.secretKey,
data: 'ok'
})
}, function (err) {
if (err) {
strapi.log.warn(err);
}
strapi.log.info('Connected with the Studio server.');
});
} else {
strapi.log.warn('The use of the Studio is restricted to development environment.');
}
2015-11-13 14:08:18 +01:00
}
});
2015-11-13 14:08:18 +01:00
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')) {
const syncPromise = function (file, index) {
const deferred = Promise.defer();
_self.unzipper(file)
.then(function () {
if (!_.isEmpty(data.files[index + 1])) {
return syncPromise(data.files[index + 1], index + 1);
} else {
deferred.resolve();
2015-11-13 14:08:18 +01:00
}
})
.then(function () {
deferred.resolve();
})
.catch(function (err) {
deferred.reject(err);
});
2015-11-13 14:08:18 +01:00
return deferred.promise;
};
2015-10-01 00:30:16 +02:00
if (_.isEmpty(data.files)) {
fn({
appId: strapi.config.studio.appId,
token: strapi.token,
encrypted: strapi.rsa.encrypt({
err: null,
data: stringify({}, null, 2)
})
});
} else {
syncPromise(_.first(data.files), 0)
.then(function () {
if (data.hasOwnProperty('action') && _.isFunction(_self[data.action])) {
_self[data.action](data, function (err, obj) {
if (err) {
fn({
appId: strapi.config.studio.appId,
token: strapi.token,
encrypted: strapi.rsa.encrypt({
err: stringify(err, null, 2),
data: null
})
});
return false;
}
2015-10-01 00:30:16 +02:00
fn({
appId: strapi.config.studio.appId,
2015-10-01 00:30:16 +02:00
token: strapi.token,
encrypted: strapi.rsa.encrypt({
err: null,
data: stringify(obj, null, 2)
2015-10-01 00:30:16 +02:00
})
});
});
} else if (!data.hasOwnProperty('action')) {
2015-10-01 00:30:16 +02:00
fn({
appId: strapi.config.studio.appId,
2015-10-01 00:30:16 +02:00
token: strapi.token,
encrypted: strapi.rsa.encrypt({
err: null,
data: stringify(true, null, 2)
2015-10-01 00:30:16 +02:00
})
});
} else {
fn({
appId: strapi.config.studio.appId,
token: strapi.token,
encrypted: strapi.rsa.encrypt({
2015-12-18 16:23:36 -08:00
err: stringify('Unknown action', null, 2),
data: null
})
});
}
})
.catch(function (err) {
2015-10-01 00:30:16 +02:00
fn({
appId: strapi.config.studio.appId,
2015-10-01 00:30:16 +02:00
token: strapi.token,
encrypted: strapi.rsa.encrypt({
err: err,
2015-10-01 00:30:16 +02:00
data: null
})
});
});
}
2015-11-13 14:08:18 +01:00
} 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.studio.appId,
token: strapi.token,
encrypted: strapi.rsa.encrypt({
err: err,
data: null
})
});
return false;
}
fn({
appId: strapi.config.studio.appId,
token: strapi.token,
encrypted: strapi.rsa.encrypt({
2015-11-13 14:08:18 +01:00
err: null,
data: stringify(obj, null, 2)
})
});
2015-11-13 14:08:18 +01:00
});
} else {
2015-11-13 14:08:18 +01:00
fn({
appId: strapi.config.studio.appId,
token: strapi.token,
encrypted: strapi.rsa.encrypt({
2015-12-18 16:23:36 -08:00
err: stringify('Unknown action', null, 2),
2015-11-13 14:08:18 +01:00
data: null
})
});
2015-10-01 00:30:16 +02:00
}
2015-11-13 14:08:18 +01:00
} else {
fn(stringify('Bad user token', null, 2), null);
}
});
2015-10-01 00:30:16 +02:00
2015-11-13 14:08:18 +01:00
socket.on('err', function (data) {
strapi.log.warn(data.text);
2015-11-05 15:42:32 +01:00
});
2015-10-01 00:30:16 +02:00
cb();
} else {
2015-10-01 00:30:16 +02:00
cb();
}
},
connectWithStudio: function (socket) {
strapi.log.info('Connection with the Studio server found, please wait a few seconds...');
2015-12-01 14:20:55 +01:00
// Purge
delete strapi.rsa;
2015-10-01 00:30:16 +02:00
strapi.rsa = new RSA({
b: 2048
});
fs.readFile(path.resolve(process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'], '.strapirc'), {
encoding: 'utf8'
}, function (err, config) {
2015-10-01 00:30:16 +02:00
if (err) {
strapi.log.warn('Continuing without credentials.');
} else {
config = JSON.parse(config);
strapi.token = config.token;
2015-11-05 15:42:32 +01:00
socket.emit('getPublicKey', null, function (publicKey) {
2015-11-05 15:42:32 +01:00
if (publicKey && strapi.config.environment === 'development') {
2015-10-01 00:30:16 +02:00
const key = new RSA();
key.importKey(publicKey, 'public');
2015-11-05 15:42:32 +01:00
const object = {
appId: strapi.config.studio.appId,
appName: strapi.config.name,
publicKey: strapi.rsa.exportKey('private'),
secretKey: strapi.config.studio.secretKey,
token: strapi.token,
env: strapi.config.environment
};
socket.emit('check', key.encrypt(object));
2015-10-01 00:30:16 +02:00
}
});
}
});
},
/**
* Pull global strapi variable from local server
*
* @param {Object} data
*
* @return {Function} cb
*/
pullServer: function (data, cb) {
2015-11-13 14:08:18 +01:00
const obj = {
token: strapi.token,
config: strapi.config,
models: _.mapValues(_.cloneDeep(strapi.models), function (model) {
model.attributes = _.omit(model.attributes, _.isFunction);
2015-11-13 14:09:55 +01:00
2015-11-13 14:08:18 +01:00
return model;
}),
api: _.mapValues(_.cloneDeep(strapi.api), function (api) {
2015-11-13 15:53:23 +01:00
_.forEach(api.models, function (model) {
2015-11-13 14:08:18 +01:00
model.attributes = _.omit(model.attributes, _.isFunction);
});
2015-11-13 15:53:23 +01:00
return api;
2016-02-08 11:05:06 +01:00
})
2015-11-13 14:08:18 +01:00
};
2015-10-01 00:30:16 +02:00
cb(null, obj);
},
2015-11-20 11:40:33 +01:00
/**
* Pull file from local server
*
* @param {Object} data
*
* @return {Function} cb
*/
pullFile: function (data, cb) {
const rootPath = path.normalize(data.path);
2015-11-20 11:40:33 +01:00
fs.exists(rootPath, function (exists) {
if (exists) {
fs.readFile(rootPath, 'utf8', function (err, file) {
2015-11-20 11:40:33 +01:00
if (err) {
cb('Impossible to read `' + rootPath + '`', null);
} else {
cb(null, {
path: data.path,
value: JSON.parse(file)
2015-11-20 11:40:33 +01:00
});
}
});
} else {
2015-12-18 16:23:36 -08:00
cb('Unknown path `' + rootPath + '`', null);
2015-11-20 11:40:33 +01:00
}
});
},
2015-10-01 00:30:16 +02:00
/**
* Remove file or folder
*
* @param {Object} data
*
* @return {Function} cb
*/
removeFileOrFolder: function (data, cb) {
const arrayOfPromises = [];
2015-10-01 00:30:16 +02:00
if (data.hasOwnProperty('toRemove') && _.isArray(data.toRemove)) {
const toRemove = function (path) {
const deferred = Promise.defer();
2015-10-01 00:30:16 +02:00
fs.exists(path, function (exists) {
2015-10-01 00:30:16 +02:00
if (exists) {
fs.remove(path, function (err) {
2015-10-01 00:30:16 +02:00
if (err) {
deferred.reject(err);
} else {
deferred.resolve();
}
});
} else {
2015-12-18 16:23:36 -08:00
deferred.reject('Unknown path `' + path + '`');
2015-10-01 00:30:16 +02:00
}
});
return deferred.promise;
};
_.forEach(data.toRemove, function (fileOrFolder) {
2015-10-01 00:30:16 +02:00
arrayOfPromises.push(toRemove(fileOrFolder.path));
});
Promise.all(arrayOfPromises)
.then(function () {
2015-10-01 00:30:16 +02:00
cb(null, true);
})
.catch(function (err) {
2015-10-01 00:30:16 +02:00
cb(err, null);
});
} else {
cb('Attribute `toRemove` 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) {
2015-10-01 00:30:16 +02:00
const deferred = Promise.defer();
crypto.pbkdf2(data.token, strapi.token, 4096, 32, 'sha256', function (err, derivedKey) {
if (err) {
deferred.reject(err);
}
2015-10-01 00:30:16 +02:00
2016-01-12 23:18:37 +01:00
// Force posix to be fully compatible with the Studio server
const fileToUnzip = path.posix.normalize(path.join(strapi.config.appPath, '.tmp', derivedKey.toString('hex') + '.zip'));
2015-10-01 00:30:16 +02:00
request({
method: 'POST',
preambleCRLF: true,
postambleCRLF: true,
json: true,
uri: 'http://studio.strapi.io/socket/download',
2015-10-01 00:30:16 +02:00
encoding: null,
body: {
token: strapi.token,
fileId: data.token,
src: data.src
}
})
.pipe(fs.createWriteStream(fileToUnzip))
.on('close', function () {
2015-10-01 00:30:16 +02:00
let folderDest;
2016-01-12 23:18:37 +01:00
const folderOrFiletoRemove = path.normalize(data.dest);
2015-10-01 00:30:16 +02:00
if (data.src === 'api') {
2015-10-01 00:30:16 +02:00
folderDest = folderOrFiletoRemove;
} else {
2016-01-12 23:18:37 +01:00
folderDest = path.join(data.dest, '..');
2015-10-01 00:30:16 +02:00
}
fs.remove(folderOrFiletoRemove, function (err) {
2015-10-01 00:30:16 +02:00
if (err) {
deferred.reject(err);
}
_.defer(function () {
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);
2015-10-01 00:30:16 +02:00
});
});
2015-10-01 00:30:16 +02:00
});
})
.on('error', function () {
deferred.reject('Download ZIP or unzip not worked fine', null);
2015-10-01 00:30:16 +02:00
});
});
return deferred.promise;
}
};
return hook;
};