From 45a74d6bcd7222101e59ddf85160814d88ac3931 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 26 Apr 2017 13:05:21 -0300 Subject: [PATCH 1/3] create a method 'create token' --- packages/rocketchat-api/server/v1/users.js | 11 ++ .../server/startup.js | 1 + packages/rocketchat-lib/package.js | 1 + .../server/methods/createToken.js | 13 +++ tests/end-to-end/api/01-users.js | 107 +++++++++++++++++- 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 packages/rocketchat-lib/server/methods/createToken.js diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js index e808a0ff10b6..3dc3c38773ba 100644 --- a/packages/rocketchat-api/server/v1/users.js +++ b/packages/rocketchat-api/server/v1/users.js @@ -263,3 +263,14 @@ RocketChat.API.v1.addRoute('users.update', { authRequired: true }, { return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(this.bodyParams.userId, { fields: RocketChat.API.v1.defaultFieldsToExclude }) }); } }); + +RocketChat.API.v1.addRoute('users.createToken', { authRequired: true }, { + post() { + const user = this.getUserFromParams(); + let data; + Meteor.runAsUser(this.userId, () => { + data = Meteor.call('createToken', {user}); + }); + return data ? RocketChat.API.v1.success({data}) : RocketChat.API.v1.unauthorized(); + } +}); diff --git a/packages/rocketchat-authorization/server/startup.js b/packages/rocketchat-authorization/server/startup.js index cf4ac5c7bd72..af9678861630 100644 --- a/packages/rocketchat-authorization/server/startup.js +++ b/packages/rocketchat-authorization/server/startup.js @@ -46,6 +46,7 @@ Meteor.startup(function() { { _id: 'set-moderator', roles : ['admin', 'owner'] }, { _id: 'set-owner', roles : ['admin', 'owner'] }, { _id: 'unarchive-room', roles : ['admin'] }, + { _id: 'user-generate-access-token', roles : ['admin'] }, { _id: 'view-c-room', roles : ['admin', 'user', 'bot'] }, { _id: 'view-d-room', roles : ['admin', 'user', 'bot'] }, { _id: 'view-full-other-user-info', roles : ['admin'] }, diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 8902fce8f259..b09e0428bd3d 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -134,6 +134,7 @@ Package.onUse(function(api) { api.addFiles('server/methods/checkRegistrationSecretURL.js', 'server'); api.addFiles('server/methods/cleanChannelHistory.js', 'server'); api.addFiles('server/methods/createChannel.js', 'server'); + api.addFiles('server/methods/createToken.js', 'server'); api.addFiles('server/methods/createPrivateGroup.js', 'server'); api.addFiles('server/methods/deleteMessage.js', 'server'); api.addFiles('server/methods/deleteUserOwnAccount.js', 'server'); diff --git a/packages/rocketchat-lib/server/methods/createToken.js b/packages/rocketchat-lib/server/methods/createToken.js new file mode 100644 index 000000000000..6ea2e4ec649d --- /dev/null +++ b/packages/rocketchat-lib/server/methods/createToken.js @@ -0,0 +1,13 @@ +Meteor.methods({ + createToken({user}) { + if (Meteor.userId() !== user._id && !RocketChat.authz.hasPermission(Meteor.userId(), 'user-generate-access-token')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'createToken' }); + } + const token = Accounts._generateStampedLoginToken(); + Accounts._insertLoginToken(user._id, token); + return { + userId: user._id, + authToken: token.token + }; + } +}); diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js index e5fa8fc3eb08..17621b246095 100644 --- a/tests/end-to-end/api/01-users.js +++ b/tests/end-to-end/api/01-users.js @@ -5,7 +5,6 @@ import {getCredentials, api, login, request, credentials, apiEmail, apiUsername, targetUser, log} from '../../data/api-data.js'; import {adminEmail, password} from '../../data/user.js'; import {imgURL} from '../../data/interactions.js'; -import supertest from 'supertest'; describe('Users', function() { this.retries(0); @@ -143,4 +142,110 @@ describe('Users', function() { }) .end(done); }); + + describe('/users.createToken', () => { + let user; + beforeEach((done) => { + const username = `user.test.${ Date.now() }`; + const email = `${ username }@rocket.chat`; + request.post(api('users.create')) + .set(credentials) + .send({ email, name: username, username, password }) + .end((err, res) => { + user = res.body.user; + done(); + }); + }); + + let userCredentials; + beforeEach((done) => { + request.post(api('login')) + .send({ + user: user.username, + password + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + userCredentials = {}; + userCredentials['X-Auth-Token'] = res.body.data.authToken; + userCredentials['X-User-Id'] = res.body.data.userId; + }) + .end(done); + }); + afterEach(done => { + request.post(api('users.delete')).set(credentials).send({ + userId: user._id + }).end(done); + user = undefined; + }); + + describe('logged as admin', () => { + it('should return the user id and a new token', (done) => { + request.post(api('users.createToken')) + .set(credentials) + .send({ + username: user.username + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.deep.property('data.userId', user._id); + expect(res.body).to.have.deep.property('data.authToken'); + }) + .end(done); + }); + }); + + describe('logged as itself', () => { + it('should return the user id and a new token', (done) => { + request.post(api('users.createToken')) + .set(userCredentials) + .send({ + username: user.username + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.deep.property('data.userId', user._id); + expect(res.body).to.have.deep.property('data.authToken'); + }) + .end(done); + }); + }); + + describe('As an user not allowed', () => { + it('should return 401 unauthorized', (done) => { + request.post(api('users.createToken')) + .set(userCredentials) + .send({ + username: 'rocket.cat' + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('errorType'); + expect(res.body).to.have.property('error'); + }) + .end(done); + }); + }); + + describe('Not logged in', () => { + it('should return 401 unauthorized', (done) => { + request.post(api('users.createToken')) + .send({ + username: user.username + }) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res) => { + expect(res.body).to.have.property('message'); + }) + .end(done); + }); + }); + }); }); From 99adf308534517c13af6aec16e5f7eea388cb06d Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 26 Apr 2017 14:51:30 -0300 Subject: [PATCH 2/3] createToken just receive userId --- packages/rocketchat-api/server/v1/users.js | 2 +- packages/rocketchat-lib/server/methods/createToken.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js index 3dc3c38773ba..746358988519 100644 --- a/packages/rocketchat-api/server/v1/users.js +++ b/packages/rocketchat-api/server/v1/users.js @@ -269,7 +269,7 @@ RocketChat.API.v1.addRoute('users.createToken', { authRequired: true }, { const user = this.getUserFromParams(); let data; Meteor.runAsUser(this.userId, () => { - data = Meteor.call('createToken', {user}); + data = Meteor.call('createToken', user._id); }); return data ? RocketChat.API.v1.success({data}) : RocketChat.API.v1.unauthorized(); } diff --git a/packages/rocketchat-lib/server/methods/createToken.js b/packages/rocketchat-lib/server/methods/createToken.js index 6ea2e4ec649d..29879093c1c5 100644 --- a/packages/rocketchat-lib/server/methods/createToken.js +++ b/packages/rocketchat-lib/server/methods/createToken.js @@ -1,12 +1,12 @@ Meteor.methods({ - createToken({user}) { - if (Meteor.userId() !== user._id && !RocketChat.authz.hasPermission(Meteor.userId(), 'user-generate-access-token')) { + createToken(userId) { + if (Meteor.userId() !== userId && !RocketChat.authz.hasPermission(Meteor.userId(), 'user-generate-access-token')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'createToken' }); } const token = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(user._id, token); + Accounts._insertLoginToken(userId, token); return { - userId: user._id, + userId, authToken: token.token }; } From e0b584c9acad4b3321d5291a538877df1164d08f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 26 Apr 2017 18:12:34 -0300 Subject: [PATCH 3/3] createToken - tested if the generated token is valid --- tests/end-to-end/api/01-users.js | 258 +++++++++++++++++-------------- 1 file changed, 138 insertions(+), 120 deletions(-) diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js index 17621b246095..6d8e42bc3755 100644 --- a/tests/end-to-end/api/01-users.js +++ b/tests/end-to-end/api/01-users.js @@ -13,134 +13,134 @@ describe('Users', function() { it('/users.create', (done) => { request.post(api('users.create')) - .set(credentials) - .send({ - email: apiEmail, - name: apiUsername, - username: apiUsername, - password, - active: true, - roles: ['user'], - joinDefaultChannels: true, - verified:true - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.deep.property('user.username', apiUsername); - expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail); - expect(res.body).to.have.deep.property('user.active', true); - expect(res.body).to.have.deep.property('user.name', apiUsername); - targetUser._id = res.body.user._id; - }) - .end(done); + .set(credentials) + .send({ + email: apiEmail, + name: apiUsername, + username: apiUsername, + password, + active: true, + roles: ['user'], + joinDefaultChannels: true, + verified:true + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.deep.property('user.username', apiUsername); + expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail); + expect(res.body).to.have.deep.property('user.active', true); + expect(res.body).to.have.deep.property('user.name', apiUsername); + targetUser._id = res.body.user._id; + }) + .end(done); }); it('/users.info', (done) => { request.get(api('users.info')) - .set(credentials) - .query({ - userId: targetUser._id - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.deep.property('user.username', apiUsername); - expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail); - expect(res.body).to.have.deep.property('user.active', true); - expect(res.body).to.have.deep.property('user.name', apiUsername); - }) - .end(done); + .set(credentials) + .query({ + userId: targetUser._id + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.deep.property('user.username', apiUsername); + expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail); + expect(res.body).to.have.deep.property('user.active', true); + expect(res.body).to.have.deep.property('user.name', apiUsername); + }) + .end(done); }); it('/users.getPresence', (done) => { request.get(api('users.getPresence')) - .set(credentials) - .query({ - userId: targetUser._id - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.deep.property('presence', 'offline'); - }) - .end(done); + .set(credentials) + .query({ + userId: targetUser._id + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.deep.property('presence', 'offline'); + }) + .end(done); }); it('/users.list', (done) => { request.get(api('users.list')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - }) - .end(done); + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + }) + .end(done); }); it.skip('/users.list', (done) => { - //filtering user list + //filtering user list request.get(api('users.list')) - .set(credentials) - .query({ - name: { '$regex': 'g' } - }) - .field('username', 1) - .sort('createdAt', -1) - .expect(log) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - }) - .end(done); + .set(credentials) + .query({ + name: { '$regex': 'g' } + }) + .field('username', 1) + .sort('createdAt', -1) + .expect(log) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + }) + .end(done); }); it.skip('/users.setAvatar', (done) => { request.post(api('users.setAvatar')) - .set(credentials) - .attach('avatarUrl', imgURL) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end(done); + .set(credentials) + .attach('avatarUrl', imgURL) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .end(done); }); it('/users.update', (done) => { request.post(api('users.update')) - .set(credentials) - .send({ - userId: targetUser._id, - data :{ - email: apiEmail, - name: `edited${ apiUsername }`, - username: `edited${ apiUsername }`, - password, - active: true, - roles: ['user'] - } - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.deep.property('user.username', `edited${ apiUsername }`); - expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail); - expect(res.body).to.have.deep.property('user.active', true); - expect(res.body).to.have.deep.property('user.name', `edited${ apiUsername }`); - }) - .end(done); + .set(credentials) + .send({ + userId: targetUser._id, + data :{ + email: apiEmail, + name: `edited${ apiUsername }`, + username: `edited${ apiUsername }`, + password, + active: true, + roles: ['user'] + } + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.deep.property('user.username', `edited${ apiUsername }`); + expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail); + expect(res.body).to.have.deep.property('user.active', true); + expect(res.body).to.have.deep.property('user.name', `edited${ apiUsername }`); + }) + .end(done); }); describe('/users.createToken', () => { @@ -149,29 +149,29 @@ describe('Users', function() { const username = `user.test.${ Date.now() }`; const email = `${ username }@rocket.chat`; request.post(api('users.create')) - .set(credentials) - .send({ email, name: username, username, password }) - .end((err, res) => { - user = res.body.user; - done(); - }); + .set(credentials) + .send({ email, name: username, username, password }) + .end((err, res) => { + user = res.body.user; + done(); + }); }); let userCredentials; beforeEach((done) => { request.post(api('login')) - .send({ - user: user.username, - password - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - userCredentials = {}; - userCredentials['X-Auth-Token'] = res.body.data.authToken; - userCredentials['X-User-Id'] = res.body.data.userId; - }) - .end(done); + .send({ + user: user.username, + password + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + userCredentials = {}; + userCredentials['X-Auth-Token'] = res.body.data.authToken; + userCredentials['X-User-Id'] = res.body.data.userId; + }) + .end(done); }); afterEach(done => { request.post(api('users.delete')).set(credentials).send({ @@ -247,5 +247,23 @@ describe('Users', function() { .end(done); }); }); + + describe('Testing if the returned token is valid', (done) => { + it('should return 200', (done) => { + return request.post(api('users.createToken')) + .set(credentials) + .send({ username: user.username }) + .expect('Content-Type', 'application/json') + .end((err, res) => { + return err ? done () : request.get(api('me')) + .set({ 'X-Auth-Token': `${ res.body.data.authToken }`, 'X-User-Id': res.body.data.userId }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .end(done); + }); + }); + }); }); });