From a9c258c3b9ea635a9118830b8ca70af052693ec4 Mon Sep 17 00:00:00 2001 From: gabino Date: Mon, 27 Jan 2025 22:40:49 -0600 Subject: [PATCH 01/17] Add sensitive metadata annotation to resource --- cuenca/resources/api_keys.py | 6 +++--- cuenca/resources/login_tokens.py | 5 ++++- cuenca/resources/otps.py | 5 +++-- cuenca/resources/sessions.py | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cuenca/resources/api_keys.py b/cuenca/resources/api_keys.py index f211a5fc..e4cce0f9 100644 --- a/cuenca/resources/api_keys.py +++ b/cuenca/resources/api_keys.py @@ -1,7 +1,7 @@ import datetime as dt -from typing import ClassVar, Optional +from typing import Annotated, ClassVar, Optional -from cuenca_validations.types import ApiKeyQuery, ApiKeyUpdateRequest +from cuenca_validations.types import ApiKeyQuery, ApiKeyUpdateRequest, Metadata from pydantic import ConfigDict from ..http import Session, session as global_session @@ -12,7 +12,7 @@ class ApiKey(Creatable, Queryable, Retrievable, Updateable): _resource: ClassVar = 'api_keys' _query_params: ClassVar = ApiKeyQuery - secret: str + secret: Annotated[str, Metadata(sensitive=True, log_chars=4)] deactivated_at: Optional[dt.datetime] = None user_id: Optional[str] = None model_config = ConfigDict( diff --git a/cuenca/resources/login_tokens.py b/cuenca/resources/login_tokens.py index 2be84eac..754d91fd 100644 --- a/cuenca/resources/login_tokens.py +++ b/cuenca/resources/login_tokens.py @@ -1,5 +1,6 @@ -from typing import ClassVar +from typing import Annotated, ClassVar +from cuenca_validations.types import Metadata from pydantic import ConfigDict from ..http import Session, session as global_session @@ -9,6 +10,8 @@ class LoginToken(Creatable): _resource: ClassVar = 'login_tokens' + id: Annotated[str, Metadata(sensitive=True, log_chars=4)] + model_config = ConfigDict( json_schema_extra={'example': {'id': 'LTNEUInh69SuKXXmK95sROwQ'}} ) diff --git a/cuenca/resources/otps.py b/cuenca/resources/otps.py index f6e2ba3c..11e85f57 100644 --- a/cuenca/resources/otps.py +++ b/cuenca/resources/otps.py @@ -1,5 +1,6 @@ -from typing import ClassVar +from typing import Annotated, ClassVar +from cuenca_validations.types import Metadata from pydantic import ConfigDict from ..http import Session, session as global_session @@ -8,7 +9,7 @@ class Otp(Creatable): _resource: ClassVar = 'otps' - secret: str + secret: Annotated[str, Metadata(sensitive=True, log_chars=4)] model_config = ConfigDict( json_schema_extra={ diff --git a/cuenca/resources/sessions.py b/cuenca/resources/sessions.py index f08caa88..d4773427 100644 --- a/cuenca/resources/sessions.py +++ b/cuenca/resources/sessions.py @@ -1,7 +1,7 @@ import datetime as dt -from typing import ClassVar, Optional +from typing import Annotated, ClassVar, Optional -from cuenca_validations.types import SessionRequest, SessionType +from cuenca_validations.types import Metadata, SessionRequest, SessionType from cuenca_validations.types.general import SerializableAnyUrl from pydantic import ConfigDict @@ -12,7 +12,7 @@ class Session(Creatable, Retrievable, Queryable): _resource: ClassVar = 'sessions' - id: str + id: Annotated[str, Metadata(sensitive=True, log_chars=4)] created_at: dt.datetime user_id: str platform_id: str From a51cd1ef7603b45e56928bab4b6139eeae3025e8 Mon Sep 17 00:00:00 2001 From: gabino Date: Mon, 27 Jan 2025 22:41:08 -0600 Subject: [PATCH 02/17] Add JwtToken resource for generating JWT tokens --- cuenca/__init__.py | 2 + cuenca/resources/__init__.py | 3 + cuenca/resources/jwt_tokens.py | 44 ++++++ .../resources/cassettes/test_jwt_tokens.yaml | 138 ++++++++++++++++++ tests/resources/test_jwt_tokens.py | 28 ++++ 5 files changed, 215 insertions(+) create mode 100644 cuenca/resources/jwt_tokens.py create mode 100644 tests/resources/cassettes/test_jwt_tokens.yaml create mode 100644 tests/resources/test_jwt_tokens.py diff --git a/cuenca/__init__.py b/cuenca/__init__.py index 32bce1f4..32950045 100644 --- a/cuenca/__init__.py +++ b/cuenca/__init__.py @@ -42,6 +42,7 @@ 'WhatsappTransfer', 'configure', 'get_balance', + 'JwtToken', ] from . import http @@ -65,6 +66,7 @@ FileBatch, Identity, IdentityEvent, + JwtToken, KYCValidation, KYCVerification, LimitedWallet, diff --git a/cuenca/resources/__init__.py b/cuenca/resources/__init__.py index f161cee3..8daf3c7d 100644 --- a/cuenca/resources/__init__.py +++ b/cuenca/resources/__init__.py @@ -38,6 +38,7 @@ 'WalletTransaction', 'Webhook', 'WhatsappTransfer', + 'JwtToken', ] from .accounts import Account @@ -59,6 +60,7 @@ from .files import File from .identities import Identity from .identity_events import IdentityEvent +from .jwt_tokens import JwtToken from .kyc_validations import KYCValidation from .kyc_verifications import KYCVerification from .limited_wallets import LimitedWallet @@ -123,6 +125,7 @@ WhatsappTransfer, Webhook, Platform, + JwtToken, ] for resource_cls in resource_classes: RESOURCES[resource_cls._resource] = resource_cls # type: ignore diff --git a/cuenca/resources/jwt_tokens.py b/cuenca/resources/jwt_tokens.py new file mode 100644 index 00000000..7435436f --- /dev/null +++ b/cuenca/resources/jwt_tokens.py @@ -0,0 +1,44 @@ +import datetime as dt +from typing import Annotated, ClassVar + +from cuenca_validations.types import Metadata +from pydantic import ConfigDict + +from ..http import Session, session as global_session +from .base import Creatable + + +class JwtToken(Creatable): + _resource: ClassVar = 'token' + + id: Annotated[str, Metadata(sensitive=True, log_chars=4)] + token: Annotated[str, Metadata(sensitive=True, log_chars=4)] + created_at: dt.datetime + api_key_uri: str + + model_config = ConfigDict( + json_schema_extra={ + 'example': { + 'id': ( + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzgzNjI' + '4NzcsImlhdCI6MTczNzc1ODA3Nywic3ViIjoiQUtzY3p5N3RzaVJkMkl' + 'iakxfbllGb2xRIiwidWlkIjoiNjRiZmQ0OTItZGFhMy0xMWVmLWEyMWU' + 'tMGE1OGE5ZmVhYzAyIn0.Er8kDsw4rtGkwAXpEgUhwyXFiBjYlwDVTGF' + 'tYW7o0go' + ), + 'token': ( + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzgzNjI' + '4NzcsImlhdCI6MTczNzc1ODA3Nywic3ViIjoiQUtzY3p5N3RzaVJkMkl' + 'iakxfbllGb2xRIiwidWlkIjoiNjRiZmQ0OTItZGFhMy0xMWVmLWEyMWU' + 'tMGE1OGE5ZmVhYzAyIn0.Er8kDsw4rtGkwAXpEgUhwyXFiBjYlwDVTGF' + 'tYW7o0go' + ), + 'created_at': '2025-01-24T22:34:37.659667', + 'api_key_uri': '/api_key/AKsczy7tsiRd2IbjL_nYFolQ', + } + } + ) + + @classmethod + def create(cls, session: Session = global_session) -> 'JwtToken': + return cls._create(session=session) diff --git a/tests/resources/cassettes/test_jwt_tokens.yaml b/tests/resources/cassettes/test_jwt_tokens.yaml new file mode 100644 index 00000000..d921f1f7 --- /dev/null +++ b/tests/resources/cassettes/test_jwt_tokens.yaml @@ -0,0 +1,138 @@ +interactions: +- request: + body: '{"password": "111111"}' + headers: + Authorization: + - DUMMY + Content-Length: + - '26' + Content-Type: + - application/json + User-Agent: + - cuenca-python/2.0.1 + X-Cuenca-Api-Version: + - '2020-03-19' + method: POST + uri: https://sandbox.cuenca.com/user_logins + response: + body: + string: '{"success":true,"id":"ULm7x8e0hfQYyUWwM3ysqgTw","last_login_at":"2025-01-27T22:50:04.745000Z"}' + headers: + Connection: + - keep-alive + Content-Length: + - '94' + Content-Type: + - application/json + Date: + - Mon, 27 Jan 2025 23:08:55 GMT + X-Request-Time: + - 'value: 0.421' + x-amz-apigw-id: + - FEfBLEMtCYcEKuQ= + x-amzn-Remapped-Connection: + - keep-alive + x-amzn-Remapped-Content-Length: + - '94' + x-amzn-Remapped-Date: + - Mon, 27 Jan 2025 23:08:55 GMT + x-amzn-Remapped-Server: + - nginx/1.26.2 + x-amzn-RequestId: + - 855b6c10-2e41-4b1f-b0fe-6030bd4e0032 + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Authorization: + - DUMMY + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - cuenca-python/2.0.1 + X-Cuenca-Api-Version: + - '2020-03-19' + X-Cuenca-LoginId: + - ULm7x8e0hfQYyUWwM3ysqgTw + method: POST + uri: https://sandbox.cuenca.com/login_tokens + response: + body: + string: '{"id":"LTqXrT9Z64S329W0D-uJKwmQ"}' + headers: + Connection: + - keep-alive + Content-Length: + - '33' + Content-Type: + - application/json + Date: + - Mon, 27 Jan 2025 23:08:56 GMT + X-Request-Time: + - 'value: 0.202' + x-amz-apigw-id: + - FEfBSG6ZCYcEPMg= + x-amzn-Remapped-Connection: + - keep-alive + x-amzn-Remapped-Content-Length: + - '33' + x-amzn-Remapped-Date: + - Mon, 27 Jan 2025 23:08:56 GMT + x-amzn-Remapped-Server: + - nginx/1.26.2 + x-amzn-RequestId: + - 7876a2df-cbdc-40f5-b2b8-c4dcfcdc2d23 + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Authorization: + - DUMMY + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - cuenca-python/2.0.1 + X-Cuenca-Api-Version: + - '2020-03-19' + X-Cuenca-LoginToken: + - LTqXrT9Z64S329W0D-uJKwmQ + method: POST + uri: https://sandbox.cuenca.com/token + response: + body: + string: '{"id":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Mzg2MjQxMzYsImlhdCI6MTczODAxOTMzNiwic3ViIjoiQUsyTDh6Y0x2YlRJR1ZsQXdndm9LX1dnIiwidWlkIjoiYWYxM2JmZDgtZGQwMy0xMWVmLThkN2MtMGE1OGE5ZmVhYzAyIn0.tpCxBTcKGWvuRUsKmo7a7IcofgrhVmxIIEiaDBRbAhY","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Mzg2MjQxMzYsImlhdCI6MTczODAxOTMzNiwic3ViIjoiQUsyTDh6Y0x2YlRJR1ZsQXdndm9LX1dnIiwidWlkIjoiYWYxM2JmZDgtZGQwMy0xMWVmLThkN2MtMGE1OGE5ZmVhYzAyIn0.tpCxBTcKGWvuRUsKmo7a7IcofgrhVmxIIEiaDBRbAhY","created_at":"2025-01-27T23:08:56.372428","api_key_uri":"/api_key/AK2L8zcLvbTIGVlAwgvoK_Wg"}' + headers: + Connection: + - keep-alive + Content-Length: + - '576' + Content-Type: + - application/json + Date: + - Mon, 27 Jan 2025 23:08:56 GMT + X-Request-Time: + - 'value: 0.104' + x-amz-apigw-id: + - FEfBWHrtiYcEAJA= + x-amzn-Remapped-Connection: + - keep-alive + x-amzn-Remapped-Content-Length: + - '576' + x-amzn-Remapped-Date: + - Mon, 27 Jan 2025 23:08:56 GMT + x-amzn-Remapped-Server: + - nginx/1.26.2 + x-amzn-RequestId: + - 79544974-46c4-4003-a548-48731d96f703 + status: + code: 201 + message: Created +version: 1 diff --git a/tests/resources/test_jwt_tokens.py b/tests/resources/test_jwt_tokens.py new file mode 100644 index 00000000..2c97eeaf --- /dev/null +++ b/tests/resources/test_jwt_tokens.py @@ -0,0 +1,28 @@ +import pytest + +from cuenca import JwtToken, LoginToken, UserLogin +from cuenca.http.client import Session + + +@pytest.fixture(scope='function') +def session(): + session = Session() + session.configure( + 'api_key', + 'api_secret', + sandbox=True, + ) + return session + + +@pytest.mark.vcr +def test_jwt_tokens(session): + UserLogin.create('111111', session=session) + login_token = LoginToken.create(session=session) + session.headers.pop( + 'X-Cuenca-LoginId', + ) + session.configure(login_token=login_token.id) + jwt_token = JwtToken.create(session=session) + assert jwt_token + assert isinstance(jwt_token.token, str) From 53eeab33283c6c883978d437acf610da1cdb2dd3 Mon Sep 17 00:00:00 2001 From: gabino Date: Mon, 27 Jan 2025 22:41:17 -0600 Subject: [PATCH 03/17] Bump cuenca-validations to version 2.0.5.dev1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3568110f..b0fd02e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests==2.32.3 -cuenca-validations==2.0.4 +cuenca-validations==2.0.5.dev1 pydantic-extra-types==2.10.2 From 6ae522ed670d8b3ce7c4d09eeb69359b65b6bd1a Mon Sep 17 00:00:00 2001 From: gabino Date: Mon, 27 Jan 2025 22:41:39 -0600 Subject: [PATCH 04/17] Bump version to 2.0.2.dev1 --- cuenca/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuenca/version.py b/cuenca/version.py index 68b04b6a..0f50b45a 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.0.1' +__version__ = '2.0.2.dev1' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' From b286db3a657e42065598500296a4309076e4cb1c Mon Sep 17 00:00:00 2001 From: gabino Date: Tue, 28 Jan 2025 08:56:45 -0600 Subject: [PATCH 05/17] Bump version to 2.0.2.dev2 --- cuenca/version.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cuenca/version.py b/cuenca/version.py index 0f50b45a..323de6d4 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.0.2.dev1' +__version__ = '2.0.2.dev2' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' diff --git a/requirements.txt b/requirements.txt index b0fd02e9..1f63ce19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests==2.32.3 -cuenca-validations==2.0.5.dev1 +cuenca-validations==2.0.5.dev3 pydantic-extra-types==2.10.2 From d351e5030fa74c5b6571d016c75bae5438f303ea Mon Sep 17 00:00:00 2001 From: gabino Date: Wed, 29 Jan 2025 16:16:14 -0600 Subject: [PATCH 06/17] Update cuenca-validations type annotations from Metadata to LogConfig --- cuenca/resources/api_keys.py | 8 ++++++-- cuenca/resources/jwt_tokens.py | 6 +++--- cuenca/resources/login_tokens.py | 4 ++-- cuenca/resources/otps.py | 4 ++-- cuenca/resources/sessions.py | 4 ++-- requirements.txt | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/cuenca/resources/api_keys.py b/cuenca/resources/api_keys.py index e4cce0f9..660b42f3 100644 --- a/cuenca/resources/api_keys.py +++ b/cuenca/resources/api_keys.py @@ -1,7 +1,11 @@ import datetime as dt from typing import Annotated, ClassVar, Optional -from cuenca_validations.types import ApiKeyQuery, ApiKeyUpdateRequest, Metadata +from cuenca_validations.types import ( + ApiKeyQuery, + ApiKeyUpdateRequest, + LogConfig, +) from pydantic import ConfigDict from ..http import Session, session as global_session @@ -12,7 +16,7 @@ class ApiKey(Creatable, Queryable, Retrievable, Updateable): _resource: ClassVar = 'api_keys' _query_params: ClassVar = ApiKeyQuery - secret: Annotated[str, Metadata(sensitive=True, log_chars=4)] + secret: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] deactivated_at: Optional[dt.datetime] = None user_id: Optional[str] = None model_config = ConfigDict( diff --git a/cuenca/resources/jwt_tokens.py b/cuenca/resources/jwt_tokens.py index 7435436f..0083af1d 100644 --- a/cuenca/resources/jwt_tokens.py +++ b/cuenca/resources/jwt_tokens.py @@ -1,7 +1,7 @@ import datetime as dt from typing import Annotated, ClassVar -from cuenca_validations.types import Metadata +from cuenca_validations.types import LogConfig from pydantic import ConfigDict from ..http import Session, session as global_session @@ -11,8 +11,8 @@ class JwtToken(Creatable): _resource: ClassVar = 'token' - id: Annotated[str, Metadata(sensitive=True, log_chars=4)] - token: Annotated[str, Metadata(sensitive=True, log_chars=4)] + id: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] + token: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] created_at: dt.datetime api_key_uri: str diff --git a/cuenca/resources/login_tokens.py b/cuenca/resources/login_tokens.py index 754d91fd..79ba20b1 100644 --- a/cuenca/resources/login_tokens.py +++ b/cuenca/resources/login_tokens.py @@ -1,6 +1,6 @@ from typing import Annotated, ClassVar -from cuenca_validations.types import Metadata +from cuenca_validations.types import LogConfig from pydantic import ConfigDict from ..http import Session, session as global_session @@ -10,7 +10,7 @@ class LoginToken(Creatable): _resource: ClassVar = 'login_tokens' - id: Annotated[str, Metadata(sensitive=True, log_chars=4)] + id: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] model_config = ConfigDict( json_schema_extra={'example': {'id': 'LTNEUInh69SuKXXmK95sROwQ'}} diff --git a/cuenca/resources/otps.py b/cuenca/resources/otps.py index 11e85f57..aad134aa 100644 --- a/cuenca/resources/otps.py +++ b/cuenca/resources/otps.py @@ -1,6 +1,6 @@ from typing import Annotated, ClassVar -from cuenca_validations.types import Metadata +from cuenca_validations.types import LogConfig from pydantic import ConfigDict from ..http import Session, session as global_session @@ -9,7 +9,7 @@ class Otp(Creatable): _resource: ClassVar = 'otps' - secret: Annotated[str, Metadata(sensitive=True, log_chars=4)] + secret: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] model_config = ConfigDict( json_schema_extra={ diff --git a/cuenca/resources/sessions.py b/cuenca/resources/sessions.py index d4773427..ba51be64 100644 --- a/cuenca/resources/sessions.py +++ b/cuenca/resources/sessions.py @@ -1,7 +1,7 @@ import datetime as dt from typing import Annotated, ClassVar, Optional -from cuenca_validations.types import Metadata, SessionRequest, SessionType +from cuenca_validations.types import LogConfig, SessionRequest, SessionType from cuenca_validations.types.general import SerializableAnyUrl from pydantic import ConfigDict @@ -12,7 +12,7 @@ class Session(Creatable, Retrievable, Queryable): _resource: ClassVar = 'sessions' - id: Annotated[str, Metadata(sensitive=True, log_chars=4)] + id: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] created_at: dt.datetime user_id: str platform_id: str diff --git a/requirements.txt b/requirements.txt index 1f63ce19..55a20559 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests==2.32.3 -cuenca-validations==2.0.5.dev3 +cuenca-validations==2.0.5.dev5 pydantic-extra-types==2.10.2 From 77c7a5b06014018e8c110e3eb372d8890d505dd1 Mon Sep 17 00:00:00 2001 From: gabino Date: Wed, 29 Jan 2025 16:16:24 -0600 Subject: [PATCH 07/17] Bump version to 2.0.2.dev3 --- cuenca/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuenca/version.py b/cuenca/version.py index 323de6d4..b23b7631 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.0.2.dev2' +__version__ = '2.0.2.dev3' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' From 58bab3167fc76aaa1631f995d111c6dea50d6d5c Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 30 Jan 2025 11:04:58 -0600 Subject: [PATCH 08/17] Release version 2.0.2 --- cuenca/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuenca/version.py b/cuenca/version.py index b23b7631..43d494e3 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.0.2.dev3' +__version__ = '2.0.2' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' From 1b24464c3cd5bdd6ea538df3610e1378969fbe3a Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 30 Jan 2025 16:02:32 -0600 Subject: [PATCH 09/17] Update JwtToken example with redacted JWT tokens --- cuenca/resources/jwt_tokens.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/cuenca/resources/jwt_tokens.py b/cuenca/resources/jwt_tokens.py index 0083af1d..e8cf4625 100644 --- a/cuenca/resources/jwt_tokens.py +++ b/cuenca/resources/jwt_tokens.py @@ -19,20 +19,8 @@ class JwtToken(Creatable): model_config = ConfigDict( json_schema_extra={ 'example': { - 'id': ( - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzgzNjI' - '4NzcsImlhdCI6MTczNzc1ODA3Nywic3ViIjoiQUtzY3p5N3RzaVJkMkl' - 'iakxfbllGb2xRIiwidWlkIjoiNjRiZmQ0OTItZGFhMy0xMWVmLWEyMWU' - 'tMGE1OGE5ZmVhYzAyIn0.Er8kDsw4rtGkwAXpEgUhwyXFiBjYlwDVTGF' - 'tYW7o0go' - ), - 'token': ( - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzgzNjI' - '4NzcsImlhdCI6MTczNzc1ODA3Nywic3ViIjoiQUtzY3p5N3RzaVJkMkl' - 'iakxfbllGb2xRIiwidWlkIjoiNjRiZmQ0OTItZGFhMy0xMWVmLWEyMWU' - 'tMGE1OGE5ZmVhYzAyIn0.Er8kDsw4rtGkwAXpEgUhwyXFiBjYlwDVTGF' - 'tYW7o0go' - ), + 'id': 'jwt_XXXX...redacted', + 'token': 'jwt_XXXX...redacted', 'created_at': '2025-01-24T22:34:37.659667', 'api_key_uri': '/api_key/AKsczy7tsiRd2IbjL_nYFolQ', } From c508ed0c53a0f394c3e73d690793ba71af48e4aa Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 30 Jan 2025 16:22:40 -0600 Subject: [PATCH 10/17] Remove unmasked characters length from OTP secret logging --- cuenca/resources/otps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuenca/resources/otps.py b/cuenca/resources/otps.py index aad134aa..6b65bc40 100644 --- a/cuenca/resources/otps.py +++ b/cuenca/resources/otps.py @@ -9,7 +9,7 @@ class Otp(Creatable): _resource: ClassVar = 'otps' - secret: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] + secret: Annotated[str, LogConfig(masked=True)] model_config = ConfigDict( json_schema_extra={ From 1a1d12e4a9b45bf3b0386f5143fc20380e890772 Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 30 Jan 2025 16:22:59 -0600 Subject: [PATCH 11/17] Simplify JwtToken test by removing session configuration --- tests/resources/test_jwt_tokens.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/tests/resources/test_jwt_tokens.py b/tests/resources/test_jwt_tokens.py index 2c97eeaf..7ade20e0 100644 --- a/tests/resources/test_jwt_tokens.py +++ b/tests/resources/test_jwt_tokens.py @@ -1,28 +1,10 @@ import pytest -from cuenca import JwtToken, LoginToken, UserLogin -from cuenca.http.client import Session - - -@pytest.fixture(scope='function') -def session(): - session = Session() - session.configure( - 'api_key', - 'api_secret', - sandbox=True, - ) - return session +from cuenca import JwtToken @pytest.mark.vcr -def test_jwt_tokens(session): - UserLogin.create('111111', session=session) - login_token = LoginToken.create(session=session) - session.headers.pop( - 'X-Cuenca-LoginId', - ) - session.configure(login_token=login_token.id) - jwt_token = JwtToken.create(session=session) +def test_jwt_tokens(): + jwt_token = JwtToken.create() assert jwt_token assert isinstance(jwt_token.token, str) From e01cab2d6d4a0f9d0cf8f75505bb84a77dd49463 Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 30 Jan 2025 16:23:14 -0600 Subject: [PATCH 12/17] Add masked logging configuration for UserLogin ID --- cuenca/resources/user_logins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cuenca/resources/user_logins.py b/cuenca/resources/user_logins.py index bfe22745..b23768f1 100644 --- a/cuenca/resources/user_logins.py +++ b/cuenca/resources/user_logins.py @@ -1,6 +1,7 @@ import datetime as dt -from typing import ClassVar, Optional +from typing import Annotated, ClassVar, Optional +from cuenca_validations.types import LogConfig from cuenca_validations.types.requests import UserLoginRequest from pydantic import ConfigDict @@ -11,6 +12,8 @@ class UserLogin(Creatable): _resource: ClassVar = 'user_logins' + id: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] + last_login_at: Optional[dt.datetime] = None success: bool From abfc35baae3fe669438db1618a9e02acc43007a7 Mon Sep 17 00:00:00 2001 From: gabino Date: Fri, 31 Jan 2025 10:32:14 -0600 Subject: [PATCH 13/17] Add masked logging configuration for card number --- cuenca/resources/cards.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cuenca/resources/cards.py b/cuenca/resources/cards.py index 5cd24564..bf706578 100644 --- a/cuenca/resources/cards.py +++ b/cuenca/resources/cards.py @@ -1,5 +1,5 @@ import datetime as dt -from typing import ClassVar, Optional +from typing import Annotated, ClassVar, Optional from cuenca_validations.types import ( CardFundingType, @@ -7,6 +7,7 @@ CardStatus, CardType, ) +from cuenca_validations.types.general import LogConfig from cuenca_validations.types.queries import CardQuery from cuenca_validations.types.requests import CardRequest, CardUpdateRequest @@ -22,7 +23,7 @@ class Card(Retrievable, Queryable, Creatable, Updateable): _query_params: ClassVar = CardQuery user_id: Optional[str] = None - number: str + number: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] exp_month: int exp_year: int cvv2: str From 47bd1512ab698eb9e46f52efe3ab3f8c32904423 Mon Sep 17 00:00:00 2001 From: gabino Date: Tue, 4 Feb 2025 09:33:56 -0600 Subject: [PATCH 14/17] Bump version to 2.0.2.dev3 --- cuenca/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuenca/version.py b/cuenca/version.py index 43d494e3..b23b7631 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.0.2' +__version__ = '2.0.2.dev3' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' From 8e04b61a334ae1088725b4a66553b3fbda38b610 Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 6 Feb 2025 11:26:23 -0600 Subject: [PATCH 15/17] Update cuenca-validations to version 2.1.0 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 55a20559..190cc68d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests==2.32.3 -cuenca-validations==2.0.5.dev5 +cuenca-validations==2.1.0 pydantic-extra-types==2.10.2 diff --git a/setup.py b/setup.py index 2d847811..4ba3904e 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ python_requires='>=3.9', install_requires=[ 'requests>=2.32.0', - 'cuenca-validations>=2.0.4', + 'cuenca-validations>=2.1.0', 'pydantic-extra-types>=2.10.0', ], classifiers=[ From 722428a1ffa609222aa65ed980470f1ab6c4d0fe Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 6 Feb 2025 11:26:43 -0600 Subject: [PATCH 16/17] Change unmasked characters length from ApiKey secret logging --- cuenca/resources/api_keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuenca/resources/api_keys.py b/cuenca/resources/api_keys.py index 660b42f3..de62e7ad 100644 --- a/cuenca/resources/api_keys.py +++ b/cuenca/resources/api_keys.py @@ -16,7 +16,7 @@ class ApiKey(Creatable, Queryable, Retrievable, Updateable): _resource: ClassVar = 'api_keys' _query_params: ClassVar = ApiKeyQuery - secret: Annotated[str, LogConfig(masked=True, unmasked_chars_length=4)] + secret: Annotated[str, LogConfig(masked=True)] deactivated_at: Optional[dt.datetime] = None user_id: Optional[str] = None model_config = ConfigDict( From 79d9af78d073007c3d7be72aedbac002edb64fd2 Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 6 Feb 2025 11:27:18 -0600 Subject: [PATCH 17/17] Release version 2.1.0 --- cuenca/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuenca/version.py b/cuenca/version.py index b23b7631..04511fde 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '2.0.2.dev3' +__version__ = '2.1.0' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19'