diff --git a/backend/src/appointment/l10n/en/main.ftl b/backend/src/appointment/l10n/en/main.ftl index ddab4c8b3..e30d9aa42 100644 --- a/backend/src/appointment/l10n/en/main.ftl +++ b/backend/src/appointment/l10n/en/main.ftl @@ -34,6 +34,7 @@ slot-not-found = There are no available time slots to book. slot-already-taken = The time slot you have selected is no longer available. Please try again. slot-invalid-email = The email you have provided was not valid. Please try again. invite-code-not-valid = The invite code you used is not valid. +not-in-allow-list = Your email is not in the allow list. schedule-not-active = The schedule has been turned off. Please contact the schedule owner for more information. diff --git a/backend/src/appointment/routes/auth.py b/backend/src/appointment/routes/auth.py index 0a0945ba8..db425e4e8 100644 --- a/backend/src/appointment/routes/auth.py +++ b/backend/src/appointment/routes/auth.py @@ -72,11 +72,21 @@ def fxa_login( fxa_client.setup() - try: - url, state = fxa_client.get_redirect_url(db, token_urlsafe(32), email) - except NotInAllowListException: - if not invite_code: - raise HTTPException(status_code=403, detail='Your email is not in the allow list') + # Check if they're in the allowed list, but only if they didn't provide an invite code + # This checks to see if they're already a user (bypasses allow list) or in the allow list. + is_in_allow_list = fxa_client.is_in_allow_list(db, email) + + if not is_in_allow_list and not invite_code: + raise HTTPException(status_code=403, detail=l10n('not-in-allow-list')) + elif not is_in_allow_list and invite_code: + # For slightly nicer error handling do the invite code check now. + # Only if they're not in the allow list and have an invite code. + if not repo.invite.code_exists(db, invite_code): + raise HTTPException(404, l10n('invite-code-not-valid')) + if not repo.invite.code_is_available(db, invite_code): + raise HTTPException(403, l10n('invite-code-not-valid')) + + url, state = fxa_client.get_redirect_url(db, token_urlsafe(32), email) request.session['fxa_state'] = state request.session['fxa_user_email'] = email @@ -142,6 +152,7 @@ def fxa_callback( new_subscriber_flow = not fxa_subscriber and not subscriber if new_subscriber_flow: + # Double check: # Ensure the invite code exists and is available # Use some inline-errors for now. We don't have a good error flow! is_in_allow_list = fxa_client.is_in_allow_list(db, email) diff --git a/backend/test/integration/test_auth.py b/backend/test/integration/test_auth.py index 2a51bc23e..f2a596ca7 100644 --- a/backend/test/integration/test_auth.py +++ b/backend/test/integration/test_auth.py @@ -98,6 +98,74 @@ def test_fxa_login(self, with_client): assert 'url' in data assert data.get('url') == FXA_CLIENT_PATCH.get('authorization_url') + def test_fxa_with_allowlist_and_without_invite(self, with_client, with_l10n): + os.environ['AUTH_SCHEME'] = 'fxa' + os.environ['FXA_ALLOW_LIST'] = '@example.org' + + email = 'not-in-allow-list@bad-example.org' + response = with_client.get( + '/fxa_login', + params={ + 'email': email, + }, + ) + assert response.status_code == 403, response.text + data = response.json() + assert data.get('detail') == l10n('not-in-allow-list') + + def test_fxa_with_allowlist_and_with_bad_invite_code(self, with_client, with_l10n): + os.environ['AUTH_SCHEME'] = 'fxa' + os.environ['FXA_ALLOW_LIST'] = '@example.org' + + email = 'not-in-allow-list@bad-example.org' + response = with_client.get( + '/fxa_login', + params={ + 'email': email, + 'invite_code': 'absolute nonsense!' + }, + ) + assert response.status_code == 404, response.text + data = response.json() + assert data.get('detail') == l10n('invite-code-not-valid') + + def test_fxa_with_allowlist_and_with_used_invite_code(self, with_client, with_l10n, make_invite, make_pro_subscriber): + os.environ['AUTH_SCHEME'] = 'fxa' + os.environ['FXA_ALLOW_LIST'] = '@example.org' + + other_guy = make_pro_subscriber() + invite = make_invite(subscriber_id=other_guy.id) + + email = 'not-in-allow-list@bad-example.org' + response = with_client.get( + '/fxa_login', + params={ + 'email': email, + 'invite_code': invite.code + }, + ) + assert response.status_code == 403, response.text + data = response.json() + assert data.get('detail') == l10n('invite-code-not-valid') + + def test_fxa_with_allowlist_and_with_invite(self, with_client, with_l10n, make_invite): + os.environ['AUTH_SCHEME'] = 'fxa' + os.environ['FXA_ALLOW_LIST'] = '@example.org' + + invite = make_invite() + email = 'not-in-allow-list@bad-example.org' + response = with_client.get( + '/fxa_login', + params={ + 'email': email, + 'invite_code': invite.code, + }, + ) + assert response.status_code == 200, response.text + data = response.json() + assert 'url' in data + assert data.get('url') == FXA_CLIENT_PATCH.get('authorization_url') + def test_fxa_callback_with_invite(self, with_db, with_client, monkeypatch, make_invite): """Test that our callback function correctly handles the session states, and creates a new subscriber""" os.environ['AUTH_SCHEME'] = 'fxa'