Skip to content

Commit

Permalink
Added token.getUserInfo (#41)
Browse files Browse the repository at this point in the history
Resolves: OKTA-100558
  • Loading branch information
lboyette-okta authored Sep 20, 2016
1 parent e8ac02c commit a4147b3
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 12 deletions.
3 changes: 2 additions & 1 deletion lib/clientBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ function OktaAuthBuilder(args) {
getWithRedirect: util.bind(token.getWithRedirect, sdk, sdk),
parseFromUrl: util.bind(token.parseFromUrl, sdk, sdk),
decode: util.bind(token.decodeToken, sdk),
refresh: util.bind(token.refreshToken, sdk, sdk)
refresh: util.bind(token.refreshToken, sdk, sdk),
getUserInfo: util.bind(token.getUserInfo, sdk, sdk)
};

// This is exposed so we can set window.location in our tests
Expand Down
30 changes: 25 additions & 5 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,32 @@ var Q = require('q');
var AuthApiError = require('./errors/AuthApiError');
var config = require('./config');

function httpRequest(sdk, url, method, args, dontSaveResponse) {
function httpRequest(sdk, options) {
options = options || {};
var url = options.url,
method = options.method,
args = options.args,
dontSaveResponse = options.dontSaveResponse,
accessToken = options.accessToken;

var headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Okta-User-Agent-Extended': 'okta-auth-js-' + config.SDK_VERSION
};
util.extend(headers, sdk.options.headers || {});

var options = {
if (accessToken && util.isString(accessToken)) {
headers['Authorization'] = 'Bearer ' + accessToken;
}

var ajaxOptions = {
headers: headers,
data: args || undefined
};

var err, res;
return new Q(sdk.options.ajaxRequest(method, url, options))
return new Q(sdk.options.ajaxRequest(method, url, ajaxOptions))
.then(function(resp) {
res = resp.responseText;
if (res && util.isString(res)) {
Expand Down Expand Up @@ -70,12 +81,21 @@ function httpRequest(sdk, url, method, args, dontSaveResponse) {

function get(sdk, url, saveResponse) {
url = util.isAbsoluteUrl(url) ? url : sdk.options.url + url;
return httpRequest(sdk, url, 'GET', undefined, !saveResponse);
return httpRequest(sdk, {
url: url,
method: 'GET',
dontSaveResponse: !saveResponse
});
}

function post(sdk, url, args, dontSaveResponse) {
url = util.isAbsoluteUrl(url) ? url : sdk.options.url + url;
return httpRequest(sdk, url, 'POST', args, dontSaveResponse);
return httpRequest(sdk, {
url: url,
method: 'POST',
args: args,
dontSaveResponse: dontSaveResponse
});
}

module.exports = {
Expand Down
6 changes: 5 additions & 1 deletion lib/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ function getSession(sdk) {
}

function closeSession(sdk) {
return http.httpRequest(sdk, sdk.options.url + '/api/v1/sessions/me', 'DELETE', undefined, true);
return http.httpRequest(sdk, {
url: sdk.options.url + '/api/v1/sessions/me',
method: 'DELETE',
dontSaveResponse: true
});
}

function refreshSession(sdk) {
Expand Down
31 changes: 30 additions & 1 deletion lib/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,34 @@ function parseFromUrl(sdk, url) {
});
}

function getUserInfo(sdk, accessTokenObject) {
if (!accessTokenObject ||
(!isToken(accessTokenObject) && !accessTokenObject.accessToken)) {
return Q.reject(new AuthSdkError('getUserInfo requires an access token object'));
}
return http.httpRequest(sdk, {
url: sdk.options.url + '/oauth2/v1/userinfo',
method: 'GET',
dontSaveResponse: true,
accessToken: accessTokenObject.accessToken
})
.fail(function(err) {
if (err.xhr && (err.xhr.status === 401 || err.xhr.status === 403)) {
var authenticateHeader = err.xhr.getResponseHeader('WWW-Authenticate');
if (authenticateHeader) {
var errorMatches = authenticateHeader.match(/error="(.*?)"/) || [];
var errorDescriptionMatches = authenticateHeader.match(/error_description="(.*?)"/) || [];
var error = errorMatches[1];
var errorDescription = errorDescriptionMatches[1];
if (error && errorDescription) {
err = new OAuthError(error, errorDescription);
}
}
}
throw err;
});
}

module.exports = {
getToken: getToken,
getWithoutPrompt: getWithoutPrompt,
Expand All @@ -686,5 +714,6 @@ module.exports = {
refreshIdToken: refreshIdToken,
decodeToken: decodeToken,
verifyIdToken: verifyIdToken,
refreshToken: refreshToken
refreshToken: refreshToken,
getUserInfo: getUserInfo
};
125 changes: 121 additions & 4 deletions test/spec/token.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
define(function(require) {
var OktaAuth = require('OktaAuth');
var tokens = require('../util/tokens');
var util = require('../util/util');
var oauthUtil = require('../util/oauthUtil');
var packageJson = require('../../package.json');
var Q = require('q');

describe('token.decode', function () {
function setupSync() {
return new OktaAuth({ url: 'http://example.okta.com' });
}

function setupSync() {
return new OktaAuth({ url: 'http://example.okta.com' });
}
describe('token.decode', function () {

it('correctly decodes a token', function () {
var oa = setupSync();
Expand Down Expand Up @@ -947,4 +950,118 @@ define(function(require) {
}
);
});

describe('token.getUserInfo', function() {
util.itMakesCorrectRequestResponse({
title: 'allows retrieving UserInfo',
setup: {
request: {
uri: '/oauth2/v1/userinfo',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Okta-User-Agent-Extended': 'okta-auth-js-' + packageJson.version,
'Authorization': 'Bearer ' + tokens.standardAccessToken
}
},
response: 'userinfo'
},
execute: function (test) {
return test.oa.token.getUserInfo(tokens.standardAccessTokenParsed);
},
expectations: function (test, res) {
expect(res).toEqual({
'sub': '00u15ozp26ACQTGHJEBH',
'email': 'samljackson@example.com',
'email_verified': true
});
}
});

it('throws an error if no arguments are passed instead', function(done) {
return Q.resolve(setupSync())
.then(function (oa) {
return oa.token.getUserInfo();
})
.then(function () {
expect('not to be hit').toBe(true);
})
.fail(function (err) {
expect(err.name).toEqual('AuthSdkError');
expect(err.errorSummary).toBe('getUserInfo requires an access token object');
})
.fin(function () {
done();
});
});

it('throws an error if a string is passed instead of an accessToken object', function(done) {
return Q.resolve(setupSync())
.then(function (oa) {
return oa.token.getUserInfo('just a string');
})
.then(function () {
expect('not to be hit').toBe(true);
})
.fail(function (err) {
expect(err.name).toEqual('AuthSdkError');
expect(err.errorSummary).toBe('getUserInfo requires an access token object');
})
.fin(function () {
done();
});
});

util.itErrorsCorrectly({
title: 'returns correct error for 403',
setup: {
request: {
uri: '/oauth2/v1/userinfo',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Okta-User-Agent-Extended': 'okta-auth-js-' + packageJson.version,
'Authorization': 'Bearer ' + tokens.standardAccessToken
}
},
response: 'error-userinfo-insufficient-scope'
},
execute: function (test) {
return test.oa.token.getUserInfo(tokens.standardAccessTokenParsed);
},
expectations: function (test, err) {
expect(err.name).toEqual('OAuthError');
expect(err.message).toEqual('The access token must provide access to at least one' +
' of these scopes - profile, email, address or phone');
expect(err.errorCode).toEqual('insufficient_scope');
expect(err.errorSummary).toEqual('The access token must provide access to at least one' +
' of these scopes - profile, email, address or phone');
}
});

util.itErrorsCorrectly({
title: 'returns correct error for 401',
setup: {
request: {
uri: '/oauth2/v1/userinfo',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Okta-User-Agent-Extended': 'okta-auth-js-' + packageJson.version,
'Authorization': 'Bearer ' + tokens.standardAccessToken
}
},
response: 'error-userinfo-invalid-token'
},
execute: function (test) {
return test.oa.token.getUserInfo(tokens.standardAccessTokenParsed);
},
expectations: function (test, err) {
expect(err.name).toEqual('OAuthError');
expect(err.message).toEqual('The access token is invalid.');
expect(err.errorCode).toEqual('invalid_token');
expect(err.errorSummary).toEqual('The access token is invalid.');
}
});
});
});
5 changes: 5 additions & 0 deletions test/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ define(function(require) {

var deferred = $.Deferred();
var xhr = pair.response;

xhr.getResponseHeader = function(name) {
return xhr.headers && xhr.headers[name];
};

if (xhr.status > 0 && xhr.status < 300) {
// $.ajax send (data, textStatus, jqXHR) on success
_.defer(function () { deferred.resolve(xhr.response, null, xhr); });
Expand Down
8 changes: 8 additions & 0 deletions test/xhr/error-userinfo-insufficient-scope.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
define({
"status": 403,
"responseType": "json",
"headers": {
"WWW-Authenticate": "Bearer error=\"insufficient_scope\", error_description=\"The access token must provide access to at least one of these scopes - profile, email, address or phone\""
},
"response": {}
});
8 changes: 8 additions & 0 deletions test/xhr/error-userinfo-invalid-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
define({
"status": 401,
"responseType": "json",
"headers": {
"WWW-Authenticate": "Bearer error=\"invalid_token\", error_description=\"The access token is invalid.\""
},
"response": {}
});
9 changes: 9 additions & 0 deletions test/xhr/userinfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
define({
"status": 200,
"responseType": "json",
"response": {
"sub":"00u15ozp26ACQTGHJEBH",
"email":"samljackson@example.com",
"email_verified":true
}
});

0 comments on commit a4147b3

Please sign in to comment.