Skip to content

Commit 56f904c

Browse files
committed
Implement reCAPTCHA Enterprise flow for phone provider (#7814)
* Update injectRecaptchaFields to inject recaptcha enterprise fields into phone API requests (#7786) * Update injectRecaptchaFields to inject recaptcha fields into phone API requests * Fix lint * Rename captchaResp and fakeToken params * Format * Implement reCAPTCHA Enterprise flow for phone provider * Cleanup tests * Make recaptchaEnterpriseVerifier.verify return a mock when appVerificationDisabledForTesting is true * Lint fix * yarn docgen devsite * Mark appVerifier param in Phone Auth APIs as required * Update API reports * Change RecaptchaProvider to RecaptchaAuthProvider * Fix reference docs * Add more unit tests --------- Co-authored-by: NhienLam <[email protected]>
1 parent 24954c3 commit 56f904c

16 files changed

+1186
-176
lines changed

docs-devsite/auth.phoneauthprovider.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ verifyPhoneNumber(phoneOptions: PhoneInfoOptions | string, applicationVerifier:
217217

218218
Promise&lt;string&gt;
219219

220-
A Promise for a verification ID that can be passed to [PhoneAuthProvider.credential()](./auth.phoneauthprovider.md#phoneauthprovidercredential) to identify this flow..
220+
A Promise for a verification ID that can be passed to [PhoneAuthProvider.credential()](./auth.phoneauthprovider.md#phoneauthprovidercredential) to identify this flow.
221221

222222
### Example 1
223223

packages/auth/src/api/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const enum EnforcementState {
100100
}
101101

102102
// Providers that have reCAPTCHA Enterprise support.
103-
export const enum RecaptchaProvider {
103+
export const enum RecaptchaAuthProvider {
104104
EMAIL_PASSWORD_PROVIDER = 'EMAIL_PASSWORD_PROVIDER',
105105
PHONE_PROVIDER = 'PHONE_PROVIDER'
106106
}

packages/auth/src/core/credentials/email.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ describe('core/credentials/email', () => {
137137

138138
beforeEach(async () => {
139139
auth = await testAuth();
140+
auth.settings.appVerificationDisabledForTesting = false;
140141
});
141142

142143
context('email & password', () => {

packages/auth/src/core/credentials/email.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ import { AuthErrorCode } from '../errors';
3232
import { _fail } from '../util/assert';
3333
import { AuthCredential } from './auth_credential';
3434
import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier';
35-
import { RecaptchaActionName, RecaptchaClientType } from '../../api';
35+
import {
36+
RecaptchaActionName,
37+
RecaptchaClientType,
38+
RecaptchaAuthProvider
39+
} from '../../api';
3640
import { SignUpRequest } from '../../api/authentication/sign_up';
3741
/**
3842
* Interface that represents the credentials returned by {@link EmailAuthProvider} for
@@ -128,7 +132,8 @@ export class EmailAuthCredential extends AuthCredential {
128132
auth,
129133
request,
130134
RecaptchaActionName.SIGN_IN_WITH_PASSWORD,
131-
signInWithPassword
135+
signInWithPassword,
136+
RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER
132137
);
133138
case SignInMethod.EMAIL_LINK:
134139
return signInWithEmailLink(auth, {
@@ -158,7 +163,8 @@ export class EmailAuthCredential extends AuthCredential {
158163
auth,
159164
request,
160165
RecaptchaActionName.SIGN_UP_PASSWORD,
161-
linkEmailPassword
166+
linkEmailPassword,
167+
RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER
162168
);
163169
case SignInMethod.EMAIL_LINK:
164170
return signInWithEmailLinkForLinking(auth, {

packages/auth/src/core/strategies/email_and_password.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ describe('core/strategies/sendPasswordResetEmail', () => {
7474

7575
beforeEach(async () => {
7676
auth = await testAuth();
77+
auth.settings.appVerificationDisabledForTesting = false;
7778
mockFetch.setUp();
7879
});
7980

packages/auth/src/core/strategies/email_and_password.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ import { getModularInstance } from '@firebase/util';
4141
import { OperationType } from '../../model/enums';
4242
import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier';
4343
import { IdTokenResponse } from '../../model/id_token';
44-
import { RecaptchaActionName, RecaptchaClientType } from '../../api';
44+
import {
45+
RecaptchaActionName,
46+
RecaptchaClientType,
47+
RecaptchaAuthProvider
48+
} from '../../api';
4549
import { _isFirebaseServerApp } from '@firebase/app';
4650

4751
/**
@@ -117,7 +121,8 @@ export async function sendPasswordResetEmail(
117121
authInternal,
118122
request,
119123
RecaptchaActionName.GET_OOB_CODE,
120-
authentication.sendPasswordResetEmail
124+
authentication.sendPasswordResetEmail,
125+
RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER
121126
);
122127
}
123128

@@ -291,7 +296,8 @@ export async function createUserWithEmailAndPassword(
291296
authInternal,
292297
request,
293298
RecaptchaActionName.SIGN_UP_PASSWORD,
294-
signUp
299+
signUp,
300+
RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER
295301
);
296302
const response = await signUpResponse.catch(error => {
297303
if (

packages/auth/src/core/strategies/email_link.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe('core/strategies/sendSignInLinkToEmail', () => {
5858

5959
beforeEach(async () => {
6060
auth = await testAuth();
61+
auth.settings.appVerificationDisabledForTesting = false;
6162
mockFetch.setUp();
6263
});
6364

packages/auth/src/core/strategies/email_link.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ import { _assert } from '../util/assert';
3333
import { getModularInstance } from '@firebase/util';
3434
import { _castAuth } from '../auth/auth_impl';
3535
import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier';
36-
import { RecaptchaActionName, RecaptchaClientType } from '../../api';
36+
import {
37+
RecaptchaActionName,
38+
RecaptchaClientType,
39+
RecaptchaAuthProvider
40+
} from '../../api';
3741
import { _isFirebaseServerApp } from '@firebase/app';
3842
import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert';
3943

@@ -108,7 +112,8 @@ export async function sendSignInLinkToEmail(
108112
authInternal,
109113
request,
110114
RecaptchaActionName.GET_OOB_CODE,
111-
api.sendSignInLinkToEmail
115+
api.sendSignInLinkToEmail,
116+
RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER
112117
);
113118
}
114119

packages/auth/src/platform_browser/providers/phone.test.ts

+99-11
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,37 @@
1818
import { expect } from 'chai';
1919
import * as sinon from 'sinon';
2020

21-
import { mockEndpoint } from '../../../test/helpers/api/helper';
21+
import {
22+
mockEndpoint,
23+
mockEndpointWithParams
24+
} from '../../../test/helpers/api/helper';
2225
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
2326
import * as fetch from '../../../test/helpers/mock_fetch';
24-
import { Endpoint } from '../../api';
27+
import {
28+
Endpoint,
29+
RecaptchaClientType,
30+
RecaptchaVersion,
31+
RecaptchaAuthProvider,
32+
EnforcementState
33+
} from '../../api';
2534
import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_verifier';
2635
import { PhoneAuthProvider } from './phone';
36+
import { FAKE_TOKEN } from '../recaptcha/recaptcha_enterprise_verifier';
37+
import { MockGreCAPTCHATopLevel } from '../recaptcha/recaptcha_mock';
38+
import { ApplicationVerifierInternal } from '../../model/application_verifier';
2739

2840
describe('platform_browser/providers/phone', () => {
2941
let auth: TestAuth;
42+
let v2Verifier: ApplicationVerifierInternal;
3043

3144
beforeEach(async () => {
3245
fetch.setUp();
3346
auth = await testAuth();
47+
auth.settings.appVerificationDisabledForTesting = false;
48+
v2Verifier = new RecaptchaVerifier(auth, document.createElement('div'), {});
49+
sinon
50+
.stub(v2Verifier, 'verify')
51+
.returns(Promise.resolve('verification-code'));
3452
});
3553

3654
afterEach(() => {
@@ -39,26 +57,96 @@ describe('platform_browser/providers/phone', () => {
3957
});
4058

4159
context('#verifyPhoneNumber', () => {
42-
it('calls verify on the appVerifier and then calls the server', async () => {
60+
it('calls verify on the appVerifier and then calls the server when recaptcha enterprise is disabled', async () => {
61+
const recaptchaConfigResponseOff = {
62+
recaptchaKey: 'foo/bar/to/site-key',
63+
recaptchaEnforcementState: [
64+
{
65+
provider: RecaptchaAuthProvider.PHONE_PROVIDER,
66+
enforcementState: EnforcementState.OFF
67+
}
68+
]
69+
};
70+
const recaptcha = new MockGreCAPTCHATopLevel();
71+
if (typeof window === 'undefined') {
72+
return;
73+
}
74+
window.grecaptcha = recaptcha;
75+
sinon
76+
.stub(recaptcha.enterprise, 'execute')
77+
.returns(Promise.resolve('enterprise-token'));
78+
79+
mockEndpointWithParams(
80+
Endpoint.GET_RECAPTCHA_CONFIG,
81+
{
82+
clientType: RecaptchaClientType.WEB,
83+
version: RecaptchaVersion.ENTERPRISE
84+
},
85+
recaptchaConfigResponseOff
86+
);
87+
4388
const route = mockEndpoint(Endpoint.SEND_VERIFICATION_CODE, {
4489
sessionInfo: 'verification-id'
4590
});
4691

47-
const verifier = new RecaptchaVerifier(
48-
auth,
49-
document.createElement('div'),
50-
{}
92+
const provider = new PhoneAuthProvider(auth);
93+
const result = await provider.verifyPhoneNumber(
94+
'+15105550000',
95+
v2Verifier
5196
);
97+
expect(result).to.eq('verification-id');
98+
expect(route.calls[0].request).to.eql({
99+
phoneNumber: '+15105550000',
100+
recaptchaToken: 'verification-code',
101+
captchaResponse: FAKE_TOKEN,
102+
clientType: RecaptchaClientType.WEB,
103+
recaptchaVersion: RecaptchaVersion.ENTERPRISE
104+
});
105+
});
106+
107+
it('calls the server when recaptcha enterprise is enabled', async () => {
108+
const recaptchaConfigResponseEnforce = {
109+
recaptchaKey: 'foo/bar/to/site-key',
110+
recaptchaEnforcementState: [
111+
{
112+
provider: RecaptchaAuthProvider.PHONE_PROVIDER,
113+
enforcementState: EnforcementState.ENFORCE
114+
}
115+
]
116+
};
117+
const recaptcha = new MockGreCAPTCHATopLevel();
118+
if (typeof window === 'undefined') {
119+
return;
120+
}
121+
window.grecaptcha = recaptcha;
52122
sinon
53-
.stub(verifier, 'verify')
54-
.returns(Promise.resolve('verification-code'));
123+
.stub(recaptcha.enterprise, 'execute')
124+
.returns(Promise.resolve('enterprise-token'));
125+
126+
mockEndpointWithParams(
127+
Endpoint.GET_RECAPTCHA_CONFIG,
128+
{
129+
clientType: RecaptchaClientType.WEB,
130+
version: RecaptchaVersion.ENTERPRISE
131+
},
132+
recaptchaConfigResponseEnforce
133+
);
134+
135+
const route = mockEndpoint(Endpoint.SEND_VERIFICATION_CODE, {
136+
sessionInfo: 'verification-id'
137+
});
55138

56139
const provider = new PhoneAuthProvider(auth);
57-
const result = await provider.verifyPhoneNumber('+15105550000', verifier);
140+
const result = await provider.verifyPhoneNumber(
141+
'+15105550000',
142+
v2Verifier
143+
);
58144
expect(result).to.eq('verification-id');
59145
expect(route.calls[0].request).to.eql({
60146
phoneNumber: '+15105550000',
61-
recaptchaToken: 'verification-code'
147+
captchaResponse: 'enterprise-token',
148+
clientType: RecaptchaClientType.WEB,
149+
recaptchaVersion: RecaptchaVersion.ENTERPRISE
62150
});
63151
});
64152
});

packages/auth/src/platform_browser/providers/phone.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export class PhoneAuthProvider {
100100
* {@link RecaptchaVerifier}.
101101
*
102102
* @returns A Promise for a verification ID that can be passed to
103-
* {@link PhoneAuthProvider.credential} to identify this flow..
103+
* {@link PhoneAuthProvider.credential} to identify this flow.
104104
*/
105105
verifyPhoneNumber(
106106
phoneOptions: PhoneInfoOptions | string,

packages/auth/src/platform_browser/recaptcha/recaptcha.test.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929

3030
import { isV2, isEnterprise, RecaptchaConfig } from './recaptcha';
3131
import { GetRecaptchaConfigResponse } from '../../api/authentication/recaptcha';
32-
import { EnforcementState, RecaptchaProvider } from '../../api/index';
32+
import { EnforcementState, RecaptchaAuthProvider } from '../../api/index';
3333

3434
use(chaiAsPromised);
3535
use(sinonChai);
@@ -46,11 +46,11 @@ describe('platform_browser/recaptcha/recaptcha', () => {
4646
recaptchaKey: 'projects/testproj/keys/' + TEST_SITE_KEY,
4747
recaptchaEnforcementState: [
4848
{
49-
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
49+
provider: RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER,
5050
enforcementState: EnforcementState.ENFORCE
5151
},
5252
{
53-
provider: RecaptchaProvider.PHONE_PROVIDER,
53+
provider: RecaptchaAuthProvider.PHONE_PROVIDER,
5454
enforcementState: EnforcementState.AUDIT
5555
}
5656
]
@@ -60,11 +60,11 @@ describe('platform_browser/recaptcha/recaptcha', () => {
6060
recaptchaKey: 'projects/testproj/keys/' + TEST_SITE_KEY,
6161
recaptchaEnforcementState: [
6262
{
63-
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
63+
provider: RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER,
6464
enforcementState: EnforcementState.OFF
6565
},
6666
{
67-
provider: RecaptchaProvider.PHONE_PROVIDER,
67+
provider: RecaptchaAuthProvider.PHONE_PROVIDER,
6868
enforcementState: EnforcementState.OFF
6969
}
7070
]
@@ -75,11 +75,11 @@ describe('platform_browser/recaptcha/recaptcha', () => {
7575
recaptchaKey: 'projects/testproj/keys/' + TEST_SITE_KEY,
7676
recaptchaEnforcementState: [
7777
{
78-
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
78+
provider: RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER,
7979
enforcementState: EnforcementState.ENFORCE
8080
},
8181
{
82-
provider: RecaptchaProvider.PHONE_PROVIDER,
82+
provider: RecaptchaAuthProvider.PHONE_PROVIDER,
8383
enforcementState: EnforcementState.OFF
8484
}
8585
]
@@ -120,33 +120,33 @@ describe('platform_browser/recaptcha/recaptcha', () => {
120120
it('should construct the recaptcha config from the backend response', () => {
121121
expect(recaptchaConfig.siteKey).to.eq(TEST_SITE_KEY);
122122
expect(recaptchaConfig.recaptchaEnforcementState[0]).to.eql({
123-
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
123+
provider: RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER,
124124
enforcementState: EnforcementState.ENFORCE
125125
});
126126
expect(recaptchaConfig.recaptchaEnforcementState[1]).to.eql({
127-
provider: RecaptchaProvider.PHONE_PROVIDER,
127+
provider: RecaptchaAuthProvider.PHONE_PROVIDER,
128128
enforcementState: EnforcementState.AUDIT
129129
});
130130
expect(recaptchaConfigEnforceAndOff.recaptchaEnforcementState[1]).to.eql({
131-
provider: RecaptchaProvider.PHONE_PROVIDER,
131+
provider: RecaptchaAuthProvider.PHONE_PROVIDER,
132132
enforcementState: EnforcementState.OFF
133133
});
134134
});
135135

136136
it('#getProviderEnforcementState should return the correct enforcement state of the provider', () => {
137137
expect(
138138
recaptchaConfig.getProviderEnforcementState(
139-
RecaptchaProvider.EMAIL_PASSWORD_PROVIDER
139+
RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER
140140
)
141141
).to.eq(EnforcementState.ENFORCE);
142142
expect(
143143
recaptchaConfig.getProviderEnforcementState(
144-
RecaptchaProvider.PHONE_PROVIDER
144+
RecaptchaAuthProvider.PHONE_PROVIDER
145145
)
146146
).to.eq(EnforcementState.AUDIT);
147147
expect(
148148
recaptchaConfigEnforceAndOff.getProviderEnforcementState(
149-
RecaptchaProvider.PHONE_PROVIDER
149+
RecaptchaAuthProvider.PHONE_PROVIDER
150150
)
151151
).to.eq(EnforcementState.OFF);
152152
expect(recaptchaConfig.getProviderEnforcementState('invalid-provider')).to
@@ -156,15 +156,15 @@ describe('platform_browser/recaptcha/recaptcha', () => {
156156
it('#isProviderEnabled should return the enablement state of the provider', () => {
157157
expect(
158158
recaptchaConfig.isProviderEnabled(
159-
RecaptchaProvider.EMAIL_PASSWORD_PROVIDER
159+
RecaptchaAuthProvider.EMAIL_PASSWORD_PROVIDER
160160
)
161161
).to.be.true;
162162
expect(
163-
recaptchaConfig.isProviderEnabled(RecaptchaProvider.PHONE_PROVIDER)
163+
recaptchaConfig.isProviderEnabled(RecaptchaAuthProvider.PHONE_PROVIDER)
164164
).to.be.true;
165165
expect(
166166
recaptchaConfigEnforceAndOff.isProviderEnabled(
167-
RecaptchaProvider.PHONE_PROVIDER
167+
RecaptchaAuthProvider.PHONE_PROVIDER
168168
)
169169
).to.be.false;
170170
expect(recaptchaConfig.isProviderEnabled('invalid-provider')).to.be.false;

0 commit comments

Comments
 (0)