Skip to content

Commit 821f4d3

Browse files
authored
[Auth] Implement origin validation for fast erroring-out in Cordova popup redirect resolver (#5103)
* Implement extra origin validation on Cordova for quick-error-out * Formatting
1 parent db98eda commit 821f4d3

File tree

5 files changed

+75
-8
lines changed

5 files changed

+75
-8
lines changed

packages-exp/auth-exp/src/api/project_config/get_project_config.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,23 @@
1818
import { _performApiRequest, Endpoint, HttpMethod } from '../index';
1919
import { Auth } from '../../model/public_types';
2020

21-
export interface GetProjectConfigRequest {}
21+
export interface GetProjectConfigRequest {
22+
androidPackageName?: string;
23+
iosBundleId?: string;
24+
}
2225

2326
export interface GetProjectConfigResponse {
2427
authorizedDomains: string[];
2528
}
2629

2730
export async function _getProjectConfig(
28-
auth: Auth
31+
auth: Auth,
32+
request: GetProjectConfigRequest = {}
2933
): Promise<GetProjectConfigResponse> {
3034
return _performApiRequest<GetProjectConfigRequest, GetProjectConfigResponse>(
3135
auth,
3236
HttpMethod.GET,
3337
Endpoint.GET_PROJECT_CONFIG,
34-
{}
38+
request
3539
);
3640
}

packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {
6161

6262
beforeEach(async () => {
6363
auth = await testAuth();
64-
resolver = new (cordovaPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolverInternal>)();
64+
resolver =
65+
new (cordovaPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolverInternal>)();
6566
provider = new GoogleAuthProvider();
6667
utilsStubs = sinon.stub(utils);
6768
eventsStubs = {
@@ -88,7 +89,7 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {
8889
afterEach(() => {
8990
sinon.restore();
9091
universalLinksCb = null;
91-
const anyWindow = (win as unknown) as Record<string, unknown>;
92+
const anyWindow = win as unknown as Record<string, unknown>;
9293
delete anyWindow.universalLinks;
9394
delete anyWindow.BuildInfo;
9495
});
@@ -105,6 +106,7 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {
105106
);
106107
utilsStubs._performRedirect.returns(Promise.resolve({}));
107108
utilsStubs._waitForAppResume.returns(Promise.resolve());
109+
utilsStubs._validateOrigin.returns(Promise.resolve());
108110
eventsStubs._generateNewEvent!.returns(event);
109111

110112
const redirectPromise = resolver._openRedirect(

packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
_checkCordovaConfiguration,
3131
_generateHandlerUrl,
3232
_performRedirect,
33+
_validateOrigin,
3334
_waitForAppResume
3435
} from './utils';
3536
import {
@@ -54,6 +55,7 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
5455
readonly _redirectPersistence = browserSessionPersistence;
5556
readonly _shouldInitProactively = true; // This is lightweight for Cordova
5657
private readonly eventManagers = new Map<string, CordovaAuthEventManager>();
58+
private readonly originValidationPromises: Record<string, Promise<void>> = {};
5759

5860
_completeRedirectFn = _getRedirectResult;
5961

@@ -88,6 +90,8 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
8890
manager.resetRedirect();
8991
_clearRedirectOutcomes();
9092

93+
await this._originValidation(auth);
94+
9195
const event = _generateNewEvent(auth, authType, eventId);
9296
await _savePartialEvent(auth, event);
9397
const url = await _generateHandlerUrl(auth, event, provider);
@@ -102,8 +106,13 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
102106
throw new Error('Method not implemented.');
103107
}
104108

105-
_originValidation(): Promise<void> {
106-
return Promise.resolve();
109+
_originValidation(auth: AuthInternal): Promise<void> {
110+
const key = auth._key();
111+
if (!this.originValidationPromises[key]) {
112+
this.originValidationPromises[key] = _validateOrigin(auth);
113+
}
114+
115+
return this.originValidationPromises[key];
107116
}
108117

109118
private attachCallbackListeners(
@@ -176,7 +185,8 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
176185
*
177186
* @public
178187
*/
179-
export const cordovaPopupRedirectResolver: PopupRedirectResolver = CordovaPopupRedirectResolver;
188+
export const cordovaPopupRedirectResolver: PopupRedirectResolver =
189+
CordovaPopupRedirectResolver;
180190

181191
function generateNoEvent(): AuthEvent {
182192
return {

packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
_checkCordovaConfiguration,
2626
_generateHandlerUrl,
2727
_performRedirect,
28+
_validateOrigin,
2829
_waitForAppResume
2930
} from './utils';
3031
import { AuthEvent, AuthEventType } from '../../model/popup_redirect';
@@ -37,6 +38,7 @@ import {
3738
} from '../../../test/helpers/timeout_stub';
3839
import { FirebaseError } from '@firebase/util';
3940
import { InAppBrowserRef, _cordovaWindow } from '../plugins';
41+
import * as projectConfig from '../../api/project_config/get_project_config';
4042

4143
const ANDROID_UA = 'UserAgent/5.0 (Linux; Android 0.0.0)';
4244
const IOS_UA = 'UserAgent/5.0 (iPhone; CPU iPhone 0.0.0)';
@@ -185,6 +187,33 @@ describe('platform_cordova/popup_redirect/utils', () => {
185187
});
186188
});
187189

190+
describe('_validateOrigin', () => {
191+
beforeEach(() => {
192+
sinon.stub(win.BuildInfo, 'packageName').value('com.example.myapp');
193+
sinon
194+
.stub(projectConfig, '_getProjectConfig')
195+
.returns(
196+
Promise.resolve({ /* does not matter here */ authorizedDomains: [] })
197+
);
198+
});
199+
200+
it('sets the correct fields for android', async () => {
201+
setUA(ANDROID_UA);
202+
await _validateOrigin(auth);
203+
expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, {
204+
androidPackageName: 'com.example.myapp'
205+
});
206+
});
207+
208+
it('sets the correct fields for ios', async () => {
209+
setUA(IOS_UA);
210+
await _validateOrigin(auth);
211+
expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, {
212+
iosBundleId: 'com.example.myapp'
213+
});
214+
});
215+
});
216+
188217
describe('_performRedirect', () => {
189218
let isBrowsertabAvailable: boolean;
190219
beforeEach(() => {

packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import { _getRedirectUrl } from '../../core/util/handler';
2828
import { AuthInternal } from '../../model/auth';
2929
import { AuthEvent } from '../../model/popup_redirect';
3030
import { InAppBrowserRef, _cordovaWindow } from '../plugins';
31+
import {
32+
GetProjectConfigRequest,
33+
_getProjectConfig
34+
} from '../../api/project_config/get_project_config';
3135

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

83+
/**
84+
* Validates that this app is valid for this project configuration
85+
*/
86+
export async function _validateOrigin(auth: AuthInternal): Promise<void> {
87+
const { BuildInfo } = _cordovaWindow();
88+
const request: GetProjectConfigRequest = {};
89+
if (_isIOS()) {
90+
request.iosBundleId = BuildInfo.packageName;
91+
} else if (_isAndroid()) {
92+
request.androidPackageName = BuildInfo.packageName;
93+
} else {
94+
_fail(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED);
95+
}
96+
97+
// Will fail automatically if package name is not authorized
98+
await _getProjectConfig(auth, request);
99+
}
100+
79101
export function _performRedirect(
80102
handlerUrl: string
81103
): Promise<InAppBrowserRef | null> {

0 commit comments

Comments
 (0)