From c4509f507282d0d58771d051bbdd5c9d43b0fd19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Fri, 24 Oct 2025 20:05:56 -0300 Subject: [PATCH] add scope to id --- api.saladeaula.digital/app/app.py | 9 +++- .../app/routes/users/__init__.py | 3 ++ .../app/routes/users/emails.py | 3 ++ .../app/routes/users/orgs.py | 3 ++ .../tests/routes/test_users.py | 20 ++++++++ api.saladeaula.digital/tests/seeds.jsonl | 3 ++ .../events/stopgap/patch_course_metadata.py | 8 ---- http-api/cli/jsonl2sqlite.py | 23 ++++++++-- http-api/pyproject.toml | 1 + http-api/uv.lock | 23 ++++++++++ id.saladeaula.digital/app/config.py | 11 ++--- .../apigateway_oauth2/resource_protector.py | 17 ++++++- .../integrations/apigateway_oauth2/tokens.py | 3 +- id.saladeaula.digital/app/oauth2.py | 41 +++++++++++++---- id.saladeaula.digital/app/routes/userinfo.py | 4 +- .../tests/routes/test_authorize.py | 3 +- id.saladeaula.digital/tests/seeds.jsonl | 10 ++-- streams-events/uv.lock | 46 ++++++++++++------- 18 files changed, 173 insertions(+), 58 deletions(-) diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index 07493b0..defc7f5 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -3,7 +3,7 @@ import os from functools import partial from typing import Any -from aws_lambda_powertools import Logger +from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.event_handler.api_gateway import ( APIGatewayHttpResolver, CORSConfig, @@ -17,6 +17,7 @@ from json_encoder import JSONEncoder from routes import courses, enrollments, orders, users logger = Logger(__name__) +tracer = Tracer() debug = 'AWS_SAM_LOCAL' in os.environ serializer = partial(json.dumps, separators=(',', ':'), cls=JSONEncoder) cors = CORSConfig( @@ -26,7 +27,10 @@ cors = CORSConfig( allow_credentials=False, ) app = APIGatewayHttpResolver( - enable_validation=True, cors=cors, debug=debug, serializer=serializer + enable_validation=True, + cors=cors, + debug=debug, + serializer=serializer, ) app.include_router(courses.router, prefix='/courses') app.include_router(enrollments.router, prefix='/enrollments') @@ -52,5 +56,6 @@ def exc_error(exc: ServiceError): @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP) +@tracer.capture_lambda_handler def lambda_handler(event: dict[str, Any], context: LambdaContext) -> dict[str, Any]: return app.resolve(event, context) diff --git a/api.saladeaula.digital/app/routes/users/__init__.py b/api.saladeaula.digital/app/routes/users/__init__.py index 4ce0fc9..57b320e 100644 --- a/api.saladeaula.digital/app/routes/users/__init__.py +++ b/api.saladeaula.digital/app/routes/users/__init__.py @@ -1,3 +1,5 @@ +import time + from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.exceptions import ( @@ -20,6 +22,7 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) @router.get('/') def get_user(user_id: str): + time.sleep(2) return dyn.collection.get_item( KeyPair(user_id, '0'), exc_cls=NotFoundError, diff --git a/api.saladeaula.digital/app/routes/users/emails.py b/api.saladeaula.digital/app/routes/users/emails.py index 81cec01..c453930 100644 --- a/api.saladeaula.digital/app/routes/users/emails.py +++ b/api.saladeaula.digital/app/routes/users/emails.py @@ -1,3 +1,5 @@ +import time + from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler.api_gateway import Router from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair @@ -12,6 +14,7 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) @router.get('//emails') def get_emails(user_id: str): + time.sleep(1) start_key = router.current_event.get_query_string_value('start_key', None) return dyn.collection.query( diff --git a/api.saladeaula.digital/app/routes/users/orgs.py b/api.saladeaula.digital/app/routes/users/orgs.py index 013c12d..199f321 100644 --- a/api.saladeaula.digital/app/routes/users/orgs.py +++ b/api.saladeaula.digital/app/routes/users/orgs.py @@ -1,3 +1,5 @@ +import time + from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler.api_gateway import Router from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair @@ -12,6 +14,7 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) @router.get('//orgs') def get_orgs(user_id: str): + time.sleep(1) start_key = router.current_event.get_query_string_value('start_key', None) return dyn.collection.query( diff --git a/api.saladeaula.digital/tests/routes/test_users.py b/api.saladeaula.digital/tests/routes/test_users.py index 7f5aeec..7c42ab8 100644 --- a/api.saladeaula.digital/tests/routes/test_users.py +++ b/api.saladeaula.digital/tests/routes/test_users.py @@ -1,8 +1,28 @@ +import json from http import HTTPMethod, HTTPStatus from ..conftest import HttpApiProxy, LambdaContext +def test_get_emails( + app, + seeds, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails', + method=HTTPMethod.GET, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.OK + + body = json.loads(r['body']) + assert len(body['items']) == 1 + + def test_get_orgs( app, seeds, diff --git a/api.saladeaula.digital/tests/seeds.jsonl b/api.saladeaula.digital/tests/seeds.jsonl index 18c7184..59a280c 100644 --- a/api.saladeaula.digital/tests/seeds.jsonl +++ b/api.saladeaula.digital/tests/seeds.jsonl @@ -1,4 +1,7 @@ {"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest" } +// User emails +{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "emails#sergio@somosbeta.com.br"} + // User orgs {"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#286f7729-7765-482a-880a-0b153ea799be", "name": "ACME", "cnpj": "00000000000191"} \ No newline at end of file diff --git a/enrollments-events/app/events/stopgap/patch_course_metadata.py b/enrollments-events/app/events/stopgap/patch_course_metadata.py index 9aae6b6..abf6494 100644 --- a/enrollments-events/app/events/stopgap/patch_course_metadata.py +++ b/enrollments-events/app/events/stopgap/patch_course_metadata.py @@ -39,14 +39,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ':access_expires_at': access_expires_at, }, ) - transact.put( - item={ - 'id': new_image['id'], - 'sk': 'METADATA#DEDUPLICATION_WINDOW', - 'offset_days': 90, - 'created_at': now_, - } - ) except Exception as exc: logger.exception(exc) return False diff --git a/http-api/cli/jsonl2sqlite.py b/http-api/cli/jsonl2sqlite.py index 756d269..7eb1096 100644 --- a/http-api/cli/jsonl2sqlite.py +++ b/http-api/cli/jsonl2sqlite.py @@ -7,6 +7,7 @@ from typing import Generator import jsonlines from aws_lambda_powertools.shared.json_encoder import Encoder from layercake.dynamodb import deserialize +from layercake.strutils import md5_hash from tqdm import tqdm @@ -42,7 +43,12 @@ if __name__ == '__main__': with sqlite3.connect('mydatabase.db') as conn: cursor = conn.cursor() cursor.execute( - 'CREATE TABLE IF NOT EXISTS %s (pk TEXT, sk TEXT, json JSON)' + """ + CREATE TABLE IF NOT EXISTS %s ( + _id TEXT PRIMARY KEY, + json JSON NOT NULL + ) + """ % table_name ) @@ -50,12 +56,19 @@ if __name__ == '__main__': readlines(input_dirpath), desc=f'⏳ Inserting into table {table_name}', ): + _id = md5_hash( + str( + { + 'id': record['id'], + 'sk': record['sk'], + } + ) + ) + cursor.execute( - 'INSERT INTO %s (pk, sk, json) VALUES (:pk, :sk, :json)' - % table_name, + 'INSERT INTO %s (_id, json) VALUES (:_id, :json)' % table_name, { - 'pk': record['id'], - 'sk': record['sk'], + '_id': _id, 'json': record, }, ) diff --git a/http-api/pyproject.toml b/http-api/pyproject.toml index 927d791..e0b005e 100644 --- a/http-api/pyproject.toml +++ b/http-api/pyproject.toml @@ -14,6 +14,7 @@ dev = [ "pycouchdb>=1.16.0", "pytest>=8.3.4", "pytest-cov>=6.0.0", + "python-slugify>=8.0.4", "ruff>=0.9.1", "sqlite-utils>=3.38", "tqdm>=4.67.1", diff --git a/http-api/uv.lock b/http-api/uv.lock index 3bbb32c..633bd61 100644 --- a/http-api/uv.lock +++ b/http-api/uv.lock @@ -435,6 +435,7 @@ dev = [ { name = "pycouchdb" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "python-slugify" }, { name = "ruff" }, { name = "sqlite-utils" }, { name = "tqdm" }, @@ -451,6 +452,7 @@ dev = [ { name = "pycouchdb", specifier = ">=1.16.0" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-cov", specifier = ">=6.0.0" }, + { name = "python-slugify", specifier = ">=8.0.4" }, { name = "ruff", specifier = ">=0.9.1" }, { name = "sqlite-utils", specifier = ">=3.38" }, { name = "tqdm", specifier = ">=4.67.1" }, @@ -937,6 +939,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + [[package]] name = "pytz" version = "2025.2" @@ -1059,6 +1073,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + [[package]] name = "tqdm" version = "4.67.1" diff --git a/id.saladeaula.digital/app/config.py b/id.saladeaula.digital/app/config.py index 8c7f2c6..856a4ea 100644 --- a/id.saladeaula.digital/app/config.py +++ b/id.saladeaula.digital/app/config.py @@ -9,14 +9,9 @@ OAUTH2_SCOPES_SUPPORTED: list[str] = [ 'profile', 'email', 'offline_access', - 'read:users', - 'write:users', - 'read:enrollments', - 'write:enrollments', - 'read:orders', - 'write:orders', - 'read:courses', - 'write:courses', + 'apps:admin', + 'apps:studio', + 'apps:insights', ] SESSION_EXPIRES_IN = 86400 * 30 # 30 days diff --git a/id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py b/id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py index e668ab5..b04f01c 100644 --- a/id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py +++ b/id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py @@ -1,4 +1,19 @@ from authlib.oauth2 import ResourceProtector as _ResourceProtector +from aws_lambda_powertools.event_handler import APIGatewayHttpResolver +from aws_lambda_powertools.event_handler.middlewares import NextMiddleware + +from .requests import APIGatewayJsonRequest -class ResourceProtector(_ResourceProtector): ... +class ResourceProtector(_ResourceProtector): + def __call__(self, scopes=None, optional=False, **kwargs): + claims = kwargs + # backward compatibility + claims['scopes'] = scopes + + def wrapper(app: APIGatewayHttpResolver, next_middleware: NextMiddleware): + request = APIGatewayJsonRequest(app.current_event) + print(request) + return next_middleware(app) + + return wrapper diff --git a/id.saladeaula.digital/app/integrations/apigateway_oauth2/tokens.py b/id.saladeaula.digital/app/integrations/apigateway_oauth2/tokens.py index a6e5e63..3bf42a5 100644 --- a/id.saladeaula.digital/app/integrations/apigateway_oauth2/tokens.py +++ b/id.saladeaula.digital/app/integrations/apigateway_oauth2/tokens.py @@ -1,5 +1,5 @@ import time -from dataclasses import dataclass +from dataclasses import dataclass, field from authlib.oauth2.rfc6749 import ( AuthorizationCodeMixin, @@ -15,6 +15,7 @@ class User: name: str email: str email_verified: bool = False + scope: list[str] | None = None def get_user_id(self): return self.id diff --git a/id.saladeaula.digital/app/oauth2.py b/id.saladeaula.digital/app/oauth2.py index 9919c42..c052a4c 100644 --- a/id.saladeaula.digital/app/oauth2.py +++ b/id.saladeaula.digital/app/oauth2.py @@ -1,3 +1,5 @@ +from os import rename + from authlib.common.security import generate_token from authlib.common.urls import add_params_to_uri from authlib.jose import JsonWebKey @@ -26,6 +28,7 @@ from config import ISSUER, OAUTH2_SCOPES_SUPPORTED, OAUTH2_TABLE from integrations.apigateway_oauth2.authorization_server import ( AuthorizationServer, ) +from integrations.apigateway_oauth2.resource_protector import ResourceProtector from integrations.apigateway_oauth2.tokens import ( OAuth2AuthorizationCode, OAuth2Token, @@ -65,15 +68,18 @@ class OpenIDCode(OpenIDCode_): } def generate_user_info(self, user: User, scope: str) -> UserInfo: - print(scope) - print('--' * 100) - return UserInfo( + user_info = UserInfo( sub=user.id, name=user.name, email=user.email, email_verified=user.email_verified, ).filter(scope) + if user.scope: + user_info['scope'] = ' '.join(user.scope) + + return user_info + class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): TOKEN_ENDPOINT_AUTH_METHODS = [ @@ -166,20 +172,22 @@ class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): authorization_code: OAuth2AuthorizationCode, ) -> User: """Authenticate the user related to this authorization_code.""" - user = dyn.get_item( - KeyPair( - pk=authorization_code.user_id, - sk='0', - ), + user = dyn.collection.get_items( + TransactKey(authorization_code.user_id) + + SortKey('0') + + SortKey('SCOPE', path_spec='scope', rename_key='scope'), ) - return User(**pick(('id', 'name', 'email', 'email_verified'), user)) + return User(**pick(('id', 'name', 'email', 'email_verified', 'scope'), user)) class TokenExchangeGrant(grants.BaseGrant): GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange' - TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post'] + TOKEN_ENDPOINT_AUTH_METHODS = [ + 'client_secret_basic', + 'client_secret_post', + ] @hooked def validate_token_request(self): @@ -288,6 +296,17 @@ class RevocationEndpoint(rfc7009.RevocationEndpoint): token: OAuth2Token, request: OAuth2Request, ): + """ + Mark token as revoked. Since token MUST be unique, it would be dangerous + to delete it. Consider this situation: + + - Jane obtained a token XYZ + - Jane revoked (deleted) token XYZ + - Bob generated a new token XYZ + - Jane can use XYZ to access Bob’s resource + + - https://docs.authlib.org/en/latest/specs/rfc7009.html#authlib.oauth2.rfc7009.RevocationEndpoint.revoke_token + """ user_id = token.user['id'] r = dyn.collection.query(KeyPair(pk=user_id, sk='SESSION')) @@ -377,3 +396,5 @@ server.register_grant(TokenExchangeGrant) server.register_grant(RefreshTokenGrant) server.register_endpoint(RevocationEndpoint) server.register_extension(IssuerParameter()) + +require_oauth = ResourceProtector() diff --git a/id.saladeaula.digital/app/routes/userinfo.py b/id.saladeaula.digital/app/routes/userinfo.py index 97ff9ca..10e64e2 100644 --- a/id.saladeaula.digital/app/routes/userinfo.py +++ b/id.saladeaula.digital/app/routes/userinfo.py @@ -1,8 +1,10 @@ from aws_lambda_powertools.event_handler.api_gateway import Router +from oauth2 import require_oauth + router = Router() -@router.get('/userinfo') +@router.get('/userinfo', middlewares=[require_oauth('profile')]) def userinfo(): return {'name': 'test'} diff --git a/id.saladeaula.digital/tests/routes/test_authorize.py b/id.saladeaula.digital/tests/routes/test_authorize.py index 286118b..c653457 100644 --- a/id.saladeaula.digital/tests/routes/test_authorize.py +++ b/id.saladeaula.digital/tests/routes/test_authorize.py @@ -17,6 +17,7 @@ def test_authorize( lambda_context: LambdaContext, ): session = new_session(USER_ID) + print(session) r = app.lambda_handler( http_api_proxy( @@ -26,7 +27,7 @@ def test_authorize( 'response_type': 'code', 'client_id': 'd72d4005-1fa7-4430-9754-80d5e2487bb6', 'redirect_uri': 'https://localhost/callback', - 'scope': 'openid offline_access read:users', + 'scope': 'openid profile email offline_access apps:admin', 'nonce': '123', 'state': '456', }, diff --git a/id.saladeaula.digital/tests/seeds.jsonl b/id.saladeaula.digital/tests/seeds.jsonl index 63b954d..75cb066 100644 --- a/id.saladeaula.digital/tests/seeds.jsonl +++ b/id.saladeaula.digital/tests/seeds.jsonl @@ -1,9 +1,9 @@ // OAuth2 -{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access read:users", "token_endpoint_auth_method": "client_secret_basic"} -{"id": "OAUTH2", "sk": "CLIENT_ID#8c5e92b0-9ed4-451e-8935-66084d8544b1", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access read:users", "token_endpoint_auth_method": "none"} +{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "client_secret_basic"} +{"id": "OAUTH2", "sk": "CLIENT_ID#8c5e92b0-9ed4-451e-8935-66084d8544b1", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "none"} {"id": "OAUTH2", "sk": "CLIENT_ID#6ebe1709-0831-455c-84c0-d4c753bf33c6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 2", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access", "token_endpoint_auth_method": "none"} -{"id": "OAUTH2", "sk": "CLIENT_ID#1db63660-063d-4280-b2ea-388aca4a9459", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 3", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access read:users", "token_endpoint_auth_method": "client_secret_basic"} -{"id": "OAUTH2#CODE", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "redirect_uri": "https://localhost/callback", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419", "nonce": null, "scope": "openid profile email read:users", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"} +{"id": "OAUTH2", "sk": "CLIENT_ID#1db63660-063d-4280-b2ea-388aca4a9459", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 3", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "client_secret_basic"} +{"id": "OAUTH2#CODE", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "redirect_uri": "https://localhost/callback", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419", "nonce": null, "scope": "openid profile email apps:admins", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"} {"id": "OAUTH2#TOKEN", "sk": "REFRESH_TOKEN#CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "token_type": "Bearer", "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IlRjT0VuV3JGSUFEYlZJNjJlY1pzU28ydEI1eW5mbkZZNTZ0Uy05b0stNW8ifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiZXhwIjoxNzU5NTg2NzgzLCJjbGllbnRfaWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYiLCJpYXQiOjE3NTg5ODE5ODMsImp0aSI6Ik9uVzRIZm1FdFl2a21CbE4iLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIHJlYWQ6dXNlcnMiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJhdWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYifQ.i0NVgvPuf5jvl8JcYNsVCzjVUTDLihgQO4LmLeNijx9Ed3p_EgtVtcHFWFvEebe_LwTuDDtIJveH22Piyp4zresNSc_YNumnuvoY1aNd0ic2RIEtXaklRroq0xHwL_IVT-Dt6P9xL5Hyygx47Pvmci4U3wWK32a6Sb1Mm7ZZgXA00xWI1bJ_zwxFLvDkHDp9nrAa_vEWN6zRBcWc7JYNsgiaPMC0DoL8it0k48_g44zfsjGAZLcWFMoPlYt3wIcQQDeCKMsSJI0VPnqKK0pq4OOVs-pjkMyAU5aEMPvVOwdAL3VZY16RXt3eTzsmMH1XoRdCMP6UAx4ZS10RLGUPeA", "scope": "openid profile email read:users", "user": {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "name": "S\u00e9rgio R Siqueira", "email": "sergio@somosbeta.com.br", "email_verified": false}, "expires_in": 180, "issued_at": 1758981984, "ttl": 1759586784} @@ -16,7 +16,7 @@ // User data {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br"} {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"} -{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SCOPE", "scope": ["openid", "profile", "email", "offline_access", "read:users", "read:courses", "impersonate:users"]} +{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SCOPE", "scope": ["openid", "profile", "email", "offline_access", "apps:admin"]} {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SESSION#36af142e-9f6d-49d3-bfe9-6a6bd6ab2712", "created_at": "2025-09-17T13:44:34.544491-03:00", "ttl": 1760719474} {"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "0", "name": "Johnny Cash", "email": "johnny@johnnycash.com"} diff --git a/streams-events/uv.lock b/streams-events/uv.lock index 76b1b72..f6ca92f 100644 --- a/streams-events/uv.lock +++ b/streams-events/uv.lock @@ -31,14 +31,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.2" +version = "1.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/95/e4f4ab5ce465821fe2229e10985ab80462941fe5d96387ae76bafd36f0ba/authlib-1.6.2.tar.gz", hash = "sha256:3bde83ac0392683eeef589cd5ab97e63cbe859e552dd75dca010548e79202cb1", size = 160429, upload-time = "2025-08-23T08:34:32.665Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/00/fb65909bf4c8d7da893a12006074343402a8dc8c00d916b3cee524d97f3f/authlib-1.6.2-py2.py3-none-any.whl", hash = "sha256:2dd5571013cacf6b15f7addce03ed057ffdf629e9e81bacd9c08455a190e9b57", size = 239601, upload-time = "2025-08-23T08:34:31.4Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" }, ] [[package]] @@ -470,6 +470,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] +[[package]] +name = "joserfc" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/a0/4b8dfecc8ec3c15aa1f2ff7d5b947344378b5b595ce37c8a8fe6e25c1400/joserfc-1.4.0.tar.gz", hash = "sha256:e8c2f327bf10a937d284d57e9f8aec385381e5e5850469b50a7dade1aba59759", size = 196339, upload-time = "2025-10-09T07:47:00.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/05/342459b7629c6fcb5f99a646886ee2904491955b8cce6b26b0b9a498f67c/joserfc-1.4.0-py3-none-any.whl", hash = "sha256:46917e6b53f1ec0c7e20d34d6f3e6c27da0fa43d0d4ebfb89aada7c86582933a", size = 66390, upload-time = "2025-10-09T07:46:59.591Z" }, +] + [[package]] name = "jsonpath-ng" version = "1.7.0" @@ -484,7 +496,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.9.14" +version = "0.11.0" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, @@ -493,6 +505,7 @@ dependencies = [ { name = "dictdiffer" }, { name = "ftfy" }, { name = "glom" }, + { name = "joserfc" }, { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, @@ -500,7 +513,7 @@ dependencies = [ { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-extra-types" }, - { name = "pyjwt" }, + { name = "python-multipart" }, { name = "pytz" }, { name = "requests" }, { name = "smart-open", extra = ["s3"] }, @@ -511,11 +524,12 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "arnparse", specifier = ">=0.0.2" }, - { name = "authlib", specifier = ">=1.6.1" }, + { name = "authlib", specifier = ">=1.6.5" }, { name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" }, { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "ftfy", specifier = ">=6.3.1" }, { name = "glom", specifier = ">=24.11.0" }, + { name = "joserfc", specifier = ">=1.2.2" }, { name = "meilisearch", specifier = ">=0.34.0" }, { name = "orjson", specifier = ">=3.10.15" }, { name = "passlib", specifier = ">=1.7.4" }, @@ -523,7 +537,7 @@ requires-dist = [ { name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" }, - { name = "pyjwt", specifier = ">=2.10.1" }, + { name = "python-multipart", specifier = ">=0.0.20" }, { name = "pytz", specifier = ">=2025.1" }, { name = "requests", specifier = ">=2.32.3" }, { name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" }, @@ -802,15 +816,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" }, ] -[[package]] -name = "pyjwt" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, -] - [[package]] name = "pytest" version = "8.3.4" @@ -860,6 +865,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, ] +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + [[package]] name = "pytz" version = "2025.2"