Skip to content

Commit

Permalink
feat(auth): add option to disable idp oauth flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashwin Kumar committed May 29, 2024
1 parent b88df66 commit e2d577f
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 11 deletions.
123 changes: 123 additions & 0 deletions packages/auth/__tests__/auth-unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ const createMockLocalStorage = () =>

import { AuthOptions, SignUpParams, AwsCognitoOAuthOpts } from '../src/types';
import { AuthClass as Auth } from '../src/Auth';
import * as urlListener from '../src/urlListener';
import { Credentials, StorageHelper, Hub } from '@aws-amplify/core';
import { AuthError, NoUserPoolError } from '../src/Errors';
import { AuthErrorTypes } from '../src/types/Auth';
Expand Down Expand Up @@ -3544,6 +3545,53 @@ describe('auth unit test', () => {
spyon3.mockClear();
expect(urlOpener).not.toBeCalled();
});

test('should add SP initiated inflight flag to local storage', async () => {
const localStorageSetItemMock = jest.fn();
jest
.spyOn(StorageHelper.prototype, 'getStorage')
.mockImplementation(() => {
return {
setItem: localStorageSetItemMock,
getItem: jest.fn(),
removeItem: jest.fn(),
};
});
let user;
jest.spyOn(Credentials, 'set').mockImplementationOnce(() => {
user = { name: 'username', email: '[email protected]' };
return Promise.resolve('cred' as any);
});
jest
.spyOn(Auth.prototype, 'currentAuthenticatedUser')
.mockImplementation(() => {
if (!user) return Promise.reject('error');
else return Promise.resolve(user);
});

const options: AuthOptions = {
region: 'region',
identityPoolId: 'awsCognitoIdentityPoolId',
userPoolId: 'userPoolId',
oauth: {
domain: 'mydomain.auth.us-east-1.amazoncognito.com',
scope: ['aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code',
urlOpener: jest.fn(),
},
};

const auth = new Auth(options);
await auth.federatedSignIn();

expect(localStorageSetItemMock).toBeCalled();
expect(localStorageSetItemMock).toHaveBeenCalledWith(
'amplify-sp-initiated-oauth-inFlight',
'true'
);
});
});

describe('handleAuthResponse test', () => {
Expand All @@ -3561,6 +3609,12 @@ describe('auth unit test', () => {
setItem() {
return null;
},
getItem() {
return null;
},
removeItem() {
return null;
},
};
});
});
Expand Down Expand Up @@ -3759,6 +3813,75 @@ describe('auth unit test', () => {
});
});

describe('OAuth flow', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('should call urlListener by default', async () => {
const urlListenerSpy = jest.spyOn(urlListener, 'default');
const options: AuthOptions = {
region: 'region',
userPoolId: 'userPoolId',
oauth: {
domain: 'mydomain.auth.us-east-1.amazoncognito.com',
scope: ['aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code',
},
identityPoolId: 'awsCognitoIdentityPoolId',
};
new Auth(options);
expect(urlListenerSpy).toHaveBeenCalledTimes(1);
});

test('should not call urlListener when IDP initiated oauth is disabled', async () => {
const urlListenerSpy = jest.spyOn(urlListener, 'default');
const options: AuthOptions = {
region: 'region',
userPoolId: 'userPoolId',
oauth: {
domain: 'mydomain.auth.us-east-1.amazoncognito.com',
scope: ['aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code',
idpEnabled: false,
},
identityPoolId: 'awsCognitoIdentityPoolId',
};
new Auth(options);
expect(urlListenerSpy).not.toHaveBeenCalled();
});

test('should call urlListener when SP initiated oauth is in flight and IDP is disabled', async () => {
const mockLocalStorage = createMockLocalStorage();
jest
.spyOn(StorageHelper.prototype, 'getStorage')
.mockImplementation(() => mockLocalStorage);
mockLocalStorage.setItem('amplify-sp-initiated-oauth-inFlight', 'true');

const urlListenerSpy = jest.spyOn(urlListener, 'default');

const options: AuthOptions = {
region: 'region',
userPoolId: 'userPoolId',
oauth: {
domain: 'mydomain.auth.us-east-1.amazoncognito.com',
scope: ['aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code',
idpEnabled: false,
},
identityPoolId: 'awsCognitoIdentityPoolId',
};
new Auth(options);
expect(urlListenerSpy).toHaveBeenCalledTimes(1);
});
});

describe('verifiedContact test', () => {
test('happy case with unverified', async () => {
const spyon = jest
Expand Down
34 changes: 23 additions & 11 deletions packages/auth/src/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ export class AuthClass {
: (<any>oauth).awsCognito
: undefined;

const isIdpInitiatedOAuthEnabled =
cognitoHostedUIConfig?.idpEnabled ?? true; // default true, avoid breaking change
const isSpInitiatedOAuthInFlight =
this._storage.getItem('amplify-sp-initiated-oauth-inFlight') === 'true';
if (cognitoHostedUIConfig) {
const cognitoAuthParams = Object.assign(
{
Expand All @@ -249,18 +253,20 @@ export class AuthClass {
cognitoClientId: cognitoAuthParams.cognitoClientId,
});

// **NOTE** - Remove this in a future major release as it is a breaking change
// Prevents _handleAuthResponse from being called multiple times in Expo
// See https://github.com/aws-amplify/amplify-js/issues/4388
const usedResponseUrls = {};
urlListener(({ url }) => {
if (usedResponseUrls[url]) {
return;
}
if (isIdpInitiatedOAuthEnabled || isSpInitiatedOAuthInFlight) {
// **NOTE** - Remove this in a future major release as it is a breaking change
// Prevents _handleAuthResponse from being called multiple times in Expo
// See https://github.com/aws-amplify/amplify-js/issues/4388
const usedResponseUrls = {};
urlListener(({ url }) => {
if (usedResponseUrls[url]) {
return;
}

usedResponseUrls[url] = true;
this._handleAuthResponse(url);
});
usedResponseUrls[url] = true;
this._handleAuthResponse(url);
});
}
}

dispatchAuthEvent(
Expand Down Expand Up @@ -2437,6 +2443,7 @@ export class AuthClass {
? this._config.oauth.redirectSignIn
: this._config.oauth.redirectUri;

this._storage.setItem('amplify-sp-initiated-oauth-inFlight', 'true');
this._oAuthHandler.oauthSignIn(
this._config.oauth.responseType,
this._config.oauth.domain,
Expand Down Expand Up @@ -2517,6 +2524,11 @@ export class AuthClass {

if (hasCodeOrError || hasTokenOrError) {
this._storage.setItem('amplify-redirected-from-hosted-ui', 'true');
// clear temp value
if (this._storage.getItem('amplify-sp-initiated-oauth-inFlight')) {
this._storage.removeItem('amplify-sp-initiated-oauth-inFlight');
}

try {
const { accessToken, idToken, refreshToken, state } =
await this._oAuthHandler.handleAuthResponse(currentUrl);
Expand Down
1 change: 1 addition & 0 deletions packages/auth/src/types/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export interface AwsCognitoOAuthOpts {
redirectSignOut: string;
responseType: string;
options?: object;
idpEnabled?: boolean;
urlOpener?: (url: string, redirectUrl: string) => Promise<any>;
}

Expand Down

0 comments on commit e2d577f

Please sign in to comment.