From b5922983656d5f64355c0850edc51b1cf6168d9d Mon Sep 17 00:00:00 2001 From: Mila <107142260+milaGGL@users.noreply.github.com> Date: Fri, 13 Oct 2023 12:04:20 -0400 Subject: [PATCH 1/2] MIEQ: add negative test cases (#7680) --- .../test/integration/api/query.test.ts | 28 +++++++++++++++++++ .../emulators/firestore-emulator.ts | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index 767853bebde..a30575341ee 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1912,6 +1912,34 @@ apiDescribe('Queries', persistence => { }); } ); + + it('inequality query will reject if document key is not the last orderBy field', () => { + return withEmptyTestCollection(persistence, async coll => { + // Implicitly ordered by: __name__ asc, 'key' asc, + const queryForRejection = query( + coll, + where('key', '!=', 42), + orderBy(documentId()) + ); + + await expect(getDocs(queryForRejection)).to.be.eventually.rejectedWith( + 'order by clause cannot contain more fields after the key' + ); + }); + }); + + it('inequality query will reject if document key appears only in equality filter', () => { + return withEmptyTestCollection(persistence, async coll => { + const query_ = query( + coll, + where('key', '!=', 42), + where(documentId(), '==', 'doc1') + ); + await expect(getDocs(query_)).to.be.eventually.rejectedWith( + 'Equality on key is not allowed if there are other inequality fields and key does not appear in inequalities.' + ); + }); + }); }); // OR Query tests only run when the SDK's local cache is configured to use diff --git a/scripts/emulator-testing/emulators/firestore-emulator.ts b/scripts/emulator-testing/emulators/firestore-emulator.ts index 53dcb713d55..57186af0f64 100644 --- a/scripts/emulator-testing/emulators/firestore-emulator.ts +++ b/scripts/emulator-testing/emulators/firestore-emulator.ts @@ -26,7 +26,7 @@ export class FirestoreEmulator extends Emulator { // Use locked version of emulator for test to be deterministic. // The latest version can be found from firestore emulator doc: // https://firebase.google.com/docs/firestore/security/test-rules-emulator - 'https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.18.1.jar', + 'https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.18.2.jar', port ); this.projectId = projectId; From 3533b32b1be6a9800b1b58a6a2b08f50fae18eeb Mon Sep 17 00:00:00 2001 From: renkelvin Date: Fri, 13 Oct 2023 16:53:42 -0700 Subject: [PATCH 2/2] Create handleRecaptchaFlow helper method (#7666) --- .changeset/lucky-dragons-juggle.md | 5 + packages/auth/src/core/credentials/email.ts | 34 ++----- .../src/core/strategies/email_and_password.ts | 96 +++---------------- .../auth/src/core/strategies/email_link.ts | 40 ++------ .../recaptcha_enterprise_verifier.ts | 40 ++++++++ 5 files changed, 75 insertions(+), 140 deletions(-) create mode 100644 .changeset/lucky-dragons-juggle.md diff --git a/.changeset/lucky-dragons-juggle.md b/.changeset/lucky-dragons-juggle.md new file mode 100644 index 00000000000..f21b661d465 --- /dev/null +++ b/.changeset/lucky-dragons-juggle.md @@ -0,0 +1,5 @@ +--- +'@firebase/auth': patch +--- + +Create handleRecaptchaFlow helper method diff --git a/packages/auth/src/core/credentials/email.ts b/packages/auth/src/core/credentials/email.ts index 6421f33b5a1..edcde2ea053 100644 --- a/packages/auth/src/core/credentials/email.ts +++ b/packages/auth/src/core/credentials/email.ts @@ -31,7 +31,7 @@ import { IdTokenResponse } from '../../model/id_token'; import { AuthErrorCode } from '../errors'; import { _fail } from '../util/assert'; import { AuthCredential } from './auth_credential'; -import { injectRecaptchaFields } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; +import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; import { RecaptchaActionName, RecaptchaClientType } from '../../api'; /** * Interface that represents the credentials returned by {@link EmailAuthProvider} for @@ -123,32 +123,12 @@ export class EmailAuthCredential extends AuthCredential { password: this._password, clientType: RecaptchaClientType.WEB }; - if (auth._getRecaptchaConfig()?.emailPasswordEnabled) { - const requestWithRecaptcha = await injectRecaptchaFields( - auth, - request, - RecaptchaActionName.SIGN_IN_WITH_PASSWORD - ); - return signInWithPassword(auth, requestWithRecaptcha); - } else { - return signInWithPassword(auth, request).catch(async error => { - if ( - error.code === `auth/${AuthErrorCode.MISSING_RECAPTCHA_TOKEN}` - ) { - console.log( - 'Sign-in with email address and password is protected by reCAPTCHA for this project. Automatically triggering the reCAPTCHA flow and restarting the sign-in flow.' - ); - const requestWithRecaptcha = await injectRecaptchaFields( - auth, - request, - RecaptchaActionName.SIGN_IN_WITH_PASSWORD - ); - return signInWithPassword(auth, requestWithRecaptcha); - } else { - return Promise.reject(error); - } - }); - } + return handleRecaptchaFlow( + auth, + request, + RecaptchaActionName.SIGN_IN_WITH_PASSWORD, + signInWithPassword + ); case SignInMethod.EMAIL_LINK: return signInWithEmailLink(auth, { email: this._email, diff --git a/packages/auth/src/core/strategies/email_and_password.ts b/packages/auth/src/core/strategies/email_and_password.ts index 33a2ef8af8d..0ff21810a49 100644 --- a/packages/auth/src/core/strategies/email_and_password.ts +++ b/packages/auth/src/core/strategies/email_and_password.ts @@ -36,7 +36,7 @@ import { _castAuth } from '../auth/auth_impl'; import { AuthErrorCode } from '../errors'; import { getModularInstance } from '@firebase/util'; import { OperationType } from '../../model/enums'; -import { injectRecaptchaFields } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; +import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; import { IdTokenResponse } from '../../model/id_token'; import { RecaptchaActionName, RecaptchaClientType } from '../../api'; @@ -103,61 +103,15 @@ export async function sendPasswordResetEmail( email, clientType: RecaptchaClientType.WEB }; - if (authInternal._getRecaptchaConfig()?.emailPasswordEnabled) { - const requestWithRecaptcha = await injectRecaptchaFields( - authInternal, - request, - RecaptchaActionName.GET_OOB_CODE, - true - ); - if (actionCodeSettings) { - _setActionCodeSettingsOnRequest( - authInternal, - requestWithRecaptcha, - actionCodeSettings - ); - } - await authentication.sendPasswordResetEmail( - authInternal, - requestWithRecaptcha - ); - } else { - if (actionCodeSettings) { - _setActionCodeSettingsOnRequest( - authInternal, - request, - actionCodeSettings - ); - } - await authentication - .sendPasswordResetEmail(authInternal, request) - .catch(async error => { - if (error.code === `auth/${AuthErrorCode.MISSING_RECAPTCHA_TOKEN}`) { - console.log( - 'Password resets are protected by reCAPTCHA for this project. Automatically triggering the reCAPTCHA flow and restarting the password reset flow.' - ); - const requestWithRecaptcha = await injectRecaptchaFields( - authInternal, - request, - RecaptchaActionName.GET_OOB_CODE, - true - ); - if (actionCodeSettings) { - _setActionCodeSettingsOnRequest( - authInternal, - requestWithRecaptcha, - actionCodeSettings - ); - } - await authentication.sendPasswordResetEmail( - authInternal, - requestWithRecaptcha - ); - } else { - return Promise.reject(error); - } - }); + if (actionCodeSettings) { + _setActionCodeSettingsOnRequest(authInternal, request, actionCodeSettings); } + await handleRecaptchaFlow( + authInternal, + request, + RecaptchaActionName.GET_OOB_CODE, + authentication.sendPasswordResetEmail + ); } /** @@ -318,32 +272,12 @@ export async function createUserWithEmailAndPassword( password, clientType: RecaptchaClientType.WEB }; - let signUpResponse: Promise; - if (authInternal._getRecaptchaConfig()?.emailPasswordEnabled) { - const requestWithRecaptcha = await injectRecaptchaFields( - authInternal, - request, - RecaptchaActionName.SIGN_UP_PASSWORD - ); - signUpResponse = signUp(authInternal, requestWithRecaptcha); - } else { - signUpResponse = signUp(authInternal, request).catch(async error => { - if (error.code === `auth/${AuthErrorCode.MISSING_RECAPTCHA_TOKEN}`) { - console.log( - 'Sign-up is protected by reCAPTCHA for this project. Automatically triggering the reCAPTCHA flow and restarting the sign-up flow.' - ); - const requestWithRecaptcha = await injectRecaptchaFields( - authInternal, - request, - RecaptchaActionName.SIGN_UP_PASSWORD - ); - return signUp(authInternal, requestWithRecaptcha); - } - - throw error; - }); - } - + const signUpResponse: Promise = handleRecaptchaFlow( + authInternal, + request, + RecaptchaActionName.SIGN_UP_PASSWORD, + signUp + ); const response = await signUpResponse.catch(error => { if ( error.code === `auth/${AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS}` diff --git a/packages/auth/src/core/strategies/email_link.ts b/packages/auth/src/core/strategies/email_link.ts index f67a1e3ea03..55f95226656 100644 --- a/packages/auth/src/core/strategies/email_link.ts +++ b/packages/auth/src/core/strategies/email_link.ts @@ -32,7 +32,7 @@ import { AuthErrorCode } from '../errors'; import { _assert } from '../util/assert'; import { getModularInstance } from '@firebase/util'; import { _castAuth } from '../auth/auth_impl'; -import { injectRecaptchaFields } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; +import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; import { RecaptchaActionName, RecaptchaClientType } from '../../api'; /** @@ -101,37 +101,13 @@ export async function sendSignInLinkToEmail( ); } } - if (authInternal._getRecaptchaConfig()?.emailPasswordEnabled) { - const requestWithRecaptcha = await injectRecaptchaFields( - authInternal, - request, - RecaptchaActionName.GET_OOB_CODE, - true - ); - setActionCodeSettings(requestWithRecaptcha, actionCodeSettings); - await api.sendSignInLinkToEmail(authInternal, requestWithRecaptcha); - } else { - setActionCodeSettings(request, actionCodeSettings); - await api - .sendSignInLinkToEmail(authInternal, request) - .catch(async error => { - if (error.code === `auth/${AuthErrorCode.MISSING_RECAPTCHA_TOKEN}`) { - console.log( - 'Email link sign-in is protected by reCAPTCHA for this project. Automatically triggering the reCAPTCHA flow and restarting the sign-in flow.' - ); - const requestWithRecaptcha = await injectRecaptchaFields( - authInternal, - request, - RecaptchaActionName.GET_OOB_CODE, - true - ); - setActionCodeSettings(requestWithRecaptcha, actionCodeSettings); - await api.sendSignInLinkToEmail(authInternal, requestWithRecaptcha); - } else { - return Promise.reject(error); - } - }); - } + setActionCodeSettings(request, actionCodeSettings); + await handleRecaptchaFlow( + authInternal, + request, + RecaptchaActionName.GET_OOB_CODE, + api.sendSignInLinkToEmail + ); } /** diff --git a/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts index c2c0303088a..63ec91fceae 100644 --- a/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts +++ b/packages/auth/src/platform_browser/recaptcha/recaptcha_enterprise_verifier.ts @@ -28,6 +28,7 @@ import { Auth } from '../../model/public_types'; import { AuthInternal } from '../../model/auth'; import { _castAuth } from '../../core/auth/auth_impl'; import * as jsHelpers from '../load_js'; +import { AuthErrorCode } from '../../core/errors'; const RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js?render='; @@ -175,6 +176,45 @@ export async function injectRecaptchaFields( return newRequest; } +type ActionMethod = ( + auth: Auth, + request: TRequest +) => Promise; + +export async function handleRecaptchaFlow( + authInstance: AuthInternal, + request: TRequest, + actionName: RecaptchaActionName, + actionMethod: ActionMethod +): Promise { + if (authInstance._getRecaptchaConfig()?.emailPasswordEnabled) { + const requestWithRecaptcha = await injectRecaptchaFields( + authInstance, + request, + actionName, + actionName === RecaptchaActionName.GET_OOB_CODE + ); + return actionMethod(authInstance, requestWithRecaptcha); + } else { + return actionMethod(authInstance, request).catch(async error => { + if (error.code === `auth/${AuthErrorCode.MISSING_RECAPTCHA_TOKEN}`) { + console.log( + `${actionName} is protected by reCAPTCHA Enterprise for this project. Automatically triggering the reCAPTCHA flow and restarting the flow.` + ); + const requestWithRecaptcha = await injectRecaptchaFields( + authInstance, + request, + actionName, + actionName === RecaptchaActionName.GET_OOB_CODE + ); + return actionMethod(authInstance, requestWithRecaptcha); + } else { + return Promise.reject(error); + } + }); + } +} + export async function _initializeRecaptchaConfig(auth: Auth): Promise { const authInternal = _castAuth(auth);