diff --git a/src/appstore.js b/src/appstore.js index fa41e6e40..36a2da599 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -5,12 +5,13 @@ exports = module.exports = { getApp: getApp, getAppVersion: getAppVersion, + registerCloudron: registerCloudron, + purchase: purchase, unpurchase: unpurchase, getSubscription: getSubscription, isFreePlan: isFreePlan, - subscribeCloudron: subscribeCloudron, sendAliveStatus: sendAliveStatus, @@ -352,7 +353,7 @@ function getAppUpdate(app, callback) { }); } -function registerCloudron(token, callback) { +function subscribeCloudron(token, callback) { assert.strictEqual(typeof token, 'string'); assert.strictEqual(typeof callback, 'function'); @@ -381,7 +382,7 @@ function registerCloudron(token, callback) { }); } -function subscribeCloudron(options, callback) { +function registerCloudron(options, callback) { assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof callback, 'function'); @@ -397,7 +398,7 @@ function subscribeCloudron(options, callback) { login(options.email, options.password, options.totpToken || '', function (error, result) { if (error) return callback(error); - registerCloudron(result.accessToken, callback); + subscribeCloudron(result.accessToken, callback); }); }); } diff --git a/src/routes/appstore.js b/src/routes/appstore.js index 5588155bd..7f86e1b6c 100644 --- a/src/routes/appstore.js +++ b/src/routes/appstore.js @@ -3,7 +3,10 @@ exports = module.exports = { getApps: getApps, getApp: getApp, - getAppVersion: getAppVersion + getAppVersion: getAppVersion, + + registerCloudron: registerCloudron, + getSubscription: getSubscription }; var appstore = require('../appstore.js'), @@ -48,3 +51,33 @@ function getAppVersion(req, res, next) { next(new HttpSuccess(200, manifest)); }); } + +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')); + if (typeof req.body.password !== 'string' || !req.body.password) return next(new HttpError(400, 'password must be string')); + 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.registerCloudron(req.body, function (error) { + if (error && error.reason === AppstoreError.ALREADY_EXISTS) return next(new HttpError(409, error.message)); + if (error && error.reason === AppstoreError.ACCESS_DENIED) return next(new HttpError(412, error.message)); + if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return next(new HttpError(424, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(201, {})); + }); +} + +function getSubscription(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + appstore.getSubscription(function (error, result) { + 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)); + + next(new HttpSuccess(200, result)); // { email, subscription } + }); +} diff --git a/src/routes/index.js b/src/routes/index.js index 45263e53a..b9600837b 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -19,7 +19,6 @@ exports = module.exports = { provision: require('./provision.js'), services: require('./services.js'), settings: require('./settings.js'), - subscription: require('./subscription.js'), support: require('./support.js'), sysadmin: require('./sysadmin.js'), tasks: require('./tasks.js'), diff --git a/src/routes/subscription.js b/src/routes/subscription.js deleted file mode 100644 index bc27e64f3..000000000 --- a/src/routes/subscription.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -exports = module.exports = { - subscribeCloudron: subscribeCloudron, - getSubscription: getSubscription -}; - -var appstore = require('../appstore.js'), - AppstoreError = appstore.AppstoreError, - assert = require('assert'), - HttpError = require('connect-lastmile').HttpError, - HttpSuccess = require('connect-lastmile').HttpSuccess; - -function subscribeCloudron(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')); - if (typeof req.body.password !== 'string' || !req.body.password) return next(new HttpError(400, 'password must be string')); - 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.subscribeCloudron(req.body, function (error) { - if (error && error.reason === AppstoreError.ALREADY_EXISTS) return next(new HttpError(409, error.message)); - if (error && error.reason === AppstoreError.ACCESS_DENIED) return next(new HttpError(412, error.message)); - if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return next(new HttpError(424, error.message)); - if (error) return next(new HttpError(500, error)); - - next(new HttpSuccess(201, {})); - }); -} - -function getSubscription(req, res, next) { - assert.strictEqual(typeof req.body, 'object'); - - appstore.getSubscription(function (error, result) { - 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)); - - next(new HttpSuccess(200, result)); // { email, subscription } - }); -} diff --git a/src/routes/test/appstore-test.js b/src/routes/test/appstore-test.js index 8daff4156..b35170c03 100644 --- a/src/routes/test/appstore-test.js +++ b/src/routes/test/appstore-test.js @@ -59,7 +59,7 @@ function cleanup(done) { }); } -describe('Appstore API', function () { +describe('Appstore Apps API', function () { before(setup); after(cleanup); @@ -81,7 +81,7 @@ describe('Appstore API', function () { }); }); - it('setup subscription', function (done) { + it('register cloudron', function (done) { var scope1 = nock(config.apiServerOrigin()) .post('/api/v1/login', (body) => body.email && body.password) .reply(200, { userId: 'userId', accessToken: 'SECRET_TOKEN' }); @@ -90,7 +90,7 @@ describe('Appstore API', function () { .post('/api/v1/register_cloudron?accessToken=SECRET_TOKEN', (body) => !!body.domain) .reply(201, { cloudronId: 'cid', cloudronToken: 'CLOUDRON_TOKEN', licenseKey: 'lkey' }); - superagent.post(SERVER_URL + '/api/v1/subscription') + superagent.post(SERVER_URL + '/api/v1/appstore/register_cloudron') .send({ email: 'test@cloudron.io', password: 'secret', signup: false }) .query({ access_token: token }) .end(function (error, result) { @@ -144,3 +144,69 @@ describe('Appstore API', function () { }); }); + +describe('Subscription API', function () { + before(setup); + after(cleanup); + + it('can setup subscription - no signup', function (done) { + var scope1 = nock(config.apiServerOrigin()) + .post('/api/v1/login', (body) => body.email && body.password) + .reply(200, { userId: 'userId', accessToken: 'SECRET_TOKEN' }); + + var scope2 = nock(config.apiServerOrigin()) + .post('/api/v1/register_cloudron?accessToken=SECRET_TOKEN', (body) => !!body.domain) + .reply(201, { cloudronId: 'cid', cloudronToken: 'CLOUDRON_TOKEN', licenseKey: 'lkey' }); + + superagent.post(SERVER_URL + '/api/v1/appstore/register_cloudron') + .send({ email: 'test@cloudron.io', password: 'secret', signup: false }) + .query({ access_token: token }) + .end(function (error, result) { + expect(result.statusCode).to.equal(201); + expect(scope1.isDone()).to.be.ok(); + expect(scope2.isDone()).to.be.ok(); + done(); + }); + }); + + it('can setup subscription - signup', function (done) { + var scope1 = nock(config.apiServerOrigin()) + .post('/api/v1/register_user', (body) => body.email && body.password) + .reply(201, { }); + + var scope2 = nock(config.apiServerOrigin()) + .post('/api/v1/login', (body) => body.email && body.password) + .reply(200, { userId: 'userId', accessToken: 'SECRET_TOKEN' }); + + var scope3 = nock(config.apiServerOrigin()) + .post('/api/v1/register_cloudron?accessToken=SECRET_TOKEN', (body) => !!body.domain) + .reply(201, { cloudronId: 'cid', cloudronToken: 'CLOUDRON_TOKEN', licenseKey: 'lkey' }); + + superagent.post(SERVER_URL + '/api/v1/appstore/register_cloudron') + .send({ email: 'test@cloudron.io', password: 'secret', signup: true }) + .query({ access_token: token }) + .end(function (error, result) { + expect(result.statusCode).to.equal(201); + expect(scope1.isDone()).to.be.ok(); + expect(scope2.isDone()).to.be.ok(); + expect(scope3.isDone()).to.be.ok(); + done(); + }); + }); + + it('can get subscription', function (done) { + var scope1 = nock(config.apiServerOrigin()) + .get('/api/v1/subscription?accessToken=CLOUDRON_TOKEN', () => true) + .reply(200, { subscription: { plan: { id: 'free' } }, email: 'test@cloudron.io' }); + + superagent.get(SERVER_URL + '/api/v1/appstore/subscription') + .query({ access_token: token }) + .end(function (error, result) { + expect(result.statusCode).to.equal(200); + expect(result.body.email).to.be('test@cloudron.io'); + expect(result.body.subscription).to.be.an('object'); + expect(scope1.isDone()).to.be.ok(); + done(); + }); + }); +}); diff --git a/src/routes/test/subscription-test.js b/src/routes/test/subscription-test.js deleted file mode 100644 index 201b6ba66..000000000 --- a/src/routes/test/subscription-test.js +++ /dev/null @@ -1,126 +0,0 @@ -/* global it:false */ -/* global describe:false */ -/* global before:false */ -/* global after:false */ - -'use strict'; - -var async = require('async'), - config = require('../../config.js'), - database = require('../../database.js'), - expect = require('expect.js'), - nock = require('nock'), - path = require('path'), - safe = require('safetydance'), - superagent = require('superagent'), - server = require('../../server.js'); - -var SERVER_URL = 'http://localhost:' + config.get('port'); - -var USERNAME = 'superadmin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com'; -var AUTHORIZED_KEYS_FILE = path.join(config.baseDir(), 'authorized_keys'); -var token = null; - -function setup(done) { - nock.cleanAll(); - config._reset(); - config.setFqdn('example-ssh-test.com'); - safe.fs.unlinkSync(AUTHORIZED_KEYS_FILE); - - async.series([ - server.start.bind(server), - - database._clear, - - function createAdmin(callback) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: PASSWORD, email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(201); - - // stash token for further use - token = result.body.token; - - callback(); - }); - } - ], done); -} - -function cleanup(done) { - database._clear(function (error) { - expect(error).to.not.be.ok(); - - config._reset(); - - server.stop(done); - }); -} - -describe('Subscription API', function () { - before(setup); - after(cleanup); - - it('can setup subscription - no signup', function (done) { - var scope1 = nock(config.apiServerOrigin()) - .post('/api/v1/login', (body) => body.email && body.password) - .reply(200, { userId: 'userId', accessToken: 'SECRET_TOKEN' }); - - var scope2 = nock(config.apiServerOrigin()) - .post('/api/v1/register_cloudron?accessToken=SECRET_TOKEN', (body) => !!body.domain) - .reply(201, { cloudronId: 'cid', cloudronToken: 'CLOUDRON_TOKEN', licenseKey: 'lkey' }); - - superagent.post(SERVER_URL + '/api/v1/subscription') - .send({ email: 'test@cloudron.io', password: 'secret', signup: false }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(201); - expect(scope1.isDone()).to.be.ok(); - expect(scope2.isDone()).to.be.ok(); - done(); - }); - }); - - it('can setup subscription - signup', function (done) { - var scope1 = nock(config.apiServerOrigin()) - .post('/api/v1/register_user', (body) => body.email && body.password) - .reply(201, { }); - - var scope2 = nock(config.apiServerOrigin()) - .post('/api/v1/login', (body) => body.email && body.password) - .reply(200, { userId: 'userId', accessToken: 'SECRET_TOKEN' }); - - var scope3 = nock(config.apiServerOrigin()) - .post('/api/v1/register_cloudron?accessToken=SECRET_TOKEN', (body) => !!body.domain) - .reply(201, { cloudronId: 'cid', cloudronToken: 'CLOUDRON_TOKEN', licenseKey: 'lkey' }); - - superagent.post(SERVER_URL + '/api/v1/subscription') - .send({ email: 'test@cloudron.io', password: 'secret', signup: true }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(201); - expect(scope1.isDone()).to.be.ok(); - expect(scope2.isDone()).to.be.ok(); - expect(scope3.isDone()).to.be.ok(); - done(); - }); - }); - - it('can get subscription', function (done) { - var scope1 = nock(config.apiServerOrigin()) - .get('/api/v1/subscription?accessToken=CLOUDRON_TOKEN', () => true) - .reply(200, { subscription: { plan: { id: 'free' } }, email: 'test@cloudron.io' }); - - superagent.get(SERVER_URL + '/api/v1/subscription') - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(result.body.email).to.be('test@cloudron.io'); - expect(result.body.subscription).to.be.an('object'); - expect(scope1.isDone()).to.be.ok(); - done(); - }); - }); -}); diff --git a/src/server.js b/src/server.js index c23e9ecb6..cdf067797 100644 --- a/src/server.js +++ b/src/server.js @@ -137,10 +137,6 @@ function initializeExpressSync() { router.get ('/api/v1/cloudron/eventlog', cloudronScope, routes.eventlog.list); router.get ('/api/v1/cloudron/eventlog/:eventId', cloudronScope, routes.eventlog.get); - // subscription routes - router.post('/api/v1/subscription', subscriptionScope, routes.subscription.subscribeCloudron); - router.get ('/api/v1/subscription', subscriptionScope, routes.subscription.getSubscription); - // tasks router.get ('/api/v1/tasks', settingsScope, routes.tasks.list); router.get ('/api/v1/tasks/:taskId', settingsScope, routes.tasks.get); @@ -217,7 +213,9 @@ function initializeExpressSync() { router.del ('/api/v1/clients/:clientId/tokens', clientsScope, routes.clients.delTokens); router.del ('/api/v1/clients/:clientId/tokens/:tokenId', clientsScope, routes.clients.delToken); - // appstore routes + // appstore and subscription routes + router.post('/api/v1/appstore/register_cloudron', subscriptionScope, routes.appstore.registerCloudron); + router.get ('/api/v1/appstore/subscription', subscriptionScope, routes.appstore.getSubscription); router.get ('/api/v1/appstore/apps', appstoreScope, routes.appstore.getApps); router.get ('/api/v1/appstore/apps/:appstoreId', appstoreScope, routes.appstore.getApp); router.get ('/api/v1/appstore/apps/:appstoreId/versions/:versionId', appstoreScope, routes.appstore.getAppVersion);