rename params

This commit is contained in:
2025-03-27 21:35:36 -03:00
parent 5756451738
commit f757334899
12 changed files with 53 additions and 41 deletions

View File

@@ -1,4 +1,4 @@
class UserNotFound(Exception): ... class UnauthorizedError(Exception): ...
def get_user(access_token: str, *, idp_client) -> dict | None: def get_user(access_token: str, *, idp_client) -> dict | None:
@@ -6,6 +6,6 @@ def get_user(access_token: str, *, idp_client) -> dict | None:
try: try:
user = idp_client.get_user(AccessToken=access_token) user = idp_client.get_user(AccessToken=access_token)
except idp_client.exceptions.ClientError: except idp_client.exceptions.ClientError:
raise UserNotFound() raise UnauthorizedError()
else: else:
return {attr['Name']: attr['Value'] for attr in user['UserAttributes']} return {attr['Name']: attr['Value'] for attr in user['UserAttributes']}

View File

@@ -1,5 +1,3 @@
import json
from aws_lambda_powertools.event_handler.api_gateway import ( from aws_lambda_powertools.event_handler.api_gateway import (
APIGatewayHttpResolver, APIGatewayHttpResolver,
Response, Response,
@@ -8,7 +6,9 @@ from aws_lambda_powertools.event_handler.middlewares import (
BaseMiddlewareHandler, BaseMiddlewareHandler,
NextMiddleware, NextMiddleware,
) )
from aws_lambda_powertools.shared.json_encoder import Encoder from aws_lambda_powertools.shared.functions import (
extract_event_from_common_models,
)
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
from layercake.dynamodb import ComposeKey, DynamoDBCollection, KeyPair from layercake.dynamodb import ComposeKey, DynamoDBCollection, KeyPair
from pydantic import UUID4, BaseModel, Field from pydantic import UUID4, BaseModel, Field
@@ -16,6 +16,14 @@ from pydantic import UUID4, BaseModel, Field
LOG_RETENTION_DAYS = 365 * 2 # 2 years LOG_RETENTION_DAYS = 365 * 2 # 2 years
class AuthenticatedUser(BaseModel):
id: str = Field(alias='custom:user_id')
name: str
email: str
email_verified: bool
sub: UUID4
class AuthorizerMiddleware(BaseMiddlewareHandler): class AuthorizerMiddleware(BaseMiddlewareHandler):
def handler( def handler(
self, self,
@@ -33,14 +41,6 @@ class AuthorizerMiddleware(BaseMiddlewareHandler):
return next_middleware(app) return next_middleware(app)
class AuthenticatedUser(BaseModel):
id: str = Field(alias='custom:user_id')
name: str
email: str
email_verified: bool
sub: UUID4
class AuditLogMiddleware(BaseMiddlewareHandler): class AuditLogMiddleware(BaseMiddlewareHandler):
"""This middleware logs audit details for successful requests, storing user, """This middleware logs audit details for successful requests, storing user,
action, and IP info with a specified retention period..""" action, and IP info with a specified retention period.."""
@@ -71,7 +71,9 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
if 200 <= response.status_code < 300 and user: if 200 <= response.status_code < 300 and user:
now_ = now() now_ = now()
data = ( data = (
json.dumps(response.body, cls=Encoder) if response.is_json() else None extract_event_from_common_models(response.body)
if response.is_json()
else None
) )
retention_days = ( retention_days = (
ttl(start_dt=now_, days=self.retention_days) ttl(start_dt=now_, days=self.retention_days)

View File

@@ -53,7 +53,7 @@ def post_course(payload: Course):
) )
return Response( return Response(
body={'id': str(payload.id), }, body=payload,
content_type=content_types.APPLICATION_JSON, content_type=content_types.APPLICATION_JSON,
status_code=HTTPStatus.CREATED, status_code=HTTPStatus.CREATED,
) )

View File

@@ -23,6 +23,7 @@ from pydantic import UUID4, BaseModel, StringConstraints
import elastic import elastic
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from middlewares import AuditLogMiddleware
from models import User from models import User
from settings import ELASTIC_CONN, USER_TABLE from settings import ELASTIC_CONN, USER_TABLE
@@ -32,7 +33,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ...
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError) collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError)
elastic_client = Elasticsearch(**ELASTIC_CONN) elastic_client = Elasticsearch(**ELASTIC_CONN)
@@ -60,6 +61,7 @@ def get_users():
compress=True, compress=True,
tags=['User'], tags=['User'],
summary='Create user', summary='Create user',
middlewares=[AuditLogMiddleware('USER_ADD', collect)],
) )
def post_user(payload: User): def post_user(payload: User):
return Response(status_code=HTTPStatus.CREATED) return Response(status_code=HTTPStatus.CREATED)

View File

@@ -23,7 +23,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:25 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:26
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -13,6 +13,11 @@ PK = os.getenv('DYNAMODB_PARTITION_KEY', 'pk')
SK = os.getenv('DYNAMODB_SORT_KEY', 'sk') SK = os.getenv('DYNAMODB_SORT_KEY', 'sk')
patch = pytest.MonkeyPatch()
patch.setenv('USER_TABLE', PYTEST_TABLE_NAME)
patch.setenv('COURSE_TABLE', PYTEST_TABLE_NAME)
@dataclass @dataclass
class LambdaContext: class LambdaContext:
function_name: str = 'test' function_name: str = 'test'
@@ -137,9 +142,6 @@ def dynamodb_seeds(dynamodb_client):
@pytest.fixture @pytest.fixture
def mock_app(monkeypatch): def mock_app(monkeypatch):
for table_name in ['USER_TABLE', 'COURSE_TABLE']:
monkeypatch.setenv(table_name, PYTEST_TABLE_NAME)
import app import app
return app return app

