diff --git a/github_nonpublic_api/api.py b/github_nonpublic_api/api.py index e6f73f6..c4e564e 100644 --- a/github_nonpublic_api/api.py +++ b/github_nonpublic_api/api.py @@ -96,9 +96,14 @@ def create_login_session( def _login_callback(data): data.update(dict(login=username, password=password)) - _get_and_submit_form( + res = _get_and_submit_form( session=session, url="https://github.com/login", data_callback=_login_callback ) + if "two-factor" not in res.url and "github.com" in res.url: + logging.error("Login challenged by GitHub (Captcha/Device Verification). Stuck at: %s", res.url) + raise RuntimeError( + f"Login challenged by GitHub (Captcha/Device Verification). Stuck at: {res.url}" + ) def _tfa_callback(data): data.update(dict(otp=tfa_callback())) @@ -322,8 +327,7 @@ def _data_callback(data): ) if __name__ == "__main__": - config = ConfigObj(os.path.expanduser("~/github.ini"), _inspec=True) - + config = ConfigObj(os.path.expanduser("~/github.ini"), _inspec=True) # type: ignore[not-callable] api = Api( username=config["username"], password=config["password"], diff --git a/tests/test_api.py b/tests/test_api.py index 0aa515d..75b47d5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -74,6 +74,29 @@ def test_get_and_submit_form_by_id_error(self): session=self.session, url='http://github.com', form_matcher=lambda form: False) + def test_create_login_session_captcha_challenge(self): + with mock.patch.object(api, '_get_and_submit_form') as mock_submit: + mock_res = mock.MagicMock() + mock_res.url = 'https://github.com/sessions/verified-device' + mock_submit.return_value = mock_res + + with self.assertRaisesRegex(RuntimeError, 'Login challenged by GitHub'): + api.create_login_session( + username='user', password='password', tfa_callback=lambda: '123' + ) + + def test_create_login_session_success(self): + with mock.patch.object(api, '_get_and_submit_form') as mock_submit: + mock_res = mock.MagicMock() + mock_res.url = 'https://github.com/sessions/two-factor' + mock_submit.return_value = mock_res + + session = api.create_login_session( + username='user', password='password', tfa_callback=lambda: '123' + ) + self.assertIsNotNone(session) + self.assertEqual(mock_submit.call_count, 2) + def test_create_business_org(self): self._seed_session_with_file(NEW_ORG_FORM_HTML) gh = api.Api(username='user', password='pass', session=self.session)