Skip to content

Commit b942e9e

Browse files
authored
Add reCAPTCHA Enterprise support for Phone Auth (#8568)
* 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 (#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]> * Make ApplicationVerifier params optional in Phone Auth APIs (#8366) * Make ApplicationVerifier params optional in Phone APIs * Add more unit tests for when ApplicationVerifier is not available * Only fetch reCAPTCHA v2 token when FAKE_TOKEN (#8493) * Proceed to reCAPTCHA v2 if unable to get reCAPTCHA Enterprise enablement state (#8500) * Proceed to reCAPTCHA v2 if cannot get phone enablement state * nit: Add a missing period. * Add integration tests for rCE ENFORCE (#8538) * Add integration test for rCE ENFORCE * format * Add changeset and refdocs * Bump main firebase package in changeset * Addressed refdocs feedback --------- Co-authored-by: NhienLam <[email protected]>
1 parent 8fb044e commit b942e9e

28 files changed

+1948
-223
lines changed

.changeset/shy-bikes-explain.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/auth': minor
3+
'firebase': minor
4+
---
5+
6+
[feature] Added reCAPTCHA Enterprise support for app verification during phone authentication.

common/api-review/auth.api.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export function isSignInWithEmailLink(auth: Auth, emailLink: string): boolean;
445445
export function linkWithCredential(user: User, credential: AuthCredential): Promise<UserCredential>;
446446

447447
// @public
448-
export function linkWithPhoneNumber(user: User, phoneNumber: string, appVerifier: ApplicationVerifier): Promise<ConfirmationResult>;
448+
export function linkWithPhoneNumber(user: User, phoneNumber: string, appVerifier?: ApplicationVerifier): Promise<ConfirmationResult>;
449449

450450
// @public
451451
export function linkWithPopup(user: User, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise<UserCredential>;
@@ -625,7 +625,7 @@ export class PhoneAuthProvider {
625625
static readonly PHONE_SIGN_IN_METHOD: 'phone';
626626
static readonly PROVIDER_ID: 'phone';
627627
readonly providerId: "phone";
628-
verifyPhoneNumber(phoneOptions: PhoneInfoOptions | string, applicationVerifier: ApplicationVerifier): Promise<string>;
628+
verifyPhoneNumber(phoneOptions: PhoneInfoOptions | string, applicationVerifier?: ApplicationVerifier): Promise<string>;
629629
}
630630

631631
// @public
@@ -692,7 +692,7 @@ export interface ReactNativeAsyncStorage {
692692
export function reauthenticateWithCredential(user: User, credential: AuthCredential): Promise<UserCredential>;
693693

694694
// @public
695-
export function reauthenticateWithPhoneNumber(user: User, phoneNumber: string, appVerifier: ApplicationVerifier): Promise<ConfirmationResult>;
695+
export function reauthenticateWithPhoneNumber(user: User, phoneNumber: string, appVerifier?: ApplicationVerifier): Promise<ConfirmationResult>;
696696

697697
// @public
698698
export function reauthenticateWithPopup(user: User, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise<UserCredential>;
@@ -778,7 +778,7 @@ export function signInWithEmailAndPassword(auth: Auth, email: string, password:
778778
export function signInWithEmailLink(auth: Auth, email: string, emailLink?: string): Promise<UserCredential>;
779779

780780
// @public
781-
export function signInWithPhoneNumber(auth: Auth, phoneNumber: string, appVerifier: ApplicationVerifier): Promise<ConfirmationResult>;
781+
export function signInWithPhoneNumber(auth: Auth, phoneNumber: string, appVerifier?: ApplicationVerifier): Promise<ConfirmationResult>;
782782

783783
// @public
784784
export function signInWithPopup(auth: Auth, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise<UserCredential>;

docs-devsite/auth.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -923,14 +923,16 @@ Asynchronously signs in using a phone number.
923923

924924
This method sends a code via SMS to the given phone number, and returns a [ConfirmationResult](./auth.confirmationresult.md#confirmationresult_interface)<!-- -->. After the user provides the code sent to their phone, call [ConfirmationResult.confirm()](./auth.confirmationresult.md#confirmationresultconfirm) with the code to sign the user in.
925925

926-
For abuse prevention, this method also requires a [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface)<!-- -->. This SDK includes a reCAPTCHA-based implementation, [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class)<!-- -->. This function can work on other platforms that do not support the [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class) (like React Native), but you need to use a third-party [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface) implementation.
926+
For abuse prevention, this method requires a [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface)<!-- -->. This SDK includes an implementation based on reCAPTCHA v2, [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class)<!-- -->. This function can work on other platforms that do not support the [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class) (like React Native), but you need to use a third-party [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface) implementation.
927+
928+
If you've enabled project-level reCAPTCHA Enterprise bot protection in Enforce mode, you can omit the [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface)<!-- -->.
927929

928930
This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface)<!-- -->.
929931

930932
<b>Signature:</b>
931933

932934
```typescript
933-
export declare function signInWithPhoneNumber(auth: Auth, phoneNumber: string, appVerifier: ApplicationVerifier): Promise<ConfirmationResult>;
935+
export declare function signInWithPhoneNumber(auth: Auth, phoneNumber: string, appVerifier?: ApplicationVerifier): Promise<ConfirmationResult>;
934936
```
935937

936938
#### Parameters
@@ -1304,7 +1306,7 @@ This method does not work in a Node.js environment.
13041306
<b>Signature:</b>
13051307

13061308
```typescript
1307-
export declare function linkWithPhoneNumber(user: User, phoneNumber: string, appVerifier: ApplicationVerifier): Promise<ConfirmationResult>;
1309+
export declare function linkWithPhoneNumber(user: User, phoneNumber: string, appVerifier?: ApplicationVerifier): Promise<ConfirmationResult>;
13081310
```
13091311

13101312
#### Parameters
@@ -1457,7 +1459,7 @@ This method does not work in a Node.js environment or on any [User](./auth.user.
14571459
<b>Signature:</b>
14581460

14591461
```typescript
1460-
export declare function reauthenticateWithPhoneNumber(user: User, phoneNumber: string, appVerifier: ApplicationVerifier): Promise<ConfirmationResult>;
1462+
export declare function reauthenticateWithPhoneNumber(user: User, phoneNumber: string, appVerifier?: ApplicationVerifier): Promise<ConfirmationResult>;
14611463
```
14621464

14631465
#### Parameters

docs-devsite/auth.phoneauthprovider.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -203,21 +203,21 @@ Starts a phone number authentication flow by sending a verification code to the
203203
<b>Signature:</b>
204204

205205
```typescript
206-
verifyPhoneNumber(phoneOptions: PhoneInfoOptions | string, applicationVerifier: ApplicationVerifier): Promise<string>;
206+
verifyPhoneNumber(phoneOptions: PhoneInfoOptions | string, applicationVerifier?: ApplicationVerifier): Promise<string>;
207207
```
208208

209209
#### Parameters
210210

211211
| Parameter | Type | Description |
212212
| --- | --- | --- |
213213
| phoneOptions | [PhoneInfoOptions](./auth.md#phoneinfooptions) \| string | |
214-
| applicationVerifier | [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface) | For abuse prevention, this method also requires a [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface)<!-- -->. This SDK includes a reCAPTCHA-based implementation, [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class)<!-- -->. |
214+
| applicationVerifier | [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface) | An [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface)<!-- -->, which prevents requests from unauthorized clients. This SDK includes an implementation based on reCAPTCHA v2, [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class)<!-- -->. If you've enabled reCAPTCHA Enterprise bot protection in Enforce mode, this parameter is optional; in all other configurations, the parameter is required. |
215215

216216
<b>Returns:</b>
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/README.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ firebase emulators:exec --project foo-bar --only auth "yarn test:integration:loc
5454

5555
### Integration testing with the production backend
5656

57-
Currently, MFA TOTP and password policy tests only run against the production backend (since they are not supported on the emulator yet).
57+
Currently, MFA TOTP, password policy, and reCAPTCHA Enterprise phone verification tests only run
58+
against the production backend (since they are not supported on the emulator yet).
5859
Running against the backend also makes it a more reliable end-to-end test.
5960

61+
#### TOTP
62+
6063
The TOTP tests require the following email/password combination to exist in the project, so if you are running this test against your test project, please create this user:
6164

6265
'[email protected]', 'password'
@@ -71,6 +74,8 @@ curl -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Conten
7174
}'
7275
```
7376

77+
#### Password policy
78+
7479
The password policy tests require a tenant configured with a password policy that requires all options to exist in the project.
7580

7681
If you are running this test against your test project, please create the tenant and configure the policy with the following curl command:
@@ -98,6 +103,32 @@ curl -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Conten
98103

99104
Replace the tenant ID `passpol-tenant-d7hha` in [test/integration/flows/password_policy.test.ts](https://github.com/firebase/firebase-js-sdk/blob/main/packages/auth/test/integration/flows/password_policy.test.ts) with the ID for the newly created tenant. The tenant ID can be found at the end of the `name` property in the response and is in the format `passpol-tenant-xxxxx`.
100105

106+
#### reCAPTCHA Enterprise phone verification
107+
108+
The reCAPTCHA Enterprise phone verification tests require reCAPTCHA Enterprise to be enabled and
109+
the following fictional phone number to be configured and in the project.
110+
111+
If you are running this
112+
test against your project, please [add this test phone number](https://firebase.google.com/docs/auth/web/phone-auth#create-fictional-phone-numbers-and-verification-codes):
113+
114+
'+1 555-555-1000', SMS code: '123456'
115+
116+
Follow [this guide](https://cloud.google.com/identity-platform/docs/recaptcha-enterprise) to enable reCAPTCHA
117+
Enterprise, then use the following curl command to set reCAPTCHA Enterprise to ENFORCE for phone provider:
118+
119+
```
120+
curl -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" -H "X-Goog-User-Project: $
121+
{PROJECT_ID}" -X POST https://identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config?updateMask=recaptchaConfig.phoneEnforcementState,recaptchaConfig.useSmsBotScore,recaptchaConfig.useSmsTollFraudProtection -d '
122+
{
123+
"name": "projects/{PROJECT_ID}",
124+
"recaptchaConfig": {
125+
"phoneEnforcementState": "ENFORCE",
126+
"useSmsBotScore": "true",
127+
"useSmsTollFraudProtection": "true",
128+
},
129+
}'
130+
```
131+
101132
### Selenium Webdriver tests
102133

103134
These tests assume that you have both Firefox and Chrome installed on your

packages/auth/karma.conf.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ function getTestFiles(argv) {
5151
if (argv.prodbackend) {
5252
return [
5353
'test/integration/flows/totp.test.ts',
54-
'test/integration/flows/password_policy.test.ts'
54+
'test/integration/flows/password_policy.test.ts',
55+
'test/integration/flows/recaptcha_enterprise.test.ts'
5556
];
5657
}
5758
return argv.local

packages/auth/src/api/account_management/mfa.test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import chaiAsPromised from 'chai-as-promised';
2020

2121
import { FirebaseError } from '@firebase/util';
2222

23-
import { Endpoint, HttpHeader } from '../';
23+
import {
24+
Endpoint,
25+
HttpHeader,
26+
RecaptchaClientType,
27+
RecaptchaVersion
28+
} from '../';
2429
import { mockEndpoint } from '../../../test/helpers/api/helper';
2530
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
2631
import * as mockFetch from '../../../test/helpers/mock_fetch';
@@ -40,7 +45,10 @@ describe('api/account_management/startEnrollPhoneMfa', () => {
4045
idToken: 'id-token',
4146
phoneEnrollmentInfo: {
4247
phoneNumber: 'phone-number',
43-
recaptchaToken: 'captcha-token'
48+
recaptchaToken: 'captcha-token',
49+
captchaResponse: 'captcha-response',
50+
clientType: RecaptchaClientType.WEB,
51+
recaptchaVersion: RecaptchaVersion.ENTERPRISE
4452
}
4553
};
4654

packages/auth/src/api/account_management/mfa.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import {
1919
Endpoint,
2020
HttpMethod,
21+
RecaptchaClientType,
22+
RecaptchaVersion,
2123
_addTidIfNecessary,
2224
_performApiRequest
2325
} from '../index';
@@ -55,7 +57,12 @@ export interface StartPhoneMfaEnrollmentRequest {
5557
idToken: string;
5658
phoneEnrollmentInfo: {
5759
phoneNumber: string;
58-
recaptchaToken: string;
60+
// reCAPTCHA v2 token
61+
recaptchaToken?: string;
62+
// reCAPTCHA Enterprise token
63+
captchaResponse?: string;
64+
clientType?: RecaptchaClientType;
65+
recaptchaVersion?: RecaptchaVersion;
5966
};
6067
tenantId?: string;
6168
}

packages/auth/src/api/authentication/mfa.test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import chaiAsPromised from 'chai-as-promised';
2020

2121
import { FirebaseError } from '@firebase/util';
2222

23-
import { Endpoint, HttpHeader } from '../';
23+
import {
24+
Endpoint,
25+
HttpHeader,
26+
RecaptchaClientType,
27+
RecaptchaVersion
28+
} from '../';
2429
import { mockEndpoint } from '../../../test/helpers/api/helper';
2530
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
2631
import * as mockFetch from '../../../test/helpers/mock_fetch';
@@ -34,7 +39,10 @@ describe('api/authentication/startSignInPhoneMfa', () => {
3439
mfaPendingCredential: 'my-creds',
3540
mfaEnrollmentId: 'my-enrollment-id',
3641
phoneSignInInfo: {
37-
recaptchaToken: 'captcha-token'
42+
recaptchaToken: 'captcha-token',
43+
captchaResponse: 'captcha-response',
44+
clientType: RecaptchaClientType.WEB,
45+
recaptchaVersion: RecaptchaVersion.ENTERPRISE
3846
}
3947
};
4048

packages/auth/src/api/authentication/mfa.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
_performApiRequest,
2020
Endpoint,
2121
HttpMethod,
22+
RecaptchaClientType,
23+
RecaptchaVersion,
2224
_addTidIfNecessary
2325
} from '../index';
2426
import { Auth } from '../../model/public_types';
@@ -47,7 +49,12 @@ export interface StartPhoneMfaSignInRequest {
4749
mfaPendingCredential: string;
4850
mfaEnrollmentId: string;
4951
phoneSignInInfo: {
50-
recaptchaToken: string;
52+
// reCAPTCHA v2 token
53+
recaptchaToken?: string;
54+
// reCAPTCHA Enterprise token
55+
captchaResponse?: string;
56+
clientType?: RecaptchaClientType;
57+
recaptchaVersion?: RecaptchaVersion;
5158
};
5259
tenantId?: string;
5360
}

packages/auth/src/api/authentication/sms.test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import chaiAsPromised from 'chai-as-promised';
2121
import { ProviderId } from '../../model/enums';
2222
import { FirebaseError } from '@firebase/util';
2323

24-
import { Endpoint, HttpHeader } from '../';
24+
import {
25+
Endpoint,
26+
HttpHeader,
27+
RecaptchaClientType,
28+
RecaptchaVersion
29+
} from '../';
2530
import { mockEndpoint } from '../../../test/helpers/api/helper';
2631
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
2732
import * as mockFetch from '../../../test/helpers/mock_fetch';
@@ -38,7 +43,10 @@ use(chaiAsPromised);
3843
describe('api/authentication/sendPhoneVerificationCode', () => {
3944
const request = {
4045
phoneNumber: '123456789',
41-
recaptchaToken: 'captchad'
46+
recaptchaToken: 'captchad',
47+
captchaResponse: 'captcha-response',
48+
clientType: RecaptchaClientType.WEB,
49+
recaptchaVersion: RecaptchaVersion.ENTERPRISE
4250
};
4351

4452
let auth: TestAuth;

packages/auth/src/api/authentication/sms.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import {
1919
Endpoint,
2020
HttpMethod,
21+
RecaptchaClientType,
22+
RecaptchaVersion,
2123
_addTidIfNecessary,
2224
_makeTaggedError,
2325
_performApiRequest,
@@ -30,8 +32,13 @@ import { Auth } from '../../model/public_types';
3032

3133
export interface SendPhoneVerificationCodeRequest {
3234
phoneNumber: string;
33-
recaptchaToken: string;
35+
// reCAPTCHA v2 token
36+
recaptchaToken?: string;
3437
tenantId?: string;
38+
// reCAPTCHA Enterprise token
39+
captchaResponse?: string;
40+
clientType?: RecaptchaClientType;
41+
recaptchaVersion?: RecaptchaVersion;
3542
}
3643

3744
export interface SendPhoneVerificationCodeResponse {

packages/auth/src/api/index.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ export const enum RecaptchaVersion {
8686
export const enum RecaptchaActionName {
8787
SIGN_IN_WITH_PASSWORD = 'signInWithPassword',
8888
GET_OOB_CODE = 'getOobCode',
89-
SIGN_UP_PASSWORD = 'signUpPassword'
89+
SIGN_UP_PASSWORD = 'signUpPassword',
90+
SEND_VERIFICATION_CODE = 'sendVerificationCode',
91+
MFA_SMS_ENROLLMENT = 'mfaSmsEnrollment',
92+
MFA_SMS_SIGNIN = 'mfaSmsSignIn'
9093
}
9194

9295
export const enum EnforcementState {
@@ -97,8 +100,9 @@ export const enum EnforcementState {
97100
}
98101

99102
// Providers that have reCAPTCHA Enterprise support.
100-
export const enum RecaptchaProvider {
101-
EMAIL_PASSWORD_PROVIDER = 'EMAIL_PASSWORD_PROVIDER'
103+
export const enum RecaptchaAuthProvider {
104+
EMAIL_PASSWORD_PROVIDER = 'EMAIL_PASSWORD_PROVIDER',
105+
PHONE_PROVIDER = 'PHONE_PROVIDER'
102106
}
103107

104108
export const DEFAULT_API_TIMEOUT_MS = new Delay(30_000, 60_000);

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

0 commit comments

Comments
 (0)