Skip to content

Commit 29c8b7a

Browse files
authored
Error handling improvements in email action link APIs (#312)
* New error handling support in create/update/delete user APIs * Fixing some lint errors * Error handling update in email action link APIs
1 parent 8a0cf08 commit 29c8b7a

File tree

4 files changed

+57
-29
lines changed

4 files changed

+57
-29
lines changed

firebase_admin/_auth_utils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,19 @@ class UidAlreadyExistsError(exceptions.AlreadyExistsError):
198198

199199
default_message = 'The user with the provided uid already exists'
200200

201-
def __init__(self, message, cause, http_response=None):
201+
def __init__(self, message, cause, http_response):
202202
exceptions.AlreadyExistsError.__init__(self, message, cause, http_response)
203203

204204

205+
class InvalidDynamicLinkDomainError(exceptions.InvalidArgumentError):
206+
"""Dynamic link domain in ActionCodeSettings is not authorized."""
207+
208+
default_message = 'Dynamic link domain specified in ActionCodeSettings is not authorized'
209+
210+
def __init__(self, message, cause, http_response):
211+
exceptions.InvalidArgumentError.__init__(self, message, cause, http_response)
212+
213+
205214
class InvalidIdTokenError(exceptions.InvalidArgumentError):
206215
"""The provided ID token is not a valid Firebase ID token."""
207216

@@ -229,6 +238,7 @@ def __init__(self, message, cause=None, http_response=None):
229238

230239
_CODE_TO_EXC_TYPE = {
231240
'DUPLICATE_LOCAL_ID': UidAlreadyExistsError,
241+
'INVALID_DYNAMIC_LINK_DOMAIN': InvalidDynamicLinkDomainError,
232242
'INVALID_ID_TOKEN': InvalidIdTokenError,
233243
'USER_NOT_FOUND': UserNotFoundError,
234244
}

firebase_admin/_user_mgt.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
USER_IMPORT_ERROR = 'USER_IMPORT_ERROR'
2828
USER_DOWNLOAD_ERROR = 'LIST_USERS_ERROR'
29-
GENERATE_EMAIL_ACTION_LINK_ERROR = 'GENERATE_EMAIL_ACTION_LINK_ERROR'
3029

3130
MAX_LIST_USERS_RESULTS = 1000
3231
MAX_IMPORT_USERS_SIZE = 1000
@@ -654,14 +653,15 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
654653
payload.update(encode_action_code_settings(action_code_settings))
655654

656655
try:
657-
response = self._client.body('post', '/accounts:sendOobCode', json=payload)
656+
body, http_resp = self._client.body_and_response(
657+
'post', '/accounts:sendOobCode', json=payload)
658658
except requests.exceptions.RequestException as error:
659-
self._handle_http_error(GENERATE_EMAIL_ACTION_LINK_ERROR, 'Failed to generate link.',
660-
error)
659+
raise _auth_utils.handle_auth_backend_error(error)
661660
else:
662-
if not response or not response.get('oobLink'):
663-
raise ApiCallError(GENERATE_EMAIL_ACTION_LINK_ERROR, 'Failed to generate link.')
664-
return response.get('oobLink')
661+
if not body or not body.get('oobLink'):
662+
raise _auth_utils.UnexpectedResponseError(
663+
'Failed to generate email action link.', http_response=http_resp)
664+
return body.get('oobLink')
665665

666666
def _handle_http_error(self, code, msg, error):
667667
if error.response is not None:

firebase_admin/auth.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
'ErrorInfo',
4343
'ExportedUserRecord',
4444
'ImportUserRecord',
45+
'InvalidDynamicLinkDomainError',
46+
'InvalidIdTokenError',
4547
'ListUsersPage',
4648
'TokenSignError',
4749
'UidAlreadyExistsError',
@@ -80,6 +82,7 @@
8082
ListUsersPage = _user_mgt.ListUsersPage
8183
UserImportHash = _user_import.UserImportHash
8284
ImportUserRecord = _user_import.ImportUserRecord
85+
InvalidDynamicLinkDomainError = _auth_utils.InvalidDynamicLinkDomainError
8386
InvalidIdTokenError = _auth_utils.InvalidIdTokenError
8487
TokenSignError = _token_gen.TokenSignError
8588
UidAlreadyExistsError = _auth_utils.UidAlreadyExistsError
@@ -465,14 +468,11 @@ def generate_password_reset_link(email, action_code_settings=None, app=None):
465468
466469
Raises:
467470
ValueError: If the provided arguments are invalid
468-
AuthError: If an error occurs while generating the link
471+
FirebaseError: If an error occurs while generating the link
469472
"""
470473
user_manager = _get_auth_service(app).user_manager
471-
try:
472-
return user_manager.generate_email_action_link('PASSWORD_RESET', email,
473-
action_code_settings=action_code_settings)
474-
except _user_mgt.ApiCallError as error:
475-
raise AuthError(error.code, str(error), error.detail)
474+
return user_manager.generate_email_action_link(
475+
'PASSWORD_RESET', email, action_code_settings=action_code_settings)
476476

477477

478478
def generate_email_verification_link(email, action_code_settings=None, app=None):
@@ -490,14 +490,11 @@ def generate_email_verification_link(email, action_code_settings=None, app=None)
490490
491491
Raises:
492492
ValueError: If the provided arguments are invalid
493-
AuthError: If an error occurs while generating the link
493+
FirebaseError: If an error occurs while generating the link
494494
"""
495495
user_manager = _get_auth_service(app).user_manager
496-
try:
497-
return user_manager.generate_email_action_link('VERIFY_EMAIL', email,
498-
action_code_settings=action_code_settings)
499-
except _user_mgt.ApiCallError as error:
500-
raise AuthError(error.code, str(error), error.detail)
496+
return user_manager.generate_email_action_link(
497+
'VERIFY_EMAIL', email, action_code_settings=action_code_settings)
501498

502499

503500
def generate_sign_in_with_email_link(email, action_code_settings, app=None):
@@ -515,14 +512,11 @@ def generate_sign_in_with_email_link(email, action_code_settings, app=None):
515512
516513
Raises:
517514
ValueError: If the provided arguments are invalid
518-
AuthError: If an error occurs while generating the link
515+
FirebaseError: If an error occurs while generating the link
519516
"""
520517
user_manager = _get_auth_service(app).user_manager
521-
try:
522-
return user_manager.generate_email_action_link('EMAIL_SIGNIN', email,
523-
action_code_settings=action_code_settings)
524-
except _user_mgt.ApiCallError as error:
525-
raise AuthError(error.code, str(error), error.detail)
518+
return user_manager.generate_email_action_link(
519+
'EMAIL_SIGNIN', email, action_code_settings=action_code_settings)
526520

527521

528522
def _check_jwt_revoked(verified_claims, error_code, label, app):

tests/test_user_mgt.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,9 +1200,29 @@ def test_password_reset_with_settings(self, user_mgt_app):
12001200
auth.generate_password_reset_link,
12011201
])
12021202
def test_api_call_failure(self, user_mgt_app, func):
1203-
_instrument_user_manager(user_mgt_app, 500, '{"error":"dummy error"}')
1204-
with pytest.raises(auth.AuthError):
1203+
_instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "UNEXPECTED_CODE"}}')
1204+
with pytest.raises(exceptions.InternalError) as excinfo:
1205+
func('[email protected]', MOCK_ACTION_CODE_SETTINGS, app=user_mgt_app)
1206+
assert str(excinfo.value) == 'Error while calling Auth service (UNEXPECTED_CODE).'
1207+
assert excinfo.value.http_response is not None
1208+
assert excinfo.value.cause is not None
1209+
1210+
@pytest.mark.parametrize('func', [
1211+
auth.generate_sign_in_with_email_link,
1212+
auth.generate_email_verification_link,
1213+
auth.generate_password_reset_link,
1214+
])
1215+
def test_invalid_dynamic_link(self, user_mgt_app, func):
1216+
resp = '{"error":{"message": "INVALID_DYNAMIC_LINK_DOMAIN: Because of this reason."}}'
1217+
_instrument_user_manager(user_mgt_app, 500, resp)
1218+
with pytest.raises(auth.InvalidDynamicLinkDomainError) as excinfo:
12051219
func('[email protected]', MOCK_ACTION_CODE_SETTINGS, app=user_mgt_app)
1220+
assert isinstance(excinfo.value, exceptions.InvalidArgumentError)
1221+
assert str(excinfo.value) == ('Dynamic link domain specified in ActionCodeSettings is '
1222+
'not authorized (INVALID_DYNAMIC_LINK_DOMAIN). Because '
1223+
'of this reason.')
1224+
assert excinfo.value.http_response is not None
1225+
assert excinfo.value.cause is not None
12061226

12071227
@pytest.mark.parametrize('func', [
12081228
auth.generate_sign_in_with_email_link,
@@ -1211,8 +1231,12 @@ def test_api_call_failure(self, user_mgt_app, func):
12111231
])
12121232
def test_api_call_no_link(self, user_mgt_app, func):
12131233
_instrument_user_manager(user_mgt_app, 200, '{}')
1214-
with pytest.raises(auth.AuthError):
1234+
with pytest.raises(auth.UnexpectedResponseError) as excinfo:
12151235
func('[email protected]', MOCK_ACTION_CODE_SETTINGS, app=user_mgt_app)
1236+
assert str(excinfo.value) == 'Failed to generate email action link.'
1237+
assert excinfo.value.http_response is not None
1238+
assert excinfo.value.cause is None
1239+
assert isinstance(excinfo.value, exceptions.UnknownError)
12161240

12171241
@pytest.mark.parametrize('func', [
12181242
auth.generate_sign_in_with_email_link,

0 commit comments

Comments
 (0)