appstore and support: async'ify
This commit is contained in:
+8
-4
@@ -595,9 +595,9 @@ function downloadManifest(appStoreId, manifest, callback) {
|
||||
superagent.get(url).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Network error downloading manifest:' + error.message));
|
||||
|
||||
if (result.statusCode !== 200) return callback(new BoxError(BoxError.NOT_FOUND, util.format('Failed to get app info from store.', result.statusCode, result.text)));
|
||||
if (result.status !== 200) return callback(new BoxError(BoxError.NOT_FOUND, util.format('Failed to get app info from store.', result.status, result.text)));
|
||||
|
||||
if (!result.body.manifest || typeof result.body.manifest !== 'object') return callback(new BoxError(BoxError.NOT_FOUND, util.format('Missing manifest. Failed to get app info from store.', result.statusCode, result.text)));
|
||||
if (!result.body.manifest || typeof result.body.manifest !== 'object') return callback(new BoxError(BoxError.NOT_FOUND, util.format('Missing manifest. Failed to get app info from store.', result.status, result.text)));
|
||||
|
||||
callback(null, parts[0], result.body.manifest);
|
||||
});
|
||||
@@ -1611,7 +1611,9 @@ function purchaseApp(data, callback) {
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
appstore.purchaseApp(data, function (error) {
|
||||
const purchaseApp = util.callbackify(appstore.purchaseApp);
|
||||
|
||||
purchaseApp(data, function (error) {
|
||||
if (!error) return callback();
|
||||
|
||||
// if purchase failed, rollback the appdb record
|
||||
@@ -1733,7 +1735,9 @@ function uninstall(app, auditSource, callback) {
|
||||
let error = checkAppState(app, exports.ISTATE_PENDING_UNINSTALL);
|
||||
if (error) return callback(error);
|
||||
|
||||
appstore.unpurchaseApp(appId, { appstoreId: app.appStoreId, manifestId: app.manifest.id || 'customapp' }, function (error) {
|
||||
const unpurchaseApp = util.callbackify(appstore.unpurchaseApp);
|
||||
|
||||
unpurchaseApp(appId, { appstoreId: app.appStoreId, manifestId: app.manifest.id || 'customapp' }, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const task = {
|
||||
|
||||
+250
-314
@@ -13,7 +13,7 @@ exports = module.exports = {
|
||||
purchaseApp,
|
||||
unpurchaseApp,
|
||||
|
||||
getUserToken,
|
||||
createUserToken,
|
||||
getSubscription,
|
||||
isFreePlan,
|
||||
|
||||
@@ -23,9 +23,8 @@ exports = module.exports = {
|
||||
createTicket
|
||||
};
|
||||
|
||||
var apps = require('./apps.js'),
|
||||
const apps = require('./apps.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:appstore'),
|
||||
@@ -74,99 +73,89 @@ function isAppAllowed(appstoreId, listingConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getCloudronToken(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
if (!token) return callback(new BoxError(BoxError.LICENSE_ERROR, 'Missing token'));
|
||||
|
||||
callback(null, token);
|
||||
});
|
||||
async function getCloudronToken() {
|
||||
const token = await settings.getCloudronToken();
|
||||
if (!token) throw new BoxError(BoxError.LICENSE_ERROR, 'Missing token');
|
||||
return token;
|
||||
}
|
||||
|
||||
function login(email, password, totpToken, callback) {
|
||||
async function login(email, password, totpToken) {
|
||||
assert.strictEqual(typeof email, 'string');
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
assert.strictEqual(typeof totpToken, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var data = {
|
||||
email: email,
|
||||
password: password,
|
||||
totpToken: totpToken
|
||||
};
|
||||
const data = { email, password, totpToken };
|
||||
|
||||
const url = settings.apiServerOrigin() + '/api/v1/login';
|
||||
superagent.post(url).send(data).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `login status code: ${result.statusCode}`));
|
||||
const [error, response] = await safe(superagent.post(url)
|
||||
.send(data)
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
callback(null, result.body); // { userId, accessToken }
|
||||
});
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `login status code: ${response.status}`);
|
||||
|
||||
return response.body; // { userId, accessToken }
|
||||
}
|
||||
|
||||
function registerUser(email, password, callback) {
|
||||
async function registerUser(email, password) {
|
||||
assert.strictEqual(typeof email, 'string');
|
||||
assert.strictEqual(typeof password, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var data = {
|
||||
email: email,
|
||||
password: password,
|
||||
};
|
||||
const data = { email, password };
|
||||
|
||||
const url = settings.apiServerOrigin() + '/api/v1/register_user';
|
||||
superagent.post(url).send(data).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 409) return callback(new BoxError(BoxError.ALREADY_EXISTS, error.message));
|
||||
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `register status code: ${result.statusCode}`));
|
||||
const [error, response] = await safe(superagent.post(url)
|
||||
.send(data)
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
|
||||
if (response.status === 409) throw new BoxError(BoxError.ALREADY_EXISTS, error.message);
|
||||
if (response.status !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, `register status code: ${response.status}`);
|
||||
}
|
||||
|
||||
function getUserToken(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
async function createUserToken() {
|
||||
if (settings.isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode');
|
||||
|
||||
if (settings.isDemo()) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'));
|
||||
const token = await getCloudronToken();
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/user_token`;
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const [error, response] = await safe(superagent.post(url)
|
||||
.send({})
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/user_token`;
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, `getUserToken status code: ${response.status}`);
|
||||
|
||||
superagent.post(url).send({}).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `getUserToken status code: ${result.status}`));
|
||||
|
||||
callback(null, result.body.accessToken);
|
||||
});
|
||||
});
|
||||
return response.body.accessToken;
|
||||
}
|
||||
|
||||
function getSubscription(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
async function getSubscription() {
|
||||
const token = await getCloudronToken();
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const url = settings.apiServerOrigin() + '/api/v1/subscription';
|
||||
|
||||
const url = settings.apiServerOrigin() + '/api/v1/subscription';
|
||||
superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR));
|
||||
if (result.statusCode === 502) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Stripe error: ${error.message}`));
|
||||
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${error.message}`));
|
||||
const [error, response] = await safe(superagent.get(url)
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
// update the features cache
|
||||
gFeatures = result.body.features;
|
||||
safe.fs.writeFileSync(paths.FEATURES_INFO_FILE, JSON.stringify(gFeatures), 'utf8');
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR);
|
||||
if (response.status === 502) throw new BoxError(BoxError.EXTERNAL_ERROR, `Stripe error: ${error.message}`);
|
||||
if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${error.message}`);
|
||||
|
||||
callback(null, result.body);
|
||||
});
|
||||
});
|
||||
// update the features cache
|
||||
gFeatures = response.body.features;
|
||||
safe.fs.writeFileSync(paths.FEATURES_INFO_FILE, JSON.stringify(gFeatures), 'utf8');
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
function isFreePlan(subscription) {
|
||||
@@ -174,225 +163,203 @@ function isFreePlan(subscription) {
|
||||
}
|
||||
|
||||
// See app.js install it will create a db record first but remove it again if appstore purchase fails
|
||||
function purchaseApp(data, callback) {
|
||||
async function purchaseApp(data) {
|
||||
assert.strictEqual(typeof data, 'object'); // { appstoreId, manifestId, appId }
|
||||
assert(data.appstoreId || data.manifestId);
|
||||
assert.strictEqual(typeof data.appId, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const token = await getCloudronToken();
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/cloudronapps`;
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/cloudronapps`;
|
||||
const [error, response] = await safe(superagent.post(url)
|
||||
.send(data)
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
superagent.post(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND)); // appstoreId does not exist
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 402) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
// 200 if already purchased, 201 is newly purchased
|
||||
if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body)));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND); // appstoreId does not exist
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 402) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
// 200 if already purchased, 201 is newly purchased
|
||||
if (response.status !== 201 && response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', response.status, response.body));
|
||||
}
|
||||
|
||||
function unpurchaseApp(appId, data, callback) {
|
||||
async function unpurchaseApp(appId, data) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof data, 'object'); // { appstoreId, manifestId }
|
||||
assert(data.appstoreId || data.manifestId);
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const token = await getCloudronToken();
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/cloudronapps/${appId}`;
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/cloudronapps/${appId}`;
|
||||
let [error, response] = await safe(superagent.get(url)
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 404) return callback(null); // was never purchased
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body)));
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 404) return; // was never purchased
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status !== 201 && response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', response.status, response.body));
|
||||
|
||||
superagent.del(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode !== 204) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body)));
|
||||
[error, response] = await safe(superagent.del(url)
|
||||
.send(data)
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status !== 204) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', response.status, response.body));
|
||||
}
|
||||
|
||||
function getBoxUpdate(options, callback) {
|
||||
async function getBoxUpdate(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const token = await getCloudronToken();
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/boxupdate`;
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/boxupdate`;
|
||||
const query = {
|
||||
accessToken: token,
|
||||
boxVersion: constants.VERSION,
|
||||
automatic: options.automatic
|
||||
};
|
||||
|
||||
const query = {
|
||||
accessToken: token,
|
||||
boxVersion: constants.VERSION,
|
||||
automatic: options.automatic
|
||||
};
|
||||
const [error, response] = await safe(superagent.get(url)
|
||||
.query(query)
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
superagent.get(url).query(query).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode === 204) return callback(null, null); // no update
|
||||
if (result.statusCode !== 200 || !result.body) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status === 204) return; // no update
|
||||
if (response.status !== 200 || !response.body) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', response.status, response.text));
|
||||
|
||||
var updateInfo = result.body;
|
||||
const updateInfo = response.body;
|
||||
|
||||
if (!semver.valid(updateInfo.version) || semver.gt(constants.VERSION, updateInfo.version)) {
|
||||
return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Update version invalid or is a downgrade: %s %s', result.statusCode, result.text)));
|
||||
}
|
||||
if (!semver.valid(updateInfo.version) || semver.gt(constants.VERSION, updateInfo.version)) {
|
||||
throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Update version invalid or is a downgrade: %s %s', response.status, response.text));
|
||||
}
|
||||
|
||||
// updateInfo: { version, changelog, sourceTarballUrl, sourceTarballSigUrl, boxVersionsUrl, boxVersionsSigUrl }
|
||||
if (!updateInfo.version || typeof updateInfo.version !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad version): %s %s', result.statusCode, result.text)));
|
||||
if (!updateInfo.changelog || !Array.isArray(updateInfo.changelog)) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad version): %s %s', result.statusCode, result.text)));
|
||||
if (!updateInfo.sourceTarballUrl || typeof updateInfo.sourceTarballUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad sourceTarballUrl): %s %s', result.statusCode, result.text)));
|
||||
if (!updateInfo.sourceTarballSigUrl || typeof updateInfo.sourceTarballSigUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad sourceTarballSigUrl): %s %s', result.statusCode, result.text)));
|
||||
if (!updateInfo.boxVersionsUrl || typeof updateInfo.boxVersionsUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad boxVersionsUrl): %s %s', result.statusCode, result.text)));
|
||||
if (!updateInfo.boxVersionsSigUrl || typeof updateInfo.boxVersionsSigUrl !== 'string') return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad boxVersionsSigUrl): %s %s', result.statusCode, result.text)));
|
||||
// updateInfo: { version, changelog, sourceTarballUrl, sourceTarballSigUrl, boxVersionsUrl, boxVersionsSigUrl }
|
||||
if (!updateInfo.version || typeof updateInfo.version !== 'string') throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad version): %s %s', response.status, response.text));
|
||||
if (!updateInfo.changelog || !Array.isArray(updateInfo.changelog)) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad version): %s %s', response.status, response.text));
|
||||
if (!updateInfo.sourceTarballUrl || typeof updateInfo.sourceTarballUrl !== 'string') throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad sourceTarballUrl): %s %s', response.status, response.text));
|
||||
if (!updateInfo.sourceTarballSigUrl || typeof updateInfo.sourceTarballSigUrl !== 'string') throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad sourceTarballSigUrl): %s %s', response.status, response.text));
|
||||
if (!updateInfo.boxVersionsUrl || typeof updateInfo.boxVersionsUrl !== 'string') throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad boxVersionsUrl): %s %s', response.status, response.text));
|
||||
if (!updateInfo.boxVersionsSigUrl || typeof updateInfo.boxVersionsSigUrl !== 'string') throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response (bad boxVersionsSigUrl): %s %s', response.status, response.text));
|
||||
|
||||
callback(null, updateInfo);
|
||||
});
|
||||
});
|
||||
return updateInfo;
|
||||
}
|
||||
|
||||
function getAppUpdate(app, options, callback) {
|
||||
async function getAppUpdate(app, options) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const token = await getCloudronToken();
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/appupdate`;
|
||||
const query = {
|
||||
accessToken: token,
|
||||
boxVersion: constants.VERSION,
|
||||
appId: app.appStoreId,
|
||||
appVersion: app.manifest.version,
|
||||
automatic: options.automatic
|
||||
};
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/appupdate`;
|
||||
const query = {
|
||||
accessToken: token,
|
||||
boxVersion: constants.VERSION,
|
||||
appId: app.appStoreId,
|
||||
appVersion: app.manifest.version,
|
||||
automatic: options.automatic
|
||||
};
|
||||
const [error, response] = await safe(superagent.get(url)
|
||||
.query(query)
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
superagent.get(url).query(query).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode === 204) return callback(null); // no update
|
||||
if (result.statusCode !== 200 || !result.body) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error);
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status === 204) return; // no update
|
||||
if (response.status !== 200 || !response.body) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', response.status, response.text));
|
||||
|
||||
const updateInfo = result.body;
|
||||
const updateInfo = response.body;
|
||||
|
||||
// for the appstore, x.y.z is the same as x.y.z-0 but in semver, x.y.z > x.y.z-0
|
||||
const curAppVersion = semver.prerelease(app.manifest.version) ? app.manifest.version : `${app.manifest.version}-0`;
|
||||
// for the appstore, x.y.z is the same as x.y.z-0 but in semver, x.y.z > x.y.z-0
|
||||
const curAppVersion = semver.prerelease(app.manifest.version) ? app.manifest.version : `${app.manifest.version}-0`;
|
||||
|
||||
// do some sanity checks
|
||||
if (!safe.query(updateInfo, 'manifest.version') || semver.gt(curAppVersion, safe.query(updateInfo, 'manifest.version'))) {
|
||||
debug('Skipping malformed update of app %s version: %s. got %j', app.id, curAppVersion, updateInfo);
|
||||
return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Malformed update: %s %s', result.statusCode, result.text)));
|
||||
}
|
||||
// do some sanity checks
|
||||
if (!safe.query(updateInfo, 'manifest.version') || semver.gt(curAppVersion, safe.query(updateInfo, 'manifest.version'))) {
|
||||
debug('Skipping malformed update of app %s version: %s. got %j', app.id, curAppVersion, updateInfo);
|
||||
throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Malformed update: %s %s', response.status, response.text));
|
||||
}
|
||||
|
||||
updateInfo.unstable = !!updateInfo.unstable;
|
||||
updateInfo.unstable = !!updateInfo.unstable;
|
||||
|
||||
// { id, creationDate, manifest, unstable }
|
||||
callback(null, updateInfo);
|
||||
});
|
||||
});
|
||||
// { id, creationDate, manifest, unstable }
|
||||
return updateInfo;
|
||||
}
|
||||
|
||||
function registerCloudron(data, callback) {
|
||||
async function registerCloudron(data) {
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/register_cloudron`;
|
||||
|
||||
superagent.post(url).send(data).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Unable to register cloudron: ${result.statusCode} ${error.message}`));
|
||||
const [error, response] = await safe(superagent.post(url)
|
||||
.send(data)
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
// cloudronId, token, licenseKey
|
||||
if (!result.body.cloudronId) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no cloudron id'));
|
||||
if (!result.body.cloudronToken) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no token'));
|
||||
if (!result.body.licenseKey) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no license'));
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unable to register cloudron: ${response.statusCode} ${error.message}`);
|
||||
|
||||
async.series([
|
||||
settings.setCloudronId.bind(null, result.body.cloudronId),
|
||||
settings.setCloudronToken.bind(null, result.body.cloudronToken),
|
||||
settings.setLicenseKey.bind(null, result.body.licenseKey),
|
||||
], function (error) {
|
||||
if (error) return callback(error);
|
||||
// cloudronId, token, licenseKey
|
||||
if (!response.body.cloudronId) throw new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no cloudron id');
|
||||
if (!response.body.cloudronToken) throw new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no token');
|
||||
if (!response.body.licenseKey) throw new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid response - no license');
|
||||
|
||||
debug(`registerCloudron: Cloudron registered with id ${result.body.cloudronId}`);
|
||||
await settings.setCloudronId(response.body.cloudronId);
|
||||
await settings.setCloudronToken(response.body.cloudronToken);
|
||||
await settings.setLicenseKey(response.body.licenseKey);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
debug(`registerCloudron: Cloudron registered with id ${response.body.cloudronId}`);
|
||||
}
|
||||
|
||||
function updateCloudron(data, callback) {
|
||||
async function updateCloudron(data) {
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error && error.reason === BoxError.LICENSE_ERROR) return callback(null); // missing token. not registered yet
|
||||
if (error) return callback(error);
|
||||
const token = await getCloudronToken();
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/update_cloudron`;
|
||||
const query = {
|
||||
accessToken: token
|
||||
};
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/update_cloudron`;
|
||||
const query = {
|
||||
accessToken: token
|
||||
};
|
||||
const [error, response] = await safe(superagent.post(url)
|
||||
.query(query)
|
||||
.send(data)
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
superagent.post(url).query(query).send(data).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error);
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', response.status, response.text));
|
||||
|
||||
debug(`updateCloudron: Cloudron updated with data ${JSON.stringify(data)}`);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
debug(`updateCloudron: Cloudron updated with data ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
function registerWithLoginCredentials(options, callback) {
|
||||
async function registerWithLoginCredentials(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
function maybeSignup(done) {
|
||||
if (!options.signup) return done();
|
||||
const token = await getCloudronToken();
|
||||
if (token) throw new BoxError(BoxError.CONFLICT, 'Cloudron is already registered');
|
||||
|
||||
registerUser(options.email, options.password, done);
|
||||
}
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (token) return callback(new BoxError(BoxError.CONFLICT, 'Cloudron is already registered'));
|
||||
|
||||
maybeSignup(function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
login(options.email, options.password, options.totpToken || '', function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
registerCloudron({ domain: settings.dashboardDomain(), accessToken: result.accessToken, version: constants.VERSION }, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
if (!options.signup) return;
|
||||
await registerUser(options.email, options.password);
|
||||
const result = await login(options.email, options.password, options.totpToken || '');
|
||||
await registerCloudron({ domain: settings.dashboardDomain(), accessToken: result.accessToken, version: constants.VERSION });
|
||||
}
|
||||
|
||||
function createTicket(info, auditSource, callback) {
|
||||
async function createTicket(info, auditSource) {
|
||||
assert.strictEqual(typeof info, 'object');
|
||||
assert.strictEqual(typeof info.email, 'string');
|
||||
assert.strictEqual(typeof info.displayName, 'string');
|
||||
@@ -400,127 +367,96 @@ function createTicket(info, auditSource, callback) {
|
||||
assert.strictEqual(typeof info.subject, 'string');
|
||||
assert.strictEqual(typeof info.description, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
function collectAppInfoIfNeeded(callback) {
|
||||
if (!info.appId) return callback();
|
||||
apps.get(info.appId, callback);
|
||||
const token = await getCloudronToken();
|
||||
|
||||
if (info.enableSshSupport) {
|
||||
await safe(support.enableRemoteSupport(true, auditSource));
|
||||
}
|
||||
|
||||
function enableSshIfNeeded(callback) {
|
||||
if (!info.enableSshSupport) return callback();
|
||||
info.app = info.appId ? await util.promisify(apps.get)(info.appId) : null;
|
||||
info.supportEmail = constants.SUPPORT_EMAIL; // destination address for tickets
|
||||
|
||||
support.enableRemoteSupport(true, auditSource, function (error) {
|
||||
// ensure we can at least get the ticket through
|
||||
if (error) debug('Unable to enable SSH support.', error);
|
||||
const request = superagent.post(`${settings.apiServerOrigin()}/api/v1/ticket`)
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true);
|
||||
|
||||
callback();
|
||||
// either send as JSON through body or as multipart, depending on attachments
|
||||
if (info.app) {
|
||||
request.field('infoJSON', JSON.stringify(info));
|
||||
|
||||
apps.getLocalLogfilePaths(info.app).forEach(function (filePath) {
|
||||
const logs = safe.child_process.execSync(`tail --lines=1000 ${filePath}`);
|
||||
if (logs) request.attach(path.basename(filePath), logs, path.basename(filePath));
|
||||
});
|
||||
} else {
|
||||
request.send(info);
|
||||
}
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const [error, response] = await safe(request);
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', response.status, response.text));
|
||||
|
||||
enableSshIfNeeded(function (error) {
|
||||
if (error) return callback(error);
|
||||
eventlog.add(eventlog.ACTION_SUPPORT_TICKET, auditSource, info);
|
||||
|
||||
collectAppInfoIfNeeded(function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app) info.app = app;
|
||||
|
||||
info.supportEmail = constants.SUPPORT_EMAIL; // destination address for tickets
|
||||
|
||||
var req = superagent.post(`${settings.apiServerOrigin()}/api/v1/ticket`)
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000);
|
||||
|
||||
// either send as JSON through body or as multipart, depending on attachments
|
||||
if (info.app) {
|
||||
req.field('infoJSON', JSON.stringify(info));
|
||||
|
||||
apps.getLocalLogfilePaths(info.app).forEach(function (filePath) {
|
||||
var logs = safe.child_process.execSync(`tail --lines=1000 ${filePath}`);
|
||||
if (logs) req.attach(path.basename(filePath), logs, path.basename(filePath));
|
||||
});
|
||||
} else {
|
||||
req.send(info);
|
||||
}
|
||||
|
||||
req.end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
|
||||
|
||||
eventlog.add(eventlog.ACTION_SUPPORT_TICKET, auditSource, info);
|
||||
|
||||
callback(null, { message: `An email was sent to ${constants.SUPPORT_EMAIL}. We will get back shortly!` });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return { message: `An email was sent to ${constants.SUPPORT_EMAIL}. We will get back shortly!` };
|
||||
}
|
||||
|
||||
function getApps(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
async function getApps() {
|
||||
const token = await getCloudronToken();
|
||||
|
||||
getCloudronToken(async function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const unstable = await settings.getUnstableAppsConfig();
|
||||
|
||||
const [settingsError, unstable] = await settings.getUnstableAppsConfig();
|
||||
if (settingsError) return callback(settingsError);
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/apps`;
|
||||
|
||||
const url = `${settings.apiServerOrigin()}/api/v1/apps`;
|
||||
superagent.get(url).query({ accessToken: token, boxVersion: constants.VERSION, unstable: unstable }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body)));
|
||||
if (!result.body.apps) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text)));
|
||||
const [error, response] = await safe(superagent.get(url)
|
||||
.query({ accessToken: token, boxVersion: constants.VERSION, unstable: unstable })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
settings.getAppstoreListingConfig(function (error, listingConfig) {
|
||||
if (error) return callback(error);
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 403 || response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('App listing failed. %s %j', response.status, response.body));
|
||||
if (!response.body.apps) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('Bad response: %s %s', response.status, response.text));
|
||||
|
||||
const filteredApps = result.body.apps.filter(app => isAppAllowed(app.id, listingConfig));
|
||||
|
||||
callback(null, filteredApps);
|
||||
});
|
||||
});
|
||||
});
|
||||
const listingConfig = await settings.getAppstoreListingConfig();
|
||||
const filteredApps = response.body.apps.filter(app => isAppAllowed(app.id, listingConfig));
|
||||
return filteredApps;
|
||||
}
|
||||
|
||||
function getAppVersion(appId, version, callback) {
|
||||
async function getAppVersion(appId, version) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof version, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.getAppstoreListingConfig(function (error, listingConfig) {
|
||||
if (error) return callback(error);
|
||||
const listingConfig = await settings.getAppstoreListingConfig();
|
||||
|
||||
if (!isAppAllowed(appId, listingConfig)) return callback(new BoxError(BoxError.FEATURE_DISABLED));
|
||||
if (!isAppAllowed(appId, listingConfig)) throw new BoxError(BoxError.FEATURE_DISABLED);
|
||||
|
||||
getCloudronToken(function (error, token) {
|
||||
if (error) return callback(error);
|
||||
const token = await getCloudronToken();
|
||||
|
||||
let url = `${settings.apiServerOrigin()}/api/v1/apps/${appId}`;
|
||||
if (version !== 'latest') url += `/versions/${version}`;
|
||||
let url = `${settings.apiServerOrigin()}/api/v1/apps/${appId}`;
|
||||
if (version !== 'latest') url += `/versions/${version}`;
|
||||
|
||||
superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) {
|
||||
if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message));
|
||||
if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
|
||||
if (result.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND));
|
||||
if (result.statusCode === 422) return callback(new BoxError(BoxError.LICENSE_ERROR, result.body.message));
|
||||
if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('App fetch failed. %s %j', result.status, result.body)));
|
||||
const [error, response] = await safe(superagent.get(url)
|
||||
.query({ accessToken: token })
|
||||
.timeout(30 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
callback(null, result.body);
|
||||
});
|
||||
});
|
||||
});
|
||||
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
|
||||
if (response.status === 403 || response.statusCode === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
||||
if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND);
|
||||
if (response.status === 422) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message);
|
||||
if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, util.format('App fetch failed. %s %j', response.status, response.body));
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
function getApp(appId, callback) {
|
||||
async function getApp(appId) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getAppVersion(appId, 'latest', callback);
|
||||
return await getAppVersion(appId, 'latest');
|
||||
}
|
||||
|
||||
+2
-2
@@ -314,10 +314,10 @@ function setDashboardDomain(domain, auditSource, callback) {
|
||||
|
||||
const fqdn = dns.fqdn(constants.DASHBOARD_LOCATION, domainObject);
|
||||
|
||||
settings.setDashboardLocation(domain, fqdn, function (error) {
|
||||
settings.setDashboardLocation(domain, fqdn, async function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
appstore.updateCloudron({ domain }, NOOP_CALLBACK);
|
||||
await safe(appstore.updateCloudron({ domain }));
|
||||
|
||||
eventlog.add(eventlog.ACTION_DASHBOARD_DOMAIN_UPDATE, auditSource, { domain, fqdn });
|
||||
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ async function startJobs() {
|
||||
// this is run separately from the update itself so that the user can disable automatic updates but can still get a notification
|
||||
gJobs.updateCheckerJob = new CronJob({
|
||||
cronTime: `${randomTick} ${randomTick} 1,5,9,13,17,21,23 * * *`,
|
||||
onTick: () => updateChecker.checkForUpdates({ automatic: true }, NOOP_CALLBACK),
|
||||
onTick: async () => await updateChecker.checkForUpdates({ automatic: true }),
|
||||
start: true
|
||||
});
|
||||
|
||||
|
||||
+6
-8
@@ -35,17 +35,15 @@ const MAIL_TEMPLATES_DIR = path.join(__dirname, 'mail_templates');
|
||||
function getMailConfig(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.getCloudronName(function (error, cloudronName) {
|
||||
settings.getCloudronName(async function (error, cloudronName) {
|
||||
if (error) debug('Error getting cloudron name: ', error);
|
||||
|
||||
settings.getSupportConfig(function (error, supportConfig) {
|
||||
if (error) debug('Error getting support config: ', error);
|
||||
const supportConfig = await settings.getSupportConfig();
|
||||
|
||||
callback(null, {
|
||||
cloudronName: cloudronName || '',
|
||||
notificationFrom: `"${cloudronName}" <no-reply@${settings.dashboardDomain()}>`,
|
||||
supportEmail: supportConfig.email
|
||||
});
|
||||
callback(null, {
|
||||
cloudronName: cloudronName || '',
|
||||
notificationFrom: `"${cloudronName}" <no-reply@${settings.dashboardDomain()}>`,
|
||||
supportEmail: supportConfig.email
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+27
-32
@@ -10,50 +10,47 @@ exports = module.exports = {
|
||||
getSubscription
|
||||
};
|
||||
|
||||
var appstore = require('../appstore.js'),
|
||||
const appstore = require('../appstore.js'),
|
||||
assert = require('assert'),
|
||||
BoxError = require('../boxerror.js'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess;
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
safe = require('safetydance');
|
||||
|
||||
function getApps(req, res, next) {
|
||||
appstore.getApps(function (error, apps) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
async function getApps(req, res, next) {
|
||||
const [error, apps] = await safe(appstore.getApps());
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { apps }));
|
||||
});
|
||||
next(new HttpSuccess(200, { apps }));
|
||||
}
|
||||
|
||||
function getApp(req, res, next) {
|
||||
async function getApp(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.appstoreId, 'string');
|
||||
|
||||
appstore.getApp(req.params.appstoreId, function (error, app) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, app] = await safe(appstore.getApp(req.params.appstoreId));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, app));
|
||||
});
|
||||
next(new HttpSuccess(200, app));
|
||||
}
|
||||
|
||||
function getAppVersion(req, res, next) {
|
||||
async function getAppVersion(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.appstoreId, 'string');
|
||||
assert.strictEqual(typeof req.params.versionId, 'string');
|
||||
|
||||
appstore.getAppVersion(req.params.appstoreId, req.params.versionId, function (error, manifest) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, manifest] = await safe(appstore.getAppVersion(req.params.appstoreId, req.params.versionId));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, manifest));
|
||||
});
|
||||
next(new HttpSuccess(200, manifest));
|
||||
}
|
||||
|
||||
function createUserToken(req, res, next) {
|
||||
appstore.getUserToken(function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
async function createUserToken(req, res, next) {
|
||||
const [error, accessToken] = await safe(appstore.createUserToken());
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(201, { accessToken: result }));
|
||||
});
|
||||
next(new HttpSuccess(201, { accessToken }));
|
||||
}
|
||||
|
||||
function registerCloudron(req, res, next) {
|
||||
async function registerCloudron(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (typeof req.body.email !== 'string' || !req.body.email) return next(new HttpError(400, 'email must be string'));
|
||||
@@ -61,19 +58,17 @@ function registerCloudron(req, res, next) {
|
||||
if ('totpToken' in req.body && typeof req.body.totpToken !== 'string') return next(new HttpError(400, 'totpToken must be string'));
|
||||
if (typeof req.body.signup !== 'boolean') return next(new HttpError(400, 'signup must be a boolean'));
|
||||
|
||||
appstore.registerWithLoginCredentials(req.body, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(appstore.registerWithLoginCredentials(req.body));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(201, {}));
|
||||
});
|
||||
next(new HttpSuccess(201, {}));
|
||||
}
|
||||
|
||||
function getSubscription(req, res, next) {
|
||||
async function getSubscription(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
appstore.getSubscription(function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, result] = await safe(appstore.getSubscription());
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, result)); // { email, cloudronId, cloudronCreatedAt, plan, current_period_end, canceled_at, cancel_at, status, features }
|
||||
});
|
||||
next(new HttpSuccess(200, result)); // { email, cloudronId, cloudronCreatedAt, plan, current_period_end, canceled_at, cancel_at, status, features }
|
||||
}
|
||||
|
||||
+8
-10
@@ -55,7 +55,7 @@ function getCloudronName(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function setAppstoreListingConfig(req, res, next) {
|
||||
async function setAppstoreListingConfig(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
const listingConfig = _.pick(req.body, 'whitelist', 'blacklist');
|
||||
@@ -73,19 +73,17 @@ function setAppstoreListingConfig(req, res, next) {
|
||||
if (!listingConfig.blacklist.every(id => typeof id === 'string')) return next(new HttpError(400, 'blacklist must be array of strings'));
|
||||
}
|
||||
|
||||
settings.setAppstoreListingConfig(listingConfig, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(settings.setAppstoreListingConfig(listingConfig));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
});
|
||||
next(new HttpSuccess(202, {}));
|
||||
}
|
||||
|
||||
function getAppstoreListingConfig(req, res, next) {
|
||||
settings.getAppstoreListingConfig(function (error, listingConfig) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
async function getAppstoreListingConfig(req, res, next) {
|
||||
const [error, listingConfig] = await safe(settings.getAppstoreListingConfig());
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, listingConfig));
|
||||
});
|
||||
next(new HttpSuccess(200, listingConfig));
|
||||
}
|
||||
|
||||
async function setCloudronAvatar(req, res, next) {
|
||||
|
||||
@@ -196,13 +196,12 @@ function getUpdateInfo(req, res, next) {
|
||||
next(new HttpSuccess(200, { update: updateChecker.getUpdateInfo() }));
|
||||
}
|
||||
|
||||
function checkForUpdates(req, res, next) {
|
||||
async function checkForUpdates(req, res, next) {
|
||||
// it can take a while sometimes to get all the app updates one by one
|
||||
req.clearTimeout();
|
||||
|
||||
updateChecker.checkForUpdates({ automatic: false }, function () {
|
||||
next(new HttpSuccess(200, { update: updateChecker.getUpdateInfo() }));
|
||||
});
|
||||
await updateChecker.checkForUpdates({ automatic: false });
|
||||
next(new HttpSuccess(200, { update: updateChecker.getUpdateInfo() }));
|
||||
}
|
||||
|
||||
function getLogs(req, res, next) {
|
||||
|
||||
@@ -26,7 +26,7 @@ const assert = require('assert'),
|
||||
async function authorize(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
const [error, directoryConfig] = await settings.getDirectoryConfig();
|
||||
const [error, directoryConfig] = await safe(settings.getDirectoryConfig());
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
if (directoryConfig.lockUserProfiles) return next(new HttpError(403, 'admin has disallowed users from editing profiles'));
|
||||
|
||||
@@ -58,12 +58,11 @@ function setTimeZone(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function getSupportConfig(req, res, next) {
|
||||
settings.getSupportConfig(function (error, supportConfig) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
async function getSupportConfig(req, res, next) {
|
||||
const [error, supportConfig] = await settings.getSupportConfig();
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, supportConfig));
|
||||
});
|
||||
next(new HttpSuccess(200, supportConfig));
|
||||
}
|
||||
|
||||
function getBackupConfig(req, res, next) {
|
||||
|
||||
+27
-32
@@ -10,27 +10,27 @@ exports = module.exports = {
|
||||
canEnableRemoteSupport
|
||||
};
|
||||
|
||||
var appstore = require('../appstore.js'),
|
||||
const appstore = require('../appstore.js'),
|
||||
assert = require('assert'),
|
||||
auditSource = require('../auditsource.js'),
|
||||
constants = require('../constants.js'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
safe = require('safetydance'),
|
||||
settings = require('../settings.js'),
|
||||
support = require('../support.js'),
|
||||
_ = require('underscore');
|
||||
|
||||
function canCreateTicket(req, res, next) {
|
||||
settings.getSupportConfig(function (error, supportConfig) {
|
||||
if (error) return next(new HttpError(503, error.message));
|
||||
async function canCreateTicket(req, res, next) {
|
||||
const [error, supportConfig] = await safe(settings.getSupportConfig());
|
||||
if (error) return next(new HttpError(503, error.message));
|
||||
|
||||
if (!supportConfig.submitTickets) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
if (!supportConfig.submitTickets) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
next();
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
function createTicket(req, res, next) {
|
||||
async function createTicket(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
const VALID_TYPES = [ 'feedback', 'ticket', 'app_missing', 'app_error', 'upgrade_request', 'email_error' ];
|
||||
@@ -43,44 +43,39 @@ function createTicket(req, res, next) {
|
||||
if (req.body.altEmail && typeof req.body.altEmail !== 'string') return next(new HttpError(400, 'altEmail must be string'));
|
||||
if (req.body.enableSshSupport && typeof req.body.enableSshSupport !== 'boolean') return next(new HttpError(400, 'enableSshSupport must be a boolean'));
|
||||
|
||||
settings.getSupportConfig(function (error, supportConfig) {
|
||||
if (error) return next(new HttpError(503, `Error getting support config: ${error.message}`));
|
||||
if (supportConfig.email !== constants.SUPPORT_EMAIL) return next(new HttpError(503, 'Sending to non-cloudron email not implemented yet'));
|
||||
const [error, supportConfig] = await safe(settings.getSupportConfig());
|
||||
if (error) return next(new HttpError(503, `Error getting support config: ${error.message}`));
|
||||
if (supportConfig.email !== constants.SUPPORT_EMAIL) return next(new HttpError(503, 'Sending to non-cloudron email not implemented yet'));
|
||||
|
||||
appstore.createTicket(_.extend({ }, req.body, { email: req.user.email, displayName: req.user.displayName }), auditSource.fromRequest(req), function (error, result) {
|
||||
if (error) return next(new HttpError(503, `Error contacting cloudron.io: ${error.message}. Please email ${constants.SUPPORT_EMAIL}`));
|
||||
const [ticketError, result] = await safe(appstore.createTicket(_.extend({ }, req.body, { email: req.user.email, displayName: req.user.displayName }), auditSource.fromRequest(req)));
|
||||
if (ticketError) return next(new HttpError(503, `Error contacting cloudron.io: ${error.message}. Please email ${constants.SUPPORT_EMAIL}`));
|
||||
|
||||
next(new HttpSuccess(201, result));
|
||||
});
|
||||
});
|
||||
next(new HttpSuccess(201, result));
|
||||
}
|
||||
|
||||
function canEnableRemoteSupport(req, res, next) {
|
||||
settings.getSupportConfig(function (error, supportConfig) {
|
||||
if (error) return next(new HttpError(503, error.message));
|
||||
async function canEnableRemoteSupport(req, res, next) {
|
||||
const [error, supportConfig] = await safe(settings.getSupportConfig());
|
||||
if (error) return next(new HttpError(503, error.message));
|
||||
|
||||
if (!supportConfig.remoteSupport) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
if (!supportConfig.remoteSupport) return next(new HttpError(405, 'feature disabled by admin'));
|
||||
|
||||
next();
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
function enableRemoteSupport(req, res, next) {
|
||||
async function enableRemoteSupport(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enabled is required'));
|
||||
|
||||
support.enableRemoteSupport(req.body.enable, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(new HttpError(503, 'Error enabling remote support. Try running "cloudron-support --enable-ssh" on the server'));
|
||||
const [error] = await safe(support.enableRemoteSupport(req.body.enable, auditSource.fromRequest(req)));
|
||||
if (error) return next(new HttpError(503, 'Error enabling remote support. Try running "cloudron-support --enable-ssh" on the server'));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
});
|
||||
next(new HttpSuccess(202, {}));
|
||||
}
|
||||
|
||||
function getRemoteSupport(req, res, next) {
|
||||
support.getRemoteSupport(function (error, status) {
|
||||
if (error) return next(new HttpError(500, error));
|
||||
async function getRemoteSupport(req, res, next) {
|
||||
const [error, enabled] = await safe(support.getRemoteSupport());
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
next(new HttpSuccess(200, status));
|
||||
});
|
||||
next(new HttpSuccess(200, { enabled }));
|
||||
}
|
||||
|
||||
+33
-80
@@ -384,7 +384,7 @@ function setDynamicDnsConfig(enabled, callback) {
|
||||
|
||||
async function getUnstableAppsConfig() {
|
||||
const result = await get(exports.UNSTABLE_APPS_KEY);
|
||||
if (result === null) gDefaults[exports.UNSTABLE_APPS_KEY];
|
||||
if (result === null) return gDefaults[exports.UNSTABLE_APPS_KEY];
|
||||
return !!result; // db holds string values only
|
||||
}
|
||||
|
||||
@@ -650,28 +650,18 @@ async function setDirectoryConfig(directoryConfig) {
|
||||
notifyChange(exports.DIRECTORY_CONFIG_KEY, directoryConfig);
|
||||
}
|
||||
|
||||
function getAppstoreListingConfig(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
async function getAppstoreListingConfig() {
|
||||
const value = await get(exports.APPSTORE_LISTING_CONFIG_KEY);
|
||||
if (value === null) return gDefaults[exports.APPSTORE_LISTING_CONFIG_KEY];
|
||||
|
||||
settingsdb.get(exports.APPSTORE_LISTING_CONFIG_KEY, function (error, value) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.APPSTORE_LISTING_CONFIG_KEY]);
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, JSON.parse(value));
|
||||
});
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
function setAppstoreListingConfig(listingConfig, callback) {
|
||||
async function setAppstoreListingConfig(listingConfig) {
|
||||
assert.strictEqual(typeof listingConfig, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.set(exports.APPSTORE_LISTING_CONFIG_KEY, JSON.stringify(listingConfig), function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
notifyChange(exports.APPSTORE_LISTING_CONFIG_KEY, listingConfig);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
await set(exports.APPSTORE_LISTING_CONFIG_KEY, JSON.stringify(listingConfig));
|
||||
notifyChange(exports.APPSTORE_LISTING_CONFIG_KEY, listingConfig);
|
||||
}
|
||||
|
||||
async function getFirewallBlocklist() {
|
||||
@@ -688,39 +678,24 @@ async function setFirewallBlocklist(blocklist) {
|
||||
await setBlob(exports.FIREWALL_BLOCKLIST_KEY, Buffer.from(blocklist));
|
||||
}
|
||||
|
||||
function getSupportConfig(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
async function getSupportConfig() {
|
||||
const value = await get(exports.SUPPORT_CONFIG_KEY);
|
||||
if (value === null) return gDefaults[exports.SUPPORT_CONFIG_KEY];
|
||||
|
||||
settingsdb.get(exports.SUPPORT_CONFIG_KEY, function (error, value) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.SUPPORT_CONFIG_KEY]);
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, JSON.parse(value));
|
||||
});
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
function getLicenseKey(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.get(exports.LICENSE_KEY, function (error, value) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.LICENSE_KEY]);
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, value);
|
||||
});
|
||||
async function getLicenseKey() {
|
||||
const value = get(exports.LICENSE_KEY);
|
||||
if (value === null) return gDefaults[exports.LICENSE_KEY];
|
||||
return value;
|
||||
}
|
||||
|
||||
function setLicenseKey(licenseKey, callback) {
|
||||
async function setLicenseKey(licenseKey) {
|
||||
assert.strictEqual(typeof licenseKey, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.set(exports.LICENSE_KEY, licenseKey, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
notifyChange(exports.LICENSE_KEY, licenseKey);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
await set(exports.LICENSE_KEY, licenseKey);
|
||||
notifyChange(exports.LICENSE_KEY, licenseKey);
|
||||
}
|
||||
|
||||
function getLanguage(callback) {
|
||||
@@ -753,52 +728,30 @@ function setLanguage(language, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getCloudronId(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.get(exports.CLOUDRON_ID_KEY, function (error, value) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.CLOUDRON_ID_KEY]);
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, value);
|
||||
});
|
||||
async function getCloudronId() {
|
||||
const value = await get(exports.CLOUDRON_ID_KEY);
|
||||
if (value === null) return gDefaults[exports.CLOUDRON_ID_KEY];
|
||||
return value;
|
||||
}
|
||||
|
||||
function setCloudronId(cid, callback) {
|
||||
async function setCloudronId(cid) {
|
||||
assert.strictEqual(typeof cid, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.set(exports.CLOUDRON_ID_KEY, cid, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
notifyChange(exports.CLOUDRON_ID_KEY, cid);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
await set(exports.CLOUDRON_ID_KEY, cid);
|
||||
notifyChange(exports.CLOUDRON_ID_KEY, cid);
|
||||
}
|
||||
|
||||
function getCloudronToken(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.get(exports.CLOUDRON_TOKEN_KEY, function (error, value) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.CLOUDRON_TOKEN_KEY]);
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, value);
|
||||
});
|
||||
async function getCloudronToken() {
|
||||
const value = await get(exports.CLOUDRON_TOKEN_KEY);
|
||||
if (value === null) return gDefaults[exports.CLOUDRON_TOKEN_KEY];
|
||||
return value;
|
||||
}
|
||||
|
||||
function setCloudronToken(token, callback) {
|
||||
async function setCloudronToken(token) {
|
||||
assert.strictEqual(typeof token, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settingsdb.set(exports.CLOUDRON_TOKEN_KEY, token, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
notifyChange(exports.CLOUDRON_TOKEN_KEY, token);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
await set(exports.CLOUDRON_TOKEN_KEY, token);
|
||||
notifyChange(exports.CLOUDRON_TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
async function list() {
|
||||
|
||||
+3
-1
@@ -53,6 +53,7 @@ function spawn(tag, file, args, options, callback) {
|
||||
|
||||
debug(tag + ' spawn: %s %s', file, args.join(' ').replace(/\n/g, '\\n'));
|
||||
const cp = child_process.spawn(file, args, options);
|
||||
let stdoutResult = '';
|
||||
|
||||
if (options.logStream) {
|
||||
cp.stdout.pipe(options.logStream);
|
||||
@@ -60,6 +61,7 @@ function spawn(tag, file, args, options, callback) {
|
||||
} else {
|
||||
cp.stdout.on('data', function (data) {
|
||||
debug(tag + ' (stdout): %s', data.toString('utf8'));
|
||||
stdoutResult += data.toString('utf8');
|
||||
});
|
||||
|
||||
cp.stderr.on('data', function (data) {
|
||||
@@ -69,7 +71,7 @@ function spawn(tag, file, args, options, callback) {
|
||||
|
||||
cp.on('exit', function (code, signal) {
|
||||
if (code || signal) debug(tag + ' code: %s, signal: %s', code, signal);
|
||||
if (code === 0) return callback(null);
|
||||
if (code === 0) return callback(null, stdoutResult);
|
||||
|
||||
let e = new BoxError(BoxError.SPAWN_ERROR, `${tag} exited with code ${code} signal ${signal}`);
|
||||
e.code = code;
|
||||
|
||||
+8
-21
@@ -11,7 +11,6 @@ const assert = require('assert'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
once = require('once'),
|
||||
path = require('path'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
@@ -30,38 +29,26 @@ function sshInfo() {
|
||||
filePath = '/home/ubuntu/.ssh/authorized_keys';
|
||||
user = 'ubuntu';
|
||||
} else {
|
||||
filePath = '/root/.ssh/authorized_keys';
|
||||
user = 'root';
|
||||
}
|
||||
|
||||
return { filePath, user };
|
||||
}
|
||||
|
||||
function getRemoteSupport(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
async function getRemoteSupport() {
|
||||
const [error, stdoutResult] = await safe(shell.promises.sudo('support', [ AUTHORIZED_KEYS_CMD, 'is-enabled', sshInfo().filePath ], {}));
|
||||
if (error) throw new BoxError(BoxError.FS_ERROR, error);
|
||||
|
||||
callback = once(callback); // exit may or may not be called after an 'error'
|
||||
|
||||
let result = '';
|
||||
let cp = shell.sudo('support', [ AUTHORIZED_KEYS_CMD, 'is-enabled', sshInfo().filePath ], {}, function (error) {
|
||||
if (error) callback(new BoxError(BoxError.FS_ERROR, error));
|
||||
|
||||
callback(null, { enabled: result.trim() === 'true' });
|
||||
});
|
||||
cp.stdout.on('data', (data) => result = result + data.toString('utf8'));
|
||||
return stdoutResult.trim() === 'true';
|
||||
}
|
||||
|
||||
function enableRemoteSupport(enable, auditSource, callback) {
|
||||
async function enableRemoteSupport(enable, auditSource) {
|
||||
assert.strictEqual(typeof enable, 'boolean');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const si = sshInfo();
|
||||
shell.sudo('support', [ AUTHORIZED_KEYS_CMD, enable ? 'enable' : 'disable', si.filePath, si.user ], {}, function (error) {
|
||||
if (error) callback(new BoxError(BoxError.FS_ERROR, error));
|
||||
const [error] = await safe(shell.promises.sudo('support', [ AUTHORIZED_KEYS_CMD, enable ? 'enable' : 'disable', si.filePath, si.user ], {}));
|
||||
if (error) throw new BoxError(BoxError.FS_ERROR, error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_SUPPORT_SSH, auditSource, { enable });
|
||||
|
||||
callback();
|
||||
});
|
||||
await eventlog.add(eventlog.ACTION_SUPPORT_SSH, auditSource, { enable });
|
||||
}
|
||||
|
||||
@@ -30,54 +30,45 @@ describe('updatechecker', function () {
|
||||
settings.setAutoupdatePattern(constants.AUTOUPDATE_PATTERN_NEVER, done);
|
||||
});
|
||||
|
||||
it('no updates', function (done) {
|
||||
it('no updates', async function () {
|
||||
nock.cleanAll();
|
||||
|
||||
var scope = nock(mockApiServerOrigin)
|
||||
const scope = nock(mockApiServerOrigin)
|
||||
.get('/api/v1/boxupdate')
|
||||
.query({ boxVersion: constants.VERSION, accessToken: appstoreToken, automatic: false })
|
||||
.reply(204, { } );
|
||||
|
||||
updatechecker.checkForUpdates({ automatic: false }, function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
expect(updatechecker.getUpdateInfo().box).to.not.be.ok();
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
done();
|
||||
});
|
||||
await updatechecker.checkForUpdates({ automatic: false });
|
||||
expect(updatechecker.getUpdateInfo().box).to.not.be.ok();
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
});
|
||||
|
||||
it('new version', function (done) {
|
||||
it('new version', async function () {
|
||||
nock.cleanAll();
|
||||
|
||||
var scope = nock(mockApiServerOrigin)
|
||||
const scope = nock(mockApiServerOrigin)
|
||||
.get('/api/v1/boxupdate')
|
||||
.query({ boxVersion: constants.VERSION, accessToken: appstoreToken, automatic: false })
|
||||
.reply(200, { version: UPDATE_VERSION, changelog: [''], sourceTarballUrl: 'box.tar.gz', sourceTarballSigUrl: 'box.tar.gz.sig', boxVersionsUrl: 'box.versions', boxVersionsSigUrl: 'box.versions.sig' } );
|
||||
|
||||
updatechecker.checkForUpdates({ automatic: false }, function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
expect(updatechecker.getUpdateInfo().box.version).to.be(UPDATE_VERSION);
|
||||
expect(updatechecker.getUpdateInfo().box.sourceTarballUrl).to.be('box.tar.gz');
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
done();
|
||||
});
|
||||
await updatechecker.checkForUpdates({ automatic: false });
|
||||
expect(updatechecker.getUpdateInfo().box.version).to.be(UPDATE_VERSION);
|
||||
expect(updatechecker.getUpdateInfo().box.sourceTarballUrl).to.be('box.tar.gz');
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
});
|
||||
|
||||
it('bad response offers whatever was last valid', function (done) {
|
||||
it('bad response offers whatever was last valid', async function () {
|
||||
nock.cleanAll();
|
||||
|
||||
var scope = nock(mockApiServerOrigin)
|
||||
const scope = nock(mockApiServerOrigin)
|
||||
.get('/api/v1/boxupdate')
|
||||
.query({ boxVersion: constants.VERSION, accessToken: appstoreToken, automatic: false })
|
||||
.reply(404, { version: '2.0.0-pre.0', changelog: [''], sourceTarballUrl: 'box-pre.tar.gz' } );
|
||||
|
||||
updatechecker.checkForUpdates({ automatic: false }, function (error) {
|
||||
expect(error).to.be.ok();
|
||||
expect(updatechecker.getUpdateInfo().box.version).to.be(UPDATE_VERSION);
|
||||
expect(updatechecker.getUpdateInfo().box.sourceTarballUrl).to.be('box.tar.gz');
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
done();
|
||||
});
|
||||
await updatechecker.checkForUpdates({ automatic: false });
|
||||
expect(updatechecker.getUpdateInfo().box.version).to.be(UPDATE_VERSION);
|
||||
expect(updatechecker.getUpdateInfo().box.sourceTarballUrl).to.be('box.tar.gz');
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,62 +79,50 @@ describe('updatechecker', function () {
|
||||
settings.setAutoupdatePattern(constants.AUTOUPDATE_PATTERN_NEVER, done);
|
||||
});
|
||||
|
||||
it('no updates', function (done) {
|
||||
it('no updates', async function () {
|
||||
nock.cleanAll();
|
||||
|
||||
var scope = nock(mockApiServerOrigin)
|
||||
const scope = nock(mockApiServerOrigin)
|
||||
.get('/api/v1/appupdate')
|
||||
.query({ boxVersion: constants.VERSION, accessToken: appstoreToken, appId: app.appStoreId, appVersion: app.manifest.version, automatic: false })
|
||||
.reply(204, { } );
|
||||
|
||||
updatechecker._checkAppUpdates({ automatic: false }, function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({});
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
done();
|
||||
});
|
||||
await updatechecker._checkAppUpdates({ automatic: false });
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({});
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
});
|
||||
|
||||
it('bad response', function (done) {
|
||||
it('bad response', async function () {
|
||||
nock.cleanAll();
|
||||
|
||||
var scope = nock(mockApiServerOrigin)
|
||||
const scope = nock(mockApiServerOrigin)
|
||||
.get('/api/v1/appupdate')
|
||||
.query({ boxVersion: constants.VERSION, accessToken: appstoreToken, appId: app.appStoreId, appVersion: app.manifest.version, automatic: false })
|
||||
.reply(500, { update: { manifest: { version: '1.0.0', changelog: '* some changes' } } } );
|
||||
|
||||
updatechecker._checkAppUpdates({ automatic: false }, function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({});
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
done();
|
||||
});
|
||||
await updatechecker._checkAppUpdates({ automatic: false });
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({});
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
});
|
||||
|
||||
it('offers new version', function (done) {
|
||||
it('offers new version', async function () {
|
||||
nock.cleanAll();
|
||||
|
||||
var scope = nock(mockApiServerOrigin)
|
||||
const scope = nock(mockApiServerOrigin)
|
||||
.get('/api/v1/appupdate')
|
||||
.query({ boxVersion: constants.VERSION, accessToken: appstoreToken, appId: app.appStoreId, appVersion: app.manifest.version, automatic: false })
|
||||
.reply(200, { manifest: { version: '2.0.0', changelog: '* some changes' } } );
|
||||
|
||||
updatechecker._checkAppUpdates({ automatic: false }, function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({ 'appid': { manifest: { version: '2.0.0', changelog: '* some changes' }, unstable: false } });
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
done();
|
||||
});
|
||||
await updatechecker._checkAppUpdates({ automatic: false });
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({ 'appid': { manifest: { version: '2.0.0', changelog: '* some changes' }, unstable: false } });
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
});
|
||||
|
||||
it('does not offer old version', function (done) {
|
||||
it('does not offer old version', async function () {
|
||||
nock.cleanAll();
|
||||
|
||||
updatechecker._checkAppUpdates({ automatic: false }, function (error) {
|
||||
expect(!error).to.be.ok();
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({ });
|
||||
done();
|
||||
});
|
||||
await updatechecker._checkAppUpdates({ automatic: false });
|
||||
expect(updatechecker.getUpdateInfo()).to.eql({ });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+42
-62
@@ -11,11 +11,11 @@ exports = module.exports = {
|
||||
const apps = require('./apps.js'),
|
||||
appstore = require('./appstore.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
debug = require('debug')('box:updatechecker'),
|
||||
notifications = require('./notifications.js'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance');
|
||||
safe = require('safetydance'),
|
||||
util = require('util');
|
||||
|
||||
function setUpdateInfo(state) {
|
||||
// appid -> update info { creationDate, manifest }
|
||||
@@ -31,95 +31,75 @@ function getUpdateInfo() {
|
||||
return state;
|
||||
}
|
||||
|
||||
function checkAppUpdates(options, callback) {
|
||||
async function checkAppUpdates(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('checkAppUpdates: checking for updates');
|
||||
|
||||
let state = getUpdateInfo();
|
||||
let newState = { }; // create new state so that old app ids are removed
|
||||
|
||||
apps.getAll(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const appsGetAllAsync = util.promisify(apps.getAll);
|
||||
|
||||
async.eachSeries(result, function (app, iteratorDone) {
|
||||
if (app.appStoreId === '') return iteratorDone(); // appStoreId can be '' for dev apps
|
||||
const result = await appsGetAllAsync();
|
||||
|
||||
appstore.getAppUpdate(app, options, function (error, updateInfo) {
|
||||
if (error) {
|
||||
debug('checkAppUpdates: Error getting app update info for %s', app.id, error);
|
||||
return iteratorDone(); // continue to next
|
||||
}
|
||||
for (const app of result) {
|
||||
if (app.appStoreId === '') continue; // appStoreId can be '' for dev apps
|
||||
|
||||
if (!updateInfo) return iteratorDone(); // skip if no next version is found
|
||||
const [error, updateInfo] = await safe(appstore.getAppUpdate(app, options));
|
||||
if (error) {
|
||||
debug('checkAppUpdates: Error getting app update info for %s', app.id, error);
|
||||
continue; // continue to next
|
||||
}
|
||||
|
||||
newState[app.id] = updateInfo;
|
||||
if (!updateInfo) continue; // skip if no next version is found
|
||||
newState[app.id] = updateInfo;
|
||||
}
|
||||
|
||||
iteratorDone();
|
||||
});
|
||||
}, function () {
|
||||
if ('box' in state) newState.box = state.box; // preserve the latest box state information
|
||||
|
||||
setUpdateInfo(newState);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
if ('box' in state) newState.box = state.box; // preserve the latest box state information
|
||||
setUpdateInfo(newState);
|
||||
}
|
||||
|
||||
function checkBoxUpdates(options, callback) {
|
||||
async function checkBoxUpdates(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('checkBoxUpdates: checking for updates');
|
||||
|
||||
appstore.getBoxUpdate(options, async function (error, updateInfo) {
|
||||
if (error) return callback(error);
|
||||
const updateInfo = await appstore.getBoxUpdate(options);
|
||||
|
||||
let state = getUpdateInfo();
|
||||
let state = getUpdateInfo();
|
||||
|
||||
if (!updateInfo) { // no update
|
||||
if ('box' in state) {
|
||||
delete state.box;
|
||||
setUpdateInfo(state);
|
||||
}
|
||||
debug('checkBoxUpdates: no updates');
|
||||
return callback(null);
|
||||
if (!updateInfo) { // no update
|
||||
if ('box' in state) {
|
||||
delete state.box;
|
||||
setUpdateInfo(state);
|
||||
}
|
||||
debug('checkBoxUpdates: no updates');
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.box && state.box.version === updateInfo.version) {
|
||||
debug(`checkBoxUpdates: Skipping notification of box update ${updateInfo.version} as user was already notified`);
|
||||
return callback(null);
|
||||
}
|
||||
if (state.box && state.box.version === updateInfo.version) {
|
||||
debug(`checkBoxUpdates: Skipping notification of box update ${updateInfo.version} as user was already notified`);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`checkBoxUpdates: ${updateInfo.version} is available`);
|
||||
debug(`checkBoxUpdates: ${updateInfo.version} is available`);
|
||||
|
||||
const changelog = updateInfo.changelog.map((m) => `* ${m}\n`).join('');
|
||||
const changelog = updateInfo.changelog.map((m) => `* ${m}\n`).join('');
|
||||
const message = `Changelog:\n${changelog}\n\nGo to the settings view to update.\n\n`;
|
||||
|
||||
const message = `Changelog:\n${changelog}\n\nGo to the settings view to update.\n\n`;
|
||||
await notifications.alert(notifications.ALERT_BOX_UPDATE, `Cloudron v${updateInfo.version} is available`, message);
|
||||
|
||||
[error] = await safe(notifications.alert(notifications.ALERT_BOX_UPDATE, `Cloudron v${updateInfo.version} is available`, message));
|
||||
if (error) return callback(error);
|
||||
|
||||
state.box = updateInfo;
|
||||
setUpdateInfo(state);
|
||||
|
||||
callback(null);
|
||||
});
|
||||
state.box = updateInfo;
|
||||
setUpdateInfo(state);
|
||||
}
|
||||
|
||||
function checkForUpdates(options, callback) {
|
||||
async function checkForUpdates(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
checkBoxUpdates(options, function (boxError) {
|
||||
if (boxError) debug('checkForUpdates: error checking for box updates:', boxError);
|
||||
const [boxError] = await safe(checkBoxUpdates(options));
|
||||
if (boxError) debug('checkForUpdates: error checking for box updates:', boxError);
|
||||
|
||||
checkAppUpdates(options, function (appError) {
|
||||
if (appError) debug('checkForUpdates: error checking for app updates:', appError);
|
||||
|
||||
callback(boxError || appError || null);
|
||||
});
|
||||
});
|
||||
const [appError] = await safe(checkAppUpdates(options));
|
||||
if (appError) debug('checkForUpdates: error checking for app updates:', appError);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user