rename params
This commit is contained in:
@@ -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']}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -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
2
http-api/uv.lock
generated
@@ -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"] },
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
2
layercake/uv.lock
generated
@@ -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"] },
|
||||||
|
|||||||
Reference in New Issue
Block a user