Skip to content

Add the outward-bound part of the Cordova redirect flow to auth@exp #4413

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

Merged
merged 7 commits into from
Feb 8, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions packages-exp/auth-exp/index.cordova.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export { indexedDBLocalPersistence } from './src/platform_browser/persistence/in
export { browserLocalPersistence } from './src/platform_browser/persistence/local_storage';
export { browserSessionPersistence } from './src/platform_browser/persistence/session_storage';

export { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect';
export { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect/popup_redirect';
export { signInWithRedirect } from './src/platform_cordova/strategies/redirect';

import { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect';
import { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect/popup_redirect';

export function getAuth(app: FirebaseApp): Auth {
return initializeAuth(app, {
Expand Down
2 changes: 2 additions & 0 deletions packages-exp/auth-exp/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ function getTestFiles(argv) {
return ['src/**/*.test.ts', 'test/helpers/**/*.test.ts'];
} else if (argv.integration) {
return ['test/integration/**/*.test.ts'];
} else if (argv.cordova) {
return ['src/platform_cordova/**/*.test.ts'];
Copy link
Member

Choose a reason for hiding this comment

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

Question: shall we add the other tests too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which other tests?

} else {
// For the catch-all yarn:test, ignore the phone integration test
return [
Expand Down
2 changes: 2 additions & 0 deletions packages-exp/auth-exp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"test:browser:integration": "karma start --single-run --integration",
"test:browser:debug": "karma start --auto-watch",
"test:browser:unit:debug": "karma start --auto-watch --unit",
"test:cordova": "karma start --single-run --cordova",
"test:cordova:debug": "karma start --auto-watch --cordova",
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/!(platform_browser|platform_react_native|platform_cordova)/**/*.test.ts' --file index.node.ts --config ../../config/mocharc.node.js",
"api-report": "api-extractor run --local --verbose",
"predoc": "node ../../scripts/exp/remove-exp.js temp",
Expand Down
13 changes: 13 additions & 0 deletions packages-exp/auth-exp/rollup.config.shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ export function getConfig({ isReleaseBuild }) {
plugins: es5BuildPlugins,
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
},
/**
* Cordova Builds
*/
{
input: {
index: 'index.cordova.ts',
internal: 'internal/index.ts'
},
output: [{ dir: 'dist/cordova', format: 'es', sourcemap: true }],
plugins: es5BuildPlugins,
external: id =>
[...deps, 'cordova'].some(dep => id === dep || id.startsWith(`${dep}/`))
},
/**
* React Native Builds
*/
Expand Down
23 changes: 15 additions & 8 deletions packages-exp/auth-exp/src/core/util/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function _getBrowserName(userAgent: string): BrowserName | string {
return BrowserName.OTHER;
}

export function _isFirefox(ua: string): boolean {
export function _isFirefox(ua = getUA()): boolean {
return /firefox\//i.test(ua);
}

Expand All @@ -102,31 +102,38 @@ export function _isSafari(userAgent: string): boolean {
);
}

export function _isChromeIOS(ua: string): boolean {
export function _isChromeIOS(ua = getUA()): boolean {
return /crios\//i.test(ua);
}

export function _isIEMobile(ua: string): boolean {
export function _isIEMobile(ua = getUA()): boolean {
return /iemobile/i.test(ua);
}

export function _isAndroid(ua: string): boolean {
export function _isAndroid(ua = getUA()): boolean {
return /android/i.test(ua);
}

export function _isBlackBerry(ua: string): boolean {
export function _isBlackBerry(ua = getUA()): boolean {
return /blackberry/i.test(ua);
}

export function _isWebOS(ua: string): boolean {
export function _isWebOS(ua = getUA()): boolean {
return /webos/i.test(ua);
}

export function _isIOS(ua: string): boolean {
export function _isIOS(ua = getUA()): boolean {
return /iphone|ipad|ipod/i.test(ua);
}

export function _isIOSStandalone(ua: string): boolean {
export function _isIOS7Or8(ua = getUA()): boolean {
return (
/(iPad|iPhone|iPod).*OS 7_\d/i.test(ua) ||
/(iPad|iPhone|iPod).*OS 8_\d/i.test(ua)
);
}

export function _isIOSStandalone(ua = getUA()): boolean {
return _isIOS(ua) && !!(window.navigator as NavigatorStandalone)?.standalone;
}

Expand Down
120 changes: 120 additions & 0 deletions packages-exp/auth-exp/src/core/util/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { SDK_VERSION } from '@firebase/app-exp';
import * as externs from '@firebase/auth-types-exp';
import { ApiKey, AppName, Auth } from '../../model/auth';
import { AuthEventType } from '../../model/popup_redirect';
import { AuthErrorCode } from '../errors';
import { OAuthProvider } from '../providers/oauth';
import { _assert } from './assert';
import { isEmpty, querystring } from '@firebase/util';
import { _emulatorUrl } from './emulator';

/**
* URL for Authentication widget which will initiate the OAuth handshake
*
* @internal
*/
const WIDGET_PATH = '__/auth/handler';

/**
* URL for emulated environment
*
* @internal
*/
const EMULATOR_WIDGET_PATH = 'emulator/auth/handler';

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type WidgetParams = {
apiKey: ApiKey;
appName: AppName;
authType: AuthEventType;
redirectUrl?: string;
v: string;
providerId?: string;
scopes?: string;
customParameters?: string;
eventId?: string;
tid?: string;
} & { [key: string]: string | undefined };

export function _getRedirectUrl(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is moved from platform_browser/popup_redirect. The only change is that it now has an "additionalParams" parameter

auth: Auth,
provider: externs.AuthProvider,
authType: AuthEventType,
redirectUrl?: string,
eventId?: string,
additionalParams?: Record<string, string>
): string {
_assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN);
_assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY);

const params: WidgetParams = {
apiKey: auth.config.apiKey,
appName: auth.name,
authType,
redirectUrl,
v: SDK_VERSION,
eventId
};

if (provider instanceof OAuthProvider) {
provider.setDefaultLanguage(auth.languageCode);
params.providerId = provider.providerId || '';
if (!isEmpty(provider.getCustomParameters())) {
params.customParameters = JSON.stringify(provider.getCustomParameters());
}
const scopes = provider.getScopes().filter(scope => scope !== '');
if (scopes.length > 0) {
params.scopes = scopes.join(',');
}
// TODO set additionalParams?
for (const [key, value] of Object.entries(additionalParams || {})) {
params[key] = value;
}
}

if (auth.tenantId) {
params.tid = auth.tenantId;
}

for (const key of Object.keys(params)) {
if ((params as Record<string, unknown>)[key] === undefined) {
delete (params as Record<string, unknown>)[key];
}
}

// TODO: maybe set eid as endipointId
// TODO: maybe set fw as Frameworks.join(",")

const url = new URL(
`${getHandlerBase(auth)}?${querystring(
params as Record<string, string | number>
).slice(1)}`
);

return url.toString();
}

function getHandlerBase({ config }: Auth): string {
if (!config.emulator) {
return `https://${config.authDomain}/${WIDGET_PATH}`;
}

return _emulatorUrl(config, EMULATOR_WIDGET_PATH);
}
2 changes: 1 addition & 1 deletion packages-exp/auth-exp/src/model/popup_redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export interface PopupRedirectResolver extends externs.PopupRedirectResolver {
provider: externs.AuthProvider,
authType: AuthEventType,
eventId?: string
): Promise<never>;
): Promise<unknown>;
Copy link
Member

Choose a reason for hiding this comment

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

Why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In Cordova environments, the redirect does not necessarily end the activity, so a call to signInWithRedirect() can actually resolve

_isIframeWebStorageSupported(
auth: Auth,
cb: (support: boolean) => unknown
Expand Down
112 changes: 12 additions & 100 deletions packages-exp/auth-exp/src/platform_browser/popup_redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,15 @@
* limitations under the License.
*/

import { SDK_VERSION } from '@firebase/app-exp';
import * as externs from '@firebase/auth-types-exp';
import { isEmpty, querystring } from '@firebase/util';

import { AuthEventManager } from '../core/auth/auth_event_manager';
import { AuthErrorCode } from '../core/errors';
import { OAuthProvider } from '../core/providers/oauth';
import { _assert, debugAssert, _fail } from '../core/util/assert';
import { _emulatorUrl } from '../core/util/emulator';
import { _generateEventId } from '../core/util/event_id';
import { _getCurrentUrl } from '../core/util/location';
import { _validateOrigin } from '../core/util/validate_origin';
import { ApiKey, AppName, Auth } from '../model/auth';
import { Auth } from '../model/auth';
import {
AuthEventType,
EventManager,
Expand All @@ -40,18 +36,7 @@ import { _openIframe } from './iframe/iframe';
import { browserSessionPersistence } from './persistence/session_storage';
import { _open, AuthPopup } from './util/popup';
import { _getRedirectResult } from './strategies/redirect';

/**
* URL for Authentication widget which will initiate the OAuth handshake
*
*/
const WIDGET_PATH = '__/auth/handler';

/**
* URL for emulated environment
*
*/
const EMULATOR_WIDGET_PATH = 'emulator/auth/handler';
import { _getRedirectUrl } from '../core/util/handler';

/**
* The special web storage event
Expand Down Expand Up @@ -89,7 +74,13 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver {
);

await this.originValidation(auth);
const url = getRedirectUrl(auth, provider, authType, eventId);
const url = _getRedirectUrl(
auth,
provider,
authType,
_getCurrentUrl(),
eventId
);
return _open(auth, url, _generateEventId());
}

Expand All @@ -100,7 +91,9 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver {
eventId?: string
): Promise<never> {
await this.originValidation(auth);
_setWindowLocation(getRedirectUrl(auth, provider, authType, eventId));
_setWindowLocation(
_getRedirectUrl(auth, provider, authType, _getCurrentUrl(), eventId)
);
return new Promise(() => {});
}

Expand Down Expand Up @@ -180,84 +173,3 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver {
* @public
*/
export const browserPopupRedirectResolver: externs.PopupRedirectResolver = BrowserPopupRedirectResolver;

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type WidgetParams = {
apiKey: ApiKey;
appName: AppName;
authType: AuthEventType;
redirectUrl: string;
v: string;
providerId?: string;
scopes?: string;
customParameters?: string;
eventId?: string;
tid?: string;
};

function getRedirectUrl(
auth: Auth,
provider: externs.AuthProvider,
authType: AuthEventType,
eventId?: string
): string {
_assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN);
_assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY);

const params: WidgetParams = {
apiKey: auth.config.apiKey,
appName: auth.name,
authType,
redirectUrl: _getCurrentUrl(),
v: SDK_VERSION,
eventId
};

if (provider instanceof OAuthProvider) {
provider.setDefaultLanguage(auth.languageCode);
params.providerId = provider.providerId || '';
if (!isEmpty(provider.getCustomParameters())) {
params.customParameters = JSON.stringify(provider.getCustomParameters());
}
const scopes = provider.getScopes().filter(scope => scope !== '');
if (scopes.length > 0) {
params.scopes = scopes.join(',');
}
// TODO set additionalParams?
// let additionalParams = provider.getAdditionalParams();
// for (let key in additionalParams) {
// if (!params.hasOwnProperty(key)) {
// params[key] = additionalParams[key]
// }
// }
}

if (auth.tenantId) {
params.tid = auth.tenantId;
}

for (const key of Object.keys(params)) {
if ((params as Record<string, unknown>)[key] === undefined) {
delete (params as Record<string, unknown>)[key];
}
}

// TODO: maybe set eid as endipointId
// TODO: maybe set fw as Frameworks.join(",")

const url = new URL(
`${getHandlerBase(auth)}?${querystring(
params as Record<string, string | number>
).slice(1)}`
);

return url.toString();
}

function getHandlerBase({ config }: Auth): string {
if (!config.emulator) {
return `https://${config.authDomain}/${WIDGET_PATH}`;
}

return _emulatorUrl(config, EMULATOR_WIDGET_PATH);
}
Loading