Skip to content

Commit

Permalink
Reject revoked tokens.
Browse files Browse the repository at this point in the history
  • Loading branch information
iamigo committed Feb 16, 2017
1 parent e990e46 commit 025544a
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 14 deletions.
122 changes: 122 additions & 0 deletions tests/enforceToken/revoke.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Copyright (c) 2017, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or
* https://opensource.org/licenses/BSD-3-Clause
*/

/**
* tests/tokenReq/api/token/createtoken.js
*/
const expect = require('chai').expect;
const supertest = require('supertest');
const api = supertest(require('../../index').app);
const adminUser = require('../../config').db.adminUser;
const constants = require('../../api/v1/constants');
const jwtUtil = require('../../utils/jwtUtil');
const u = require('../testUtils');
const registerPath = '/v1/register';
const tokenPath = '/v1/tokens';

describe('enforceToken: revoke:', () => {
let defaultToken;
const predefinedAdminUserToken = jwtUtil.createToken(
adminUser.name, adminUser.name
);

beforeEach((done) => {
api.post(registerPath)
.send(u.fakeUserCredentials)
.end((err, res) => {
if (err) {
done(err);
}

defaultToken = res.body.token;
done();
});
});

afterEach(u.forceDeleteToken);

it('cannot use revoked token to authorize any API calls', (done) => {
api.post(tokenPath)
.set('Authorization', defaultToken)
.send({ name: 'newToken' })
.end((err, res) => {
if (err) {
return done(err);
}

const newToken = res.body.token;
return api.post(`${tokenPath}/${res.body.id}/revoke`)
.set('Authorization', predefinedAdminUserToken)
.send({ })
.end((err2, res2) => {
if (err2 || res2.body.errors) {
return done(err2);
}

return api.get('/v1/subjects')
.set('Authorization', newToken)
.expect(constants.httpStatus.FORBIDDEN)
.end((err3, res3) => {
if (err3) {
return done(err3);
}

expect(res3.body.errors[0].description)
.to.eql('Token was revoked. Please contact your Refocus ' +
'administrator.');
return done();
});
});
});
});

it('restored token works to authorize an API call', (done) => {
api.post(tokenPath)
.set('Authorization', defaultToken)
.send({ name: 'newToken' })
.end((err, res) => {
if (err) {
return done(err);
}

const newToken = res.body.token;
const tid = res.body.id;
return api.post(`${tokenPath}/${tid}/revoke`)
.set('Authorization', predefinedAdminUserToken)
.send({ })
.end((err2, res2) => {
expect(res2.body).to.not.have.property('errors');
if (err2 || res2.body.errors) {
return done(err2);
}

return api.post(`${tokenPath}/${tid}/restore`)
.set('Authorization', predefinedAdminUserToken)
.send({ })
.end((err3, res3) => {
expect(res3.body).to.have.property('isRevoked', '0');
expect(res3.body).to.have.property('name', 'newToken');
if (err3 || res3.body.errors) {
return done(err3);
}

return api.get('/v1/subjects')
.set('Authorization', newToken)
.expect(constants.httpStatus.OK)
.end((err4, res4) => {
if (err4 || res4.body.errors) {
return done(err4);
}

return done();
});
});
});
});
});
});
93 changes: 79 additions & 14 deletions utils/jwtUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
const jwt = require('jsonwebtoken');
const apiErrors = require('../api/v1/apiErrors');
const conf = require('../config');
const env = conf.environment[conf.nodeEnv];
const secret = conf.environment[conf.nodeEnv].tokenSecret;
const User = require('../db/index').User;
const Token = require('../db/index').Token;

/**
* Attaches the resource type to the error and passes it on to the next
Expand Down Expand Up @@ -43,7 +44,47 @@ function handleInvalidToken(cb) {
}

/**
* Verify jwt token.
* Verify jwt token. If verify successful, also check the token record in the
* db (exists AND is not revoked) and load the user record from the db and add
* to the request. (Skip the token record check if the token is the default UI
* token.)
*
* @param {object} t - The Token object from the db
* @returns {boolean} true if ok, otherwise throws error
*/
function checkTokenRecord(t) {
if (t && t.isRevoked === '0') {
return true;
}

if (!t) {
const err = new apiErrors.ForbiddenError({
explanation: 'Missing user for the specified token. ' +
'Please contact your Refocus administrator.',
});
throw err;
}

if (t.isRevoked !== '0') {
const err = new apiErrors.ForbiddenError({
explanation: 'Token was revoked. Please contact your ' +
'Refocus administrator.',
});
throw err;
}

const err = new apiErrors.ForbiddenError({
explanation: 'Invalid Token.',
});
throw err;
} // checkTokenRecord

/**
* Verify jwt token. If verify successful, also check the token record in the
* db (exists AND is not revoked) and load the user record from the db and add
* to the request. (Skip the token record check if the token is the default UI
* token.)
*
* @param {object} req - request object
* @param {Function} cb - callback function
*/
Expand All @@ -56,25 +97,49 @@ function verifyToken(req, cb) {
}

if (token) {
jwt.verify(token, env.tokenSecret, {}, (err, decodedData) => {
jwt.verify(token, secret, {}, (err, decodedData) => {
if (err) {
handleInvalidToken(cb);
} else {
User.findOne({ where: { name: decodedData.username } })
return handleInvalidToken(cb);
} else { // eslint-disable-line no-else-return
return User.findOne({ where: { name: decodedData.username } })
.then((user) => {
// set user in request
if (user) {
req.user = user;
return cb();
}
req.user = user; // set user in request

return handleInvalidToken(cb);
/*
* No need to check the token record if this is the default UI
* token.
*/
if (decodedData.username === decodedData.tokenname) {
return cb();
}

return Token.findOne({
where: {
name: decodedData.tokenname,
createdBy: user.id,
}
})
.then(checkTokenRecord)
.then((ok) => {
if (ok) {
return cb();
} else { // eslint-disable-line no-else-return
return handleInvalidToken(cb);
}
})
.catch((tokenError) => {
return handleError(cb, tokenError, 'ApiToken');
});
} else { // eslint-disable-line no-else-return
return handleInvalidToken(cb);
}
});
}
});
} else {
const err = new apiErrors.ForbiddenError({
explanation: 'No authorization token was found',
explanation: 'No authorization token was found.',
});
handleError(cb, err, 'ApiToken');
}
Expand All @@ -90,7 +155,7 @@ function verifyToken(req, cb) {
function getTokenDetailsFromTokenString(s) {
return new Promise((resolve, reject) => {
if (s) {
jwt.verify(s, env.tokenSecret, {}, (err, decodedData) => {
jwt.verify(s, secret, {}, (err, decodedData) => {
if (err !== null || !decodedData) {
return reject(new apiErrors.ForbiddenError({
explanation: 'No authorization token was found',
Expand Down Expand Up @@ -138,7 +203,7 @@ function createToken(tokenName, userName) {
username: userName,
timestamp: Date.now,
};
const createdToken = jwt.sign(jwtClaim, env.tokenSecret);
const createdToken = jwt.sign(jwtClaim, secret);
return createdToken;
}

Expand Down

0 comments on commit 025544a

Please sign in to comment.