update
This commit is contained in:
@@ -22,8 +22,8 @@ OAUTH2_SCOPES_SUPPORTED = os.getenv('OAUTH2_SCOPES_SUPPORTED')
|
|||||||
|
|
||||||
|
|
||||||
GRANT_TYPES_EXPIRES_IN = {
|
GRANT_TYPES_EXPIRES_IN = {
|
||||||
'refresh_token': 900,
|
'refresh_token': 600,
|
||||||
'authorization_code': 900,
|
'authorization_code': 600,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from authlib.oauth2 import ResourceProtector as _ResourceProtector
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceProtector(_ResourceProtector): ...
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
from authlib.oauth2 import OAuth2Request
|
from authlib.oauth2 import OAuth2Request
|
||||||
from authlib.oauth2.rfc6749 import ClientMixin, TokenMixin, grants
|
from authlib.oauth2.rfc6749 import ClientMixin, TokenMixin, grants
|
||||||
from authlib.oauth2.rfc7636 import CodeChallenge
|
from authlib.oauth2.rfc7636 import CodeChallenge
|
||||||
from authlib.oidc.core import OpenIDCode as OpenIDCode_
|
from authlib.oidc.core import OpenIDCode as OpenIDCode_
|
||||||
from authlib.oidc.core import UserInfo
|
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.dateutils import now, ttl
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
from layercake.funcs import omit, pick
|
from layercake.funcs import omit, pick
|
||||||
@@ -17,6 +21,7 @@ from integrations.apigateway_oauth2.tokens import (
|
|||||||
OAuth2Token,
|
OAuth2Token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
|
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
@@ -137,21 +142,29 @@ class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
|
|||||||
return pick(('id', 'name', 'email', 'email_verified'), user)
|
return pick(('id', 'name', 'email', 'email_verified'), user)
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshTokenNotFoundError(NotFoundError):
|
||||||
|
def __init__(self, *_):
|
||||||
|
super().__init__('Refresh token not found')
|
||||||
|
|
||||||
|
|
||||||
class RefreshTokenGrant(grants.RefreshTokenGrant):
|
class RefreshTokenGrant(grants.RefreshTokenGrant):
|
||||||
INCLUDE_NEW_REFRESH_TOKEN = True
|
|
||||||
TOKEN_ENDPOINT_AUTH_METHODS = [
|
TOKEN_ENDPOINT_AUTH_METHODS = [
|
||||||
'client_secret_basic',
|
'client_secret_basic',
|
||||||
'client_secret_post',
|
'client_secret_post',
|
||||||
'none',
|
'none',
|
||||||
]
|
]
|
||||||
|
INCLUDE_NEW_REFRESH_TOKEN = True
|
||||||
|
|
||||||
def authenticate_refresh_token(self, refresh_token: str, **kwargs) -> TokenMixin:
|
def authenticate_refresh_token(self, refresh_token: str, **kwargs) -> TokenMixin:
|
||||||
token = oauth2_layer.get_item(
|
token = oauth2_layer.collection.get_item(
|
||||||
KeyPair(
|
KeyPair(
|
||||||
pk='OAUTH2#TOKEN',
|
pk='OAUTH2#TOKEN',
|
||||||
sk=f'REFRESH_TOKEN#{refresh_token}',
|
sk=f'REFRESH_TOKEN#{refresh_token}',
|
||||||
|
),
|
||||||
|
exc_cls=RefreshTokenNotFoundError,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
logger.info('Refresh token retrieved', token=token)
|
||||||
|
|
||||||
return OAuth2Token(
|
return OAuth2Token(
|
||||||
expires_in=int(token['expires_in']),
|
expires_in=int(token['expires_in']),
|
||||||
@@ -164,7 +177,10 @@ class RefreshTokenGrant(grants.RefreshTokenGrant):
|
|||||||
return refresh_token.get_user()
|
return refresh_token.get_user()
|
||||||
|
|
||||||
def revoke_old_credential(self, refresh_token: TokenMixin) -> None:
|
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(
|
oauth2_layer.delete_item(
|
||||||
KeyPair(pk='OAUTH2#TOKEN', sk=f'REFRESH_TOKEN#{token}')
|
KeyPair(pk='OAUTH2#TOKEN', sk=f'REFRESH_TOKEN#{token}')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ from aws_lambda_powertools.event_handler.api_gateway import Router
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
@router.get('/jwks.json')
|
@router.get('/.well-known/jwks.json')
|
||||||
def jwks():
|
def jwks():
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ router = Router()
|
|||||||
|
|
||||||
@router.get('/userinfo')
|
@router.get('/userinfo')
|
||||||
def userinfo():
|
def userinfo():
|
||||||
return {}
|
return {'name': 'test'}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Globals:
|
|||||||
OAUTH2_TABLE: !Ref OAuth2Table
|
OAUTH2_TABLE: !Ref OAuth2Table
|
||||||
ISSUER: https://id.saladeaula.digital
|
ISSUER: https://id.saladeaula.digital
|
||||||
JWT_SECRET: 7DUTFB1iLeSpiXvmxbOZim1yPVmQbmBpAzgscob0RDzrL2wVwRi1ti2ZSry7jJAf
|
JWT_SECRET: 7DUTFB1iLeSpiXvmxbOZim1yPVmQbmBpAzgscob0RDzrL2wVwRi1ti2ZSry7jJAf
|
||||||
OAUTH2_SCOPES_SUPPORTED: openid profile email
|
OAUTH2_SCOPES_SUPPORTED: openid profile email offline_access
|
||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
HttpLog:
|
HttpLog:
|
||||||
@@ -79,7 +79,7 @@ Resources:
|
|||||||
Jwks:
|
Jwks:
|
||||||
Type: HttpApi
|
Type: HttpApi
|
||||||
Properties:
|
Properties:
|
||||||
Path: /jwks.json
|
Path: /.well-known/jwks.json
|
||||||
Method: GET
|
Method: GET
|
||||||
ApiId: !Ref HttpApi
|
ApiId: !Ref HttpApi
|
||||||
Token:
|
Token:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def pytest_configure():
|
|||||||
os.environ['JWT_SECRET'] = 'secret'
|
os.environ['JWT_SECRET'] = 'secret'
|
||||||
os.environ['DYNAMODB_PARTITION_KEY'] = PK
|
os.environ['DYNAMODB_PARTITION_KEY'] = PK
|
||||||
os.environ['DYNAMODB_SORT_KEY'] = SK
|
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'
|
# os.environ['POWERTOOLS_LOGGER_LOG_EVENT'] = 'true'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ def test_authorize(
|
|||||||
'response_type': 'code',
|
'response_type': 'code',
|
||||||
'client_id': client_id,
|
'client_id': client_id,
|
||||||
'redirect_uri': 'https://localhost/callback',
|
'redirect_uri': 'https://localhost/callback',
|
||||||
'scope': 'openid',
|
'scope': 'openid offline_access',
|
||||||
'nonce': '123',
|
'nonce': '123',
|
||||||
|
'state': '456',
|
||||||
},
|
},
|
||||||
cookies=[
|
cookies=[
|
||||||
f'id_token={id_token}; HttpOnly; Secure',
|
f'id_token={id_token}; HttpOnly; Secure',
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_token(
|
|||||||
auth_token = json.loads(r['body'])
|
auth_token = json.loads(r['body'])
|
||||||
|
|
||||||
assert r['statusCode'] == HTTPStatus.OK
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
assert auth_token['expires_in'] == 900
|
assert auth_token['expires_in'] == 600
|
||||||
|
|
||||||
r = dynamodb_persistence_layer.query(
|
r = dynamodb_persistence_layer.query(
|
||||||
key_cond_expr='#pk = :pk',
|
key_cond_expr='#pk = :pk',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// OAuth2
|
// 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"}
|
{"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
|
// Post-migration: uncomment the following line
|
||||||
|
|||||||
Reference in New Issue
Block a user