coveralls: 
[![Coverage Status](https://coveralls.io/builds/28987207/badge)](https://coveralls.io/builds/28987207)

Coverage remained the same at 100.0% when pulling **355a77f56e30f17a2705cf12aca164f70a192e73 on revert-qrcode-auth** into **7e5e7efd691dc14ee6eb85eed9f517a3f0469aa0 on master**.

diff --git a/README.md b/README.md
index 0096c61..4922837 100644
--- a/README.md
+++ b/README.md
@@ -13,17 +13,17 @@ Disponível via pip
 ## Utilizando
 
 ### Ponto de atenção
-O Nubank pode bloquear a sua conta por 72 horas caso detecte algum comportamento anormal !!
-Por conta disso, evite enviar muitas requisições (Especialmente autenticação). Se for necessário, faça um mock da resposta ou utilize o Jupyter durante o desenvolvimento para que o bloqueio não ocorra.
+O Nubank pode trancar a sua conta por 72 horas caso detecte algum comportamento anormal !!
+Por conta disso, evite enviar muitas requisições. Se for necessário, faça um mock da resposta ou utilize o Jupyter durante o desenvolvimento para que o bloqueio não ocorra.
 
 #### Cartão de Crédito
 ```python
 from pynubank import Nubank
 
 # Utilize o CPF sem pontos ou traços
-# A senha é a mesma que você usa no app/site, não a do cartão.
 nu = Nubank()
-nu.authenticate('123456789', 'senha')
+uuid, qr_code = nu.get_qr_code()
+nu.authenticate_with_qr_code('123456789', 'senha', uuid)
 
 # Lista de dicionários contendo todas as transações de seu cartão de crédito
 card_statements = nu.get_card_statements()
@@ -44,7 +44,8 @@ from pynubank import Nubank
 
 # Utilize o CPF sem pontos ou traços
 nu = Nubank()
-nu.authenticate('123456789', 'senha')
+uuid, qr_code = nu.get_qr_code()
+nu.authenticate_with_qr_code('123456789', 'senha', uuid)
 
 # Lista de dicionários contendo todas as transações de seu cartão de crédito
 account_statements = nu.get_account_statements()
@@ -59,7 +60,23 @@ print(sum([t['amount'] for t in account_statements]))
 # Saldo atual
 print(nu.get_account_balance())
 ```
+### Autenticação
+Caso a autenticação por QRCode esteja ativada na sua conta, será necessário utilizar o seu telefone par autorizar o acesso a API.
 
+```python
+from pynubank import Nubank
+
+nu = Nubank()
+uuid, qr_code = nu.get_qr_code()
+# Nesse momeento será printado o QRCode no console
+# Você precisa escanear pelo o seu app do celular
+# Esse menu fica em NU > Perfil > Acesso pelo site
+qr_code.print_ascii(invert=True)
+input('Após escanear o QRCode pressione enter para continuar')
+# Somente após escanear o QRCode você pode chamar a linha abaixo
+nu.authenticate_with_qr_code('123456789', 'senha', uuid)
+print(nu.get_account_balance())
+```
 
 #### Utilizando com Pandas
 ```python
@@ -67,7 +84,8 @@ import pandas as pd
 from pynubank import Nubank
 
 nu = Nubank()
-nu.authenticate('123456789', 'senha')
+uuid, qr_code = nu.get_qr_code()
+nu.authenticate_with_qr_code('123456789', 'senha', uuid)
 
 transactions = nu.get_account_statements()
 
diff --git a/pynubank/nubank.py b/pynubank/nubank.py
index 787e45f..f7174db 100644
--- a/pynubank/nubank.py
+++ b/pynubank/nubank.py
@@ -1,7 +1,10 @@
 import json
 import os
+import uuid
+from typing import Tuple
 
 import requests
+from qrcode import QRCode
 from requests import Response
 
 PAYMENT_EVENT_TYPES = (
@@ -63,7 +66,7 @@ class Nubank:
 
         return self._handle_response(response)
 
-    def authenticate(self, cpf: str, password: str):
+    def _password_auth(self, cpf: str, password: str):
         payload = {
             "grant_type": "password",
             "login": cpf,
@@ -72,13 +75,7 @@ class Nubank:
             "client_secret": "yQPeLzoHuJzlMMSAjC-LgNUJdUecx8XO"
         }
         response = requests.post(self.auth_url, json=payload, headers=self.headers)
-
         data = self._handle_response(response)
-
-        self.headers['Authorization'] = f'Bearer {data["access_token"]}'
-        self.feed_url = data['_links']['events']['href']
-        self.query_url = data['_links']['ghostflame']['href']
-        self.bills_url = data['_links']['bills_summary']['href']
         return data
 
     def _handle_response(self, response: Response) -> dict:
@@ -87,6 +84,29 @@ class Nubank:
 
         return response.json()
 
+    def get_qr_code(self) -> Tuple[str, QRCode]:
+        content = str(uuid.uuid4())
+        qr = QRCode()
+        qr.add_data(content)
+        return content, qr
+
+    def authenticate_with_qr_code(self, cpf: str, password, uuid: str):
+        auth_data = self._password_auth(cpf, password)
+        self.headers['Authorization'] = f'Bearer {auth_data["access_token"]}'
+
+        payload = {
+            'qr_code_id': uuid,
+            'type': 'login-webapp'
+        }
+
+        response = requests.post(self.proxy_list_app_url['lift'], json=payload, headers=self.headers)
+
+        auth_data = self._handle_response(response)
+        self.headers['Authorization'] = f'Bearer {auth_data["access_token"]}'
+        self.feed_url = auth_data['_links']['events']['href']
+        self.query_url = auth_data['_links']['ghostflame']['href']
+        self.bills_url = auth_data['_links']['bills_summary']['href']
+
     def get_card_feed(self):
         request = requests.get(self.feed_url, headers=self.headers)
         return json.loads(request.content.decode('utf-8'))
diff --git a/requirements.txt b/requirements.txt
index 822be75..c117419 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,1 +1,2 @@
-requests==2.23.0
\ No newline at end of file
+requests==2.23.0
+qrcode==6.1
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 38bf8b4..ddd8185 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ from setuptools import setup
 
 setup(
     name='pynubank',
-    version='1.1.0',
+    version='1.0.1',
     url='https://github.com/andreroggeri/pynubank',
     author='André Roggeri Campos',
     author_email='a.roggeri.c@gmail.com',

Autenticação com QRCode voltou a ser obrigatória
Boa tarde pessoal, tudo bem?

Passando aqui só para avisar que a autenticação com QRCode voltou a ser necessária para fazer autenticação.  Parece que é algo bem recente, pois na terça-feira estava funcionando sem a necessidade do QRCode.

Acredito que seria melhor deixar as duas formas de autenticação no Pynubank, para poder alternar entre elas sem precisar alterar o código.

Abraço!


diff --git a/tests/test_nubank_client.py b/tests/test_nubank_client.py
index 889ec60..83c6a46 100644
--- a/tests/test_nubank_client.py
+++ b/tests/test_nubank_client.py
@@ -2,6 +2,7 @@ import json
 from unittest.mock import MagicMock, patch
 
 import pytest
+from qrcode import QRCode
 from requests import Response
 
 from pynubank.nubank import Nubank, NuException
@@ -397,27 +398,27 @@ def fake_update_proxy(self: Nubank):
     }
 
 
-def test_authenticate_succeeds(monkeypatch, authentication_return, proxy_list_return):
+def test_authenticate_with_qr_code_succeeds(monkeypatch, authentication_return, proxy_list_return):
     proxy_list = create_fake_response(proxy_list_return)
     monkeypatch.setattr('requests.get', MagicMock(return_value=proxy_list))
     response = create_fake_response(authentication_return)
     monkeypatch.setattr('requests.post', MagicMock(return_value=response))
 
     nubank_client = Nubank()
-    nubank_client.authenticate('12345678912', 'hunter12')
+    nubank_client.authenticate_with_qr_code('12345678912', 'hunter12', 'some-uuid')
 
     assert nubank_client.feed_url == 'https://prod-s0-webapp-proxy.nubank.com.br/api/proxy/events_123'
     assert nubank_client.headers['Authorization'] == 'Bearer access_token_123'
 
 
 @patch.object(Nubank, '_update_proxy_urls', fake_update_proxy)
-def test_authentication_failure_raise_exception(monkeypatch):
+def test_authentication_with_qr_code_failure_raise_exception(monkeypatch):
     response = create_fake_response({}, 401)
 
     monkeypatch.setattr('requests.post', MagicMock(return_value=response))
     with pytest.raises(NuException):
         nu = Nubank()
-        nu.authenticate('12345678912', 'hunter12')
+        nu.authenticate_with_qr_code('12345678912', 'hunter12', 'some-uuid')
 
 
 @patch.object(Nubank, '_update_proxy_urls', fake_update_proxy)
@@ -674,6 +675,15 @@ def test_grapql_query_raises_exeption(monkeypatch, authentication_return):
         nubank_client.get_account_balance()
 
 
+@patch.object(Nubank, '_update_proxy_urls', fake_update_proxy)
+def test_get_qr_code():
+    client = Nubank()
+    uid, qr = client.get_qr_code()
+
+    assert uid != ''
+    assert isinstance(qr, QRCode)
+
+
 @pytest.mark.parametrize('http_status', [
     100, 101, 102, 103,
     201, 202, 203, 204, 205, 206, 207, 208, 226,

pip install -e .
parse_log_pytest
requirements.txt
pytest
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
chardet==3.0.4
idna==2.10
importlib-metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
-e git+https://github.com/andreroggeri/pynubank.git@7e5e7efd691dc14ee6eb85eed9f517a3f0469aa0#egg=pynubank
pyparsing==3.1.4
pytest==7.0.1
qrcode==7.3.1
requests==2.23.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.25.11
zipp==3.6.0

name: pynubank
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - chardet==3.0.4
      - idna==2.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - qrcode==7.3.1
      - requests==2.23.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.25.11
      - zipp==3.6.0
prefix: /opt/conda/envs/pynubank

tests/test_nubank_client.py::test_authenticate_with_qr_code_succeeds tests/test_nubank_client.py::test_authentication_with_qr_code_failure_raise_exception tests/test_nubank_client.py::test_get_qr_code
tests/test_nubank_client.py::test_get_card_feed tests/test_nubank_client.py::test_get_bills tests/test_nubank_client.py::test_get_bill_details tests/test_nubank_client.py::test_get_card_statements tests/test_nubank_client.py::test_get_account_balance tests/test_nubank_client.py::test_get_account_feed tests/test_nubank_client.py::test_get_account_statements tests/test_nubank_client.py::test_grapql_query_raises_exeption tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[100] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[101] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[102] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[103] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[201] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[202] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[203] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[204] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[205] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[206] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[207] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[208] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[226] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[300] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[301] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[302] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[303] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[304] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[305] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[306] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[307] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[308] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[400] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[401] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[402] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[403] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[404] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[405] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[406] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[407] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[408] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[409] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[410] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[411] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[412] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[413] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[414] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[415] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[416] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[417] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[418] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[420] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[421] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[422] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[423] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[424] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[426] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[428] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[429] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[431] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[440] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[444] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[449] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[450] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[451] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[495] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[496] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[497] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[498] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[499] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[500] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[501] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[502] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[503] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[504] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[505] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[506] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[507] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[508] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[509] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[510] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[511] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[520] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[521] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[522] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[523] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[524] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[525] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[526] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[527] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[530] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_on_status_different_of_200[598] tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_with_status_code_attribute tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_status_code_in_the_exception_message tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_with_url_attribute tests/test_nubank_client.py::test_nubank_request_handler_throws_exception_with_response_attribute
setup.py requirements.txt README.md pynubank/nubank.py
setup.py requirements.txt README.md pynubank/nubank.py