Skip to content
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
54 changes: 54 additions & 0 deletions src/main/java/com/google/firebase/auth/AuthErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.auth;

/**
* Error codes that can be raised by the Firebase Auth APIs.
*/
public enum AuthErrorCode {

/**
* A user already exists with the provided email.
*/
EMAIL_ALREADY_EXISTS,

/**
* The provided dynamic link domain is not configured or authorized for the current project.
*/
INVALID_DYNAMIC_LINK_DOMAIN,

/**
* A user already exists with the provided phone number.
*/
PHONE_NUMBER_ALREADY_EXISTS,

/**
* A user already exists with the provided UID.
*/
UID_ALREADY_EXISTS,

/**
* The domain of the continue URL is not whitelisted. Whitelist the domain in the Firebase
* console.
*/
UNAUTHORIZED_CONTINUE_URL,

/**
* No user record found for the given identifier.
*/
USER_NOT_FOUND,
}
213 changes: 213 additions & 0 deletions src/main/java/com/google/firebase/auth/AuthErrorHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.auth;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.Key;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseException;
import com.google.firebase.internal.AbstractHttpErrorHandler;
import com.google.firebase.internal.Nullable;
import java.io.IOException;
import java.util.Map;

final class AuthErrorHandler extends AbstractHttpErrorHandler<FirebaseAuthException> {

private static final Map<String, AuthError> ERROR_CODES =
ImmutableMap.<String, AuthError>builder()
.put(
"DUPLICATE_EMAIL",
new AuthError(
ErrorCode.ALREADY_EXISTS,
"The user with the provided email already exists",
AuthErrorCode.EMAIL_ALREADY_EXISTS))
.put(
"DUPLICATE_LOCAL_ID",
new AuthError(
ErrorCode.ALREADY_EXISTS,
"The user with the provided uid already exists",
AuthErrorCode.UID_ALREADY_EXISTS))
.put(
"EMAIL_EXISTS",
new AuthError(
ErrorCode.ALREADY_EXISTS,
"The user with the provided email already exists",
AuthErrorCode.EMAIL_ALREADY_EXISTS))
.put(
"INVALID_DYNAMIC_LINK_DOMAIN",
new AuthError(
ErrorCode.INVALID_ARGUMENT,
"The provided dynamic link domain is not "
+ "configured or authorized for the current project",
AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN))
.put(
"PHONE_NUMBER_EXISTS",
new AuthError(
ErrorCode.ALREADY_EXISTS,
"The user with the provided phone number already exists",
AuthErrorCode.PHONE_NUMBER_ALREADY_EXISTS))
.put(
"UNAUTHORIZED_DOMAIN",
new AuthError(
ErrorCode.INVALID_ARGUMENT,
"The domain of the continue URL is not whitelisted",
AuthErrorCode.UNAUTHORIZED_CONTINUE_URL))
.put(
"USER_NOT_FOUND",
new AuthError(
ErrorCode.NOT_FOUND,
"No user record found for the given identifier",
AuthErrorCode.USER_NOT_FOUND))
.build();

private final JsonFactory jsonFactory;

AuthErrorHandler(JsonFactory jsonFactory) {
this.jsonFactory = checkNotNull(jsonFactory);
}

@Override
protected FirebaseAuthException createException(FirebaseException base) {
String response = getResponse(base);
AuthServiceErrorResponse parsed = safeParse(response);
AuthError errorInfo = ERROR_CODES.get(parsed.getCode());
if (errorInfo != null) {
return new FirebaseAuthException(
errorInfo.getErrorCode(),
errorInfo.buildMessage(parsed),
base.getCause(),
base.getHttpResponse(),
errorInfo.getAuthErrorCode());
}

return new FirebaseAuthException(
base.getErrorCodeNew(),
base.getMessage(),
base.getCause(),
base.getHttpResponse(),
null);
}

private String getResponse(FirebaseException base) {
if (base.getHttpResponse() == null) {
return null;
}

return base.getHttpResponse().getContent();
}

private AuthServiceErrorResponse safeParse(String response) {
AuthServiceErrorResponse parsed = new AuthServiceErrorResponse();
if (!Strings.isNullOrEmpty(response)) {
try {
jsonFactory.createJsonParser(response).parse(parsed);
} catch (IOException ignore) {
// Ignore any error that may occur while parsing the error response. The server
// may have responded with a non-json payload.
}
}

return parsed;
}

private static class AuthError {

private final ErrorCode errorCode;
private final String message;
private final AuthErrorCode authErrorCode;

AuthError(ErrorCode errorCode, String message, AuthErrorCode authErrorCode) {
this.errorCode = errorCode;
this.message = message;
this.authErrorCode = authErrorCode;
}

ErrorCode getErrorCode() {
return errorCode;
}

AuthErrorCode getAuthErrorCode() {
return authErrorCode;
}

String buildMessage(AuthServiceErrorResponse response) {
StringBuilder builder = new StringBuilder(this.message)
.append(" (").append(response.getCode()).append(")");
String detail = response.getDetail();
if (!Strings.isNullOrEmpty(detail)) {
builder.append(": ").append(detail);
} else {
builder.append(".");
}

return builder.toString();
}
}

/**
* JSON data binding for JSON error messages sent by Google identity toolkit service. These
* error messages take the form `{"error": {"message": "CODE: OPTIONAL DETAILS"}}`.
*/
private static class AuthServiceErrorResponse {

@Key("error")
private GenericJson error;

@Nullable
public String getCode() {
String message = getMessage();
if (Strings.isNullOrEmpty(message)) {
return null;
}

int separator = message.indexOf(':');
if (separator != -1) {
return message.substring(0, separator);
}

return message;
}

@Nullable
public String getDetail() {
String message = getMessage();
if (Strings.isNullOrEmpty(message)) {
return null;
}

int separator = message.indexOf(':');
if (separator != -1) {
return message.substring(separator + 1).trim();
}

return null;
}

private String getMessage() {
if (error == null) {
return null;
}

return (String) error.get("message");
}
}
}
43 changes: 30 additions & 13 deletions src/main/java/com/google/firebase/auth/FirebaseAuthException.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,57 @@

