add middleware
This commit is contained in:
@@ -12,11 +12,9 @@ from aws_lambda_powertools.event_handler.exceptions import ServiceError
|
|||||||
from aws_lambda_powertools.logging import correlation_paths
|
from aws_lambda_powertools.logging import correlation_paths
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
|
||||||
from middlewares import AuthorizerMiddleware, TenantMiddleware
|
from middlewares import AuthorizerMiddleware
|
||||||
from routes import courses, enrollments, lookup, orders, settings, users, webhooks
|
from routes import courses, enrollments, lookup, orders, settings, users, webhooks
|
||||||
|
|
||||||
DEBUG = 'AWS_SAM_LOCAL' in os.environ
|
|
||||||
|
|
||||||
tracer = Tracer()
|
tracer = Tracer()
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
cors = CORSConfig(
|
cors = CORSConfig(
|
||||||
@@ -25,8 +23,12 @@ cors = CORSConfig(
|
|||||||
max_age=600,
|
max_age=600,
|
||||||
allow_credentials=False,
|
allow_credentials=False,
|
||||||
)
|
)
|
||||||
app = APIGatewayHttpResolver(enable_validation=True, cors=cors, debug=DEBUG)
|
app = APIGatewayHttpResolver(
|
||||||
app.use(middlewares=[AuthorizerMiddleware(), TenantMiddleware()])
|
enable_validation=True,
|
||||||
|
cors=cors,
|
||||||
|
debug='AWS_SAM_LOCAL' in os.environ,
|
||||||
|
)
|
||||||
|
app.use(middlewares=[AuthorizerMiddleware()])
|
||||||
app.include_router(courses.router, prefix='/courses')
|
app.include_router(courses.router, prefix='/courses')
|
||||||
app.include_router(enrollments.router, prefix='/enrollments')
|
app.include_router(enrollments.router, prefix='/enrollments')
|
||||||
app.include_router(orders.router, prefix='/orders')
|
app.include_router(orders.router, prefix='/orders')
|
||||||
|
|||||||
11
http-api/middlewares/__init__.py
Normal file
11
http-api/middlewares/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from .audit_log_middleware import AuditLogMiddleware
|
||||||
|
from .authorizer_middleware import AuthorizerMiddleware, User
|
||||||
|
from .tenant_middelware import Tenant, TenantMiddleware
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AuthorizerMiddleware',
|
||||||
|
'AuditLogMiddleware',
|
||||||
|
'TenantMiddleware',
|
||||||
|
'User',
|
||||||
|
'Tenant',
|
||||||
|
]
|
||||||
@@ -10,69 +10,17 @@ from aws_lambda_powertools.shared.functions import (
|
|||||||
extract_event_from_common_models,
|
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 layercake.funcs import pick
|
from layercake.funcs import pick
|
||||||
from pydantic import UUID4, BaseModel, EmailStr, Field
|
|
||||||
|
|
||||||
from auth import AuthFlowType
|
from .authorizer_middleware import User
|
||||||
|
|
||||||
LOG_RETENTION_DAYS = 365 * 2 # 2 years
|
YEAR_DAYS = 365
|
||||||
|
LOG_RETENTION_DAYS = YEAR_DAYS * 2
|
||||||
|
|
||||||
class User(BaseModel):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
email: EmailStr
|
|
||||||
|
|
||||||
|
|
||||||
class CognitoUser(User):
|
|
||||||
id: str = Field(alias='custom:user_id')
|
|
||||||
email_verified: bool
|
|
||||||
sub: UUID4
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorizerMiddleware(BaseMiddlewareHandler):
|
|
||||||
def handler(
|
|
||||||
self,
|
|
||||||
app: APIGatewayHttpResolver,
|
|
||||||
next_middleware: NextMiddleware,
|
|
||||||
) -> Response:
|
|
||||||
# Gets the Lambda authorizer associated with the current API Gateway event.
|
|
||||||
# You can check the file `auth.py` for more details.
|
|
||||||
context = app.current_event.request_context.authorizer.get_lambda
|
|
||||||
auth_flow_type = context.get('auth_flow_type')
|
|
||||||
|
|
||||||
if not auth_flow_type:
|
|
||||||
return next_middleware(app)
|
|
||||||
|
|
||||||
cls = {
|
|
||||||
AuthFlowType.USER_AUTH: CognitoUser,
|
|
||||||
AuthFlowType.API_AUTH: User,
|
|
||||||
}.get(auth_flow_type)
|
|
||||||
|
|
||||||
if cls:
|
|
||||||
app.append_context(user=cls(**context['user']))
|
|
||||||
|
|
||||||
return next_middleware(app)
|
|
||||||
|
|
||||||
|
|
||||||
class TenantMiddleware(BaseMiddlewareHandler):
|
|
||||||
def handler(
|
|
||||||
self,
|
|
||||||
app: APIGatewayHttpResolver,
|
|
||||||
next_middleware: NextMiddleware,
|
|
||||||
) -> Response:
|
|
||||||
context = app.current_event.request_context.authorizer.get_lambda
|
|
||||||
tenant = app.current_event.headers.get('x-tenant')
|
|
||||||
auth_flow_type = context.get('auth_flow_type')
|
|
||||||
|
|
||||||
match auth_flow_type, tenant:
|
|
||||||
case AuthFlowType.API_AUTH, None:
|
|
||||||
app.append_context(tenant=context['tenant'])
|
|
||||||
case AuthFlowType.USER_AUTH, str():
|
|
||||||
print(tenant)
|
|
||||||
|
|
||||||
return next_middleware(app)
|
|
||||||
|
|
||||||
|
|
||||||
class AuditLogMiddleware(BaseMiddlewareHandler):
|
class AuditLogMiddleware(BaseMiddlewareHandler):
|
||||||
@@ -111,7 +59,7 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
|
|||||||
app: APIGatewayHttpResolver,
|
app: APIGatewayHttpResolver,
|
||||||
next_middleware: NextMiddleware,
|
next_middleware: NextMiddleware,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
user = app.context.get('user')
|
user: User | None = app.context.get('user')
|
||||||
req_context = app.current_event.request_context
|
req_context = app.current_event.request_context
|
||||||
ip_addr = req_context.http.source_ip
|
ip_addr = req_context.http.source_ip
|
||||||
response = next_middleware(app)
|
response = next_middleware(app)
|
||||||
@@ -133,8 +81,9 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
|
|||||||
|
|
||||||
self.collect.put_item(
|
self.collect.put_item(
|
||||||
key=KeyPair(
|
key=KeyPair(
|
||||||
# Post-migration: remove `delimiter` from ComposeKey.
|
# Post-migration: remove `delimiter` and update prefix from `log` to `logs`
|
||||||
pk=ComposeKey(user.id, prefix='logs', delimiter=':'),
|
# in ComposeKey.
|
||||||
|
pk=ComposeKey(user.id, prefix='log', delimiter=':'),
|
||||||
sk=now_.isoformat(),
|
sk=now_.isoformat(),
|
||||||
),
|
),
|
||||||
action=self.action,
|
action=self.action,
|
||||||
48
http-api/middlewares/authorizer_middleware.py
Normal file
48
http-api/middlewares/authorizer_middleware.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from aws_lambda_powertools.event_handler.api_gateway import (
|
||||||
|
APIGatewayHttpResolver,
|
||||||
|
Response,
|
||||||
|
)
|
||||||
|
from aws_lambda_powertools.event_handler.middlewares import (
|
||||||
|
BaseMiddlewareHandler,
|
||||||
|
NextMiddleware,
|
||||||
|
)
|
||||||
|
from pydantic import UUID4, BaseModel, EmailStr, Field
|
||||||
|
|
||||||
|
from auth import AuthFlowType
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
email: EmailStr
|
||||||
|
|
||||||
|
|
||||||
|
class CognitoUser(User):
|
||||||
|
id: str = Field(alias='custom:user_id')
|
||||||
|
email_verified: bool
|
||||||
|
sub: UUID4
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizerMiddleware(BaseMiddlewareHandler):
|
||||||
|
def handler(
|
||||||
|
self,
|
||||||
|
app: APIGatewayHttpResolver,
|
||||||
|
next_middleware: NextMiddleware,
|
||||||
|
) -> Response:
|
||||||
|
# Gets the Lambda authorizer associated with the current API Gateway event.
|
||||||
|
# You can check the file `auth.py` for more details.
|
||||||
|
context = app.current_event.request_context.authorizer.get_lambda
|
||||||
|
auth_flow_type = context.get('auth_flow_type')
|
||||||
|
|
||||||
|
if not auth_flow_type:
|
||||||
|
return next_middleware(app)
|
||||||
|
|
||||||
|
cls = {
|
||||||
|
AuthFlowType.USER_AUTH: CognitoUser,
|
||||||
|
AuthFlowType.API_AUTH: User,
|
||||||
|
}.get(auth_flow_type)
|
||||||
|
|
||||||
|
if cls:
|
||||||
|
app.append_context(user=cls(**context['user']))
|
||||||
|
|
||||||
|
return next_middleware(app)
|
||||||
75
http-api/middlewares/tenant_middelware.py
Normal file
75
http-api/middlewares/tenant_middelware.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from aws_lambda_powertools.event_handler.api_gateway import (
|
||||||
|
APIGatewayHttpResolver,
|
||||||
|
Response,
|
||||||
|
)
|
||||||
|
from aws_lambda_powertools.event_handler.exceptions import BadRequestError, ServiceError
|
||||||
|
from aws_lambda_powertools.event_handler.middlewares import (
|
||||||
|
BaseMiddlewareHandler,
|
||||||
|
NextMiddleware,
|
||||||
|
)
|
||||||
|
from layercake.dynamodb import ComposeKey, DynamoDBCollection, KeyPair
|
||||||
|
from pydantic import UUID4, BaseModel
|
||||||
|
|
||||||
|
from auth import AuthFlowType
|
||||||
|
|
||||||
|
from .authorizer_middleware import User
|
||||||
|
|
||||||
|
|
||||||
|
class Tenant(BaseModel):
|
||||||
|
id: UUID4 | str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class TenantMiddleware(BaseMiddlewareHandler):
|
||||||
|
def __init__(self, collect: DynamoDBCollection) -> None:
|
||||||
|
self.collect = collect
|
||||||
|
|
||||||
|
def handler(
|
||||||
|
self,
|
||||||
|
app: APIGatewayHttpResolver,
|
||||||
|
next_middleware: NextMiddleware,
|
||||||
|
) -> Response:
|
||||||
|
context = app.current_event.request_context.authorizer.get_lambda
|
||||||
|
auth_flow_type = context.get('auth_flow_type')
|
||||||
|
|
||||||
|
if auth_flow_type == AuthFlowType.API_AUTH:
|
||||||
|
app.append_context(tenant=Tenant(**context['tenant']))
|
||||||
|
|
||||||
|
if auth_flow_type == AuthFlowType.USER_AUTH:
|
||||||
|
app.append_context(
|
||||||
|
tenant=_tenant(
|
||||||
|
app.current_event.headers.get('x-tenant'),
|
||||||
|
app.context.get('user'), # type: ignore
|
||||||
|
collect=self.collect,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return next_middleware(app)
|
||||||
|
|
||||||
|
|
||||||
|
class ForbiddenError(ServiceError):
|
||||||
|
def __init__(self, msg: str):
|
||||||
|
super().__init__(HTTPStatus.FORBIDDEN, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def _tenant(
|
||||||
|
tenant_id: str | None,
|
||||||
|
user: User,
|
||||||
|
/,
|
||||||
|
collect: DynamoDBCollection,
|
||||||
|
) -> Tenant:
|
||||||
|
if not tenant_id:
|
||||||
|
raise BadRequestError('Missing tenant')
|
||||||
|
|
||||||
|
collect.get_item(
|
||||||
|
KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')),
|
||||||
|
exception_cls=ForbiddenError,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tenant_id == '*':
|
||||||
|
return Tenant(id=tenant_id, name='default')
|
||||||
|
|
||||||
|
obj = collect.get_item(KeyPair(tenant_id, '0'))
|
||||||
|
return Tenant.parse_obj(obj)
|
||||||
@@ -9,7 +9,7 @@ from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
|
|||||||
import elastic
|
import elastic
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from course import create_course
|
from course import create_course
|
||||||
from middlewares import AuditLogMiddleware
|
from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware
|
||||||
from models import Course, Org
|
from models import Course, Org
|
||||||
from settings import COURSE_TABLE, ELASTIC_CONN, USER_TABLE
|
from settings import COURSE_TABLE, ELASTIC_CONN, USER_TABLE
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ router = Router()
|
|||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
|
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
collect = DynamoDBCollection(user_layer)
|
user_collect = DynamoDBCollection(user_layer)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -44,13 +44,16 @@ def get_courses():
|
|||||||
compress=True,
|
compress=True,
|
||||||
tags=['Course'],
|
tags=['Course'],
|
||||||
middlewares=[
|
middlewares=[
|
||||||
AuditLogMiddleware('COURSE_ADD', collect, ('id', 'name')),
|
TenantMiddleware(user_collect),
|
||||||
|
AuditLogMiddleware('COURSE_ADD', user_collect, ('id', 'name')),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def post_course(payload: Course):
|
def post_course(payload: Course):
|
||||||
|
tenant: Tenant = router.context['tenant']
|
||||||
|
|
||||||
create_course(
|
create_course(
|
||||||
course=payload,
|
course=payload,
|
||||||
org=Org(id='*', name='default'),
|
org=Org(id=tenant.id, name=tenant.name),
|
||||||
persistence_layer=course_layer,
|
persistence_layer=course_layer,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ def settings():
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'acls': acls['items'],
|
'acls': acls['items'],
|
||||||
# Note: ensure compatibility with search on React's tenant menu
|
# Note: Ensure compatibility with search on React's tenant menu
|
||||||
'tenants': [x | {'id': x['sk']} for x in tenants['items']],
|
'tenants': [x | {'id': x['sk'], 'sk': '0'} for x in tenants['items']],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Globals:
|
|||||||
Architectures:
|
Architectures:
|
||||||
- x86_64
|
- x86_64
|
||||||
Layers:
|
Layers:
|
||||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:28
|
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:30
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ def dynamodb_seeds(dynamodb_client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_app(monkeypatch):
|
def mock_app():
|
||||||
import app
|
import app
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ from layercake.dynamodb import ComposeKey, DynamoDBCollection, PartitionKey
|
|||||||
|
|
||||||
from ..conftest import HttpApiProxy, LambdaContext
|
from ..conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
YEAR_DAYS = 365
|
||||||
|
|
||||||
|
|
||||||
def test_post_course(
|
def test_post_course(
|
||||||
mock_app,
|
mock_app,
|
||||||
|
dynamodb_seeds,
|
||||||
dynamodb_persistence_layer,
|
dynamodb_persistence_layer,
|
||||||
http_api_proxy: HttpApiProxy,
|
http_api_proxy: HttpApiProxy,
|
||||||
lambda_context: LambdaContext,
|
lambda_context: LambdaContext,
|
||||||
@@ -19,9 +22,9 @@ def test_post_course(
|
|||||||
headers={'X-Tenant': '*'},
|
headers={'X-Tenant': '*'},
|
||||||
body={
|
body={
|
||||||
'name': 'pytest',
|
'name': 'pytest',
|
||||||
'access_period': 365,
|
'access_period': YEAR_DAYS,
|
||||||
'cert': {
|
'cert': {
|
||||||
'exp_interval': 730, # 2 years
|
'exp_interval': YEAR_DAYS * 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -35,6 +38,6 @@ def test_post_course(
|
|||||||
|
|
||||||
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||||
logs = collect.get_items(
|
logs = collect.get_items(
|
||||||
PartitionKey(ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='logs'))
|
PartitionKey(ComposeKey('5OxmMjL-ujoR5IMGegQz', prefix='log', delimiter=':'))
|
||||||
)
|
)
|
||||||
print(logs)
|
print(logs)
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ def test_settings(
|
|||||||
],
|
],
|
||||||
'tenants': [
|
'tenants': [
|
||||||
{
|
{
|
||||||
'sk': 'cJtK9SsnJhKPyxESe7g3DG',
|
'sk': '0',
|
||||||
'name': 'Beta Educação',
|
'name': 'Beta Educação',
|
||||||
'id': '5OxmMjL-ujoR5IMGegQz',
|
'id': 'cJtK9SsnJhKPyxESe7g3DG',
|
||||||
'cnpj': '15608435000190',
|
'cnpj': '15608435000190',
|
||||||
'create_date': '2025-03-13T16:36:50.073156-03:00',
|
'create_date': '2025-03-13T16:36:50.073156-03:00',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,3 +7,4 @@
|
|||||||
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2025-03-13T16:36:50.073156-03:00"}, "name": {"S": "Beta Educação"}}
|
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2025-03-13T16:36:50.073156-03:00"}, "name": {"S": "Beta Educação"}}
|
||||||
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}}
|
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}}
|
||||||
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}}
|
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}}
|
||||||
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}}
|
||||||
|
|||||||
@@ -1,31 +1,59 @@
|
|||||||
from http import HTTPMethod
|
from http import HTTPMethod
|
||||||
|
|
||||||
|
import pytest
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayHttpResolver
|
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayHttpResolver
|
||||||
|
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer
|
||||||
|
|
||||||
from middlewares import TenantMiddleware
|
from middlewares import AuthorizerMiddleware, TenantMiddleware
|
||||||
|
|
||||||
from .conftest import HttpApiProxy, LambdaContext
|
from .conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
|
||||||
def test_eval(
|
@pytest.fixture
|
||||||
|
def mock_app(dynamodb_persistence_layer: DynamoDBPersistenceLayer):
|
||||||
|
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||||
|
app = APIGatewayHttpResolver()
|
||||||
|
app.use(middlewares=[AuthorizerMiddleware(), TenantMiddleware(collect)])
|
||||||
|
|
||||||
|
@app.get('/')
|
||||||
|
def index():
|
||||||
|
return app.context.get('tenant')
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_tenant_user_auth(
|
||||||
|
mock_app,
|
||||||
dynamodb_seeds,
|
dynamodb_seeds,
|
||||||
http_api_proxy: HttpApiProxy,
|
http_api_proxy: HttpApiProxy,
|
||||||
lambda_context: LambdaContext,
|
lambda_context: LambdaContext,
|
||||||
):
|
):
|
||||||
app = APIGatewayHttpResolver()
|
# This data was added from seeds
|
||||||
app.use(middlewares=[TenantMiddleware()])
|
result = mock_app(
|
||||||
|
|
||||||
@app.get('/')
|
|
||||||
def index():
|
|
||||||
return {}
|
|
||||||
|
|
||||||
result = app(
|
|
||||||
http_api_proxy(
|
http_api_proxy(
|
||||||
raw_path='/',
|
raw_path='/',
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
headers={'Tenant': 'cJtK9SsnJhKPyxESe7g3DG'},
|
headers={'X-Tenant': 'cJtK9SsnJhKPyxESe7g3DG'},
|
||||||
),
|
),
|
||||||
lambda_context,
|
lambda_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert result['body'] == '{"id":"cJtK9SsnJhKPyxESe7g3DG","name":"EDUSEG"}'
|
||||||
assert result['statusCode'] == 200
|
assert result['statusCode'] == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_tenant_forbidden(
|
||||||
|
mock_app,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
result = mock_app(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/',
|
||||||
|
method=HTTPMethod.GET,
|
||||||
|
headers={'X-Tenant': 'abc'},
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result['statusCode'] == 403
|
||||||
|
|||||||
2
http-api/uv.lock
generated
2
http-api/uv.lock
generated
@@ -467,7 +467,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.1.13"
|
version = "0.1.14"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
|
|||||||
Reference in New Issue
Block a user