From a77cab45c1a05440115662968797541339110a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Sun, 10 Aug 2025 02:33:00 -0300 Subject: [PATCH] update --- .../apigateway_oauth2/authorization_server.py | 4 ++-- .../apigateway_oauth2/resource_protector.py | 4 ++++ id.saladeaula.digital/app/oauth2.py | 24 +++++++++++++++---- id.saladeaula.digital/app/routes/jwks.py | 2 +- id.saladeaula.digital/app/routes/userinfo.py | 2 +- id.saladeaula.digital/template.yaml | 4 ++-- id.saladeaula.digital/tests/conftest.py | 2 +- .../tests/routes/test_authorize.py | 3 ++- .../tests/routes/test_token.py | 2 +- id.saladeaula.digital/tests/seeds.jsonl | 2 +- 10 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py 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 ff9a022..2f8c696 100644 --- a/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py +++ b/id.saladeaula.digital/app/integrations/apigateway_oauth2/authorization_server.py @@ -22,8 +22,8 @@ OAUTH2_SCOPES_SUPPORTED = os.getenv('OAUTH2_SCOPES_SUPPORTED') GRANT_TYPES_EXPIRES_IN = { - 'refresh_token': 900, - 'authorization_code': 900, + 'refresh_token': 600, + 'authorization_code': 600, } diff --git a/id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py b/id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py new file mode 100644 index 0000000..e668ab5 --- /dev/null +++ b/id.saladeaula.digital/app/integrations/apigateway_oauth2/resource_protector.py @@ -0,0 +1,4 @@ +from authlib.oauth2 import ResourceProtector as _ResourceProtector + + +class ResourceProtector(_ResourceProtector): ... diff --git a/id.saladeaula.digital/app/oauth2.py b/id.saladeaula.digital/app/oauth2.py index 96347f4..79914ca 100644 --- a/id.saladeaula.digital/app/oauth2.py +++ b/id.saladeaula.digital/app/oauth2.py @@ -1,8 +1,12 @@ +import re + from authlib.oauth2 import OAuth2Request from authlib.oauth2.rfc6749 import ClientMixin, TokenMixin, grants from authlib.oauth2.rfc7636 import CodeChallenge from authlib.oidc.core import OpenIDCode as OpenIDCode_ from authlib.oidc.core import UserInfo +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler.exceptions import NotFoundError from layercake.dateutils import now, ttl from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from layercake.funcs import omit, pick @@ -17,6 +21,7 @@ from integrations.apigateway_oauth2.tokens import ( OAuth2Token, ) +logger = Logger(__name__) oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client) @@ -137,22 +142,30 @@ class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): return pick(('id', 'name', 'email', 'email_verified'), user) +class RefreshTokenNotFoundError(NotFoundError): + def __init__(self, *_): + super().__init__('Refresh token not found') + + class RefreshTokenGrant(grants.RefreshTokenGrant): - INCLUDE_NEW_REFRESH_TOKEN = True TOKEN_ENDPOINT_AUTH_METHODS = [ 'client_secret_basic', 'client_secret_post', 'none', ] + INCLUDE_NEW_REFRESH_TOKEN = True def authenticate_refresh_token(self, refresh_token: str, **kwargs) -> TokenMixin: - token = oauth2_layer.get_item( + token = oauth2_layer.collection.get_item( KeyPair( pk='OAUTH2#TOKEN', sk=f'REFRESH_TOKEN#{refresh_token}', - ) + ), + exc_cls=RefreshTokenNotFoundError, ) + logger.info('Refresh token retrieved', token=token) + return OAuth2Token( expires_in=int(token['expires_in']), issued_at=int(token['issued_at']), @@ -164,7 +177,10 @@ class RefreshTokenGrant(grants.RefreshTokenGrant): return refresh_token.get_user() def revoke_old_credential(self, refresh_token: TokenMixin) -> None: - if token := getattr(refresh_token, 'refresh_token', None): + logger.info('Revoking old refresh token', refresh_token=refresh_token) + token = getattr(refresh_token, 'refresh_token', None) + + if token: oauth2_layer.delete_item( KeyPair(pk='OAUTH2#TOKEN', sk=f'REFRESH_TOKEN#{token}') ) diff --git a/id.saladeaula.digital/app/routes/jwks.py b/id.saladeaula.digital/app/routes/jwks.py index d0cec13..b1e41fd 100644 --- a/id.saladeaula.digital/app/routes/jwks.py +++ b/id.saladeaula.digital/app/routes/jwks.py @@ -3,6 +3,6 @@ from aws_lambda_powertools.event_handler.api_gateway import Router router = Router() -@router.get('/jwks.json') +@router.get('/.well-known/jwks.json') def jwks(): return {} diff --git a/id.saladeaula.digital/app/routes/userinfo.py b/id.saladeaula.digital/app/routes/userinfo.py index 0ca72f0..97ff9ca 100644 --- a/id.saladeaula.digital/app/routes/userinfo.py +++ b/id.saladeaula.digital/app/routes/userinfo.py @@ -5,4 +5,4 @@ router = Router() @router.get('/userinfo') def userinfo(): - return {} + return {'name': 'test'} diff --git a/id.saladeaula.digital/template.yaml b/id.saladeaula.digital/template.yaml index b18ff93..22fc594 100644 --- a/id.saladeaula.digital/template.yaml +++ b/id.saladeaula.digital/template.yaml @@ -26,7 +26,7 @@ Globals: OAUTH2_TABLE: !Ref OAuth2Table ISSUER: https://id.saladeaula.digital JWT_SECRET: 7DUTFB1iLeSpiXvmxbOZim1yPVmQbmBpAzgscob0RDzrL2wVwRi1ti2ZSry7jJAf - OAUTH2_SCOPES_SUPPORTED: openid profile email + OAUTH2_SCOPES_SUPPORTED: openid profile email offline_access Resources: HttpLog: @@ -79,7 +79,7 @@ Resources: Jwks: Type: HttpApi Properties: - Path: /jwks.json + Path: /.well-known/jwks.json Method: GET ApiId: !Ref HttpApi Token: diff --git a/id.saladeaula.digital/tests/conftest.py b/id.saladeaula.digital/tests/conftest.py index 78d6d3d..0e07802 100644 --- a/id.saladeaula.digital/tests/conftest.py +++ b/id.saladeaula.digital/tests/conftest.py @@ -20,7 +20,7 @@ def pytest_configure(): os.environ['JWT_SECRET'] = 'secret' os.environ['DYNAMODB_PARTITION_KEY'] = PK os.environ['DYNAMODB_SORT_KEY'] = SK - os.environ['OAUTH2_SCOPES_SUPPORTED'] = 'openid profile email' + os.environ['OAUTH2_SCOPES_SUPPORTED'] = 'openid profile email offline_access' # os.environ['POWERTOOLS_LOGGER_LOG_EVENT'] = 'true' diff --git a/id.saladeaula.digital/tests/routes/test_authorize.py b/id.saladeaula.digital/tests/routes/test_authorize.py index c6e56c9..c8c3688 100644 --- a/id.saladeaula.digital/tests/routes/test_authorize.py +++ b/id.saladeaula.digital/tests/routes/test_authorize.py @@ -29,8 +29,9 @@ def test_authorize( 'response_type': 'code', 'client_id': client_id, 'redirect_uri': 'https://localhost/callback', - 'scope': 'openid', + 'scope': 'openid offline_access', 'nonce': '123', + 'state': '456', }, cookies=[ f'id_token={id_token}; HttpOnly; Secure', diff --git a/id.saladeaula.digital/tests/routes/test_token.py b/id.saladeaula.digital/tests/routes/test_token.py index c74b627..75499d2 100644 --- a/id.saladeaula.digital/tests/routes/test_token.py +++ b/id.saladeaula.digital/tests/routes/test_token.py @@ -38,7 +38,7 @@ def test_token( auth_token = json.loads(r['body']) assert r['statusCode'] == HTTPStatus.OK - assert auth_token['expires_in'] == 900 + assert auth_token['expires_in'] == 600 r = dynamodb_persistence_layer.query( key_cond_expr='#pk = :pk', diff --git a/id.saladeaula.digital/tests/seeds.jsonl b/id.saladeaula.digital/tests/seeds.jsonl index 172511c..42d4506 100644 --- a/id.saladeaula.digital/tests/seeds.jsonl +++ b/id.saladeaula.digital/tests/seeds.jsonl @@ -1,5 +1,5 @@ // OAuth2 -{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email", "token_endpoint_auth_method": "none"} +{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest", "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#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", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"} // Post-migration: uncomment the following line