diff --git a/src/Firebase.Auth.Tests/IntegrationTests.cs b/src/Firebase.Auth.Tests/IntegrationTests.cs index d5d4bf4..6a522df 100644 --- a/src/Firebase.Auth.Tests/IntegrationTests.cs +++ b/src/Firebase.Auth.Tests/IntegrationTests.cs @@ -53,6 +53,62 @@ public void EmailTest() auth.FirebaseToken.Should().NotBeNullOrWhiteSpace(); } + [TestMethod] + public void Unknown_email_address_should_be_reflected_by_failure_reason() + { + using (var authProvider = new FirebaseAuthProvider(new FirebaseConfig(ApiKey))) + { + try + { + authProvider.SignInWithEmailAndPasswordAsync("someinvalidaddressxxx@foo.com", FirebasePassword).Wait(); + Assert.Fail("Sign-in should fail with invalid email."); + } + catch (Exception e) + { + var exception = (FirebaseAuthException) e.InnerException; + exception.Reason.Should().Be(AuthErrorReason.UnknownEmailAddress); + } + } + } + + [TestMethod] + public void Invalid_email_address_format_should_be_reflected_by_failure_reason() + { + using (var authProvider = new FirebaseAuthProvider(new FirebaseConfig(ApiKey))) + { + try + { + authProvider.SignInWithEmailAndPasswordAsync("notanemailaddress", FirebasePassword).Wait(); + Assert.Fail("Sign-in should fail with invalid email."); + } + catch (Exception e) + { + var exception = (FirebaseAuthException)e.InnerException; + exception.Reason.Should().Be(AuthErrorReason.InvalidEmailAddress); + } + } + } + + + + [TestMethod] + public void Invalid_password_should_be_reflected_by_failure_reason() + { + using (var authProvider = new FirebaseAuthProvider(new FirebaseConfig(ApiKey))) + { + try + { + authProvider.SignInWithEmailAndPasswordAsync(FirebaseEmail, "xx" + FirebasePassword).Wait(); + Assert.Fail("Sign-in should fail with invalid password."); + } + catch (Exception e) + { + var exception = (FirebaseAuthException)e.InnerException; + exception.Reason.Should().Be(AuthErrorReason.WrongPassword); + } + } + } + [TestMethod] public void CreateUserTest() { diff --git a/src/Firebase.Auth/AuthErrorReason.cs b/src/Firebase.Auth/AuthErrorReason.cs new file mode 100644 index 0000000..f4678f4 --- /dev/null +++ b/src/Firebase.Auth/AuthErrorReason.cs @@ -0,0 +1,27 @@ +namespace Firebase.Auth +{ + public enum AuthErrorReason + { + /// + /// Unknown error reason. + /// + Undefined, + /// + /// No user with a matching email address is registered. + /// + UnknownEmailAddress, + /// + /// The supplied ID is not a valid email address. + /// + InvalidEmailAddress, + /// + /// Wrong password. + /// + WrongPassword, + /// + /// The user was disabled and is not granted access anymore. + /// + UserDisabled + + } +} \ No newline at end of file diff --git a/src/Firebase.Auth/Firebase.Auth.csproj b/src/Firebase.Auth/Firebase.Auth.csproj index 2d3355b..c20a106 100644 --- a/src/Firebase.Auth/Firebase.Auth.csproj +++ b/src/Firebase.Auth/Firebase.Auth.csproj @@ -41,6 +41,7 @@ + diff --git a/src/Firebase.Auth/FirebaseAuthException.cs b/src/Firebase.Auth/FirebaseAuthException.cs index 250e1e5..30e0ee4 100644 --- a/src/Firebase.Auth/FirebaseAuthException.cs +++ b/src/Firebase.Auth/FirebaseAuthException.cs @@ -4,12 +4,13 @@ public class FirebaseAuthException : Exception { - public FirebaseAuthException(string requestUrl, string requestData, string responseData, Exception innerException) - : base(GenerateExceptionMessage(requestUrl, requestData, responseData), innerException) + public FirebaseAuthException(string requestUrl, string requestData, string responseData, Exception innerException, AuthErrorReason reason = AuthErrorReason.Undefined) + : base(GenerateExceptionMessage(requestUrl, requestData, responseData, reason), innerException) { this.RequestUrl = requestUrl; this.RequestData = requestData; this.ResponseData = responseData; + this.Reason = reason; } /// @@ -33,9 +34,18 @@ public string ResponseData get; } - private static string GenerateExceptionMessage(string requestUrl, string requestData, string responseData) + /// + /// indicates why a login failed. If not resolved, defaults to + /// . + /// + public AuthErrorReason Reason + { + get; + } + + private static string GenerateExceptionMessage(string requestUrl, string requestData, string responseData, AuthErrorReason errorReason) { - return $"Exception occured while authenticating.\nUrl: {requestUrl}\nRequest Data: {requestData}\nResponse: {responseData}"; + return $"Exception occured while authenticating.\nUrl: {requestUrl}\nRequest Data: {requestData}\nResponse: {responseData}\nReason: {errorReason}"; } } } diff --git a/src/Firebase.Auth/FirebaseAuthProvider.cs b/src/Firebase.Auth/FirebaseAuthProvider.cs index f03dc75..d9c2040 100644 --- a/src/Firebase.Auth/FirebaseAuthProvider.cs +++ b/src/Firebase.Auth/FirebaseAuthProvider.cs @@ -1,10 +1,11 @@ -namespace Firebase.Auth +using System.Diagnostics; + +namespace Firebase.Auth { using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; - using Newtonsoft.Json; /// @@ -31,7 +32,7 @@ public FirebaseAuthProvider(FirebaseConfig authConfig) this.authConfig = authConfig; this.client = new HttpClient(); } - + /// /// Using the provided access token from third party auth provider (google, facebook...), get the firebase auth with token and basic user credentials. /// @@ -92,7 +93,7 @@ public async Task CreateUserWithEmailAndPasswordAsync(string e signup.User.DisplayName = displayName; } - + return signup; } @@ -169,7 +170,7 @@ public async Task GetLinkedAccountsAsync(string email) /// /// Disposes all allocated resources. /// - public void Dispose() + public void Dispose() { this.client.Dispose(); } @@ -194,10 +195,57 @@ private async Task ExecuteWithPostContentAsync(string googleUr } catch (Exception ex) { - throw new FirebaseAuthException(googleUrl, postContent, responseData, ex); + AuthErrorReason errorReason = GetFailureReason(responseData); + throw new FirebaseAuthException(googleUrl, postContent, responseData, ex, errorReason); + } + } + + /// + /// Resolves failure reason flags based on the returned error code. + /// + /// Currently only provides support for failed email auth flags. + private static AuthErrorReason GetFailureReason(string responseData) + { + var failureReason = AuthErrorReason.Undefined; + try + { + if (!string.IsNullOrEmpty(responseData) && responseData != "N/A") + { + //create error data template and try to parse JSON + var errorData = new { error = new { code = 0, message = "errorid" } }; + errorData = JsonConvert.DeserializeAnonymousType(responseData, errorData); + + //errorData is just null if different JSON was received + switch (errorData?.error?.message) + { + case "INVALID_PASSWORD": + failureReason = AuthErrorReason.WrongPassword; + break; + case "EMAIL_NOT_FOUND": + failureReason = AuthErrorReason.UnknownEmailAddress; + break; + case "INVALID_EMAIL": + failureReason = AuthErrorReason.InvalidEmailAddress; + break; + case "USER_DISABLED": + failureReason = AuthErrorReason.UserDisabled; + break; + } + } + } + catch (JsonReaderException) + { + //the response wasn't JSON - no data to be parsed } + catch (Exception e) + { + Debug.WriteLine($"Unexpected error trying to parse the response: {e}"); + } + + return failureReason; } + private string GetProviderId(FirebaseAuthType authType) { switch (authType) @@ -209,8 +257,9 @@ private string GetProviderId(FirebaseAuthType authType) return authType.ToEnumString(); case FirebaseAuthType.EmailAndPassword: throw new InvalidOperationException("Email auth type cannot be used like this. Use methods specific to email & password authentication."); - default: throw new NotImplementedException(""); + default: + throw new NotImplementedException(""); } } } -} +} \ No newline at end of file