diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js index e808a0ff10b6..746358988519 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._id); + }); + 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..29879093c1c5 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/createToken.js @@ -0,0 +1,13 @@ +Meteor.methods({ + 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(userId, token); + return { + userId, + 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..6d8e42bc3755 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); @@ -14,133 +13,257 @@ 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); + }); + + 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({ - userId: targetUser._id, - data :{ - email: apiEmail, - name: `edited${ apiUsername }`, - username: `edited${ apiUsername }`, - password, - active: true, - roles: ['user'] - } + user: user.username, + password }) .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 }`); + 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); + }); + }); + + 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); + }); + }); + }); }); });