View File

@@ -87,6 +87,7 @@ def test_get_logs(
def test_post_user( def test_post_user(
mock_app, mock_app,
dynamodb_client,
http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext, lambda_context: LambdaContext,
): ):

View File

@@ -5,6 +5,8 @@ from .conftest import LambdaContext
def test_bearer_jwt(lambda_context: LambdaContext): def test_bearer_jwt(lambda_context: LambdaContext):
import auth as app
# You should mock the Cognito user to pass the test # You should mock the Cognito user to pass the test
app.get_user = lambda *args, **kwargs: { app.get_user = lambda *args, **kwargs: {
'sub': '58efed8d-d276-41a8-8502-4ab8b5a6415e', 'sub': '58efed8d-d276-41a8-8502-4ab8b5a6415e',
@@ -30,6 +32,7 @@ def test_bearer_jwt(lambda_context: LambdaContext):
def test_bearer_apikey( def test_bearer_apikey(
monkeypatch,
dynamodb_seeds, dynamodb_seeds,
lambda_context: LambdaContext, lambda_context: LambdaContext,
): ):
@@ -38,6 +41,7 @@ def test_bearer_apikey(
'authorization': 'Bearer sk-MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw', 'authorization': 'Bearer sk-MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw',
} }
} }
# This data was added from seeds # This data was added from seeds
assert app.lambda_handler(event, lambda_context) == { assert app.lambda_handler(event, lambda_context) == {
'isAuthorized': True, 'isAuthorized': True,
@@ -49,7 +53,7 @@ def test_bearer_apikey(
}, },
} }
# This data was added from seeds # # This data was added from seeds
assert app.lambda_handler( assert app.lambda_handler(
{ {
'headers': { 'headers': {

2
http-api/uv.lock generated
View File

@@ -444,7 +444,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.1.8" version = "0.1.9"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "aws-lambda-powertools", extra = ["all"] }, { name = "aws-lambda-powertools", extra = ["all"] },

View File

@@ -27,30 +27,31 @@ serializer = TypeSerializer()
deserializer = TypeDeserializer() deserializer = TypeDeserializer()
def _serialize_python_type(value: Any) -> str | dict | list: def _serialize_to_primitive_types(data: Any) -> str | dict | list:
match value: match data:
case datetime(): case datetime():
return value.isoformat() return data.isoformat()
case UUID(): case UUID():
return str(value) return str(data)
case IPv4Address(): case IPv4Address():
return str(value) return str(data)
case list() | tuple(): case list() | tuple():
return [_serialize_python_type(v) for v in value] return [_serialize_to_primitive_types(v) for v in data]
case dict(): case dict():
return {k: _serialize_python_type(v) for k, v in value.items()} return {k: _serialize_to_primitive_types(v) for k, v in data.items()}
case _: case _:
return value return data
def serialize(value: dict) -> dict: def serialize(data: dict) -> dict:
return { return {
k: serializer.serialize(_serialize_python_type(v)) for k, v in value.items() k: serializer.serialize(_serialize_to_primitive_types(v))
for k, v in data.items()
} }
def deserialize(value: dict) -> dict: def deserialize(data: dict) -> dict:
return {k: deserializer.deserialize(v) for k, v in value.items()} return {k: deserializer.deserialize(v) for k, v in data.items()}
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -555,16 +556,16 @@ class DynamoDBCollection:
self, self,
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
/, /,
exc_cls: Type[ValueError] = MissingError, exception_cls: Type[ValueError] = MissingError,
tz: str = TZ, tz: str = TZ,
) -> None: ) -> None:
if not issubclass(exc_cls, ValueError): if not issubclass(exception_cls, ValueError):
raise TypeError( raise TypeError(
f'exception_cls must be a subclass of ValueError, got {exc_cls}' f'exception_cls must be a subclass of ValueError, got {exception_cls}'
) )
self.persistence_layer = persistence_layer self.persistence_layer = persistence_layer
self.exc_cls = exc_cls self.exception_cls = exception_cls
self.tz = tz self.tz = tz
def get_item( def get_item(
@@ -575,7 +576,7 @@ class DynamoDBCollection:
default: Any = None, default: Any = None,
delimiter: str = '#', delimiter: str = '#',
) -> Any: ) -> Any:
exc_cls = self.exc_cls exc_cls = self.exception_cls
data = self.persistence_layer.get_item(key) data = self.persistence_layer.get_item(key)
if raise_on_error and not data: if raise_on_error and not data:

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "layercake" name = "layercake"
version = "0.1.8" version = "0.1.9"
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
authors = [ authors = [

2
layercake/uv.lock generated
View File

@@ -465,7 +465,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.1.6" version = "0.1.9"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aws-lambda-powertools", extra = ["all"] }, { name = "aws-lambda-powertools", extra = ["all"] },