From c9438d49fb0297b95d0368712bc0d16c65503b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Fri, 10 Oct 2025 12:52:41 -0300 Subject: [PATCH] update session --- enrollments-events/app/events/issue_cert.py | 2 +- id.saladeaula.digital/app/config.py | 16 ++++- .../apigateway_oauth2/authorization_server.py | 10 +-- .../apigateway_oauth2/requests.py | 2 +- id.saladeaula.digital/app/oauth2.py | 25 +++++++- id.saladeaula.digital/app/routes/authorize.py | 64 +++++++------------ id.saladeaula.digital/app/routes/session.py | 24 ++----- .../client/app/routes/authorize.ts | 2 +- id.saladeaula.digital/template.yaml | 4 +- id.saladeaula.digital/tests/conftest.py | 3 - .../tests/routes/test_authorize.py | 14 ++-- id.saladeaula.digital/tests/seeds.jsonl | 4 +- id.saladeaula.digital/uv.lock | 46 ++++++++----- layercake/pyproject.toml | 4 +- layercake/uv.lock | 8 +-- 15 files changed, 116 insertions(+), 112 deletions(-) diff --git a/enrollments-events/app/events/issue_cert.py b/enrollments-events/app/events/issue_cert.py index 87724b7..bf8c871 100644 --- a/enrollments-events/app/events/issue_cert.py +++ b/enrollments-events/app/events/issue_cert.py @@ -106,7 +106,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: pk=enrollment_id, sk='0', ), - update_expr='SET issued_cert = :issued_cert, uploaded_at = :now', + update_expr='SET issued_cert = :issued_cert, updated_at = :now', expr_attr_values={ ':now': now_, ':issued_cert': { diff --git a/id.saladeaula.digital/app/config.py b/id.saladeaula.digital/app/config.py index bc56940..8c7f2c6 100644 --- a/id.saladeaula.digital/app/config.py +++ b/id.saladeaula.digital/app/config.py @@ -3,8 +3,20 @@ import os ISSUER: str = os.getenv('ISSUER') # type: ignore OAUTH2_TABLE: str = os.getenv('OAUTH2_TABLE') # type: ignore -OAUTH2_SCOPES_SUPPORTED: str = os.getenv('OAUTH2_SCOPES_SUPPORTED', '') OAUTH2_REFRESH_TOKEN_EXPIRES_IN = 86_400 * 7 # 7 days +OAUTH2_SCOPES_SUPPORTED: list[str] = [ + 'openid', + 'profile', + 'email', + 'offline_access', + 'read:users', + 'write:users', + 'read:enrollments', + 'write:enrollments', + 'read:orders', + 'write:orders', + 'read:courses', + 'write:courses', +] -SESSION_SECRET: str = os.environ.get('SESSION_SECRET') # type: ignore SESSION_EXPIRES_IN = 86400 * 30 # 30 days diff --git a/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py b/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py index 08ea4e7..faebe89 100644 --- a/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py +++ b/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py @@ -1,4 +1,3 @@ -import os from dataclasses import asdict import authlib.oauth2 as oauth2 @@ -17,24 +16,19 @@ from config import OAUTH2_REFRESH_TOKEN_EXPIRES_IN from .client import OAuth2Client from .requests import APIGatewayJsonRequest, APIGatewayOAuth2Request -OAUTH2_SCOPES_SUPPORTED = os.getenv('OAUTH2_SCOPES_SUPPORTED') - logger = Logger(__name__) class AuthorizationServer(oauth2.AuthorizationServer): def __init__( self, + scopes_supported: list[str], *, persistence_layer: DynamoDBPersistenceLayer, ) -> None: + super().__init__(scopes_supported=scopes_supported) self._persistence_layer = persistence_layer - if OAUTH2_SCOPES_SUPPORTED: - super().__init__( - scopes_supported=set(OAUTH2_SCOPES_SUPPORTED.split()), - ) - def save_token( self, token: dict, diff --git a/id.saladeaula.digital/app/integrations/apigateway_oauth2/requests.py b/id.saladeaula.digital/app/integrations/apigateway_oauth2/requests.py index 51d5a5f..5ca0c27 100644 --- a/id.saladeaula.digital/app/integrations/apigateway_oauth2/requests.py +++ b/id.saladeaula.digital/app/integrations/apigateway_oauth2/requests.py @@ -49,7 +49,7 @@ class APIGatewayOAuth2Request(requests.OAuth2Request): super().__init__( request.request_context.http.method, uri, - request.headers, + headers=request.headers, ) self._request = request self.payload = APIGatewayOAuth2Payload(request) diff --git a/id.saladeaula.digital/app/oauth2.py b/id.saladeaula.digital/app/oauth2.py index a425593..9919c42 100644 --- a/id.saladeaula.digital/app/oauth2.py +++ b/id.saladeaula.digital/app/oauth2.py @@ -3,6 +3,7 @@ from authlib.common.urls import add_params_to_uri from authlib.jose import JsonWebKey from authlib.oauth2 import OAuth2Request, rfc7009, rfc9207 from authlib.oauth2.rfc6749 import ClientMixin, TokenMixin, grants +from authlib.oauth2.rfc6749.hooks import hooked from authlib.oauth2.rfc6750 import BearerTokenGenerator from authlib.oauth2.rfc7636 import CodeChallenge from authlib.oauth2.rfc9068 import JWTBearerTokenGenerator as JWTBearerTokenGenerator_ @@ -21,7 +22,7 @@ from layercake.dynamodb import ( from layercake.funcs import omit, pick from boto3clients import dynamodb_client -from config import ISSUER, OAUTH2_TABLE +from config import ISSUER, OAUTH2_SCOPES_SUPPORTED, OAUTH2_TABLE from integrations.apigateway_oauth2.authorization_server import ( AuthorizationServer, ) @@ -64,6 +65,8 @@ class OpenIDCode(OpenIDCode_): } def generate_user_info(self, user: User, scope: str) -> UserInfo: + print(scope) + print('--' * 100) return UserInfo( sub=user.id, name=user.name, @@ -173,6 +176,20 @@ class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): return User(**pick(('id', 'name', 'email', 'email_verified'), 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'] + + @hooked + def validate_token_request(self): + raise NotImplementedError() + + @hooked + def create_token_response(self): + raise NotImplementedError() + + class RefreshTokenNotFoundError(NotFoundError): def __init__(self, *_): super().__init__('Refresh token not found') @@ -337,7 +354,10 @@ class JWTBearerTokenGenerator(JWTBearerTokenGenerator_): } -server = AuthorizationServer(persistence_layer=dyn) +server = AuthorizationServer( + persistence_layer=dyn, + scopes_supported=OAUTH2_SCOPES_SUPPORTED, +) server.register_grant( AuthorizationCodeGrant, [ @@ -353,6 +373,7 @@ server.register_token_generator( expires_generator=expires_in, ), ) +server.register_grant(TokenExchangeGrant) server.register_grant(RefreshTokenGrant) server.register_endpoint(RevocationEndpoint) server.register_extension(IssuerParameter()) diff --git a/id.saladeaula.digital/app/routes/authorize.py b/id.saladeaula.digital/app/routes/authorize.py index 5b26840..d944e3d 100644 --- a/id.saladeaula.digital/app/routes/authorize.py +++ b/id.saladeaula.digital/app/routes/authorize.py @@ -1,6 +1,5 @@ from http.cookies import SimpleCookie -import jwt from authlib.oauth2.rfc6749 import errors from authlib.oauth2.rfc6749.util import scope_to_list from aws_lambda_powertools import Logger @@ -9,12 +8,12 @@ from aws_lambda_powertools.event_handler.exceptions import ( BadRequestError, ForbiddenError, ServiceError, - UnauthorizedError, ) +from joserfc.errors import JoseError from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey from boto3clients import dynamodb_client -from config import ISSUER, OAUTH2_TABLE, SESSION_SECRET +from config import OAUTH2_TABLE from oauth2 import server router = Router() @@ -26,19 +25,24 @@ dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client) def authorize(): current_event = router.current_event cookies = _parse_cookies(current_event.get('cookies', [])) - session_id = cookies.get('session_id') + session = cookies.get('__session') - if not session_id: - raise BadRequestError('Missing session_id') + if not session: + raise BadRequestError('Missing session') try: - sub, session_scope = verify_session(session_id) + sid, sub = session.split(':') + # Raise if session is not found + dyn.collection.get_item( + KeyPair('SESSION', sid), + exc_cls=InvalidSession, + ) grant = server.get_consent_grant( request=router.current_event, end_user=sub, ) + user_scopes = _user_scopes(sub) client_scopes = set(scope_to_list(grant.client.scope)) - user_scopes = set(scope_to_list(session_scope)) if session_scope else set() # Deny authorization if user lacks scopes requested by client if not client_scopes.issubset(user_scopes): @@ -49,7 +53,7 @@ def authorize(): grant_user=sub, grant=grant, ) - except jwt.exceptions.InvalidTokenError as err: + except JoseError as err: logger.exception(err) raise BadRequestError(str(err)) except errors.OAuth2Error as err: @@ -60,39 +64,19 @@ def authorize(): ) -def verify_session(session_id: str) -> tuple[str, str | None]: - payload = jwt.decode( - session_id, - SESSION_SECRET, - algorithms=['HS256'], - issuer=ISSUER, - options={ - 'require': ['exp', 'sub', 'iss', 'sid'], - }, - ) - - user = dyn.collection.get_items( - KeyPair( - pk='SESSION', - sk=payload['sid'], - rename_key='session', +def _user_scopes(sub: str) -> set: + return set( + scope_to_list( + dyn.collection.get_item( + KeyPair( + pk=sub, + sk=SortKey(sk='SCOPE', path_spec='scope'), + ), + exc_cls=BadRequestError, + ) ) - + KeyPair( - pk=payload['sub'], - sk=SortKey( - sk='SCOPE', - path_spec='scope', - rename_key='scope', - ), - ), - flatten_top=False, ) - if 'session' not in user: - raise SessionRevokedError('Session revoked') - - return payload['sub'], user.get('scope') - def _parse_cookies(cookies: list[str] | None) -> dict[str, str]: parsed_cookies = {} @@ -108,4 +92,4 @@ def _parse_cookies(cookies: list[str] | None) -> dict[str, str]: return parsed_cookies -class SessionRevokedError(UnauthorizedError): ... +class InvalidSession(BadRequestError): ... diff --git a/id.saladeaula.digital/app/routes/session.py b/id.saladeaula.digital/app/routes/session.py index d1ab3ff..f512185 100644 --- a/id.saladeaula.digital/app/routes/session.py +++ b/id.saladeaula.digital/app/routes/session.py @@ -3,7 +3,6 @@ from typing import Annotated from uuid import uuid4 import boto3 -import jwt from aws_lambda_powertools.event_handler import ( Response, ) @@ -17,10 +16,8 @@ from passlib.hash import pbkdf2_sha256 from boto3clients import dynamodb_client from config import ( - ISSUER, OAUTH2_TABLE, SESSION_EXPIRES_IN, - SESSION_SECRET, ) router = Router() @@ -45,7 +42,7 @@ def session( status_code=HTTPStatus.OK, cookies=[ Cookie( - name='session_id', + name='__session', value=new_session(user_id), http_only=True, secure=True, @@ -127,26 +124,15 @@ def _get_idp_user( def new_session(sub: str) -> str: - session_id = str(uuid4()) + sid = str(uuid4()) now_ = now() exp = ttl(start_dt=now_, seconds=SESSION_EXPIRES_IN) - token = jwt.encode( - { - 'sid': session_id, - 'sub': sub, - 'iss': ISSUER, - 'iat': int(now_.timestamp()), - 'exp': exp, - }, - SESSION_SECRET, - algorithm='HS256', - ) with dyn.transact_writer() as transact: transact.put( item={ 'id': 'SESSION', - 'sk': session_id, + 'sk': sid, 'user_id': sub, 'ttl': exp, 'created_at': now_, @@ -155,13 +141,13 @@ def new_session(sub: str) -> str: transact.put( item={ 'id': sub, - 'sk': f'SESSION#{session_id}', + 'sk': f'SESSION#{sid}', 'ttl': exp, 'created_at': now_, } ) - return token + return f'{sid}:{sub}' class UserNotFoundError(NotFoundError): diff --git a/id.saladeaula.digital/client/app/routes/authorize.ts b/id.saladeaula.digital/client/app/routes/authorize.ts index f472d34..1b8436e 100644 --- a/id.saladeaula.digital/client/app/routes/authorize.ts +++ b/id.saladeaula.digital/client/app/routes/authorize.ts @@ -11,7 +11,7 @@ export async function loader({ request, context }: Route.LoaderArgs) { issuerUrl.search = url.search redirect.search = url.search - if (!cookies.session_id) { + if (!cookies?.__session) { return new Response(null, { status: httpStatus.FOUND, headers: { diff --git a/id.saladeaula.digital/template.yaml b/id.saladeaula.digital/template.yaml index f929620..4ef0749 100644 --- a/id.saladeaula.digital/template.yaml +++ b/id.saladeaula.digital/template.yaml @@ -14,7 +14,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:96 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:98 Environment: Variables: TZ: America/Sao_Paulo @@ -25,8 +25,6 @@ Globals: DYNAMODB_SORT_KEY: sk OAUTH2_TABLE: !Ref OAuth2Table ISSUER: https://id.saladeaula.digital - SESSION_SECRET: 7DUTFB1iLeSpiXvmxbOZim1yPVmQbmBpAzgscob0RDzrL2wVwRi1ti2ZSry7jJAf - OAUTH2_SCOPES_SUPPORTED: openid profile email offline_access read:users read:enrollments read:orders read:courses write:courses Resources: HttpLog: diff --git a/id.saladeaula.digital/tests/conftest.py b/id.saladeaula.digital/tests/conftest.py index 2a9d64d..d0965f8 100644 --- a/id.saladeaula.digital/tests/conftest.py +++ b/id.saladeaula.digital/tests/conftest.py @@ -21,9 +21,6 @@ def pytest_configure(): os.environ['DYNAMODB_PARTITION_KEY'] = PK os.environ['DYNAMODB_SORT_KEY'] = SK os.environ['ISSUER'] = 'http://localhost' - os.environ['OAUTH2_SCOPES_SUPPORTED'] = ( - 'openid profile email offline_access read:users' - ) @dataclass diff --git a/id.saladeaula.digital/tests/routes/test_authorize.py b/id.saladeaula.digital/tests/routes/test_authorize.py index 8506364..286118b 100644 --- a/id.saladeaula.digital/tests/routes/test_authorize.py +++ b/id.saladeaula.digital/tests/routes/test_authorize.py @@ -16,7 +16,7 @@ def test_authorize( http_api_proxy: HttpApiProxy, lambda_context: LambdaContext, ): - session_id = new_session(USER_ID) + session = new_session(USER_ID) r = app.lambda_handler( http_api_proxy( @@ -31,7 +31,7 @@ def test_authorize( 'state': '456', }, cookies=[ - f'session_id={session_id}; HttpOnly; Secure', + f'__session={session}; HttpOnly; Secure', ], ), lambda_context, @@ -60,7 +60,7 @@ def test_forbidden( http_api_proxy: HttpApiProxy, lambda_context: LambdaContext, ): - session_id = new_session('fd5914ec-fd37-458b-b6b9-8aeab38b666b') + session = new_session('fd5914ec-fd37-458b-b6b9-8aeab38b666b') r = app.lambda_handler( http_api_proxy( @@ -75,7 +75,7 @@ def test_forbidden( 'state': '456', }, cookies=[ - f'session_id={session_id}; HttpOnly; Secure', + f'__session={session}; HttpOnly; Secure', ], ), lambda_context, @@ -84,15 +84,13 @@ def test_forbidden( assert r['statusCode'] == HTTPStatus.FORBIDDEN -def test_authorize_revoked( +def test_invalid_session( app, seeds, dynamodb_persistence_layer: DynamoDBPersistenceLayer, http_api_proxy: HttpApiProxy, lambda_context: LambdaContext, ): - invalid_session_id = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiIwNTgzNTBhYi02NGU1LTQ0MzEtYmQyNy01MGVhOWIxNmQxZGYiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiaWF0IjoxNzU1Mzk3Nzk5LCJleHAiOjE3NTUzOTg2OTl9.dDbiHYReVERbkNH2df4sXK2VIwT7G1KjNC5UrBuN6IQ' - r = app.lambda_handler( http_api_proxy( raw_path='/authorize', @@ -106,7 +104,7 @@ def test_authorize_revoked( 'state': '456', }, cookies=[ - f'session_id={invalid_session_id}; HttpOnly; Secure', + '__session=10:10; HttpOnly; Secure', ], ), lambda_context, diff --git a/id.saladeaula.digital/tests/seeds.jsonl b/id.saladeaula.digital/tests/seeds.jsonl index 3924801..63b954d 100644 --- a/id.saladeaula.digital/tests/seeds.jsonl +++ b/id.saladeaula.digital/tests/seeds.jsonl @@ -16,9 +16,9 @@ // 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"} +{"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": "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"} {"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"} -{"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "SCOPE", "scope": "openid"} \ No newline at end of file +{"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "SCOPE", "scope": ["openid"]} \ No newline at end of file diff --git a/id.saladeaula.digital/uv.lock b/id.saladeaula.digital/uv.lock index 51a83e2..ac7882c 100644 --- a/id.saladeaula.digital/uv.lock +++ b/id.saladeaula.digital/uv.lock @@ -35,14 +35,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.1" +version = "1.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" } +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/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" }, + { 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]] @@ -469,6 +469,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 = "jsonlines" version = "4.0.0" @@ -495,7 +507,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.9.14" +version = "0.10.1" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, @@ -504,6 +516,7 @@ dependencies = [ { name = "dictdiffer" }, { name = "ftfy" }, { name = "glom" }, + { name = "joserfc" }, { name = "meilisearch" }, { name = "orjson" }, { name = "passlib" }, @@ -511,7 +524,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"] }, @@ -522,11 +535,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" }, @@ -534,7 +548,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" }, @@ -825,15 +839,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[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.4.1" @@ -885,6 +890,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[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" diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index dc5b57b..2a852fb 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layercake" -version = "0.10.0" +version = "0.10.1" description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." readme = "README.md" authors = [ @@ -23,11 +23,11 @@ dependencies = [ "sqlite-utils>=3.38", "dictdiffer>=0.9.0", "unidecode>=1.4.0", - "authlib>=1.6.1", "passlib>=1.7.4", "psycopg[binary]>=3.2.9", "joserfc>=1.2.2", "python-multipart>=0.0.20", + "authlib>=1.6.5", ] [dependency-groups] diff --git a/layercake/uv.lock b/layercake/uv.lock index aa76731..a1faa01 100644 --- a/layercake/uv.lock +++ b/layercake/uv.lock @@ -44,14 +44,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.1" +version = "1.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" } +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/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" }, + { 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]] @@ -714,7 +714,7 @@ dev = [ [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" },