diff --git a/src/apps.js b/src/apps.js index 19d490be1..c4f54c804 100644 --- a/src/apps.js +++ b/src/apps.js @@ -688,21 +688,8 @@ function install(data, user, auditSource, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - appstore.purchase({ appId: appId, appstoreId: appStoreId, manifestId: manifest.id }, function (appstoreError) { - // if purchase failed, rollback the appdb record - if (appstoreError) { - appdb.del(appId, function (error) { - if (error) debug('install: Failed to rollback app installation.', error); - - if (appstoreError.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, appstoreError.message)); - if (appstoreError && appstoreError.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, appstoreError.message)); - if (appstoreError && appstoreError.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, appstoreError.message)); - - callback(new AppsError(AppsError.INTERNAL_ERROR, appstoreError)); - }); - - return; - } + purchaseApp({ appId: appId, appstoreId: appStoreId, manifestId: manifest.id }, function (error) { + if (error) return callback(error); // save cert to boxdata/certs if (cert && key) { @@ -1047,6 +1034,28 @@ function restore(appId, data, auditSource, callback) { }); } +function purchaseApp(data, callback) { + assert.strictEqual(typeof data, 'object'); + assert.strictEqual(typeof callback, 'function'); + + appstore.purchase(data, function (error) { + if (!error) return callback(); + + // if purchase failed, rollback the appdb record + appdb.del(data.appId, function (error) { + if (error) debug('install: Failed to rollback app installation.', error); + + if (error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message)); + if (error && error.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.LICENSE_ERROR) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); + + callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + }); + }); +} + function clone(appId, data, user, auditSource, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof data, 'object'); @@ -1121,21 +1130,8 @@ function clone(appId, data, user, auditSource, callback) { if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error, location, domainObject, portBindings)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - appstore.purchase({ appId: newAppId, appstoreId: app.appStoreId, manifestId: manifest.id }, function (appstoreError) { - // if purchase failed, rollback the appdb record - if (appstoreError) { - appdb.del(newAppId, function (error) { - if (error) debug('install: Failed to rollback app installation.', error); - - if (appstoreError.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, appstoreError.message)); - if (appstoreError && appstoreError.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, appstoreError.message)); - if (appstoreError && appstoreError.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, appstoreError.message)); - - callback(new AppsError(AppsError.INTERNAL_ERROR, appstoreError)); - }); - - return; - } + purchaseApp({ appId: newAppId, appstoreId: app.appStoreId, manifestId: manifest.id }, function (error) { + if (error) return callback(error); taskmanager.restartAppTask(newAppId); @@ -1167,6 +1163,8 @@ function uninstall(appId, auditSource, callback) { appstore.unpurchase(appId, { appstoreId: app.appStoreId, manifestId: app.manifest.id }, function (error) { if (error && error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND)); if (error && error.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.LICENSE_ERROR) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); diff --git a/src/appstore.js b/src/appstore.js index d30f5a4a2..fa41e6e40 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -61,7 +61,9 @@ AppstoreError.EXTERNAL_ERROR = 'External Error'; AppstoreError.ALREADY_EXISTS = 'Already Exists'; AppstoreError.ACCESS_DENIED = 'Access Denied'; AppstoreError.NOT_FOUND = 'Internal Error'; -AppstoreError.BILLING_REQUIRED = 'Billing Required'; +AppstoreError.BILLING_REQUIRED = 'Billing Required'; // upstream 402 (subsciption_expired and subscription_required) +AppstoreError.LICENSE_ERROR = 'License Error'; // upstream 422 (no license, invalid license) +AppstoreError.INVALID_TOKEN = 'Invalid token'; // upstream 401 (no token) var NOOP_CALLBACK = function (error) { if (error) debug(error); }; @@ -70,7 +72,7 @@ function getCloudronToken(callback) { settings.getCloudronToken(function (error, token) { if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error)); - if (!token) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + if (!token) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); callback(null, token); }); @@ -132,7 +134,7 @@ function getSubscription(callback) { if (result.statusCode === 502) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'stripe error')); if (result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'unknown error')); - callback(null, result.body); // { email, subscription} + callback(null, result.body); // { email, subscription } }); }); } @@ -156,8 +158,9 @@ function purchase(data, callback) { superagent.post(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 402) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED, result.body.message)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body))); callback(null); @@ -178,8 +181,9 @@ function unpurchase(appId, data, callback) { superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 404) return callback(null); // was never purchased + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.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) { @@ -271,6 +275,8 @@ function sendAliveStatus(callback) { superagent.post(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Sending alive status failed. %s %j', result.status, result.body))); callback(null); @@ -289,6 +295,8 @@ function getBoxUpdate(callback) { superagent.get(url).query({ accessToken: token, boxVersion: config.version() }).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode === 204) return callback(null); // no update if (result.statusCode !== 200 || !result.body) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text))); @@ -322,6 +330,8 @@ function getAppUpdate(app, callback) { superagent.get(url).query({ accessToken: token, boxVersion: config.version(), appId: app.appStoreId, appVersion: app.manifest.version }).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode === 204) return callback(null); // no update if (result.statusCode !== 200 || !result.body) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text))); @@ -350,7 +360,6 @@ function registerCloudron(token, callback) { superagent.post(url).send({ domain: config.adminDomain() }).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'invalid appstore token')); if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'unable to register cloudron')); // cloudronId, token, licenseKey @@ -418,6 +427,8 @@ function sendFeedback(info, callback) { superagent.post(url).query({ accessToken: token }).send(info).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text))); callback(null); @@ -437,8 +448,8 @@ function getApps(callback) { const url = `${config.apiServerOrigin()}/api/v1/apps`; superagent.get(url).query({ accessToken: token, boxVersion: config.version(), unstable: unstable }).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); - if (result.statusCode === 402) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED, result.body.message)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App listing failed. %s %j', result.status, result.body))); if (!result.body.apps) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text))); @@ -461,7 +472,8 @@ function getAppVersion(appId, version, callback) { superagent.get(url).query({ accessToken: token }).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode === 402) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED, result.body.message)); if (result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App fetch failed. %s %j', result.status, result.body))); diff --git a/src/routes/appstore.js b/src/routes/appstore.js index ec33bff1e..5588155bd 100644 --- a/src/routes/appstore.js +++ b/src/routes/appstore.js @@ -14,7 +14,8 @@ var appstore = require('../appstore.js'), function getApps(req, res, next) { appstore.getApps(function (error, apps) { - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return next(new HttpError(402, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return next(new HttpError(402, error.message)); + if (error && error.reason === AppstoreError.LICENSE_ERROR) return next(new HttpError(402, error.message)); if (error) return next(new HttpError(500, error)); next(new HttpSuccess(200, { apps: apps })); @@ -26,7 +27,8 @@ function getApp(req, res, next) { appstore.getApp(req.params.appstoreId, function (error, app) { if (error && error.reason === AppstoreError.NOT_FOUND) return next(new HttpError(404, 'No such app')); - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return next(new HttpError(402, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return next(new HttpError(402, error.message)); + if (error && error.reason === AppstoreError.LICENSE_ERROR) return next(new HttpError(402, error.message)); if (error) return next(new HttpError(500, error)); next(new HttpSuccess(200, app)); @@ -39,7 +41,8 @@ function getAppVersion(req, res, next) { appstore.getAppVersion(req.params.appstoreId, req.params.versionId, function (error, manifest) { if (error && error.reason === AppstoreError.NOT_FOUND) return next(new HttpError(404, 'No such app or version')); - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return next(new HttpError(402, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return next(new HttpError(402, error.message)); + if (error && error.reason === AppstoreError.LICENSE_ERROR) return next(new HttpError(402, error.message)); if (error) return next(new HttpError(500, error)); next(new HttpSuccess(200, manifest)); diff --git a/src/routes/subscription.js b/src/routes/subscription.js index e40d727ab..aec316b03 100644 --- a/src/routes/subscription.js +++ b/src/routes/subscription.js @@ -33,7 +33,7 @@ function getSubscription(req, res, next) { assert.strictEqual(typeof req.body, 'object'); appstore.getSubscription(function (error, result) { - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return next(new HttpError(402, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return next(new HttpError(402, error.message)); if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return next(new HttpError(424, error.message)); if (error) return next(new HttpError(500, error));