Skip to content

Commit f643c77

Browse files
author
renkelvin
authored
Merge branch 'master' into recaptcha-public-preview
2 parents 77f0785 + b04f040 commit f643c77

File tree

22 files changed

+306
-60
lines changed

22 files changed

+306
-60
lines changed

.changeset/brave-ducks-relax.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/auth': minor
3+
'@firebase/auth-compat': minor
4+
---
5+
6+
[feature] Added Firebase App Check support to Firebase Auth.

.github/workflows/e2e-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Checkout Repo
1919
uses: actions/checkout@master
2020
- name: Set up Node (14)
21-
uses: actions/setup-node@v2
21+
uses: actions/setup-node@master
2222
with:
2323
node-version: 14.x
2424
- name: install Chrome stable

.github/workflows/health-metrics-pull-request.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
if: github.event_name == 'push' || !(github.event.pull_request.head.repo.fork)
2424
runs-on: ubuntu-latest
2525
steps:
26-
- uses: actions/checkout@v2
26+
- uses: actions/checkout@v3
2727
- uses: actions/setup-node@v2
2828
with:
2929
node-version: 14.x
@@ -40,7 +40,7 @@ jobs:
4040
if: github.event_name == 'push' || !(github.event.pull_request.head.repo.fork)
4141
runs-on: ubuntu-latest
4242
steps:
43-
- uses: actions/checkout@v2
43+
- uses: actions/checkout@v3
4444
- uses: actions/setup-node@v2
4545
with:
4646
node-version: 14.x

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99

1010
steps:
11-
- uses: actions/checkout@v2
11+
- uses: actions/checkout@v3
1212
- name: Set up Node (14)
1313
uses: actions/setup-node@v2
1414
with:

.github/workflows/test-all.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
run: |
2020
sudo apt-get update
2121
sudo apt-get install google-chrome-stable
22-
- uses: actions/checkout@v2
22+
- uses: actions/checkout@v3
2323
- name: Set up Node (16)
2424
uses: actions/setup-node@v2
2525
with:

.github/workflows/test-changed-auth.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ jobs:
1414
steps:
1515
# install Chrome first, so the correct version of webdriver can be installed by chromedriver when setting up the repo
1616
- name: install Chrome stable
17-
# Install Chrome version 110.0.5481.177-1 as test starts to fail on version 111.0.5563.64-1.
18-
# We will retry to use the latest, once Chrome releases stable version 112 (April 4 ETA).
17+
# Install Chrome version 110.0.5481.177-1 as test starts to fail on version 111.
18+
# We will retry to use the latest, once version 112 becomes stable.
1919
run: |
2020
sudo apt-get update
2121
sudo apt-get install wget

.github/workflows/update-api-reports.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
uses: actions/checkout@master
1515
with:
1616
# checkout HEAD commit instead of merge commit
17-
ref: ${{ github.event.pull_request.head.sha }}
17+
ref: ${{ github.event.pull_request.head.ref }}
1818
token: ${{ github.token }}
1919
- name: Set up Node (14)
2020
uses: actions/setup-node@v2

