531 lines
24 KiB
JavaScript
531 lines
24 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
getFeatures,
|
|
|
|
getApps,
|
|
getApp,
|
|
getAppVersion,
|
|
|
|
trackBeginSetup,
|
|
trackFinishedSetup,
|
|
|
|
registerWithLoginCredentials,
|
|
|
|
purchaseApp,
|
|
unpurchaseApp,
|
|
|
|
getUserToken,
|
|
getSubscription,
|
|
isFreePlan,
|
|
|
|
getAppUpdate,
|
|
getBoxUpdate,
|
|
|
|
createTicket
|
|
};
|
|
|
|
var apps = require('./apps.js'),
|
|
assert = require('assert'),
|
|
async = require('async'),
|
|
BoxError = require('./boxerror.js'),
|
|
constants = require('./constants.js'),
|
|
debug = require('debug')('box:appstore'),
|
|
eventlog = require('./eventlog.js'),
|
|
path = require('path'),
|
|
paths = require('./paths.js'),
|
|
safe = require('safetydance'),
|
|
semver = require('semver'),
|
|
settings = require('./settings.js'),
|
|
superagent = require('superagent'),
|
|
support = require('./support.js'),
|
|
util = require('util');
|
|
|
|
// These are the default options and will be adjusted once a subscription state is obtained
|
|
// Keep in sync with appstore/routes/cloudrons.js
|
|
let gFeatures = {
|
|
userMaxCount: 5,
|
|
userGroups: false,
|
|
userRoles: false,
|
|
domainMaxCount: 1,
|
|
externalLdap: false,
|
|
privateDockerRegistry: false,
|
|
branding: false,
|
|
support: false,
|
|
directoryConfig: false,
|
|
mailboxMaxCount: 5,
|
|
emailPremium: false
|
|
};
|
|
|
|
// attempt to load feature cache in case appstore would be down
|
|
let tmp = safe.JSON.parse(safe.fs.readFileSync(paths.FEATURES_INFO_FILE, 'utf8'));
|
|
if (tmp) gFeatures = tmp;
|
|
|
|
function getFeatures() {
|
|
return gFeatures;
|
|
}
|
|
|
|
function isAppAllowed(appstoreId, listingConfig) {
|
|
assert.strictEqual(typeof listingConfig, 'object');
|
|
assert.strictEqual(typeof appstoreId, 'string');
|
|
|
|
if (listingConfig.blacklist && listingConfig.blacklist.includes(appstoreId)) return false;
|
|
|
|
if (listingConfig.whitelist) return listingConfig.whitelist.includes(appstoreId);
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
function login(email, password, totpToken, callback) {
|
|
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 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}`));
|
|
|
|
callback(null, result.body); // { userId, accessToken }
|
|
});
|
|
}
|
|
|
|
function registerUser(email, password, callback) {
|
|
assert.strictEqual(typeof email, 'string');
|
|
assert.strictEqual(typeof password, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
var data = {
|
|
email: email,
|
|
password: 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}`));
|
|
|
|
callback(null);
|
|
});
|
|
}
|
|
|
|
function getUserToken(callback) {
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
if (settings.isDemo()) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'));
|
|
|
|
getCloudronToken(function (error, token) {
|
|
if (error) return callback(error);
|
|
|
|
const url = `${settings.apiServerOrigin()}/api/v1/user_token`;
|
|
|
|
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);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getSubscription(callback) {
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
getCloudronToken(function (error, token) {
|
|
if (error) return callback(error);
|
|
|
|
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}`));
|
|
|
|
// update the features cache
|
|
gFeatures = result.body.features;
|
|
safe.fs.writeFileSync(paths.FEATURES_INFO_FILE, JSON.stringify(gFeatures), 'utf8');
|
|
|
|
callback(null, result.body);
|
|
});
|
|
});
|
|
}
|
|
|
|
function isFreePlan(subscription) {
|
|
return !subscription || subscription.plan.id === 'free';
|
|
}
|
|
|
|
// See app.js install it will create a db record first but remove it again if appstore purchase fails
|
|
function purchaseApp(data, callback) {
|
|
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 url = `${settings.apiServerOrigin()}/api/v1/cloudronapps`;
|
|
|
|
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);
|
|
});
|
|
});
|
|
}
|
|
|
|
function unpurchaseApp(appId, data, callback) {
|
|
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 url = `${settings.apiServerOrigin()}/api/v1/cloudronapps/${appId}`;
|
|
|
|
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)));
|
|
|
|
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)));
|
|
|
|
callback(null);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function getBoxUpdate(options, callback) {
|
|
assert.strictEqual(typeof options, 'object');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
getCloudronToken(function (error, token) {
|
|
if (error) return callback(error);
|
|
|
|
const url = `${settings.apiServerOrigin()}/api/v1/boxupdate`;
|
|
|
|
const query = {
|
|
accessToken: token,
|
|
boxVersion: constants.VERSION,
|
|
automatic: options.automatic
|
|
};
|
|
|
|
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)));
|
|
|
|
var updateInfo = result.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)));
|
|
}
|
|
|
|
// 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)));
|
|
|
|
callback(null, updateInfo);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getAppUpdate(app, options, callback) {
|
|
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 url = `${settings.apiServerOrigin()}/api/v1/appupdate`;
|
|
const query = {
|
|
accessToken: token,
|
|
boxVersion: constants.VERSION,
|
|
appId: app.appStoreId,
|
|
appVersion: app.manifest.version,
|
|
automatic: options.automatic
|
|
};
|
|
|
|
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)));
|
|
|
|
const updateInfo = result.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`;
|
|
|
|
// 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)));
|
|
}
|
|
|
|
updateInfo.unstable = !!updateInfo.unstable;
|
|
|
|
// { id, creationDate, manifest, unstable }
|
|
callback(null, updateInfo);
|
|
});
|
|
});
|
|
}
|
|
|
|
function registerCloudron(data, callback) {
|
|
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}`));
|
|
|
|
// 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'));
|
|
|
|
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);
|
|
|
|
debug(`registerCloudron: Cloudron registered with id ${result.body.cloudronId}`);
|
|
|
|
callback();
|
|
});
|
|
});
|
|
}
|
|
|
|
// This works without a Cloudron token as this Cloudron was not yet registered
|
|
let gBeginSetupAlreadyTracked = false;
|
|
function trackBeginSetup() {
|
|
// avoid browser reload double tracking, not perfect since box might restart, but covers most cases and is simple
|
|
if (gBeginSetupAlreadyTracked) return;
|
|
gBeginSetupAlreadyTracked = true;
|
|
|
|
const url = `${settings.apiServerOrigin()}/api/v1/helper/setup_begin`;
|
|
|
|
superagent.post(url).send({}).timeout(30 * 1000).end(function (error, result) {
|
|
if (error && !error.response) return debug(`trackBeginSetup: ${error.message}`);
|
|
if (result.statusCode !== 200) return debug(`trackBeginSetup: ${result.statusCode} ${error.message}`);
|
|
});
|
|
}
|
|
|
|
// This works without a Cloudron token as this Cloudron was not yet registered
|
|
function trackFinishedSetup(domain) {
|
|
assert.strictEqual(typeof domain, 'string');
|
|
|
|
const url = `${settings.apiServerOrigin()}/api/v1/helper/setup_finished`;
|
|
|
|
superagent.post(url).send({ domain }).timeout(30 * 1000).end(function (error, result) {
|
|
if (error && !error.response) return debug(`trackFinishedSetup: ${error.message}`);
|
|
if (result.statusCode !== 200) return debug(`trackFinishedSetup: ${result.statusCode} ${error.message}`);
|
|
});
|
|
}
|
|
|
|
function registerWithLoginCredentials(options, callback) {
|
|
assert.strictEqual(typeof options, 'object');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
function maybeSignup(done) {
|
|
if (!options.signup) return done();
|
|
|
|
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.adminDomain(), accessToken: result.accessToken, version: constants.VERSION, purpose: options.purpose || '' }, callback);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function createTicket(info, auditSource, callback) {
|
|
assert.strictEqual(typeof info, 'object');
|
|
assert.strictEqual(typeof info.email, 'string');
|
|
assert.strictEqual(typeof info.displayName, 'string');
|
|
assert.strictEqual(typeof info.type, 'string');
|
|
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);
|
|
}
|
|
|
|
function enableSshIfNeeded(callback) {
|
|
if (!info.enableSshSupport) return callback();
|
|
|
|
support.enableRemoteSupport(true, auditSource, function (error) {
|
|
// ensure we can at least get the ticket through
|
|
if (error) debug('Unable to enable SSH support.', error);
|
|
|
|
callback();
|
|
});
|
|
}
|
|
|
|
getCloudronToken(function (error, token) {
|
|
if (error) return callback(error);
|
|
|
|
enableSshIfNeeded(function (error) {
|
|
if (error) return callback(error);
|
|
|
|
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!` });
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function getApps(callback) {
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
getCloudronToken(function (error, token) {
|
|
if (error) return callback(error);
|
|
|
|
settings.getUnstableAppsConfig(function (error, unstable) {
|
|
if (error) return callback(error);
|
|
|
|
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)));
|
|
|
|
settings.getAppstoreListingConfig(function (error, listingConfig) {
|
|
if (error) return callback(error);
|
|
|
|
const filteredApps = result.body.apps.filter(app => isAppAllowed(app.id, listingConfig));
|
|
|
|
callback(null, filteredApps);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function getAppVersion(appId, version, callback) {
|
|
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);
|
|
|
|
if (!isAppAllowed(appId, listingConfig)) return callback(new BoxError(BoxError.FEATURE_DISABLED));
|
|
|
|
getCloudronToken(function (error, token) {
|
|
if (error) return callback(error);
|
|
|
|
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)));
|
|
|
|
callback(null, result.body);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function getApp(appId, callback) {
|
|
assert.strictEqual(typeof appId, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
getAppVersion(appId, 'latest', callback);
|
|
}
|