Commit 06f1ed52 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #514 from hypothesis/oauth-code-grant-type

Use correct request params when exchanging auth code for tokens
parents 3cd80a54 23991e2f
...@@ -141,11 +141,8 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -141,11 +141,8 @@ function auth($http, $window, flash, localStorage, random, settings) {
// Exchange the JWT grant token for an access token. // Exchange the JWT grant token for an access token.
// See https://tools.ietf.org/html/rfc7523#section-4 // See https://tools.ietf.org/html/rfc7523#section-4
function exchangeToken(grantToken) { function exchangeJWT(grantToken) {
var data = { var data = {
// FIXME: This should be set to the appropriate grant type if we are
// exchanging an authorization code for a grant token, which
// is the case for first-party accounts.
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: grantToken, assertion: grantToken,
}; };
...@@ -157,6 +154,24 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -157,6 +154,24 @@ function auth($http, $window, flash, localStorage, random, settings) {
}); });
} }
/**
* Exchange an authorization code from the `/oauth/authorize` endpoint for
* access and refresh tokens.
*/
function exchangeAuthCode(code) {
var data = {
client_id: settings.oauthClientId,
grant_type: 'authorization_code',
code,
};
return postToTokenUrl(data).then((response) => {
if (response.status !== 200) {
throw new Error('Authorization code exchange failed');
}
return tokenInfoFrom(response);
});
}
/** /**
* Exchange the refresh token for a new access token and refresh token pair. * Exchange the refresh token for a new access token and refresh token pair.
* See https://tools.ietf.org/html/rfc6749#section-6 * See https://tools.ietf.org/html/rfc6749#section-6
...@@ -226,7 +241,7 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -226,7 +241,7 @@ function auth($http, $window, flash, localStorage, random, settings) {
if (grantToken) { if (grantToken) {
// Exchange host-page provided grant token for a new access token. // Exchange host-page provided grant token for a new access token.
accessTokenPromise = exchangeToken(grantToken).then(function (tokenInfo) { accessTokenPromise = exchangeJWT(grantToken).then((tokenInfo) => {
refreshAccessTokenBeforeItExpires(tokenInfo, { persist: false }); refreshAccessTokenBeforeItExpires(tokenInfo, { persist: false });
return tokenInfo.accessToken; return tokenInfo.accessToken;
}).catch(function(err) { }).catch(function(err) {
...@@ -237,7 +252,7 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -237,7 +252,7 @@ function auth($http, $window, flash, localStorage, random, settings) {
} else if (authCode) { } else if (authCode) {
// Exchange authorization code retrieved from login popup for a new // Exchange authorization code retrieved from login popup for a new
// access token. // access token.
accessTokenPromise = exchangeToken(authCode).then((tokenInfo) => { accessTokenPromise = exchangeAuthCode(authCode).then((tokenInfo) => {
saveToken(tokenInfo); saveToken(tokenInfo);
refreshAccessTokenBeforeItExpires(tokenInfo, { persist: true }); refreshAccessTokenBeforeItExpires(tokenInfo, { persist: true });
return tokenInfo.accessToken; return tokenInfo.accessToken;
......
...@@ -506,8 +506,9 @@ describe('sidebar.oauth-auth', function () { ...@@ -506,8 +506,9 @@ describe('sidebar.oauth-auth', function () {
}).then(() => { }).then(() => {
// 2. Verify that auth code is exchanged for access & refresh tokens. // 2. Verify that auth code is exchanged for access & refresh tokens.
var expectedBody = var expectedBody =
'assertion=acode' + 'client_id=the-client-id' +
'&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer'; '&code=acode' +
'&grant_type=authorization_code';
assert.calledWith(fakeHttp.post, 'https://hypothes.is/api/token', expectedBody, { assert.calledWith(fakeHttp.post, 'https://hypothes.is/api/token', expectedBody, {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
}); });
...@@ -527,6 +528,24 @@ describe('sidebar.oauth-auth', function () { ...@@ -527,6 +528,24 @@ describe('sidebar.oauth-auth', function () {
assert.equal(err.message, 'Authorization window was closed'); assert.equal(err.message, 'Authorization window was closed');
}); });
}); });
it('rejects if auth code exchange fails', () => {
var loggedIn = auth.login();
// Successful response from authz popup.
fakeWindow.sendMessage({
type: 'authorization_response',
code: 'acode',
state: 'notrandom',
});
// Error response from auth code => token exchange.
fakeHttp.post.returns(Promise.resolve({status: 400}));
return loggedIn.catch(err => {
assert.equal(err.message, 'Authorization code exchange failed');
});
});
}); });
// Advance time forward so that any current access tokens will have expired. // Advance time forward so that any current access tokens will have expired.
......
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