diff --git a/.github/workflows/scripts/functions/src/index.ts b/.github/workflows/scripts/functions/src/index.ts index 659834d2e991..70ce787e1419 100644 --- a/.github/workflows/scripts/functions/src/index.ts +++ b/.github/workflows/scripts/functions/src/index.ts @@ -1,6 +1,17 @@ import * as assert from 'assert'; import * as functions from 'firebase-functions'; import * as functionsv2 from 'firebase-functions/v2'; +import { beforeUserCreated } from 'firebase-functions/v2/identity'; + +// Only reject the dedicated Auth e2e address so other tests are unaffected. +export const rejectBlockedAuthUser = beforeUserCreated((event) => { + if (event.data?.email === 'blocked-by-function@example.com') { + throw new functionsv2.https.HttpsError( + 'permission-denied', + 'Registration blocked by the e2e test function.', + ); + } +}); // For example app. @@ -221,4 +232,4 @@ export const testStreamResponse = functions.https.onCall(async (request, respons // Fallback for non-streaming clients return Promise.all(allFruits); -}); \ No newline at end of file +}); diff --git a/AUTHORS b/AUTHORS index 0c0e6b091146..490a27d2c0f1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -70,4 +70,5 @@ Marwan Salim Ba Matraf kfurue Max Wojczuk Julian Pscheid +Kanako Nakamura Artem Shevchenko diff --git a/docs/auth/errors.md b/docs/auth/errors.md index 8178ad9103eb..80715a537087 100644 --- a/docs/auth/errors.md +++ b/docs/auth/errors.md @@ -156,7 +156,7 @@ Situation | Android | Apple | Web | Windows ----------|---------|-------|-----|-------- Wrong email or password, with email enumeration protection enabled | `invalid-credential` | `invalid-credential` | `invalid-credential` | `unknown-error` Error without a more specific code | `unknown` | `unknown` or `internal-error` | `unknown` or `internal-error` | `unknown-error` -A blocking function rejected the operation | `blocking-function-error-response` | `blocking-cloud-function-returned-error` | `internal-error` | `unknown-error` +A blocking function rejected the operation | `blocking-cloud-function-returned-error` | `blocking-cloud-function-returned-error` | `internal-error` | `unknown-error` Password does not meet the password policy | `unknown` | `password-does-not-meet-requirements` | `password-does-not-meet-requirements` | `unknown-error` SMS quota exceeded | `too-many-requests` | `quota-exceeded` | `quota-exceeded` | Not supported No user signed in | `no-current-user` | `no-current-user` | `no-current-user` | `no-signed-in-user` @@ -286,8 +286,7 @@ Code | Description | Platforms Code | Description | Platforms -----|-------------|---------- -`blocking-function-error-response` | A blocking function (such as `beforeUserCreated` or `beforeUserSignedIn`) rejected the operation. `message` contains the error returned by the function. | Android -`blocking-cloud-function-returned-error` | A blocking function rejected the operation. `message` contains the error returned by the function. | Apple +`blocking-cloud-function-returned-error` | A blocking function (such as `beforeUserCreated` or `beforeUserSignedIn`) rejected the operation. `message` contains the error returned by the function. | Android, Apple ### Email actions and links diff --git a/packages/firebase_auth/firebase_auth/example/integration_test/firebase_auth_instance_e2e_test.dart b/packages/firebase_auth/firebase_auth/example/integration_test/firebase_auth_instance_e2e_test.dart index c81d5decf256..559372b7fe6d 100644 --- a/packages/firebase_auth/firebase_auth/example/integration_test/firebase_auth_instance_e2e_test.dart +++ b/packages/firebase_auth/firebase_auth/example/integration_test/firebase_auth_instance_e2e_test.dart @@ -502,6 +502,39 @@ void main() { } }); + test( + 'normalizes the error when a blocking function rejects registration', + () async { + // Matches rejectBlockedAuthUser in the Functions emulator fixtures. + await expectLater( + FirebaseAuth.instance.createUserWithEmailAndPassword( + email: 'blocked-by-function@example.com', + password: testPassword, + ), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'blocking-cloud-function-returned-error', + ) + .having( + (e) => e.message, + 'message', + contains( + 'Registration blocked by the e2e test function.', + ), + ), + ), + ); + expect(FirebaseAuth.instance.currentUser, isNull); + }, + // This regression covers the shared Android and Apple error code. + skip: + kIsWeb || + (!Platform.isAndroid && !Platform.isIOS && !Platform.isMacOS), + ); + test('fails if creating a user with an invalid email', () async { await ensureSignedIn(testEmail); try { diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart index 6daa60da6f31..f3547ec5be66 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/utils/exception.dart @@ -143,17 +143,18 @@ FirebaseException platformExceptionToFirebaseAuthException( // Check for custom error codes that are not returned in the normal errors by Firebase SDKs // The error code is only returned in a String on Android String? _getCustomCode(Map? additionalData, String? message) { - final listOfRecognizedCode = [ + const recognizedCodes = { // This code happens when using Enumerate Email protection - 'INVALID_LOGIN_CREDENTIALS', - // This code happens when using using pre-auth functions - 'BLOCKING_FUNCTION_ERROR_RESPONSE', - ]; - - for (final recognizedCode in listOfRecognizedCode) { - if (additionalData?['message'] == recognizedCode || - (message?.contains(recognizedCode) ?? false)) { - return recognizedCode.toLowerCase().replaceAll('_', '-'); + 'INVALID_LOGIN_CREDENTIALS': 'invalid-login-credentials', + // Use the same error code as on Apple platforms for blocking function errors. + 'BLOCKING_FUNCTION_ERROR_RESPONSE': + 'blocking-cloud-function-returned-error', + }; + + for (final entry in recognizedCodes.entries) { + if (additionalData?['message'] == entry.key || + (message?.contains(entry.key) ?? false)) { + return entry.value; } } diff --git a/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart b/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart index 7a3b5de62a25..38a0b25142bd 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart @@ -40,7 +40,7 @@ void main() { ); test( - 'should catch a [PlatformException] and throw a [FirebaseException] with the correct message', + 'normalizes blocking function errors and preserves the JSON message', () async { PlatformException platformException = PlatformException( code: 'UNKNOWN', @@ -55,7 +55,7 @@ void main() { .having( (e) => e.code, 'code', - 'blocking-function-error-response', + 'blocking-cloud-function-returned-error', ) .having( (e) => e.message, @@ -90,6 +90,34 @@ void main() { }); group('platformExceptionToFirebaseAuthException()', () { + test('normalizes a blocking function error from pigeon details', () { + PlatformException platformException = PlatformException( + code: 'UNKNOWN', + details: {'message': 'BLOCKING_FUNCTION_ERROR_RESPONSE'}, + ); + + FirebaseAuthException result = + platformExceptionToFirebaseAuthException(platformException) + as FirebaseAuthException; + + expect(result.code, equals('blocking-cloud-function-returned-error')); + expect(result.message, isNull); + }); + + test('preserves the invalid login credentials error code', () { + PlatformException platformException = PlatformException( + code: 'UNKNOWN', + message: + 'An internal error has occurred. [ INVALID_LOGIN_CREDENTIALS ]', + ); + + FirebaseAuthException result = + platformExceptionToFirebaseAuthException(platformException) + as FirebaseAuthException; + + expect(result.code, equals('invalid-login-credentials')); + }); + test('handles non-Map pigeon details', () { PlatformException platformException = PlatformException( code: 'ERROR_INTERNAL_ERROR',