e2e/karma.conf.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ module.exports = function (config) {
8383
'esModuleInterop': true,
8484
'sourceMap': true,
8585
'target': 'es5',
86-
'importHelpers': true
86+
'importHelpers': true,
87+
'noEmitOnError': true
8788
}
8889
},
8990
plugins: [

packages/auth-compat/src/auth.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import sinonChai from 'sinon-chai';
2424
import { Auth } from './auth';
2525
import { CompatPopupRedirectResolver } from './popup_redirect';
2626
import * as platform from './platform';
27-
import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';
27+
import {
28+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
29+
FAKE_HEARTBEAT_CONTROLLER_PROVIDER
30+
} from '../test/helpers/helpers';
2831

2932
use(sinonChai);
3033

@@ -45,6 +48,7 @@ describe('auth compat', () => {
4548
underlyingAuth = new exp.AuthImpl(
4649
app,
4750
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
51+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
4852
{
4953
apiKey: 'api-key'
5054
} as exp.ConfigInternal

packages/auth-compat/src/popup_redirect.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import * as exp from '@firebase/auth/internal';
2222
import * as platform from './platform';
2323
import { CompatPopupRedirectResolver } from './popup_redirect';
2424
import { FirebaseApp } from '@firebase/app-compat';
25-
import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';
25+
import {
26+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
27+
FAKE_HEARTBEAT_CONTROLLER_PROVIDER
28+
} from '../test/helpers/helpers';
2629

2730
use(sinonChai);
2831

@@ -42,9 +45,14 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => {
4245
beforeEach(() => {
4346
compatResolver = new CompatPopupRedirectResolver();
4447
const app = { options: { apiKey: 'api-key' } } as FirebaseApp;
45-
auth = new exp.AuthImpl(app, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
46-
apiKey: 'api-key'
47-
} as exp.ConfigInternal);
48+
auth = new exp.AuthImpl(
49+
app,
50+
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
51+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
52+
{
53+
apiKey: 'api-key'
54+
} as exp.ConfigInternal
55+
);
4856
});
4957

5058
afterEach(() => {

packages/auth-compat/test/helpers/helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER = {
3434
}
3535
} as unknown as Provider<'heartbeat'>;
3636

37+
// App Check is fully tested in core auth impl
38+
export const FAKE_APP_CHECK_CONTROLLER_PROVIDER = {
39+
getImmediate(): undefined {
40+
return undefined;
41+
}
42+
} as unknown as Provider<'app-check-internal'>;
43+
3744
export function initializeTestInstance(): void {
3845
firebase.initializeApp(getAppConfig());
3946
const stub = stubConsoleToSilenceEmulatorWarnings();

packages/auth/src/api/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export const enum HttpHeader {
4242
X_FIREBASE_LOCALE = 'X-Firebase-Locale',
4343
X_CLIENT_VERSION = 'X-Client-Version',
4444
X_FIREBASE_GMPID = 'X-Firebase-gmpid',
45-
X_FIREBASE_CLIENT = 'X-Firebase-Client'
45+
X_FIREBASE_CLIENT = 'X-Firebase-Client',
46+
X_FIREBASE_APP_CHECK = 'X-Firebase-AppCheck'
4647
}
4748

4849
export const enum Endpoint {

packages/auth/src/core/auth/auth_impl.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { FirebaseApp } from '@firebase/app';
2424
import { FirebaseError } from '@firebase/util';
2525

2626
import {
27+
FAKE_APP_CHECK_CONTROLLER,
28+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
2729
FAKE_HEARTBEAT_CONTROLLER,
2830
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
2931
testAuth,
@@ -65,6 +67,7 @@ describe('core/auth/auth_impl', () => {
6567
const authImpl = new AuthImpl(
6668
FAKE_APP,
6769
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
70+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
6871
{
6972
apiKey: FAKE_APP.options.apiKey!,
7073
apiHost: DefaultConfig.API_HOST,
@@ -585,6 +588,7 @@ describe('core/auth/auth_impl', () => {
585588
const authImpl = new AuthImpl(
586589
FAKE_APP,
587590
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
591+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
588592
{
589593
apiKey: FAKE_APP.options.apiKey!,
590594
apiHost: DefaultConfig.API_HOST,
@@ -659,6 +663,34 @@ describe('core/auth/auth_impl', () => {
659663
'X-Client-Version': 'v'
660664
});
661665
});
666+
667+
it('adds the App Check token if available', async () => {
668+
sinon
669+
.stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
670+
.returns(Promise.resolve({ token: 'fake-token' }));
671+
expect(await auth._getAdditionalHeaders()).to.eql({
672+
'X-Client-Version': 'v',
673+
'X-Firebase-AppCheck': 'fake-token'
674+
});
675+
});
676+
677+
it('does not add the App Check token if none returned', async () => {
678+
sinon
679+
.stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
680+
.returns(Promise.resolve({ token: '' }));
681+
expect(await auth._getAdditionalHeaders()).to.eql({
682+
'X-Client-Version': 'v'
683+
});
684+
});
685+
686+
it('does not add the App Check token if controller unavailable', async () => {
687+
sinon
688+
.stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
689+
.returns(undefined as any);
690+
expect(await auth._getAdditionalHeaders()).to.eql({
691+
'X-Client-Version': 'v'
692+
});
693+
});
662694
});
663695

664696
context('recaptchaEnforcementState', () => {

packages/auth/src/core/auth/auth_impl.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import { _FirebaseService, FirebaseApp } from '@firebase/app';
1919
import { Provider } from '@firebase/component';
20+
import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types';
2021
import {
2122
Auth,
2223
AuthErrorMap,
@@ -65,6 +66,7 @@ import { getRecaptchaConfig } from '../../api/authentication/recaptcha';
6566
import { RecaptchaEnterpriseVerifier } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier';
6667
import { AuthMiddlewareQueue } from './middleware';
6768
import { RecaptchaConfig } from '../../platform_browser/recaptcha/recaptcha';
69+
import { _logWarn } from '../util/log';
6870

6971
interface AsyncAction {
7072
(): Promise<void>;
@@ -113,6 +115,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
113115
constructor(
114116
public readonly app: FirebaseApp,
115117
private readonly heartbeatServiceProvider: Provider<'heartbeat'>,
118+
private readonly appCheckServiceProvider: Provider<AppCheckInternalComponentName>,
116119
public readonly config: ConfigInternal
117120
) {
118121
this.name = app.name;
@@ -677,8 +680,31 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
677680
if (heartbeatsHeader) {
678681
headers[HttpHeader.X_FIREBASE_CLIENT] = heartbeatsHeader;
679682
}
683+
684+
// If the App Check service exists, add the App Check token in the headers
685+
const appCheckToken = await this._getAppCheckToken();
686+
if (appCheckToken) {
687+
headers[HttpHeader.X_FIREBASE_APP_CHECK] = appCheckToken;
688+
}
689+
680690
return headers;
681691
}
692+
693+
async _getAppCheckToken(): Promise<string | undefined> {
694+
const appCheckTokenResult = await this.appCheckServiceProvider
695+
.getImmediate({ optional: true })
696+
?.getToken();
697+
if (appCheckTokenResult?.error) {
698+
// Context: appCheck.getToken() will never throw even if an error happened.
699+
// In the error case, a dummy token will be returned along with an error field describing
700+
// the error. In general, we shouldn't care about the error condition and just use
701+
// the token (actual or dummy) to send requests.
702+
_logWarn(
703+
`Error while retrieving App Check token: ${appCheckTokenResult.error}`
704+
);
705+
}
706+
return appCheckTokenResult?.token;
707+
}
682708
}
683709

684710
/**

packages/auth/src/core/auth/register.ts

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -63,36 +63,38 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
6363
const app = container.getProvider('app').getImmediate()!;
6464
const heartbeatServiceProvider =
6565
container.getProvider<'heartbeat'>('heartbeat');
66+
const appCheckServiceProvider =
67+
container.getProvider<'app-check-internal'>('app-check-internal');
6668
const { apiKey, authDomain } = app.options;
67-
return ((app, heartbeatServiceProvider) => {
68-
_assert(
69-
apiKey && !apiKey.includes(':'),
70-
AuthErrorCode.INVALID_API_KEY,
71-
{ appName: app.name }
72-
);
73-
// Auth domain is optional if IdP sign in isn't being used
74-
_assert(!authDomain?.includes(':'), AuthErrorCode.ARGUMENT_ERROR, {
75-
appName: app.name
76-
});
77-
const config: ConfigInternal = {
78-
apiKey,
79-
authDomain,
80-
clientPlatform,
81-
apiHost: DefaultConfig.API_HOST,
82-
tokenApiHost: DefaultConfig.TOKEN_API_HOST,
83-
apiScheme: DefaultConfig.API_SCHEME,
84-
sdkClientVersion: _getClientVersion(clientPlatform)
85-
};
8669

87-
const authInstance = new AuthImpl(
88-
app,
89-
heartbeatServiceProvider,
90-
config
91-
);
92-
_initializeAuthInstance(authInstance, deps);
70+
_assert(
71+
apiKey && !apiKey.includes(':'),
72+
AuthErrorCode.INVALID_API_KEY,
73+
{ appName: app.name }
74+
);
75+
// Auth domain is optional if IdP sign in isn't being used
76+
_assert(!authDomain?.includes(':'), AuthErrorCode.ARGUMENT_ERROR, {
77+
appName: app.name
78+
});
79+
const config: ConfigInternal = {
80+
apiKey,
81+
authDomain,
82+
clientPlatform,
83+
apiHost: DefaultConfig.API_HOST,
84+
tokenApiHost: DefaultConfig.TOKEN_API_HOST,
85+
apiScheme: DefaultConfig.API_SCHEME,
86+
sdkClientVersion: _getClientVersion(clientPlatform)
87+
};
88+
89+
const authInstance = new AuthImpl(
90+
app,
91+
heartbeatServiceProvider,
92+
appCheckServiceProvider,
93+
config
94+
);
95+
_initializeAuthInstance(authInstance, deps);
9396

94-
return authInstance;
95-
})(app, heartbeatServiceProvider);
97+
return authInstance;
9698
},
9799
ComponentType.PUBLIC
98100
)

packages/auth/src/core/util/handler.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ const WIDGET_PATH = '__/auth/handler';
4040
*/
4141
const EMULATOR_WIDGET_PATH = 'emulator/auth/handler';
4242

43+
/**
44+
* Fragment name for the App Check token that gets passed to the widget
45+
*
46+
* @internal
47+
*/
48+
const FIREBASE_APP_CHECK_FRAGMENT_ID = encodeURIComponent('fac');
49+
4350
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
4451
type WidgetParams = {
4552
apiKey: ApiKey;
@@ -54,14 +61,14 @@ type WidgetParams = {
5461
tid?: string;
5562
} & { [key: string]: string | undefined };
5663

57-
export function _getRedirectUrl(
64+
export async function _getRedirectUrl(
5865
auth: AuthInternal,
5966
provider: AuthProvider,
6067
authType: AuthEventType,
6168
redirectUrl?: string,
6269
eventId?: string,
6370
additionalParams?: Record<string, string>
64-
): string {
71+
): Promise<string> {
6572
_assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN);
6673
_assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY);
6774

@@ -107,7 +114,17 @@ export function _getRedirectUrl(
107114
delete paramsDict[key];
108115
}
109116
}
110-
return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(1)}`;
117+
118+
// Sets the App Check token to pass to the widget
119+
const appCheckToken = await auth._getAppCheckToken();
120+
const appCheckTokenFragment = appCheckToken
121+
? `#${FIREBASE_APP_CHECK_FRAGMENT_ID}=${encodeURIComponent(appCheckToken)}`
122+
: '';
123+
124+
// Start at index 1 to skip the leading '&' in the query string
125+
return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(
126+
1
127+
)}${appCheckTokenFragment}`;
111128
}
112129

113130
function getHandlerBase({ config }: AuthInternal): string {

0 commit comments

Comments
 (0)