package com.google.firebase.auth;

// TODO: Move it out from firebase-common. Temporary host it their for
// database's integration.http://b/27624510.

// TODO: Decide if changing this not enforcing an error code. Need to align
// with the decision in http://b/27677218. Also, need to turn this into abstract later.

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.base.Strings;
import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseException;
import com.google.firebase.IncomingHttpResponse;
import com.google.firebase.internal.NonNull;
import com.google.firebase.internal.Nullable;

/**
* Generic exception related to Firebase Authentication. Check the error code and message for more
* details.
*/
public class FirebaseAuthException extends FirebaseException {

private final String errorCode;
private final AuthErrorCode errorCode;
private final String deprecatedErrorCode;

FirebaseAuthException(
@NonNull ErrorCode errorCode,
@NonNull String message,
Throwable cause,
IncomingHttpResponse response,
AuthErrorCode authErrorCode) {
super(errorCode, message, cause, response);
this.errorCode = authErrorCode;
this.deprecatedErrorCode = null;
}

@Deprecated
public FirebaseAuthException(@NonNull String errorCode, @NonNull String detailMessage) {
this(errorCode, detailMessage, null);
}

public FirebaseAuthException(@NonNull String errorCode, @NonNull String detailMessage,
Throwable throwable) {
@Deprecated
public FirebaseAuthException(
@NonNull String errorCode, @NonNull String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
checkArgument(!Strings.isNullOrEmpty(errorCode));
this.errorCode = errorCode;
this.errorCode = null;
this.deprecatedErrorCode = errorCode;
}

/** Returns an error code that may provide more information about the error. */
@NonNull
public String getErrorCode() {
@Nullable
public AuthErrorCode getAuthErrorCode() {
return errorCode;
}

/** Returns an error code that may provide more information about the error. */
@Deprecated
public String getDeprecatedErrorCode() {
return deprecatedErrorCode;
}
}
Loading