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

feat: adds advancedOption to getWithPopup #1566

Open
wants to merge 2 commits into
base: 7.10
Choose a base branch
from
Open
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: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

# 7.10.2

### Other

- [#1566](https://github.com/okta/okta-auth-js/pull/1566) feat: adds advance option `monitorPopupWindow` to `getWithPopup`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [#1566](https://github.com/okta/okta-auth-js/pull/1566) feat: adds advance option `monitorPopupWindow` to `getWithPopup`
- [#1566](https://github.com/okta/okta-auth-js/pull/1566) feat: adds the advanced option `monitorPopupWindow` to `getWithPopup`.


# 7.10.1

### Other
Expand Down
23 changes: 17 additions & 6 deletions lib/oidc/getToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,25 @@ export function getToken(sdk: OktaAuthOAuthInterface, options: TokenParams & Pop
popupWindow.location.assign(requestUrl);
}

// The popup may be closed without receiving an OAuth response. Setup a poller to monitor the window.
var popupPromise = new Promise(function (resolve, reject) {
var closePoller = setInterval(function () {
if (!popupWindow || popupWindow.closed) {
clearInterval(closePoller);
reject(new AuthSdkError('Unable to parse OAuth flow response'));
let closePoller;
// Using external IDPs within a popup can cause `popupWindow.closed` to prematurely return `true`
// the origin of the popup changes (to the external IDP). Disable the popup monitor to avoid this
// WARNING: This resulting promise will remain pending until the user completes the flow or timeout
if (options?.monitorPopupWindow === false) {
if (!options.timeout) {
throw new AuthSdkError('`timeout` must be set when `monitorPopupWindow` is set to `false`!');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice touch!

}
}, 100);
}
else {
// The popup may be closed without receiving an OAuth response. Setup a poller to monitor the window.
closePoller = setInterval(function () {
if (!popupWindow || popupWindow.closed) {
clearInterval(closePoller);
reject(new AuthSdkError('Unable to parse OAuth flow response'));
}
}, 100);
}

// Proxy the OAuth promise results
oauthPromise
Expand Down
9 changes: 7 additions & 2 deletions lib/oidc/getWithPopup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@
*
*/
import { AuthSdkError } from '../errors';
import { OktaAuthOAuthInterface, TokenParams, TokenResponse } from './types';
import { OktaAuthOAuthInterface, WithPopupOptions, TokenResponse } from './types';
import { clone } from '../util';
import { getToken } from './getToken';
import { loadPopup } from './util';

export function getWithPopup(sdk: OktaAuthOAuthInterface, options: TokenParams): Promise<TokenResponse> {

// `monitorPopupWindow: false` is an advance setting. Use with caution!
// When `false`, polling to determine whether or not the popup is open is perforemd.
// A `timeout` must be provided when `monitorPopupWindow` is set to `false`
Comment on lines +20 to +22

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// `monitorPopupWindow: false` is an advance setting. Use with caution!
// When `false`, polling to determine whether or not the popup is open is perforemd.
// A `timeout` must be provided when `monitorPopupWindow` is set to `false`
// `monitorPopupWindow: false` is an advanced setting. Use with caution!
// When `false`, polling to determine whether or not the popup is open will not be performed.
// If using this option to disable polling, a `timeout` value must also be provided.


export function getWithPopup(sdk: OktaAuthOAuthInterface, options: WithPopupOptions): Promise<TokenResponse> {
if (arguments.length > 2) {
return Promise.reject(new AuthSdkError('As of version 3.0, "getWithPopup" takes only a single set of options'));
}
Expand Down
5 changes: 4 additions & 1 deletion lib/oidc/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Endpoints } from './endpoints';
export interface PopupParams {
popupTitle?: string;
popupWindow?: Window;
monitorPopupWindow?: boolean;
}

export interface TokenResponse {
Expand Down Expand Up @@ -57,6 +58,8 @@ export interface BaseTokenAPI {
exchangeCodeForTokens(params: TokenParams, urls?: CustomUrls): Promise<TokenResponse>;
}

export type WithPopupOptions = TokenParams & { monitorPopupWindow?: boolean; };

export interface TokenAPI extends BaseTokenAPI {
getUserInfo<S extends CustomUserClaims = CustomUserClaims>(
accessToken?: AccessToken,
Expand All @@ -65,7 +68,7 @@ export interface TokenAPI extends BaseTokenAPI {
getWithRedirect: GetWithRedirectFunction;
parseFromUrl: ParseFromUrlInterface;
getWithoutPrompt(params?: TokenParams): Promise<TokenResponse>;
getWithPopup(params?: TokenParams): Promise<TokenResponse>;
getWithPopup(params?: WithPopupOptions): Promise<TokenResponse>;
revoke(token: RevocableToken): Promise<object>;
renew(token: Token): Promise<Token | undefined>;
renewTokens(options?: RenewTokensParams): Promise<Tokens>;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": true,
"name": "@okta/okta-auth-js",
"description": "The Okta Auth SDK",
"version": "7.10.1",
"version": "7.10.2",
"homepage": "https://github.com/okta/okta-auth-js",
"license": "Apache-2.0",
"main": "build/cjs/exports/default.js",
Expand Down
64 changes: 64 additions & 0 deletions test/spec/oidc/getWithPopup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import tokens from '@okta/test.support/tokens';
import util from '@okta/test.support/util';
import oauthUtil from '@okta/test.support/oauthUtil';
import waitFor from '@okta/test.support/waitFor';
import { setImmediate } from 'timers';
import { AuthSdkError } from 'lib/errors';


describe('token.getWithPopup', function() {
beforeEach(() => {
Expand Down Expand Up @@ -499,4 +502,65 @@ describe('token.getWithPopup', function() {
});
});

it('does not detect when popup window closes', async function () {
// `oauthUtil.setupPopup` performs 2 assertions (other 3 are locally in this test)
expect.assertions(5); // ensures the expect()s in .catch are called
const intervalSpy = jest.spyOn(global, 'setInterval');

jest.useFakeTimers();
const promise = oauthUtil.setupPopup({
willFail: true,
closePopup: true,
oktaAuthArgs: {
pkce: false,
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
clientId: 'NPSfOkH5eZrTy8PMDlvx',
redirectUri: 'https://example.com/redirect'
},
getWithPopupArgs: {
monitorPopupWindow: false,
timeout: 120000
},
})
.catch(err => {
expect(err).toBeInstanceOf(AuthSdkError);
expect(err.message).toBe('OAuth flow timed out');
});

// flushes promise queue (can be replaced by `jest.advanceTimersByTimeAsync` in jest@29)
await (new Promise(resolve => setImmediate(resolve)));
jest.advanceTimersByTime(150000);
await promise;

expect(intervalSpy).not.toHaveBeenCalled();

jest.useRealTimers();
});

it('throws if timeout is not provided', async function () {
// `oauthUtil.setupPopup` performs 2 assertions (other 3 are locally in this test)
expect.assertions(5); // ensures the expect()s in .catch are called
const intervalSpy = jest.spyOn(global, 'setInterval');

await oauthUtil.setupPopup({
willFail: true,
closePopup: true,
oktaAuthArgs: {
pkce: false,
issuer: 'https://auth-js-test.okta.com/oauth2/aus8aus76q8iphupD0h7',
clientId: 'NPSfOkH5eZrTy8PMDlvx',
redirectUri: 'https://example.com/redirect'
},
getWithPopupArgs: {
monitorPopupWindow: false,
},
})
.catch(err => {
expect(err).toBeInstanceOf(AuthSdkError);
expect(err.message).toBe('`timeout` must be set when `monitorPopupWindow` is set to `false`!');
});

expect(intervalSpy).not.toHaveBeenCalled();
});

});
31 changes: 3 additions & 28 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12066,7 +12066,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"

"string-width-cjs@npm:string-width@^4.2.0":
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand All @@ -12084,15 +12084,6 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
Expand Down Expand Up @@ -12153,14 +12144,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

[email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", [email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -13616,7 +13600,7 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand All @@ -13642,15 +13626,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
Expand Down