diff --git a/src/routes/test/cloudron-test.js b/src/routes/test/cloudron-test.js index e8928ac86..2c8163dea 100644 --- a/src/routes/test/cloudron-test.js +++ b/src/routes/test/cloudron-test.js @@ -7,461 +7,276 @@ const async = require('async'), constants = require('../../constants.js'), + common = require('./common.js'), database = require('../../database.js'), expect = require('expect.js'), - hat = require('../../hat.js'), http = require('http'), nock = require('nock'), os = require('os'), server = require('../../server.js'), - speakeasy = require('speakeasy'), superagent = require('superagent'), - settings = require('../../settings.js'), - tokendb = require('../../tokendb.js'); + settings = require('../../settings.js'); -var SERVER_URL = 'http://localhost:' + constants.PORT; +describe('Cloudron API (pre-activation)', function () { + var SERVER_URL = 'http://localhost:' + constants.PORT; -var USERNAME = 'superadmin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com'; -var token = null; // authentication token -var USERNAME_1 = 'userTheFirst', EMAIL_1 = 'taO@zen.mac', userId_1, token_1; + before(function (done) { + nock.cleanAll(); -function setup(done) { - nock.cleanAll(); - - async.series([ - server.start.bind(server), - database._clear, - settings._setApiServerOrigin.bind(null, 'http://localhost:6060'), - settings.setBackupConfig.bind(null, { provider: 'filesystem', backupFolder: '/tmp', format: 'tgz', retentionPolicy: { keepWithinSecs: 10000 }, schedulePattern: '00 00 23 * * *' }) - ], done); -} - -function cleanup(done) { - database._clear(function (error) { - expect(error).to.not.be.ok(); - - server.stop(done); + async.series([ + server.start.bind(server), + database._clear, + settings._setApiServerOrigin.bind(null, 'http://localhost:6060'), + settings.setBackupConfig.bind(null, { provider: 'filesystem', backupFolder: '/tmp', format: 'tgz', retentionPolicy: { keepWithinSecs: 10000 }, schedulePattern: '00 00 23 * * *' }) + ], done); }); -} -describe('Cloudron API', function () { + after(function (done) { + database._clear(function (error) { + expect(error).to.not.be.ok(); - describe('activate', function () { - - before(setup); - after(cleanup); - - it('fails due to missing setupToken', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .send({ username: '', password: 'somepassword', email: 'admin@foo.bar' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('fails due to empty username', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: '', password: 'ADSFsdf$%436', email: 'admin@foo.bar' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('fails due to empty password', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: 'someuser', password: '', email: 'admin@foo.bar' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('fails due to empty email', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: 'someuser', password: 'ADSF#asd546', email: '' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('fails due to wrong displayName type', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: 'someuser', password: 'ADSF?#asd546', email: 'admin@foo.bar', displayName: 1234 }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('fails due to invalid email', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: 'someuser', password: 'ADSF#asd546', email: 'invalidemail' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: 'someuser', password: 'ADSF#asd546', email: 'admin@foo.bar', displayName: 'tester' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(201); - done(); - }); - }); - - it('fails the second time', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: 'someuser', password: 'ADSF#asd546', email: 'admin@foo.bar' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(409); - done(); - }); + server.stop(done); }); }); - describe('get config', function () { - before(function (done) { - async.series([ - setup, + it('fails due to missing setupToken', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .send({ username: '', password: 'somepassword', email: 'admin@foo.bar' }) + .end(function (error, result) { + expect(result.statusCode).to.equal(400); + done(); + }); + }); - function (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(); + it('fails due to empty username', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .query({ setupToken: 'somesetuptoken' }) + .send({ username: '', password: 'ADSFsdf$%436', email: 'admin@foo.bar' }) + .end(function (error, result) { + expect(result.statusCode).to.equal(400); + done(); + }); + }); - // stash token for further use - token = result.body.token; + it('fails due to empty password', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .query({ setupToken: 'somesetuptoken' }) + .send({ username: 'someuser', password: '', email: 'admin@foo.bar' }) + .end(function (error, result) { + expect(result.statusCode).to.equal(400); + done(); + }); + }); - callback(); - }); - }, + it('fails due to empty email', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .query({ setupToken: 'somesetuptoken' }) + .send({ username: 'someuser', password: 'ADSF#asd546', email: '' }) + .end(function (error, result) { + expect(result.statusCode).to.equal(400); + done(); + }); + }); - function (callback) { - superagent.post(SERVER_URL + '/api/v1/users') - .query({ access_token: token }) - .send({ username: USERNAME_1, email: EMAIL_1, invite: false }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(201); + it('fails due to wrong displayName type', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .query({ setupToken: 'somesetuptoken' }) + .send({ username: 'someuser', password: 'ADSF?#asd546', email: 'admin@foo.bar', displayName: 1234 }) + .end(function (error, result) { + expect(result.statusCode).to.equal(400); + done(); + }); + }); - token_1 = hat(8 * 32); - userId_1 = result.body.id; + it('fails due to invalid email', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .query({ setupToken: 'somesetuptoken' }) + .send({ username: 'someuser', password: 'ADSF#asd546', email: 'invalidemail' }) + .end(function (error, result) { + expect(result.statusCode).to.equal(400); + done(); + }); + }); - // HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...) - tokendb.add({ id: 'tid-1', accessToken: token_1, identifier: userId_1, clientId: 'test-client-id', expires: Date.now() + 100000, scope: 'cloudron', name: '' }, callback); - }); - } - ], done); + it('succeeds', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .query({ setupToken: 'somesetuptoken' }) + .send({ username: 'someuser', password: 'ADSF#asd546', email: 'admin@foo.bar', displayName: 'tester' }) + .end(function (error, result) { + expect(result.statusCode).to.equal(201); + done(); + }); + }); + + it('fails the second time', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/activate') + .query({ setupToken: 'somesetuptoken' }) + .send({ username: 'someuser', password: 'ADSF#asd546', email: 'admin@foo.bar' }) + .end(function (error, result) { + expect(result.statusCode).to.equal(409); + done(); + }); + }); +}); + +describe('Cloudron API (post activation)', function () { + const { setup, cleanup, serverUrl, owner, user } = common; + + before(setup); + after(cleanup); + + describe('config', function () { + it('cannot get config without token', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/config`) + .ok(() => true); + + expect(response.statusCode).to.equal(401); }); - after(cleanup); + it('can get config (admin)', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/config`) + .query({ access_token: owner.token }); - it('cannot get without token', function (done) { - superagent.get(SERVER_URL + '/api/v1/config') - .end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); + expect(response.statusCode).to.equal(200); + expect(response.body.apiServerOrigin).to.eql('http://localhost:6060'); + expect(response.body.webServerOrigin).to.eql('https://cloudron.io'); + expect(response.body.adminFqdn).to.eql(settings.dashboardFqdn()); + expect(response.body.version).to.eql(constants.VERSION); + expect(response.body.cloudronName).to.be.a('string'); }); - it('succeeds (admin)', function (done) { - superagent.get(SERVER_URL + '/api/v1/config') - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(result.body.apiServerOrigin).to.eql('http://localhost:6060'); - expect(result.body.webServerOrigin).to.eql('https://cloudron.io'); - expect(result.body.adminFqdn).to.eql(settings.dashboardFqdn()); - expect(result.body.version).to.eql(constants.VERSION); - expect(result.body.cloudronName).to.be.a('string'); + it('can get config (non-admin)', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/config`) + .query({ access_token: user.token }); - done(); - }); - }); - - it('succeeds (non-admin)', function (done) { - superagent.get(SERVER_URL + '/api/v1/config') - .query({ access_token: token_1 }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(result.body.apiServerOrigin).to.eql('http://localhost:6060'); - expect(result.body.webServerOrigin).to.eql('https://cloudron.io'); - expect(result.body.adminFqdn).to.eql(settings.dashboardFqdn()); - expect(result.body.version).to.eql(constants.VERSION); - expect(result.body.cloudronName).to.be.a('string'); - done(); - }); + expect(response.statusCode).to.equal(200); + expect(response.body.apiServerOrigin).to.eql('http://localhost:6060'); + expect(response.body.webServerOrigin).to.eql('https://cloudron.io'); + expect(response.body.adminFqdn).to.eql(settings.dashboardFqdn()); + expect(response.body.version).to.eql(constants.VERSION); + expect(response.body.cloudronName).to.be.a('string'); }); }); describe('login', function () { - before(function (done) { - async.series([ - setup, - function (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(); - - callback(); - }); - }, - ], done); + it('cannot login without body', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .ok(() => true); + expect(response.statusCode).to.equal(400); }); - after(cleanup); + it('cannot login without username', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ password: owner.password }) + .ok(() => true); - it('fails without body', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); + expect(response.statusCode).to.equal(400); }); - it('fails without username', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); + it('cannot login without password', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: owner.username }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('fails without password', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: USERNAME }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); + it('cannot login with empty username', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: '', password: owner.password }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('fails with empty username', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: '', password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); + it('cannot login with empty password', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: owner.username, password: '' }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('fails with empty password', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: USERNAME, password: '' }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); + it('cannot login with unknown username', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: 'somethingrandom', password: owner.password }) + .ok(() => true); + + expect(response.statusCode).to.equal(401); }); - it('fails with unknown username', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: USERNAME + USERNAME, password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); + it('cannot login with unknown email', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: 'randomgemail', password: owner.password }) + .ok(() => true); + + expect(response.statusCode).to.equal(401); }); - it('fails with unknown email', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: USERNAME + EMAIL, password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); + it('cannot login with wrong password', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: owner.username, password: owner.password.toUpperCase() }) + .ok(() => true); + + expect(response.statusCode).to.equal(401); }); - it('fails with wrong password', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: USERNAME, password: PASSWORD.toUpperCase() }) - .end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); + it('can login with username', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: owner.username, password: owner.password }); + + expect(response.statusCode).to.equal(200); + expect(new Date(response.body.expires).toString()).to.not.be('Invalid Date'); + expect(response.body.accessToken).to.be.a('string'); }); - it('with username succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: USERNAME, password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(new Date(result.body.expires).toString()).to.not.be('Invalid Date'); - expect(result.body.accessToken).to.be.a('string'); - done(); - }); + it('can login with uppercase username', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: owner.username.toUpperCase(), password: owner.password }); + + expect(response.statusCode).to.equal(200); + expect(new Date(response.body.expires).toString()).to.not.be('Invalid Date'); + expect(response.body.accessToken).to.be.a('string'); }); - it('with uppercase username succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: USERNAME.toUpperCase(), password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(new Date(result.body.expires).toString()).to.not.be('Invalid Date'); - expect(result.body.accessToken).to.be.a('string'); - done(); - }); + it('can login with email', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: owner.email, password: owner.password }); + + expect(response.statusCode).to.equal(200); + expect(new Date(response.body.expires).toString()).to.not.be('Invalid Date'); + expect(response.body.accessToken).to.be.a('string'); }); - it('with email succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: EMAIL, password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(new Date(result.body.expires).toString()).to.not.be('Invalid Date'); - expect(result.body.accessToken).to.be.a('string'); - done(); - }); - }); + it('can login with uppercase email', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: owner.email.toUpperCase(), password: owner.password }); - it('with uppercase email succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/login') - .send({ username: EMAIL.toUpperCase(), password: PASSWORD }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(new Date(result.body.expires).toString()).to.not.be('Invalid Date'); - expect(result.body.accessToken).to.be.a('string'); - done(); - }); - }); - }); - - describe('2fa login', function () { - var secret, accessToken; - - before(function (done) { - async.series([ - setup, - function (callback) { - superagent.post(`${SERVER_URL}/api/v1/cloudron/activate`).query({ setupToken: 'somesetuptoken' }).send({ username: USERNAME, password: PASSWORD, email: EMAIL }).end(function (error) { - callback(error); - }); - }, - function (callback) { - superagent.post(`${SERVER_URL}/api/v1/cloudron/login`).send({ username: USERNAME, password: PASSWORD }).end(function (error, result) { - accessToken = result.body.accessToken; - callback(error); - }); - }, - function (callback) { - superagent.post(`${SERVER_URL}/api/v1/profile/twofactorauthentication_secret`).query({ access_token: accessToken }).end(function (error, result) { - secret = result.body.secret; - callback(error); - }); - }, - function (callback) { - var totpToken = speakeasy.totp({ - secret: secret, - encoding: 'base32' - }); - - superagent.post(`${SERVER_URL}/api/v1/profile/twofactorauthentication_enable`).query({ access_token: accessToken }).send({ totpToken: totpToken }).end(function (error) { - callback(error); - }); - } - ], done); - }); - - after(function (done) { - async.series([ - function (callback) { - superagent.post(`${SERVER_URL}/api/v1/profile/twofactorauthentication_disable`).query({ access_token: accessToken }).send({ password: PASSWORD }).end(function (error) { - callback(error); - }); - }, - cleanup - ], done); - }); - - it('fails due to missing token', function (done) { - superagent.post(`${SERVER_URL}/api/v1/cloudron/login`).send({ username: USERNAME, password: PASSWORD }).end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); - }); - - it('fails due to wrong token', function (done) { - superagent.post(`${SERVER_URL}/api/v1/cloudron/login`).send({ username: USERNAME, password: PASSWORD }).send({ totpToken: 'wrongtoken' }).end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); - }); - - it('succeeds', function (done) { - var totpToken = speakeasy.totp({ - secret: secret, - encoding: 'base32' - }); - - superagent.post(`${SERVER_URL}/api/v1/cloudron/login`).send({ username: USERNAME, password: PASSWORD }).send({ totpToken: totpToken }).end(function (error, result) { - expect(error).to.be(null); - expect(result.statusCode).to.equal(200); - expect(result.body).to.be.an(Object); - expect(result.body.accessToken).to.be.a('string'); - done(); - }); + expect(response.statusCode).to.equal(200); + expect(new Date(response.body.expires).toString()).to.not.be('Invalid Date'); + expect(response.body.accessToken).to.be.a('string'); }); }); describe('logs', function () { - before(function (done) { - async.series([ - setup, + it('logStream - requires event-stream accept header', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/cloudron/logstream/box`) + .query({ access_token: owner.token, fromLine: 0 }) + .ok(() => true); - function (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(); - - // stash token for further use - token = result.body.token; - - callback(); - }); - }, - ], done); - }); - - after(cleanup); - - it('logStream - requires event-stream accept header', function (done) { - superagent.get(SERVER_URL + '/api/v1/cloudron/logstream/box') - .query({ access_token: token, fromLine: 0 }) - .end(function (err, res) { - expect(res.statusCode).to.be(400); - done(); - }); + expect(response.statusCode).to.be(400); }); it('logStream - stream logs', function (done) { - var options = { + const options = { host: 'localhost', port: constants.PORT, - path: '/api/v1/cloudron/logstream/box?lines=10&access_token=' + token, + path: '/api/v1/cloudron/logstream/box?lines=10&access_token=' + owner.token, headers: { 'Accept': 'text/event-stream', 'Connection': 'keep-alive' } }; // superagent doesn't work. maybe https://github.com/visionmedia/superagent/issues/420 - var req = http.get(options, function (res) { + const req = http.get(options, function (res) { var data = ''; res.on('data', function (d) { data += d.toString('utf8'); }); setTimeout(function checkData() { @@ -472,14 +287,14 @@ describe('Cloudron API', function () { if (line.indexOf('id: ') === 0) { expect(parseInt(line.substr('id: '.length), 10)).to.be.a('number'); } else if (line.indexOf('data: ') === 0) { - var message = JSON.parse(line.slice('data: '.length)).message; + const message = JSON.parse(line.slice('data: '.length)).message; if (Array.isArray(message) || typeof message === 'string') dataMessageFound = true; } }); expect(dataMessageFound).to.be.ok(); - req.abort(); + req.destroy(); done(); }, 1000); res.on('error', done); @@ -489,86 +304,39 @@ describe('Cloudron API', function () { }); }); - describe('misc routes', function () { - before(function (done) { - async.series([ - setup, + describe('memory', function () { + it('cannot get without token', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/cloudron/memory`) + .ok(() => true); - function (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(); - - // stash token for further use - token = result.body.token; - - callback(); - }); - }, - - function (callback) { - superagent.post(SERVER_URL + '/api/v1/users') - .query({ access_token: token }) - .send({ username: USERNAME_1, email: EMAIL_1, invite: false }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(201); - - token_1 = hat(8 * 32); - userId_1 = result.body.id; - - // HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...) - tokendb.add({ id: 'tid-1', accessToken: token_1, identifier: userId_1, clientId: 'test-client-id', expires: Date.now() + 100000, scope: 'cloudron', name: '' }, callback); - }); - } - ], done); + expect(response.statusCode).to.equal(401); }); - after(cleanup); + it('succeeds (admin)', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/cloudron/memory`) + .query({ access_token: owner.token }); - describe('memory', function () { - it('cannot get without token', function (done) { - superagent.get(SERVER_URL + '/api/v1/cloudron/memory') - .end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); - }); - - it('succeeds (admin)', function (done) { - superagent.get(SERVER_URL + '/api/v1/cloudron/memory') - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(result.body.memory).to.eql(os.totalmem()); - expect(result.body.swap).to.be.a('number'); - - done(); - }); - }); - - it('fails (non-admin)', function (done) { - superagent.get(SERVER_URL + '/api/v1/cloudron/memory') - .query({ access_token: token_1 }) - .end(function (error, result) { - expect(result.statusCode).to.equal(403); - done(); - }); - }); + expect(response.statusCode).to.equal(200); + expect(response.body.memory).to.eql(os.totalmem()); + expect(response.body.swap).to.be.a('number'); }); - describe('languages', function () { - it('succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/cloudron/languages') - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(result.body.languages).to.be.an('array'); - expect(result.body.languages.indexOf('en')).to.not.equal(-1); - done(); - }); - }); + it('fails (non-admin)', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/cloudron/memory`) + .query({ access_token: user.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(403); + }); + }); + + describe('languages', function () { + it('succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/cloudron/languages`); + + expect(response.statusCode).to.equal(200); + expect(response.body.languages).to.be.an('array'); + expect(response.body.languages.indexOf('en')).to.not.equal(-1); }); }); }); diff --git a/src/routes/test/common.js b/src/routes/test/common.js index 8d1967176..f8f4a7570 100644 --- a/src/routes/test/common.js +++ b/src/routes/test/common.js @@ -65,7 +65,7 @@ function setup(done) { function createUser(callback) { superagent.post(`${serverUrl}/api/v1/users`) .query({ access_token: owner.token }) - .send({ username: user.username, email: user.email }) + .send({ username: user.username, email: user.email, password: user.password }) .end(async function (error, result) { expect(error).to.not.be.ok(); expect(result.statusCode).to.equal(201); diff --git a/src/routes/test/profile-test.js b/src/routes/test/profile-test.js index 12d2bca6c..814ad3b79 100644 --- a/src/routes/test/profile-test.js +++ b/src/routes/test/profile-test.js @@ -8,11 +8,12 @@ const common = require('./common.js'), expect = require('expect.js'), + speakeasy = require('speakeasy'), superagent = require('superagent'), tokens = require('../../tokens.js'); describe('Profile API', function () { - const { setup, cleanup, serverUrl, owner } = common; + const { setup, cleanup, serverUrl, owner, user } = common; before(setup); after(cleanup); @@ -189,4 +190,71 @@ describe('Profile API', function () { expect(response.statusCode).to.equal(204); }); }); + + describe('2fa login', function () { + let secret; + + it('can get secret', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/profile/twofactorauthentication_secret`) + .query({ access_token: user.token }); + + secret = response.body.secret; + }); + + it('can enable 2fa', async function () { + const totpToken = speakeasy.totp({ + secret: secret, + encoding: 'base32' + }); + + await superagent.post(`${serverUrl}/api/v1/profile/twofactorauthentication_enable`) + .query({ access_token: user.token }) + .send({ totpToken: totpToken }); + }); + + it('fails due to missing token', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: user.username, password: user.password }) + .ok(() => true); + + expect(response.statusCode).to.equal(401); + }); + + it('fails due to wrong token', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: user.username, password: user.password, totpToken: '12345' }) + .ok(() => true); + + expect(response.statusCode).to.equal(401); + }); + + it('succeeds', async function () { + const totpToken = speakeasy.totp({ + secret: secret, + encoding: 'base32' + }); + + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: user.username, password: user.password, totpToken: totpToken }); + + expect(response.statusCode).to.equal(200); + expect(response.body).to.be.an(Object); + expect(response.body.accessToken).to.be.a('string'); + }); + + it('can disable 2fa', async function () { + await superagent.post(`${serverUrl}/api/v1/profile/twofactorauthentication_disable`) + .query({ access_token: user.token }) + .send({ password: user.password }); + }); + + it('did disable 2fa', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: user.username, password: user.password }); + + expect(response.statusCode).to.equal(200); + expect(response.body).to.be.an(Object); + expect(response.body.accessToken).to.be.a('string'); + }); + }); });