diff --git a/migrations/20220401045439-settings-get-appstore-web-token.js b/migrations/20220401045439-settings-get-appstore-web-token.js new file mode 100644 index 000000000..01c1ecff6 --- /dev/null +++ b/migrations/20220401045439-settings-get-appstore-web-token.js @@ -0,0 +1,37 @@ +'use strict'; + +const superagent = require('superagent'); + +exports.up = function(db, callback) { + db.all('SELECT value FROM settings WHERE name="api_server_origin"', function (error, results) { + if (error || results.length === 0) return callback(error); + const apiServerOrigin = results[0].value; + + db.all('SELECT value FROM settings WHERE name="appstore_api_token"', function (error, results) { + if (error || results.length === 0) return callback(error); + const apiToken = results[0].value; + + console.log(`Getting appstore web token from ${apiServerOrigin}`); + + superagent.post(`${apiServerOrigin}/api/v1/user_token`) + .send({}) + .query({ accessToken: apiToken }) + .timeout(30 * 1000).end(function (error, response) { + if (error && !error.response) { + console.log('Network error getting web token', error); + return callback(); + } + if (response.statusCode !== 201 || !response.body.accessToken) { + console.log(`Bad status getting web token: ${response.status} ${response.text}`); + return callback(); + } + + db.runSql('INSERT settings (name, value) VALUES(?, ?)', [ 'appstore_web_token', response.body.accessToken ], callback); + }); + }); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/src/appstore.js b/src/appstore.js index 497c23c98..a4ad76304 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -14,7 +14,7 @@ exports = module.exports = { purchaseApp, unpurchaseApp, - createUserToken, + getWebToken, getSubscription, isFreePlan, @@ -113,24 +113,13 @@ async function registerUser(email, password) { if (response.status !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, `register status code: ${response.status}`); } -async function createUserToken() { +async function getWebToken() { if (settings.isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); - const token = await settings.getAppstoreApiToken(); - if (!token) throw new BoxError(BoxError.LICENSE_ERROR, 'Missing token'); + const token = await settings.getAppstoreWebToken(); + if (!token) throw new BoxError(BoxError.NOT_FOUND); // user will have to re-login with password somehow - const url = `${settings.apiServerOrigin()}/api/v1/user_token`; - - const [error, response] = await safe(superagent.post(url) - .send({}) - .query({ accessToken: token }) - .timeout(30 * 1000) - .ok(() => true)); - - 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}`); - - return response.body.accessToken; + return token; } async function getSubscription() { @@ -308,10 +297,10 @@ async function getAppUpdate(app, options) { async function registerCloudron(data) { assert.strictEqual(typeof data, 'object'); - const url = `${settings.apiServerOrigin()}/api/v1/register_cloudron`; + const { domain, accessToken, version } = data; - const [error, response] = await safe(superagent.post(url) - .send(data) + const [error, response] = await safe(superagent.post(`${settings.apiServerOrigin()}/api/v1/register_cloudron`) + .send({ domain, accessToken, version }) .timeout(30 * 1000) .ok(() => true)); @@ -324,6 +313,7 @@ async function registerCloudron(data) { await settings.setCloudronId(response.body.cloudronId); await settings.setAppstoreApiToken(response.body.cloudronToken); + await settings.setAppstoreWebToken(accessToken); debug(`registerCloudron: Cloudron registered with id ${response.body.cloudronId}`); } diff --git a/src/routes/appstore.js b/src/routes/appstore.js index b40c35be9..44724ad29 100644 --- a/src/routes/appstore.js +++ b/src/routes/appstore.js @@ -5,7 +5,7 @@ exports = module.exports = { getApp, getAppVersion, - createUserToken, + getWebToken, registerCloudron, getSubscription }; @@ -45,11 +45,11 @@ async function getAppVersion(req, res, next) { next(new HttpSuccess(200, manifest)); } -async function createUserToken(req, res, next) { - const [error, accessToken] = await safe(appstore.createUserToken()); +async function getWebToken(req, res, next) { + const [error, accessToken] = await safe(appstore.getWebToken()); if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(201, { accessToken })); + next(new HttpSuccess(200, { accessToken })); } async function registerCloudron(req, res, next) { diff --git a/src/server.js b/src/server.js index 68cb212bf..d86dcd306 100644 --- a/src/server.js +++ b/src/server.js @@ -192,9 +192,9 @@ function initializeExpressSync() { router.del ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.remove); // appstore and subscription routes - router.post('/api/v1/appstore/register_cloudron', json, token, authorizeAdmin, routes.appstore.registerCloudron); - router.post('/api/v1/appstore/user_token', json, token, authorizeAdmin, routes.appstore.createUserToken); - router.get ('/api/v1/appstore/subscription', token, routes.appstore.getSubscription); + router.post('/api/v1/appstore/register_cloudron', json, token, authorizeOwner, routes.appstore.registerCloudron); + router.get ('/api/v1/appstore/web_token', json, token, authorizeOwner, routes.appstore.getWebToken); + router.get ('/api/v1/appstore/subscription', token, routes.appstore.getSubscription); // for all users router.get ('/api/v1/appstore/apps', token, authorizeAdmin, routes.appstore.getApps); router.get ('/api/v1/appstore/apps/:appstoreId', token, authorizeAdmin, routes.appstore.getApp); router.get ('/api/v1/appstore/apps/:appstoreId/versions/:versionId', token, authorizeAdmin, routes.appstore.getAppVersion); diff --git a/src/settings.js b/src/settings.js index 357e16b8a..05d7aa79f 100644 --- a/src/settings.js +++ b/src/settings.js @@ -49,6 +49,9 @@ exports = module.exports = { getAppstoreApiToken, setAppstoreApiToken, + getAppstoreWebToken, + setAppstoreWebToken, + getSysinfoConfig, setSysinfoConfig, @@ -113,6 +116,7 @@ exports = module.exports = { LANGUAGE_KEY: 'language', CLOUDRON_ID_KEY: 'cloudron_id', APPSTORE_API_TOKEN_KEY: 'appstore_api_token', + APPSTORE_WEB_TOKEN_KEY: 'appstore_web_token', FIREWALL_BLOCKLIST_KEY: 'firewall_blocklist', API_SERVER_ORIGIN_KEY: 'api_server_origin', @@ -171,6 +175,7 @@ const gDefaults = (function () { result[exports.LANGUAGE_KEY] = 'en'; result[exports.CLOUDRON_ID_KEY] = ''; result[exports.APPSTORE_API_TOKEN_KEY] = ''; + result[exports.APPSTORE_WEB_TOKEN_KEY] = ''; result[exports.BACKUP_CONFIG_KEY] = { provider: 'filesystem', backupFolder: '/var/backups', @@ -708,6 +713,19 @@ async function setAppstoreApiToken(token) { notifyChange(exports.APPSTORE_API_TOKEN_KEY, token); } +async function getAppstoreWebToken() { + const value = await get(exports.APPSTORE_WEB_TOKEN_KEY); + if (value === null) return gDefaults[exports.APPSTORE_WEB_TOKEN_KEY]; + return value; +} + +async function setAppstoreWebToken(token) { + assert.strictEqual(typeof token, 'string'); + + await set(exports.APPSTORE_WEB_TOKEN_KEY, token); + notifyChange(exports.APPSTORE_WEB_TOKEN_KEY, token); +} + async function list() { const settings = await database.query(`SELECT ${SETTINGS_FIELDS} FROM settings WHERE value IS NOT NULL ORDER BY name`);