Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error handling, doc, add tests for TokenManager "error" event #247

Merged
merged 4 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1673,11 +1673,15 @@ authClient.tokenManager.on('renewed', function (key, newToken, oldToken) {

// Triggered when an OAuthError is returned via the API
authClient.tokenManager.on('error', function (err) {
console.log('TokenManager error:', err.message);
console.log('TokenManager error:', err);
// err.name
// err.message
// err.errorCode
// err.errorSummary

if (err.errorCode === 'login_required') {
// Return to unauthenticated state
}
});
```

Expand Down
2 changes: 1 addition & 1 deletion lib/TokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function renew(sdk, tokenMgmtRef, storage, key) {
return freshToken;
})
.catch(function(err) {
if (err.name === 'OAuthError') {
if (err.name === 'OAuthError' || err.name === 'AuthSdkError') {
remove(tokenMgmtRef, storage, key);
emitError(tokenMgmtRef, err);
}
Expand Down
6 changes: 6 additions & 0 deletions test/app/src/testApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ function bindFunctions(testApp, window) {
function TestApp(config) {
this.config = config;
this.oktaAuth = new OktaAuth(config);
this.oktaAuth.tokenManager.on('error', e => {
console.error('Token manager error:', e);
if (e.errorCode === 'login_required') {
this.render();
}
});
}

export default TestApp;
Expand Down
50 changes: 48 additions & 2 deletions test/karma/spec/renewToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ var tokens = require('../../util/tokens');
import OktaAuth from '@okta/okta-auth-js';
import oauthUtil from '../../../lib/oauthUtil';
import pkce from '../../../lib/pkce';
import OauthError from '../../../lib/errors/OAuthError';
import OAuthError from '../../../lib/errors/OAuthError';
import AuthSdkError from '../../../lib/errors/AuthSdkError';

describe('Renew token', function() {

Expand Down Expand Up @@ -79,6 +80,51 @@ describe('Renew token', function() {
});
}

it('TokenManager::renew throws an exception if token does not exist', function() {
const expectedMessage = 'The tokenManager has no token for the key: accessToken';
return bootstrap()
.then(() => {
return sdk.tokenManager.renew('accessToken');
})
.catch(e => {
expect(e instanceof AuthSdkError).toBe(true);
expect(e.message).toBe(expectedMessage);
});
});

it('TokenManager::renew emits an error if token::renew returns an OAuthError', function() {
const errorCode = 'login_required';
const errorMessage = 'The client specified not to prompt, but the user is not logged in.';
const errorCallback = jasmine.createSpy();
return bootstrap()
.then(() => {
sdk.tokenManager.on('error', errorCallback);
sdk.tokenManager.add('accessToken', ACCCESS_TOKEN_PARSED);
spyOn(oauthUtil, 'loadFrame').and.callFake(urlStr => {
const url = new URL(urlStr);
const state = url.searchParams.get('state');
var response = {
state,
error: errorCode,
error_description: errorMessage,
name: 'OAuthError'
};

// Simulate window.postMessage() from iframe
var event = new Event('message');
event.data = response;
event.origin = ISSUER;
window.dispatchEvent(event);
});
return sdk.tokenManager.renew('accessToken');
})
.catch(e => {
expect(e instanceof OAuthError).toBe(true);
expect(e.message).toBe(errorMessage);
expect(errorCallback).toHaveBeenCalledWith(e);
});
});

it('receives/throws error from iframe', function() {
// This is the error if requesting scope='offline_access'
const error = 'access_denied';
Expand Down Expand Up @@ -110,7 +156,7 @@ describe('Renew token', function() {
})
.catch(e => {
expect(oauthUtil.loadFrame).toHaveBeenCalled();
expect(e instanceof OauthError).toBe(true);
expect(e instanceof OAuthError).toBe(true);
expect(e.message).toBe(error_description);
});
});
Expand Down
165 changes: 165 additions & 0 deletions test/spec/tokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,38 @@ describe('TokenManager', function() {
});
});
});

it('removes token if an AuthSdkError is thrown while renewing', function() {
return oauthUtil.setupFrame({
authClient: setupSync(),
willFail: true,
tokenManagerAddKeys: {
'test-accessToken': tokens.standardAccessTokenParsed,
'test-idToken': tokens.standardIdTokenParsed
},
tokenManagerRenewArgs: ['test-accessToken'],
postMessageSrc: {
baseUri: 'http://obviously.fake.foo',
},
postMessageResp: {
state: oauthUtil.mockedState
}
})
.fail(function(e) {
util.expectErrorToEqual(e, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
oauthUtil.expectTokenStorageToEqual(localStorage, {
'test-idToken': tokens.standardIdTokenParsed
});
});
});
});

describe('autoRenew', function() {
Expand Down Expand Up @@ -585,6 +617,103 @@ describe('TokenManager', function() {
});
});

it('Emits an "error" event on OAuth failure', function() {
var authClient = setupSync({
tokenManager: {
autoRenew: true
}
});
var errorEventCallback = jest.fn().mockImplementation(function(err) {
util.expectErrorToEqual(err, {
name: 'OAuthError',
message: 'something went wrong',
errorCode: 'sampleErrorCode',
errorSummary: 'something went wrong'
});
})
authClient.tokenManager.on('error', errorEventCallback);

return oauthUtil.setupFrame({
authClient: authClient,
autoRenew: true,
willFail: true,
fastForwardToTime: true,
autoRenewTokenKey: 'test-idToken',
time: tokens.standardIdTokenParsed.expiresAt + 1,
tokenManagerAddKeys: {
'test-idToken': tokens.standardIdTokenParsed
},
postMessageResp: {
error: 'sampleErrorCode',
'error_description': 'something went wrong',
state: oauthUtil.mockedState
}
})
.fail(function(err) {
util.expectErrorToEqual(err, {
name: 'OAuthError',
message: 'something went wrong',
errorCode: 'sampleErrorCode',
errorSummary: 'something went wrong'
});
oauthUtil.expectTokenStorageToEqual(localStorage, {});

expect(errorEventCallback).toHaveBeenCalled();
});
});

it('Emits an "error" event on AuthSdkError', function() {
var authClient = setupSync({
tokenManager: {
autoRenew: true
}
});
var errorEventCallback = jest.fn().mockImplementation(function(err) {
util.expectErrorToEqual(err, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
})
authClient.tokenManager.on('error', errorEventCallback);

return oauthUtil.setupFrame({
authClient: authClient,
autoRenew: true,
willFail: true,
fastForwardToTime: true,
autoRenewTokenKey: 'test-idToken',
time: tokens.standardIdTokenParsed.expiresAt + 1,
tokenManagerAddKeys: {
'test-idToken': tokens.standardIdTokenParsed
},
postMessageSrc: {
baseUri: 'http://obviously.fake.foo',
},
postMessageResp: {
state: oauthUtil.mockedState
}
})
.fail(function(err) {
util.expectErrorToEqual(err, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
oauthUtil.expectTokenStorageToEqual(localStorage, {});

expect(errorEventCallback).toHaveBeenCalled();
});
});

it('removes a token on OAuth failure', function() {
return oauthUtil.setupFrame({
authClient: setupSync({
Expand Down Expand Up @@ -617,6 +746,42 @@ describe('TokenManager', function() {
});
});

it('removes a token on AuthSdkError', function() {
return oauthUtil.setupFrame({
authClient: setupSync({
tokenManager: {
autoRenew: true
}
}),
autoRenew: true,
willFail: true,
fastForwardToTime: true,
autoRenewTokenKey: 'test-idToken',
time: tokens.standardIdTokenParsed.expiresAt + 1,
tokenManagerAddKeys: {
'test-idToken': tokens.standardIdTokenParsed
},
postMessageSrc: {
baseUri: 'http://obviously.fake.foo',
},
postMessageResp: {
state: oauthUtil.mockedState
}
})
.fail(function(e) {
util.expectErrorToEqual(e, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
oauthUtil.expectTokenStorageToEqual(localStorage, {});
});
});

it('does not renew the token if the token has not expired', function() {
var CURRENT_TIME = 0;
var EXPIRATION_TIME = CURRENT_TIME + 10;
Expand Down