Skip to content

Commit

Permalink
[Auth] Implement origin validation for fast erroring-out in Cordova p…
Browse files Browse the repository at this point in the history
…opup redirect resolver (#5103)

* Implement extra origin validation on Cordova for quick-error-out

* Formatting
  • Loading branch information
sam-gc authored Jul 2, 2021
1 parent db98eda commit 821f4d3
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@
import { _performApiRequest, Endpoint, HttpMethod } from '../index';
import { Auth } from '../../model/public_types';

export interface GetProjectConfigRequest {}
export interface GetProjectConfigRequest {
androidPackageName?: string;
iosBundleId?: string;
}

export interface GetProjectConfigResponse {
authorizedDomains: string[];
}

export async function _getProjectConfig(
auth: Auth
auth: Auth,
request: GetProjectConfigRequest = {}
): Promise<GetProjectConfigResponse> {
return _performApiRequest<GetProjectConfigRequest, GetProjectConfigResponse>(
auth,
HttpMethod.GET,
Endpoint.GET_PROJECT_CONFIG,
{}
request
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {

beforeEach(async () => {
auth = await testAuth();
resolver = new (cordovaPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolverInternal>)();
resolver =
new (cordovaPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolverInternal>)();
provider = new GoogleAuthProvider();
utilsStubs = sinon.stub(utils);
eventsStubs = {
Expand All @@ -88,7 +89,7 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {
afterEach(() => {
sinon.restore();
universalLinksCb = null;
const anyWindow = (win as unknown) as Record<string, unknown>;
const anyWindow = win as unknown as Record<string, unknown>;
delete anyWindow.universalLinks;
delete anyWindow.BuildInfo;
});
Expand All @@ -105,6 +106,7 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {
);
utilsStubs._performRedirect.returns(Promise.resolve({}));
utilsStubs._waitForAppResume.returns(Promise.resolve());
utilsStubs._validateOrigin.returns(Promise.resolve());
eventsStubs._generateNewEvent!.returns(event);

const redirectPromise = resolver._openRedirect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
_checkCordovaConfiguration,
_generateHandlerUrl,
_performRedirect,
_validateOrigin,
_waitForAppResume
} from './utils';
import {
Expand All @@ -54,6 +55,7 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
readonly _redirectPersistence = browserSessionPersistence;
readonly _shouldInitProactively = true; // This is lightweight for Cordova
private readonly eventManagers = new Map<string, CordovaAuthEventManager>();
private readonly originValidationPromises: Record<string, Promise<void>> = {};

_completeRedirectFn = _getRedirectResult;

Expand Down Expand Up @@ -88,6 +90,8 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
manager.resetRedirect();
_clearRedirectOutcomes();

await this._originValidation(auth);

const event = _generateNewEvent(auth, authType, eventId);
await _savePartialEvent(auth, event);
const url = await _generateHandlerUrl(auth, event, provider);
Expand All @@ -102,8 +106,13 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
throw new Error('Method not implemented.');
}

_originValidation(): Promise<void> {
return Promise.resolve();
_originValidation(auth: AuthInternal): Promise<void> {
const key = auth._key();
if (!this.originValidationPromises[key]) {
this.originValidationPromises[key] = _validateOrigin(auth);
}

return this.originValidationPromises[key];
}

private attachCallbackListeners(
Expand Down Expand Up @@ -176,7 +185,8 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
*
* @public
*/
export const cordovaPopupRedirectResolver: PopupRedirectResolver = CordovaPopupRedirectResolver;
export const cordovaPopupRedirectResolver: PopupRedirectResolver =
CordovaPopupRedirectResolver;

function generateNoEvent(): AuthEvent {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
_checkCordovaConfiguration,
_generateHandlerUrl,
_performRedirect,
_validateOrigin,
_waitForAppResume
} from './utils';
import { AuthEvent, AuthEventType } from '../../model/popup_redirect';
Expand All @@ -37,6 +38,7 @@ import {
} from '../../../test/helpers/timeout_stub';
import { FirebaseError } from '@firebase/util';
import { InAppBrowserRef, _cordovaWindow } from '../plugins';
import * as projectConfig from '../../api/project_config/get_project_config';

const ANDROID_UA = 'UserAgent/5.0 (Linux; Android 0.0.0)';
const IOS_UA = 'UserAgent/5.0 (iPhone; CPU iPhone 0.0.0)';
Expand Down Expand Up @@ -185,6 +187,33 @@ describe('platform_cordova/popup_redirect/utils', () => {
});
});

describe('_validateOrigin', () => {
beforeEach(() => {
sinon.stub(win.BuildInfo, 'packageName').value('com.example.myapp');
sinon
.stub(projectConfig, '_getProjectConfig')
.returns(
Promise.resolve({ /* does not matter here */ authorizedDomains: [] })
);
});

it('sets the correct fields for android', async () => {
setUA(ANDROID_UA);
await _validateOrigin(auth);
expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, {
androidPackageName: 'com.example.myapp'
});
});

it('sets the correct fields for ios', async () => {
setUA(IOS_UA);
await _validateOrigin(auth);
expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, {
iosBundleId: 'com.example.myapp'
});
});
});

describe('_performRedirect', () => {
let isBrowsertabAvailable: boolean;
beforeEach(() => {
Expand Down
22 changes: 22 additions & 0 deletions packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import { _getRedirectUrl } from '../../core/util/handler';
import { AuthInternal } from '../../model/auth';
import { AuthEvent } from '../../model/popup_redirect';
import { InAppBrowserRef, _cordovaWindow } from '../plugins';
import {
GetProjectConfigRequest,
_getProjectConfig
} from '../../api/project_config/get_project_config';

/**
* How long to wait after the app comes back into focus before concluding that
Expand Down Expand Up @@ -76,6 +80,24 @@ export async function _generateHandlerUrl(
);
}

/**
* Validates that this app is valid for this project configuration
*/
export async function _validateOrigin(auth: AuthInternal): Promise<void> {
const { BuildInfo } = _cordovaWindow();
const request: GetProjectConfigRequest = {};
if (_isIOS()) {
request.iosBundleId = BuildInfo.packageName;
} else if (_isAndroid()) {
request.androidPackageName = BuildInfo.packageName;
} else {
_fail(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED);
}

// Will fail automatically if package name is not authorized
await _getProjectConfig(auth, request);
}

export function _performRedirect(
handlerUrl: string
): Promise<InAppBrowserRef | null> {
Expand Down

0 comments on commit 821f4d3

Please sign in to comment.