Skip to content
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

Update injectRecaptchaFields to inject recaptcha enterprise fields into phone API requests #7786

Merged
merged 4 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 10 additions & 2 deletions packages/auth/src/api/account_management/mfa.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import chaiAsPromised from 'chai-as-promised';

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

import { Endpoint, HttpHeader } from '../';
import {
Endpoint,
HttpHeader,
RecaptchaClientType,
RecaptchaVersion
} from '../';
import { mockEndpoint } from '../../../test/helpers/api/helper';
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
import * as mockFetch from '../../../test/helpers/mock_fetch';
Expand All @@ -40,7 +45,10 @@ describe('api/account_management/startEnrollPhoneMfa', () => {
idToken: 'id-token',
phoneEnrollmentInfo: {
phoneNumber: 'phone-number',
recaptchaToken: 'captcha-token'
recaptchaToken: 'captcha-token',
captchaResponse: 'captcha-response',
clientType: RecaptchaClientType.WEB,
recaptchaVersion: RecaptchaVersion.ENTERPRISE
}
};

Expand Down
9 changes: 8 additions & 1 deletion packages/auth/src/api/account_management/mfa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import {
Endpoint,
HttpMethod,
RecaptchaClientType,
RecaptchaVersion,
_addTidIfNecessary,
_performApiRequest
} from '../index';
Expand Down Expand Up @@ -55,7 +57,12 @@ export interface StartPhoneMfaEnrollmentRequest {
idToken: string;
phoneEnrollmentInfo: {
phoneNumber: string;
recaptchaToken: string;
// reCAPTCHA v2 token
recaptchaToken?: string;
// reCAPTCHA Enterprise token
captchaResponse?: string;
clientType?: RecaptchaClientType;
recaptchaVersion?: RecaptchaVersion;
};
tenantId?: string;
}
Expand Down
12 changes: 10 additions & 2 deletions packages/auth/src/api/authentication/mfa.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import chaiAsPromised from 'chai-as-promised';

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

import { Endpoint, HttpHeader } from '../';
import {
Endpoint,
HttpHeader,
RecaptchaClientType,
RecaptchaVersion
} from '../';
import { mockEndpoint } from '../../../test/helpers/api/helper';
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
import * as mockFetch from '../../../test/helpers/mock_fetch';
Expand All @@ -34,7 +39,10 @@ describe('api/authentication/startSignInPhoneMfa', () => {
mfaPendingCredential: 'my-creds',
mfaEnrollmentId: 'my-enrollment-id',
phoneSignInInfo: {
recaptchaToken: 'catpcha-token'
recaptchaToken: 'catpcha-token',
captchaResponse: 'captcha-response',
clientType: RecaptchaClientType.WEB,
recaptchaVersion: RecaptchaVersion.ENTERPRISE
}
};

Expand Down
9 changes: 8 additions & 1 deletion packages/auth/src/api/authentication/mfa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
_performApiRequest,
Endpoint,
HttpMethod,
RecaptchaClientType,
RecaptchaVersion,
_addTidIfNecessary
} from '../index';
import { Auth } from '../../model/public_types';
Expand Down Expand Up @@ -47,7 +49,12 @@ export interface StartPhoneMfaSignInRequest {
mfaPendingCredential: string;
mfaEnrollmentId: string;
phoneSignInInfo: {
recaptchaToken: string;
// reCAPTCHA v2 token
recaptchaToken?: string;
// reCAPTCHA Enterprise token
captchaResponse?: string;
clientType?: RecaptchaClientType;
recaptchaVersion?: RecaptchaVersion;
};
tenantId?: string;
}
Expand Down
12 changes: 10 additions & 2 deletions packages/auth/src/api/authentication/sms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import chaiAsPromised from 'chai-as-promised';
import { ProviderId } from '../../model/enums';
import { FirebaseError } from '@firebase/util';

import { Endpoint, HttpHeader } from '../';
import {
Endpoint,
HttpHeader,
RecaptchaClientType,
RecaptchaVersion
} from '../';
import { mockEndpoint } from '../../../test/helpers/api/helper';
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
import * as mockFetch from '../../../test/helpers/mock_fetch';
Expand All @@ -38,7 +43,10 @@ use(chaiAsPromised);
describe('api/authentication/sendPhoneVerificationCode', () => {
const request = {
phoneNumber: '123456789',
recaptchaToken: 'captchad'
recaptchaToken: 'captchad',
captchaResponse: 'captcha-response',
clientType: RecaptchaClientType.WEB,
recaptchaVersion: RecaptchaVersion.ENTERPRISE
};

let auth: TestAuth;
Expand Down
9 changes: 8 additions & 1 deletion packages/auth/src/api/authentication/sms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import {
Endpoint,
HttpMethod,
RecaptchaClientType,
RecaptchaVersion,
_addTidIfNecessary,
_makeTaggedError,
_performApiRequest,
Expand All @@ -30,8 +32,13 @@ import { Auth } from '../../model/public_types';

export interface SendPhoneVerificationCodeRequest {
phoneNumber: string;
recaptchaToken: string;
// reCAPTCHA v2 token
recaptchaToken?: string;
tenantId?: string;
// reCAPTCHA Enterprise token
captchaResponse?: string;
clientType?: RecaptchaClientType;
recaptchaVersion?: RecaptchaVersion;
}

export interface SendPhoneVerificationCodeResponse {
Expand Down
8 changes: 6 additions & 2 deletions packages/auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export const enum RecaptchaVersion {
export const enum RecaptchaActionName {
SIGN_IN_WITH_PASSWORD = 'signInWithPassword',
GET_OOB_CODE = 'getOobCode',
SIGN_UP_PASSWORD = 'signUpPassword'
SIGN_UP_PASSWORD = 'signUpPassword',
SEND_VERIFICATION_CODE = 'sendVerificationCode',
MFA_SMS_ENROLLMENT = 'mfaSmsEnrollment',
MFA_SMS_SIGNIN = 'mfaSmsSignin'
}

export const enum EnforcementState {
Expand All @@ -98,7 +101,8 @@ export const enum EnforcementState {

// Providers that have reCAPTCHA Enterprise support.
export const enum RecaptchaProvider {
EMAIL_PASSWORD_PROVIDER = 'EMAIL_PASSWORD_PROVIDER'
EMAIL_PASSWORD_PROVIDER = 'EMAIL_PASSWORD_PROVIDER',
PHONE_PROVIDER = 'PHONE_PROVIDER'
}

export const DEFAULT_API_TIMEOUT_MS = new Delay(30_000, 60_000);
Expand Down
100 changes: 88 additions & 12 deletions packages/auth/src/platform_browser/recaptcha/recaptcha.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {

import { isV2, isEnterprise, RecaptchaConfig } from './recaptcha';
import { GetRecaptchaConfigResponse } from '../../api/authentication/recaptcha';
import { EnforcementState } from '../../api/index';
import { EnforcementState, RecaptchaProvider } from '../../api/index';

use(chaiAsPromised);
use(sinonChai);
Expand All @@ -39,17 +39,60 @@ describe('platform_browser/recaptcha/recaptcha', () => {
let recaptchaV2: MockReCaptcha;
let recaptchaV3: MockGreCAPTCHA;
let recaptchaEnterprise: MockGreCAPTCHATopLevel;
let recaptchaConfig: RecaptchaConfig;

const TEST_SITE_KEY = 'test-site-key';

const GET_RECAPTCHA_CONFIG_RESPONSE: GetRecaptchaConfigResponse = {
recaptchaKey: 'projects/testproj/keys/' + TEST_SITE_KEY,
recaptchaEnforcementState: [
{ provider: 'EMAIL_PASSWORD_PROVIDER', enforcementState: 'ENFORCE' }
{
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
enforcementState: EnforcementState.ENFORCE
},
{
provider: RecaptchaProvider.PHONE_PROVIDER,
enforcementState: EnforcementState.AUDIT
NhienLam marked this conversation as resolved.
Show resolved Hide resolved
}
]
};

const GET_RECAPTCHA_CONFIG_RESPONSE_OFF: GetRecaptchaConfigResponse = {
recaptchaKey: 'projects/testproj/keys/' + TEST_SITE_KEY,
recaptchaEnforcementState: [
{
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
enforcementState: EnforcementState.OFF
},
{
provider: RecaptchaProvider.PHONE_PROVIDER,
enforcementState: EnforcementState.OFF
}
]
};

const GET_RECAPTCHA_CONFIG_RESPONSE_ENFORCE_AND_OFF: GetRecaptchaConfigResponse =
{
recaptchaKey: 'projects/testproj/keys/' + TEST_SITE_KEY,
recaptchaEnforcementState: [
{
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
enforcementState: EnforcementState.ENFORCE
},
{
provider: RecaptchaProvider.PHONE_PROVIDER,
enforcementState: EnforcementState.OFF
}
]
};

const recaptchaConfig = new RecaptchaConfig(GET_RECAPTCHA_CONFIG_RESPONSE);
const recaptchaConfigOff = new RecaptchaConfig(
GET_RECAPTCHA_CONFIG_RESPONSE_OFF
);
const recaptchaConfigEnforceAndOff = new RecaptchaConfig(
GET_RECAPTCHA_CONFIG_RESPONSE_ENFORCE_AND_OFF
);

context('#verify', () => {
beforeEach(async () => {
auth = await testAuth();
Expand All @@ -74,30 +117,63 @@ describe('platform_browser/recaptcha/recaptcha', () => {
});

context('#RecaptchaConfig', () => {
beforeEach(async () => {
recaptchaConfig = new RecaptchaConfig(GET_RECAPTCHA_CONFIG_RESPONSE);
});

it('should construct the recaptcha config from the backend response', () => {
expect(recaptchaConfig.siteKey).to.eq(TEST_SITE_KEY);
expect(recaptchaConfig.recaptchaEnforcementState[0]).to.eql({
provider: 'EMAIL_PASSWORD_PROVIDER',
enforcementState: 'ENFORCE'
provider: RecaptchaProvider.EMAIL_PASSWORD_PROVIDER,
enforcementState: EnforcementState.ENFORCE
});
expect(recaptchaConfig.recaptchaEnforcementState[1]).to.eql({
provider: RecaptchaProvider.PHONE_PROVIDER,
enforcementState: EnforcementState.AUDIT
});
expect(recaptchaConfigEnforceAndOff.recaptchaEnforcementState[1]).to.eql({
provider: RecaptchaProvider.PHONE_PROVIDER,
enforcementState: EnforcementState.OFF
});
});

it('#getProviderEnforcementState should return the correct enforcement state of the provider', () => {
expect(
recaptchaConfig.getProviderEnforcementState('EMAIL_PASSWORD_PROVIDER')
recaptchaConfig.getProviderEnforcementState(
RecaptchaProvider.EMAIL_PASSWORD_PROVIDER
)
).to.eq(EnforcementState.ENFORCE);
expect(
recaptchaConfig.getProviderEnforcementState(
RecaptchaProvider.PHONE_PROVIDER
)
).to.eq(EnforcementState.AUDIT);
expect(
recaptchaConfigEnforceAndOff.getProviderEnforcementState(
RecaptchaProvider.PHONE_PROVIDER
)
).to.eq(EnforcementState.OFF);
expect(recaptchaConfig.getProviderEnforcementState('invalid-provider')).to
.be.null;
});

it('#isProviderEnabled should return the enablement state of the provider', () => {
expect(recaptchaConfig.isProviderEnabled('EMAIL_PASSWORD_PROVIDER')).to.be
.true;
expect(
recaptchaConfig.isProviderEnabled(
RecaptchaProvider.EMAIL_PASSWORD_PROVIDER
)
).to.be.true;
expect(
recaptchaConfig.isProviderEnabled(RecaptchaProvider.PHONE_PROVIDER)
).to.be.true;
expect(
recaptchaConfigEnforceAndOff.isProviderEnabled(
RecaptchaProvider.PHONE_PROVIDER
)
).to.be.false;
expect(recaptchaConfig.isProviderEnabled('invalid-provider')).to.be.false;
});

it('#isAnyProviderEnabled should return true if at least one provider is enabled', () => {
expect(recaptchaConfig.isAnyProviderEnabled()).to.be.true;
expect(recaptchaConfigEnforceAndOff.isAnyProviderEnabled()).to.be.true;
expect(recaptchaConfigOff.isAnyProviderEnabled()).to.be.false;
});
});
});
19 changes: 18 additions & 1 deletion packages/auth/src/platform_browser/recaptcha/recaptcha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
GetRecaptchaConfigResponse,
RecaptchaEnforcementProviderState
} from '../../api/authentication/recaptcha';
import { EnforcementState, _parseEnforcementState } from '../../api/index';
import {
EnforcementState,
RecaptchaProvider,
_parseEnforcementState
} from '../../api/index';

// reCAPTCHA v2 interface
export interface Recaptcha {
Expand Down Expand Up @@ -135,4 +139,17 @@ export class RecaptchaConfig {
this.getProviderEnforcementState(providerStr) === EnforcementState.AUDIT
);
}

/**
* Returns true if reCAPTCHA Enterprise protection is enabled in at least one provider, otherwise
* returns false.
*
* @returns Whether or not reCAPTCHA Enterprise protection is enabled for at least one provider.
*/
isAnyProviderEnabled(): boolean {
return (
this.isProviderEnabled(RecaptchaProvider.EMAIL_PASSWORD_PROVIDER) ||
this.isProviderEnabled(RecaptchaProvider.PHONE_PROVIDER)
);
}
}
Loading
Loading