Commit 96e740d9 authored by Eric Eastwood's avatar Eric Eastwood

Remove authorization code after used to exchange for token (OAuth)

Fix https://gitlab.com/gitlab-org/gitter/webapp/issues/2054

> The client MUST NOT use the authorization code more than once.
>
> https://tools.ietf.org/html/rfc6749#section-4.1.2
parent 33dffab8
......@@ -15,6 +15,7 @@ function createOAuthClient(fixtureName, f) {
name: name,
clientKey: f.clientKey || crypto.randomBytes(20).toString('hex'),
clientSecret: f.clientSecret || crypto.randomBytes(20).toString('hex'),
registeredRedirectUri: f.registeredRedirectUri,
revoked: f.revoked || false
});
}
......
"use strict";
var Promise = require('bluebird');
var debug = require('debug')('gitter:tests:test-fixtures');
var crypto = require('crypto');
var OAuthCode = require('gitter-web-persistence').OAuthCode;
function createOAuthCode(fixtureName, f) {
debug('Creating %s', fixtureName);
return OAuthCode.create({
code: f.code || crypto.randomBytes(20).toString('hex'),
clientId: f.clientId,
redirectUri: f.redirectUri,
userId: f.userId
});
}
function createOAuthCodes(expected, fixture) {
return Promise.map(Object.keys(expected), function(key) {
if (key.match(/^oAuthCode/)) {
const expectedOAuthCode = expected[key];
expectedOAuthCode.userId = fixture[expectedOAuthCode.user]._id;
expectedOAuthCode.clientId = fixture[expectedOAuthCode.client]._id;
expectedOAuthCode.redirectUri = fixture[expectedOAuthCode.client].registeredRedirectUri;
return createOAuthCode(key, expectedOAuthCode)
.then(function(oAuthCode) {
fixture[key] = oAuthCode;
});
}
return null;
});
}
module.exports = createOAuthCodes;
......@@ -13,6 +13,7 @@ var fixtureSteps = [
require('./delete-documents'),
require('./create-users'),
require('./create-oauth-clients'),
require('./create-oauth-codes'),
require('./create-oauth-access-tokens'),
require('./create-identities'),
require('./create-groups'),
......
......@@ -56,8 +56,12 @@ function saveAuthorizationCode(code, client, redirectUri, user, callback) {
authCode.save(callback);
}
function findAuthorizationCode(code, callback) {
persistenceService.OAuthCode.findOne({ code: code }, callback);
function findAuthorizationCode(code) {
return persistenceService.OAuthCode.findOne({ code: code });
}
function deleteAuthorizationCode(code) {
return persistenceService.OAuthCode.remove({ code: code });
}
/**
......@@ -222,6 +226,7 @@ module.exports = {
findClientById: findClientById,
saveAuthorizationCode: saveAuthorizationCode,
findAuthorizationCode: findAuthorizationCode,
deleteAuthorizationCode: deleteAuthorizationCode,
validateAccessTokenAndClient: validateAccessTokenAndClient,
removeAllAccessTokensForUser: removeAllAccessTokensForUser,
findClientByClientKey: findClientByClientKey,
......
......@@ -71,18 +71,22 @@ server.grant(oauth2orize.grant.code(function(client, redirectUri, user, ares, do
// application issues an access token on behalf of the user who authorized the
// code.
server.exchange(oauth2orize.exchange.code(function(client, code, redirectUri, done) {
oauthService.findAuthorizationCode(code, function(err, authCode) {
if (err) return done(err);
server.exchange(oauth2orize.exchange.code(async function(client, code, redirectUri, done) {
try {
const authCode = await oauthService.findAuthorizationCode(code);
if (!authCode) return done();
if (!client._id.equals(authCode.clientId)) { return done(); }
if (redirectUri !== authCode.redirectUri) { return done(); }
return oauthService.findOrCreateToken(authCode.userId, authCode.clientId)
.nodeify(done);
});
const token = await oauthService.findOrCreateToken(authCode.userId, authCode.clientId);
// > The client MUST NOT use the authorization code more than once.
// > https://tools.ietf.org/html/rfc6749#section-4.1.2
await oauthService.deleteAuthorizationCode(code);
done(null, token);
} catch(err) {
if (err) return done(err);
}
}));
......
'use strict';
process.env.DISABLE_API_LISTEN = '1';
process.env.DISABLE_API_WEB_LISTEN = '1';
process.env.TEST_EXPORT_RATE_LIMIT = 100;
var Promise = require('bluebird');
var fixtureLoader = require('gitter-web-test-utils/lib/test-fixtures');
var assert = require('assert');
var request = require('supertest-as-promised')(Promise);
var app = require('../../server/web');
describe('OAuth tests', function() {
var fixture = fixtureLoader.setup({
user1: { },
oAuthClient1: {
registeredRedirectUri: 'http://localhost:3434/callback'
},
oAuthCode1: {
user: 'user1',
client: 'oAuthClient1'
}
});
it('GET /login/oauth/token clears out authorization code so it can\'t be re-used', async function() {
this.timeout(8000);
const postData = {
client_id: fixture.oAuthClient1.clientKey,
client_secret: fixture.oAuthClient1.clientSecret,
redirect_uri: fixture.oAuthClient1.registeredRedirectUri,
grant_type: 'authorization_code',
code: fixture.oAuthCode1.code
};
await request(app)
.post(`/login/oauth/token`)
.send(postData)
.expect(200)
.then(function(result) {
assert(result.body.access_token && result.body.access_token.length > 0, 'no access token provided in body');
assert(result.body.token_type === 'Bearer', 'wrong token_type returned');
});
await request(app)
.post(`/login/oauth/token`)
.send(postData)
.expect(403)
.then(function(result) {
assert.deepEqual(result.body, {
"error": "invalid_grant",
"error_description": "Invalid authorization code"
});
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment