wip middleware
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from aws_lambda_powertools import Logger, Tracer
|
||||
@@ -13,9 +14,11 @@ from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
from middlewares import AuthorizerMiddleware
|
||||
from routes import courses, enrollments, lookup, me, orders, users, webhooks
|
||||
|
||||
DEBUG = os.getenv('LOG_LEVEL') == 'DEBUG'
|
||||
|
||||
tracer = Tracer()
|
||||
logger = Logger(__name__)
|
||||
app = APIGatewayHttpResolver(enable_validation=True)
|
||||
app = APIGatewayHttpResolver(enable_validation=True, debug=DEBUG)
|
||||
app.use(middlewares=[AuthorizerMiddleware()])
|
||||
app.include_router(courses.router, prefix='/courses')
|
||||
app.include_router(enrollments.router, prefix='/enrollments')
|
||||
|
||||
@@ -80,6 +80,19 @@ class Authorizer:
|
||||
|
||||
|
||||
def _authorizer(bearer: BearerToken) -> Authorizer:
|
||||
"""
|
||||
Build an Authorizer object based on the bearer token's auth type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bearer : BearerToken
|
||||
The bearer token containing authentication information.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Authorizer
|
||||
An Authorizer object with the appropriate authorization status and context.
|
||||
"""
|
||||
try:
|
||||
match bearer.auth_type:
|
||||
case TokenType.USER_TOKEN:
|
||||
|
||||
@@ -4,6 +4,7 @@ import boto3
|
||||
|
||||
DYNAMODB_ENDPOINT_URL: str | None = None
|
||||
|
||||
# Only when running `sam local start-api`
|
||||
if 'AWS_SAM_LOCAL' in os.environ:
|
||||
DYNAMODB_ENDPOINT_URL = 'http://host.docker.internal:8000'
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ from aws_lambda_powertools.event_handler.middlewares import (
|
||||
BaseMiddlewareHandler,
|
||||
NextMiddleware,
|
||||
)
|
||||
from layercake.dateutils import now, ttl
|
||||
from layercake.dynamodb import ComposeKey, DynamoDBCollection, KeyPair
|
||||
from pydantic import UUID4, BaseModel, Field
|
||||
|
||||
|
||||
@@ -21,7 +23,7 @@ class AuthorizerMiddleware(BaseMiddlewareHandler):
|
||||
|
||||
if 'user' in authorizer:
|
||||
user = authorizer['user']
|
||||
app.append_context(user=AuthenticatedUser(**user))
|
||||
app.append_context(authenticated_user=AuthenticatedUser(**user))
|
||||
|
||||
return next_middleware(app)
|
||||
|
||||
@@ -32,3 +34,35 @@ class AuthenticatedUser(BaseModel):
|
||||
email: str
|
||||
email_verified: bool
|
||||
sub: UUID4
|
||||
|
||||
|
||||
class AuditLogMiddleware(BaseMiddlewareHandler):
|
||||
def __init__(self, action: str, /, collect: DynamoDBCollection) -> None:
|
||||
self.action = action
|
||||
self.collect = collect
|
||||
|
||||
def handler(
|
||||
self,
|
||||
app: APIGatewayHttpResolver,
|
||||
next_middleware: NextMiddleware,
|
||||
) -> Response:
|
||||
response = next_middleware(app)
|
||||
auth_user = app.context.get('authenticated_user')
|
||||
request_ctx = app.current_event.request_context
|
||||
ip_addr = request_ctx.http.source_ip
|
||||
|
||||
# Successful request
|
||||
if 200 <= response.status_code < 300 and auth_user:
|
||||
now_ = now()
|
||||
|
||||
self.collect.put_item(
|
||||
key=KeyPair(
|
||||
pk=ComposeKey(auth_user.id, prefix='logs'),
|
||||
sk=now_.isoformat(),
|
||||
),
|
||||
action=self.action,
|
||||
ip=ip_addr,
|
||||
ttl=ttl(start_dt=now_, days=365 * 2),
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -4,18 +4,21 @@ from http import HTTPStatus
|
||||
from aws_lambda_powertools.event_handler import Response, content_types
|
||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||
from elasticsearch import Elasticsearch
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
|
||||
from pydantic import BaseModel
|
||||
|
||||
import elastic
|
||||
from boto3clients import dynamodb_client
|
||||
from course import create_course
|
||||
from middlewares import AuditLogMiddleware
|
||||
from models import Course, Org
|
||||
from settings import COURSE_TABLE, ELASTIC_CONN
|
||||
from settings import COURSE_TABLE, ELASTIC_CONN, USER_TABLE
|
||||
|
||||
router = Router()
|
||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
|
||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||
collect = DynamoDBCollection(user_layer)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -41,7 +44,12 @@ class CoursePayload(BaseModel):
|
||||
course: Course
|
||||
|
||||
|
||||
@router.post('/', compress=True, tags=['Course'])
|
||||
@router.post(
|
||||
'/',
|
||||
compress=True,
|
||||
tags=['Course'],
|
||||
middlewares=[AuditLogMiddleware('COURSE_ADD', collect)],
|
||||
)
|
||||
def post_course(payload: CoursePayload):
|
||||
create_course(
|
||||
course=payload.course,
|
||||
|
||||
@@ -10,7 +10,9 @@ KONVIVA_SECRET_KEY: str = os.getenv('KONVIVA_SECRET_KEY') # type: ignore
|
||||
|
||||
|
||||
match os.getenv('AWS_SAM_LOCAL'), os.getenv('ELASTIC_HOSTS'):
|
||||
case str() as AWS_SAM_LOCAL, _ if AWS_SAM_LOCAL:
|
||||
case str() as AWS_SAM_LOCAL, _ if (
|
||||
AWS_SAM_LOCAL
|
||||
): # Only when running `sam local start-api`
|
||||
ELASTIC_CONN = {
|
||||
'hosts': 'http://host.docker.internal:9200',
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ Globals:
|
||||
Architectures:
|
||||
- x86_64
|
||||
Layers:
|
||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:22
|
||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:25
|
||||
Environment:
|
||||
Variables:
|
||||
TZ: America/Sao_Paulo
|
||||
|
||||
@@ -3,22 +3,26 @@ from http import HTTPMethod, HTTPStatus
|
||||
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||
|
||||
import app
|
||||
|
||||
from ..conftest import HttpApiProxy, LambdaContext
|
||||
|
||||
|
||||
def test_post_course(
|
||||
mock_app,
|
||||
monkeypatch,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
):
|
||||
mock_app.courses.course_layer = dynamodb_persistence_layer
|
||||
app.courses.course_layer = dynamodb_persistence_layer
|
||||
app.courses.user_layer = dynamodb_persistence_layer
|
||||
|
||||
r = mock_app.lambda_handler(
|
||||
r = app.lambda_handler(
|
||||
http_api_proxy(
|
||||
raw_path='/courses',
|
||||
method=HTTPMethod.POST,
|
||||
headers={'Tenant': '*'},
|
||||
headers={'X-Tenant': '*'},
|
||||
body={
|
||||
'course': {
|
||||
'name': 'pytest',
|
||||
@@ -32,7 +36,7 @@ def test_post_course(
|
||||
lambda_context,
|
||||
)
|
||||
|
||||
print(r)
|
||||
# print(r)
|
||||
|
||||
assert 'id' in json.loads(r['body'])
|
||||
assert r['statusCode'] == HTTPStatus.CREATED
|
||||
|
||||
3
http-api/uv.lock
generated
3
http-api/uv.lock
generated
@@ -444,7 +444,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "layercake"
|
||||
version = "0.1.5"
|
||||
version = "0.1.8"
|
||||
source = { directory = "../layercake" }
|
||||
dependencies = [
|
||||
{ name = "aws-lambda-powertools", extra = ["all"] },
|
||||
@@ -481,6 +481,7 @@ requires-dist = [
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.29.0" },
|
||||
{ name = "pytest", specifier = ">=8.3.5" },
|
||||
{ name = "pytest-cov", specifier = ">=6.0.0" },
|
||||
{ name = "pytest-env", specifier = ">=1.1.5" },
|
||||
|
||||
Reference in New Issue
Block a user