Skip to content

Commit 22f1369

Browse files
author
renkelvin
committed
Add handleRecaptchaFlow
1 parent 53c51dc commit 22f1369

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

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

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,23 @@ import chaiAsPromised from 'chai-as-promised';
2020
import * as sinon from 'sinon';
2121
import sinonChai from 'sinon-chai';
2222

23-
import { Endpoint, RecaptchaClientType, RecaptchaVersion } from '../../api';
23+
import {
24+
Endpoint,
25+
RecaptchaClientType,
26+
RecaptchaVersion,
27+
RecaptchaActionName
28+
} from '../../api';
2429
import { mockEndpointWithParams } from '../../../test/helpers/api/helper';
2530
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
2631
import * as mockFetch from '../../../test/helpers/mock_fetch';
2732
import { ServerError } from '../../api/errors';
33+
import { AuthInternal } from '../../model/auth';
2834

2935
import { MockGreCAPTCHATopLevel } from './recaptcha_mock';
3036
import {
3137
RecaptchaEnterpriseVerifier,
32-
FAKE_TOKEN
38+
FAKE_TOKEN,
39+
handleRecaptchaFlow
3340
} from './recaptcha_enterprise_verifier';
3441

3542
use(chaiAsPromised);
@@ -117,4 +124,86 @@ describe('platform_browser/recaptcha/recaptcha_enterprise_verifier', () => {
117124
expect(await verifier.verify()).to.eq(FAKE_TOKEN);
118125
});
119126
});
127+
128+
context('handleRecaptchaFlow', () => {
129+
let mockAuthInstance: AuthInternal;
130+
let mockRequest: any;
131+
let mockActionMethod: sinon.SinonStub;
132+
133+
beforeEach(async () => {
134+
mockAuthInstance = await testAuth();
135+
mockRequest = {};
136+
mockActionMethod = sinon.stub();
137+
});
138+
139+
afterEach(() => {
140+
sinon.restore();
141+
});
142+
143+
it('should handle recaptcha when emailPasswordEnabled is true', async () => {
144+
if (typeof window === 'undefined') {
145+
return;
146+
}
147+
sinon.stub(mockAuthInstance, '_getRecaptchaConfig').returns({
148+
emailPasswordEnabled: true,
149+
siteKey: 'mock_site_key'
150+
});
151+
mockActionMethod.resolves('success');
152+
153+
const result = await handleRecaptchaFlow(
154+
mockAuthInstance,
155+
mockRequest,
156+
RecaptchaActionName.GET_OOB_CODE,
157+
mockActionMethod
158+
);
159+
160+
expect(result).to.equal('success');
161+
expect(mockActionMethod).to.have.been.calledOnce;
162+
});
163+
164+
it('should handle action without recaptcha when emailPasswordEnabled is false and no error', async () => {
165+
if (typeof window === 'undefined') {
166+
return;
167+
}
168+
sinon.stub(mockAuthInstance, '_getRecaptchaConfig').returns({
169+
emailPasswordEnabled: false,
170+
siteKey: 'mock_site_key'
171+
});
172+
mockActionMethod.resolves('success');
173+
174+
const result = await handleRecaptchaFlow(
175+
mockAuthInstance,
176+
mockRequest,
177+
RecaptchaActionName.GET_OOB_CODE,
178+
mockActionMethod
179+
);
180+
181+
expect(result).to.equal('success');
182+
expect(mockActionMethod).to.have.been.calledOnce;
183+
});
184+
185+
it('should handle MISSING_RECAPTCHA_TOKEN error when emailPasswordEnabled is false', async () => {
186+
if (typeof window === 'undefined') {
187+
return;
188+
}
189+
sinon.stub(mockAuthInstance, '_getRecaptchaConfig').returns({
190+
emailPasswordEnabled: false,
191+
siteKey: 'mock_site_key'
192+
});
193+
mockActionMethod.onFirstCall().rejects({
194+
code: 'auth/MISSING_RECAPTCHA_TOKEN'
195+
});
196+
mockActionMethod.onSecondCall().resolves('success-after-recaptcha');
197+
198+
const result = await handleRecaptchaFlow(
199+
mockAuthInstance,
200+
mockRequest,
201+
RecaptchaActionName.GET_OOB_CODE,
202+
mockActionMethod
203+
);
204+
205+
expect(result).to.equal('success-after-recaptcha');
206+
expect(mockActionMethod).to.have.been.calledTwice;
207+
});
208+
});
120209
});

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { Auth } from '../../model/public_types';
2828
import { AuthInternal } from '../../model/auth';
2929
import { _castAuth } from '../../core/auth/auth_impl';
3030
import * as jsHelpers from '../load_js';
31+
import { AuthErrorCode } from '../../core/errors';
3132

3233
const RECAPTCHA_ENTERPRISE_URL =
3334
'https://www.google.com/recaptcha/enterprise.js?render=';
@@ -175,6 +176,45 @@ export async function injectRecaptchaFields<T>(
175176
return newRequest;
176177
}
177178

179+
type ActionMethod<TRequest, TResponse> = (
180+
auth: Auth,
181+
request: TRequest
182+
) => Promise<TResponse>;
183+
184+
export async function handleRecaptchaFlow<TRequest, TResponse>(
185+
authInstance: AuthInternal,
186+
request: TRequest,
187+
actionName: RecaptchaActionName,
188+
actionMethod: ActionMethod<TRequest, TResponse>
189+
): Promise<TResponse> {
190+
if (authInstance._getRecaptchaConfig()?.emailPasswordEnabled) {
191+
const requestWithRecaptcha = await injectRecaptchaFields(
192+
authInstance,
193+
request,
194+
actionName,
195+
actionName === RecaptchaActionName.GET_OOB_CODE
196+
);
197+
return actionMethod(authInstance, requestWithRecaptcha);
198+
} else {
199+
return actionMethod(authInstance, request).catch(async error => {
200+
if (error.code === `auth/${AuthErrorCode.MISSING_RECAPTCHA_TOKEN}`) {
201+
console.log(
202+
`${actionName} is protected by reCAPTCHA Enterprise for this project. Automatically triggering the reCAPTCHA flow and restarting the flow.`
203+
);
204+
const requestWithRecaptcha = await injectRecaptchaFields(
205+
authInstance,
206+
request,
207+
actionName,
208+
actionName === RecaptchaActionName.GET_OOB_CODE
209+
);
210+
return actionMethod(authInstance, requestWithRecaptcha);
211+
} else {
212+
return Promise.reject(error);
213+
}
214+
});
215+
}
216+
}
217+
178218
export async function _initializeRecaptchaConfig(auth: Auth): Promise<void> {
179219
const authInternal = _castAuth(auth);
180220

0 commit comments

Comments
 (0)