Skip to content

Fail open and send auth request to the GCIP backend if Recaptcha toke… #7254

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 6 commits into from
Apr 25, 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
44 changes: 11 additions & 33 deletions packages/auth/src/core/credentials/email.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,32 +184,17 @@ describe('core/credentials/email', () => {
});
});

it('calls sign in with password with recaptcha forced refresh succeed', async () => {
it('calls sign in with password with recaptcha forced refresh', async () => {
if (typeof window === 'undefined') {
return;
}
// Mock recaptcha js loading method and manually set window.recaptcha
// Mock recaptcha js loading method but not set window.recaptcha to simulate recaptcha token retrieval failure
sinon
.stub(jsHelpers, '_loadJS')
.returns(Promise.resolve(new Event('')));
const recaptcha = new MockGreCAPTCHATopLevel();
window.grecaptcha = recaptcha;
const stub = sinon.stub(recaptcha.enterprise, 'execute');

// First verification should fail with 'wrong-site-key'
stub
.withArgs('wrong-site-key', {
action: RecaptchaActionName.SIGN_IN_WITH_PASSWORD
})
.rejects();
// Second verifcation should succeed with site key refreshed
stub
.withArgs('site-key', {
action: RecaptchaActionName.SIGN_IN_WITH_PASSWORD
})
.returns(Promise.resolve('recaptcha-response'));
window.grecaptcha = undefined;

mockEndpointWithParams(
const getRecaptchaConfigMock = mockEndpointWithParams(
Endpoint.GET_RECAPTCHA_CONFIG,
{
clientType: RecaptchaClientType.WEB,
Expand All @@ -218,21 +203,14 @@ describe('core/credentials/email', () => {
recaptchaConfigResponseEnforce
);
await auth.initializeRecaptchaConfig();
auth._agentRecaptchaConfig!.siteKey = 'wrong-site-key';
auth._agentRecaptchaConfig!.siteKey = 'cached-site-key';

const idTokenResponse = await credential._getIdTokenResponse(auth);
expect(idTokenResponse.idToken).to.eq('id-token');
expect(idTokenResponse.refreshToken).to.eq('refresh-token');
expect(idTokenResponse.expiresIn).to.eq('1234');
expect(idTokenResponse.localId).to.eq(serverUser.localId);
expect(apiMock.calls[0].request).to.eql({
captchaResponse: 'recaptcha-response',
clientType: RecaptchaClientType.WEB,
email: 'some-email',
password: 'some-password',
recaptchaVersion: RecaptchaVersion.ENTERPRISE,
returnSecureToken: true
});
await expect(credential._getIdTokenResponse(auth)).to.be.rejectedWith(
'No reCAPTCHA enterprise script loaded.'
);
// Should call getRecaptchaConfig once to refresh the cached recaptcha config
expect(getRecaptchaConfigMock.calls.length).to.eq(2);
expect(auth._agentRecaptchaConfig?.siteKey).to.eq('site-key');
});

it('calls fallback to recaptcha flow when receiving MISSING_RECAPTCHA_TOKEN error', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import * as mockFetch from '../../../test/helpers/mock_fetch';
import { ServerError } from '../../api/errors';

import { MockGreCAPTCHATopLevel } from './recaptcha_mock';
import { RecaptchaEnterpriseVerifier } from './recaptcha_enterprise_verifier';
import {
RecaptchaEnterpriseVerifier,
FAKE_TOKEN
} from './recaptcha_enterprise_verifier';

use(chaiAsPromised);
use(sinonChai);
Expand Down Expand Up @@ -81,7 +84,7 @@ describe('platform_browser/recaptcha/recaptcha_enterprise_verifier', () => {
expect(await verifier.verify()).to.eq('recaptcha-response');
});

it('reject if error is thrown when retieve site key', async () => {
it('reject if error is thrown when retrieve site key', async () => {
mockEndpointWithParams(
Endpoint.GET_RECAPTCHA_CONFIG,
request,
Expand All @@ -102,19 +105,16 @@ describe('platform_browser/recaptcha/recaptcha_enterprise_verifier', () => {
);
});

it('reject if error is thrown when retieve recaptcha token', async () => {
it('return fake recaptcha token if error is thrown when retrieve recaptcha token', async () => {
mockEndpointWithParams(
Endpoint.GET_RECAPTCHA_CONFIG,
request,
recaptchaConfigResponseEnforce
);
sinon
.stub(recaptcha.enterprise, 'execute')
.returns(Promise.reject(Error('retieve-recaptcha-token-error')));
await expect(verifier.verify()).to.be.rejectedWith(
Error,
'retieve-recaptcha-token-error'
);
.returns(Promise.reject(Error('retrieve-recaptcha-token-error')));
expect(await verifier.verify()).to.eq(FAKE_TOKEN);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const RECAPTCHA_ENTERPRISE_URL =
'https://www.google.com/recaptcha/enterprise.js?render=';

export const RECAPTCHA_ENTERPRISE_VERIFIER_TYPE = 'recaptcha-enterprise';
export const FAKE_TOKEN = 'NO_RECAPTCHA';

export class RecaptchaEnterpriseVerifier {
/**
Expand Down Expand Up @@ -105,18 +106,14 @@ export class RecaptchaEnterpriseVerifier {
const grecaptcha = window.grecaptcha;
if (isEnterprise(grecaptcha)) {
grecaptcha.enterprise.ready(() => {
try {
grecaptcha.enterprise
.execute(siteKey, { action })
.then(token => {
resolve(token);
})
.catch(error => {
reject(error);
});
} catch (error) {
reject(error);
}
grecaptcha.enterprise
.execute(siteKey, { action })
.then(token => {
resolve(token);
})
.catch(() => {
resolve(FAKE_TOKEN);
});
});
} else {
reject(Error('No reCAPTCHA enterprise script loaded.'));
Expand Down