wip
This commit is contained in:
1
http-api/.gitignore
vendored
1
http-api/.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
env.json
|
env.json
|
||||||
dynamodb_volume/
|
dynamodb_volume/
|
||||||
elastic_volume/
|
elastic_volume/
|
||||||
|
meili_data/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from typing import Any
|
|||||||
from aws_lambda_powertools import Logger, Tracer
|
from aws_lambda_powertools import Logger, Tracer
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import (
|
from aws_lambda_powertools.event_handler.api_gateway import (
|
||||||
APIGatewayHttpResolver,
|
APIGatewayHttpResolver,
|
||||||
|
CORSConfig,
|
||||||
Response,
|
Response,
|
||||||
content_types,
|
content_types,
|
||||||
)
|
)
|
||||||
@@ -12,20 +13,26 @@ 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, TenantMiddleware
|
||||||
from routes import courses, enrollments, lookup, me, orders, users, webhooks
|
from routes import courses, enrollments, lookup, orders, settings, users, webhooks
|
||||||
|
|
||||||
DEBUG = os.getenv('LOG_LEVEL') == 'DEBUG'
|
DEBUG = 'AWS_SAM_LOCAL' in os.environ
|
||||||
|
|
||||||
tracer = Tracer()
|
tracer = Tracer()
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
app = APIGatewayHttpResolver(enable_validation=True, debug=DEBUG)
|
cors = CORSConfig(
|
||||||
|
allow_origin='*',
|
||||||
|
allow_headers=['Content-Type', 'X-Requested-With', 'Authorization', 'X-Tenant'],
|
||||||
|
max_age=600,
|
||||||
|
allow_credentials=False,
|
||||||
|
)
|
||||||
|
app = APIGatewayHttpResolver(enable_validation=True, cors=cors, debug=DEBUG)
|
||||||
app.use(middlewares=[AuthorizerMiddleware(), TenantMiddleware()])
|
app.use(middlewares=[AuthorizerMiddleware(), TenantMiddleware()])
|
||||||
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')
|
||||||
app.include_router(users.router, prefix='/users')
|
app.include_router(users.router, prefix='/users')
|
||||||
app.include_router(webhooks.router, prefix='/webhooks')
|
app.include_router(webhooks.router, prefix='/webhooks')
|
||||||
app.include_router(me.router, prefix='/me')
|
app.include_router(settings.router, prefix='/settings')
|
||||||
app.include_router(lookup.router, prefix='/lookup')
|
app.include_router(lookup.router, prefix='/lookup')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,19 +48,21 @@ tracer = Tracer()
|
|||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
idp_client = boto3.client('cognito-idp')
|
idp_client = boto3.client('cognito-idp')
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
collect = DynamoDBCollection(user_layer)
|
user_collect = DynamoDBCollection(user_layer)
|
||||||
|
|
||||||
|
|
||||||
@tracer.capture_lambda_handler
|
@tracer.capture_lambda_handler
|
||||||
@logger.inject_lambda_context
|
@logger.inject_lambda_context
|
||||||
@event_source(data_class=APIGatewayAuthorizerEventV2)
|
@event_source(data_class=APIGatewayAuthorizerEventV2)
|
||||||
def lambda_handler(event: APIGatewayAuthorizerEventV2, context: LambdaContext) -> dict:
|
def lambda_handler(event: APIGatewayAuthorizerEventV2, context: LambdaContext) -> dict:
|
||||||
|
"""Authenticates a user using a bearer token (for user or API).
|
||||||
|
Only handles authentication; any additional logic (e.g., tenant) is performed afterward."""
|
||||||
bearer = _parse_bearer_token(event.headers.get('authorization', ''))
|
bearer = _parse_bearer_token(event.headers.get('authorization', ''))
|
||||||
|
|
||||||
if not bearer:
|
if not bearer:
|
||||||
return APIGatewayAuthorizerResponseV2(authorize=False).asdict()
|
return APIGatewayAuthorizerResponseV2(authorize=False).asdict()
|
||||||
|
|
||||||
attrs = _authorizer(bearer).asdict()
|
attrs = _authorizer(bearer, user_collect).asdict()
|
||||||
return APIGatewayAuthorizerResponseV2(**attrs).asdict()
|
return APIGatewayAuthorizerResponseV2(**attrs).asdict()
|
||||||
|
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ class BearerToken:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Authorizer:
|
class AuthorizerResponseV2:
|
||||||
authorize: bool = False
|
authorize: bool = False
|
||||||
context: dict[str, Any] | None = None
|
context: dict[str, Any] | None = None
|
||||||
auth_flow_type: AuthFlowType = AuthFlowType.USER_AUTH
|
auth_flow_type: AuthFlowType = AuthFlowType.USER_AUTH
|
||||||
@@ -92,11 +94,15 @@ class Authorizer:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def _get_apikey(token: str) -> dict[str, dict | str]:
|
def _get_apikey(token: str, /, collect: DynamoDBCollection) -> dict[str, dict | str]:
|
||||||
return collect.get_item(KeyPair('apikey', token))
|
return collect.get_item(KeyPair('apikey', token))
|
||||||
|
|
||||||
|
|
||||||
def _authorizer(bearer: BearerToken) -> Authorizer:
|
def _authorizer(
|
||||||
|
bearer: BearerToken,
|
||||||
|
/,
|
||||||
|
collect: DynamoDBCollection,
|
||||||
|
) -> AuthorizerResponseV2:
|
||||||
"""
|
"""
|
||||||
Build an Authorizer object based on the bearer token's auth type.
|
Build an Authorizer object based on the bearer token's auth type.
|
||||||
|
|
||||||
@@ -113,13 +119,13 @@ def _authorizer(bearer: BearerToken) -> Authorizer:
|
|||||||
try:
|
try:
|
||||||
if bearer.auth_flow_type == AuthFlowType.USER_AUTH:
|
if bearer.auth_flow_type == AuthFlowType.USER_AUTH:
|
||||||
user = get_user(bearer.token, idp_client)
|
user = get_user(bearer.token, idp_client)
|
||||||
return Authorizer(True, {'user': user})
|
return AuthorizerResponseV2(True, {'user': user})
|
||||||
|
|
||||||
apikey = _get_apikey(bearer.token)
|
apikey = _get_apikey(bearer.token, collect)
|
||||||
context = pick(('tenant', 'user'), apikey)
|
context = pick(('tenant', 'user'), apikey)
|
||||||
return Authorizer(True, context, AuthFlowType.API_AUTH)
|
return AuthorizerResponseV2(True, context, AuthFlowType.API_AUTH)
|
||||||
except Exception:
|
except Exception:
|
||||||
return Authorizer()
|
return AuthorizerResponseV2()
|
||||||
|
|
||||||
|
|
||||||
def _parse_bearer_token(s: str) -> BearerToken | None:
|
def _parse_bearer_token(s: str) -> BearerToken | None:
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ services:
|
|||||||
working_dir: /home/dynamodblocal
|
working_dir: /home/dynamodblocal
|
||||||
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
|
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
|
||||||
|
|
||||||
|
meilisearch:
|
||||||
|
container_name: meilisearch
|
||||||
|
image: getmeili/meilisearch:v1.13
|
||||||
|
volumes:
|
||||||
|
- ./meili_data:/meili_data
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 7700:7700
|
||||||
|
|
||||||
elastic:
|
elastic:
|
||||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.3
|
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.3
|
||||||
container_name: elastic
|
container_name: elastic
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ def token(username: str) -> KonvivaToken:
|
|||||||
r = requests.get(url.geturl(), headers=headers)
|
r = requests.get(url.geturl(), headers=headers)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
|
# Because Konviva does not return the proper HTTP status code
|
||||||
if err := glom(r.json(), 'errors.0', default=None):
|
if err := glom(r.json(), 'errors.0', default=None):
|
||||||
raise KonvivaError(err)
|
raise KonvivaError(err)
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,15 @@ class TenantMiddleware(BaseMiddlewareHandler):
|
|||||||
next_middleware: NextMiddleware,
|
next_middleware: NextMiddleware,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
context = app.current_event.request_context.authorizer.get_lambda
|
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')
|
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)
|
return next_middleware(app)
|
||||||
|
|
||||||
|
|
||||||
@@ -108,7 +116,8 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
|
|||||||
ip_addr = req_context.http.source_ip
|
ip_addr = req_context.http.source_ip
|
||||||
response = next_middleware(app)
|
response = next_middleware(app)
|
||||||
|
|
||||||
# Successful request
|
# Successful response
|
||||||
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status#successful_responses
|
||||||
if 200 <= response.status_code < 300 and user:
|
if 200 <= response.status_code < 300 and user:
|
||||||
now_ = now()
|
now_ = now()
|
||||||
data = (
|
data = (
|
||||||
@@ -124,7 +133,8 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
|
|||||||
|
|
||||||
self.collect.put_item(
|
self.collect.put_item(
|
||||||
key=KeyPair(
|
key=KeyPair(
|
||||||
pk=ComposeKey(user.id, prefix='logs'),
|
# Post-migration: remove `delimiter` from ComposeKey.
|
||||||
|
pk=ComposeKey(user.id, prefix='logs', delimiter=':'),
|
||||||
sk=now_.isoformat(),
|
sk=now_.isoformat(),
|
||||||
),
|
),
|
||||||
action=self.action,
|
action=self.action,
|
||||||
|
|||||||
@@ -13,27 +13,28 @@ from settings import USER_TABLE
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
collect = DynamoDBCollection(user_layer)
|
user_collect = DynamoDBCollection(user_layer)
|
||||||
|
|
||||||
|
|
||||||
LIMIT = 25
|
LIMIT = 25
|
||||||
|
|
||||||
|
|
||||||
@router.get('/', include_in_schema=False)
|
@router.get('/', include_in_schema=False)
|
||||||
def me():
|
def settings():
|
||||||
user: User = router.context['user']
|
user: User = router.context['user']
|
||||||
acls = collect.get_items(
|
acls = user_collect.get_items(
|
||||||
KeyPair(user.id, PrefixKey('acls')),
|
KeyPair(user.id, PrefixKey('acls')),
|
||||||
limit=LIMIT,
|
limit=LIMIT,
|
||||||
)
|
)
|
||||||
workspaces = collect.get_items(
|
tenants = user_collect.get_items(
|
||||||
KeyPair(user.id, PrefixKey('orgs')),
|
KeyPair(user.id, PrefixKey('orgs')),
|
||||||
limit=LIMIT,
|
limit=LIMIT,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'acls': acls['items'],
|
'acls': acls['items'],
|
||||||
'workspaces': workspaces['items'],
|
# Note: ensure compatibility with search on React's tenant menu
|
||||||
|
'tenants': [x | {'id': x['sk']} for x in tenants['items']],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -33,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, exception_cls=BadRequestError)
|
user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError)
|
||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ def get_users():
|
|||||||
compress=True,
|
compress=True,
|
||||||
tags=['User'],
|
tags=['User'],
|
||||||
summary='Create user',
|
summary='Create user',
|
||||||
middlewares=[AuditLogMiddleware('USER_ADD', collect)],
|
middlewares=[AuditLogMiddleware('USER_ADD', user_collect)],
|
||||||
)
|
)
|
||||||
def post_user(payload: User):
|
def post_user(payload: User):
|
||||||
return Response(status_code=HTTPStatus.CREATED)
|
return Response(status_code=HTTPStatus.CREATED)
|
||||||
@@ -84,7 +84,7 @@ def patch_reset(id: str, payload: NewPasswordPayload):
|
|||||||
summary='Get user',
|
summary='Get user',
|
||||||
)
|
)
|
||||||
def get_user(id: str):
|
def get_user(id: str):
|
||||||
return collect.get_item(KeyPair(id, '0'))
|
return user_collect.get_item(KeyPair(id, '0'))
|
||||||
|
|
||||||
|
|
||||||
@router.get('/<id>/idp', compress=True, include_in_schema=False)
|
@router.get('/<id>/idp', compress=True, include_in_schema=False)
|
||||||
@@ -99,7 +99,7 @@ def get_idp(id: str):
|
|||||||
summary='Get user emails',
|
summary='Get user emails',
|
||||||
)
|
)
|
||||||
def get_emails(id: str):
|
def get_emails(id: str):
|
||||||
return collect.get_items(
|
return user_collect.get_items(
|
||||||
KeyPair(id, PrefixKey('emails')),
|
KeyPair(id, PrefixKey('emails')),
|
||||||
start_key=router.current_event.get_query_string_value('start_key', None),
|
start_key=router.current_event.get_query_string_value('start_key', None),
|
||||||
)
|
)
|
||||||
@@ -112,7 +112,7 @@ def get_emails(id: str):
|
|||||||
summary='Get user logs',
|
summary='Get user logs',
|
||||||
)
|
)
|
||||||
def get_logs(id: str):
|
def get_logs(id: str):
|
||||||
return collect.get_items(
|
return user_collect.get_items(
|
||||||
# Post-migration: uncomment to enable PartitionKey with a composite key (id with `logs` prefix).
|
# Post-migration: uncomment to enable PartitionKey with a composite key (id with `logs` prefix).
|
||||||
# PartitionKey(ComposeKey(id, prefix='logs')),
|
# PartitionKey(ComposeKey(id, prefix='logs')),
|
||||||
PartitionKey(ComposeKey(id, prefix='log', delimiter=':')),
|
PartitionKey(ComposeKey(id, prefix='log', delimiter=':')),
|
||||||
@@ -127,7 +127,7 @@ def get_logs(id: str):
|
|||||||
summary='Get user orgs',
|
summary='Get user orgs',
|
||||||
)
|
)
|
||||||
def get_orgs(id: str):
|
def get_orgs(id: str):
|
||||||
return collect.get_items(
|
return user_collect.get_items(
|
||||||
KeyPair(id, PrefixKey('orgs')),
|
KeyPair(id, PrefixKey('orgs')),
|
||||||
start_key=router.current_event.get_query_string_value('start_key', None),
|
start_key=router.current_event.get_query_string_value('start_key', None),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Globals:
|
|||||||
Architectures:
|
Architectures:
|
||||||
- x86_64
|
- x86_64
|
||||||
Layers:
|
Layers:
|
||||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:26
|
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:28
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
@@ -39,6 +39,8 @@ Globals:
|
|||||||
ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}"
|
ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}"
|
||||||
KONVIVA_API_URL: https://saladeaula.digital
|
KONVIVA_API_URL: https://saladeaula.digital
|
||||||
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}"
|
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}"
|
||||||
|
MEILISEARCH_HOST: https://meili.vps.eduseg.com.br
|
||||||
|
MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}"
|
||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
HttpLog:
|
HttpLog:
|
||||||
@@ -53,6 +55,8 @@ Resources:
|
|||||||
AllowOrigins: ["*"]
|
AllowOrigins: ["*"]
|
||||||
AllowMethods: [GET, POST, PUT, DELETE, PATCH, OPTIONS]
|
AllowMethods: [GET, POST, PUT, DELETE, PATCH, OPTIONS]
|
||||||
AllowHeaders: [Content-Type, X-Requested-With, Authorization, X-Tenant]
|
AllowHeaders: [Content-Type, X-Requested-With, Authorization, X-Tenant]
|
||||||
|
AllowCredentials: false
|
||||||
|
MaxAge: 600
|
||||||
Auth:
|
Auth:
|
||||||
DefaultAuthorizer: LambdaRequestAuthorizer
|
DefaultAuthorizer: LambdaRequestAuthorizer
|
||||||
Authorizers:
|
Authorizers:
|
||||||
@@ -63,6 +67,7 @@ Resources:
|
|||||||
EnableSimpleResponses: true
|
EnableSimpleResponses: true
|
||||||
Identity:
|
Identity:
|
||||||
Headers: [Authorization]
|
Headers: [Authorization]
|
||||||
|
ReauthorizeEvery: 300
|
||||||
|
|
||||||
HttpApiFunction:
|
HttpApiFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
@@ -76,6 +81,14 @@ Resources:
|
|||||||
- DynamoDBCrudPolicy:
|
- DynamoDBCrudPolicy:
|
||||||
TableName: !Ref CourseTable
|
TableName: !Ref CourseTable
|
||||||
Events:
|
Events:
|
||||||
|
Preflight:
|
||||||
|
Type: HttpApi
|
||||||
|
Properties:
|
||||||
|
Path: /{proxy+}
|
||||||
|
Method: OPTIONS
|
||||||
|
ApiId: !Ref HttpApi
|
||||||
|
Auth:
|
||||||
|
Authorizer: NONE
|
||||||
AnyRequest:
|
AnyRequest:
|
||||||
Type: HttpApi
|
Type: HttpApi
|
||||||
Properties:
|
Properties:
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class HttpApiProxy:
|
|||||||
body: dict = {},
|
body: dict = {},
|
||||||
*,
|
*,
|
||||||
headers: dict = {},
|
headers: dict = {},
|
||||||
|
auth_flow_type: str = 'USER_AUTH',
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return {
|
return {
|
||||||
@@ -59,7 +60,7 @@ class HttpApiProxy:
|
|||||||
'custom:user_id': '5OxmMjL-ujoR5IMGegQz',
|
'custom:user_id': '5OxmMjL-ujoR5IMGegQz',
|
||||||
'sub': 'c4f30dbd-083e-4b84-aa50-c31afe9b9c01',
|
'sub': 'c4f30dbd-083e-4b84-aa50-c31afe9b9c01',
|
||||||
},
|
},
|
||||||
'auth_flow_type': 'USER_AUTH',
|
'auth_flow_type': auth_flow_type,
|
||||||
},
|
},
|
||||||
'jwt': {
|
'jwt': {
|
||||||
'claims': {'claim1': 'value1', 'claim2': 'value2'},
|
'claims': {'claim1': 'value1', 'claim2': 'value2'},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from http import HTTPMethod, HTTPStatus
|
|||||||
from ..conftest import HttpApiProxy, LambdaContext
|
from ..conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
|
||||||
def test_me(
|
def test_settings(
|
||||||
mock_app,
|
mock_app,
|
||||||
dynamodb_seeds,
|
dynamodb_seeds,
|
||||||
http_api_proxy: HttpApiProxy,
|
http_api_proxy: HttpApiProxy,
|
||||||
@@ -12,10 +12,7 @@ def test_me(
|
|||||||
):
|
):
|
||||||
# This data was added from seeds
|
# This data was added from seeds
|
||||||
r = mock_app.lambda_handler(
|
r = mock_app.lambda_handler(
|
||||||
http_api_proxy(
|
http_api_proxy(raw_path='/settings', method=HTTPMethod.GET),
|
||||||
raw_path='/me',
|
|
||||||
method=HTTPMethod.GET,
|
|
||||||
),
|
|
||||||
lambda_context,
|
lambda_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,7 +34,7 @@ def test_me(
|
|||||||
'roles': ['ADMIN'],
|
'roles': ['ADMIN'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'workspaces': [
|
'tenants': [
|
||||||
{
|
{
|
||||||
'sk': 'cJtK9SsnJhKPyxESe7g3DG',
|
'sk': 'cJtK9SsnJhKPyxESe7g3DG',
|
||||||
'name': 'Beta Educação',
|
'name': 'Beta Educação',
|
||||||
@@ -5,8 +5,6 @@ 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',
|
||||||
@@ -32,11 +30,7 @@ def test_bearer_jwt(lambda_context: LambdaContext):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_bearer_apikey(
|
def test_bearer_apikey(dynamodb_seeds, lambda_context: LambdaContext):
|
||||||
monkeypatch,
|
|
||||||
dynamodb_seeds,
|
|
||||||
lambda_context: LambdaContext,
|
|
||||||
):
|
|
||||||
event = {
|
event = {
|
||||||
'headers': {
|
'headers': {
|
||||||
'authorization': 'Bearer sk-MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw',
|
'authorization': 'Bearer sk-MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw',
|
||||||
|
|||||||
31
http-api/tests/test_middelwares.py
Normal file
31
http-api/tests/test_middelwares.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from http import HTTPMethod
|
||||||
|
|
||||||
|
from aws_lambda_powertools.event_handler.api_gateway import APIGatewayHttpResolver
|
||||||
|
|
||||||
|
from middlewares import TenantMiddleware
|
||||||
|
|
||||||
|
from .conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
|
||||||
|
def test_eval(
|
||||||
|
dynamodb_seeds,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
app = APIGatewayHttpResolver()
|
||||||
|
app.use(middlewares=[TenantMiddleware()])
|
||||||
|
|
||||||
|
@app.get('/')
|
||||||
|
def index():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
result = app(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/',
|
||||||
|
method=HTTPMethod.GET,
|
||||||
|
headers={'Tenant': 'cJtK9SsnJhKPyxESe7g3DG'},
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result['statusCode'] == 200
|
||||||
42
http-api/uv.lock
generated
42
http-api/uv.lock
generated
@@ -10,6 +10,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arnparse"
|
||||||
|
version = "0.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bd/42/949284e998282b167e273872fa9c39b06d41a6055163c30aa2daaeee76a0/arnparse-0.0.2.tar.gz", hash = "sha256:cb87f17200d07121108a9085d4a09cc69a55582647776b9a917b0b1f279db8f8", size = 2677 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/6f/630bedeb32964e99661990811a66389201b62c047b35c17e332dad9be2a3/arnparse-0.0.2-py2.py3-none-any.whl", hash = "sha256:b0906734e4b8f19e39b1e32944c6cd6274b6da90c066a83882ac7a11d27553e0", size = 2904 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "25.3.0"
|
version = "25.3.0"
|
||||||
@@ -107,6 +116,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/2d/5b/f96cf58c37704b907ac2f9cc94e45ba0a2aa3b2062421aa8b8614f1d78de/botocore-1.37.20-py3-none-any.whl", hash = "sha256:c34f4f25fda7c4f726adf5a948590bd6bd7892c05278d31e344b5908e7b43301", size = 13432464 },
|
{ url = "https://files.pythonhosted.org/packages/2d/5b/f96cf58c37704b907ac2f9cc94e45ba0a2aa3b2062421aa8b8614f1d78de/botocore-1.37.20-py3-none-any.whl", hash = "sha256:c34f4f25fda7c4f726adf5a948590bd6bd7892c05278d31e344b5908e7b43301", size = 13432464 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "camel-converter"
|
||||||
|
version = "4.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/3d/dd783586dc0c4aee5b6b88489666fdb2c0c344ea0aa8a5c10746cc423707/camel_converter-4.0.1.tar.gz", hash = "sha256:401414549ae4ac4073e38cdc4aa6d464dc534fc40aa06ff787bf0960b0c86535", size = 38915 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/e5/806359514cc8305f047bd6d39d63890298c0596f7328b534059724bd1a9e/camel_converter-4.0.1-py3-none-any.whl", hash = "sha256:0cba7ca1354a29ca2191983deecc9dcf28889f606c28d6ed18ac7d4586b163ac", size = 6243 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
pydantic = [
|
||||||
|
{ name = "pydantic" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2025.1.31"
|
version = "2025.1.31"
|
||||||
@@ -444,15 +467,17 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.1.11"
|
version = "0.1.13"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "arnparse" },
|
||||||
{ name = "aws-lambda-powertools", extra = ["all"] },
|
{ name = "aws-lambda-powertools", extra = ["all"] },
|
||||||
{ name = "boto3" },
|
{ name = "boto3" },
|
||||||
{ name = "elasticsearch" },
|
{ name = "elasticsearch" },
|
||||||
{ name = "elasticsearch-dsl" },
|
{ name = "elasticsearch-dsl" },
|
||||||
{ name = "ftfy" },
|
{ name = "ftfy" },
|
||||||
{ name = "glom" },
|
{ name = "glom" },
|
||||||
|
{ name = "meilisearch" },
|
||||||
{ name = "orjson" },
|
{ name = "orjson" },
|
||||||
{ name = "pycpfcnpj" },
|
{ name = "pycpfcnpj" },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
@@ -464,12 +489,14 @@ dependencies = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "arnparse", specifier = ">=0.0.2" },
|
||||||
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.8.0" },
|
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.8.0" },
|
||||||
{ name = "boto3", specifier = ">=1.37.16" },
|
{ name = "boto3", specifier = ">=1.37.16" },
|
||||||
{ name = "elasticsearch", specifier = ">=8.17.2" },
|
{ name = "elasticsearch", specifier = ">=8.17.2" },
|
||||||
{ name = "elasticsearch-dsl", specifier = ">=8.17.1" },
|
{ name = "elasticsearch-dsl", specifier = ">=8.17.1" },
|
||||||
{ name = "ftfy", specifier = ">=6.3.1" },
|
{ name = "ftfy", specifier = ">=6.3.1" },
|
||||||
{ name = "glom", specifier = ">=24.11.0" },
|
{ name = "glom", specifier = ">=24.11.0" },
|
||||||
|
{ name = "meilisearch", specifier = ">=0.34.0" },
|
||||||
{ name = "orjson", specifier = ">=3.10.15" },
|
{ name = "orjson", specifier = ">=3.10.15" },
|
||||||
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
||||||
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
||||||
@@ -488,6 +515,19 @@ dev = [
|
|||||||
{ name = "ruff", specifier = ">=0.11.1" },
|
{ name = "ruff", specifier = ">=0.11.1" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "meilisearch"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "camel-converter", extra = ["pydantic"] },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/44/42/b6a62f355057521c0d9df44a402205e3037299fdcb9cee4dfa22eebd22f0/meilisearch-0.34.0.tar.gz", hash = "sha256:6244af23fa118f5a127ebf3f1297ea8d1d73324bf189b13d61cc201e18cd9e90", size = 23623 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/2f/264c07a3f488260ea36c78cbc201b76e6baf9ef92e0c7f78657a6a5e5f22/meilisearch-0.34.0-py3-none-any.whl", hash = "sha256:fae8ad2a15d12c27fa0a1fff2ae2e4e3e2e22b869950408d63c87e2c095a9f61", size = 24373 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orjson"
|
name = "orjson"
|
||||||
version = "3.10.16"
|
version = "3.10.16"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.1.11"
|
version = "0.1.13"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
@@ -21,6 +21,8 @@ dependencies = [
|
|||||||
"pytz>=2025.1",
|
"pytz>=2025.1",
|
||||||
"shortuuid>=1.0.13",
|
"shortuuid>=1.0.13",
|
||||||
"requests>=2.32.3",
|
"requests>=2.32.3",
|
||||||
|
"meilisearch>=0.34.0",
|
||||||
|
"arnparse>=0.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
42
layercake/uv.lock
generated
42
layercake/uv.lock
generated
@@ -10,6 +10,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arnparse"
|
||||||
|
version = "0.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bd/42/949284e998282b167e273872fa9c39b06d41a6055163c30aa2daaeee76a0/arnparse-0.0.2.tar.gz", hash = "sha256:cb87f17200d07121108a9085d4a09cc69a55582647776b9a917b0b1f279db8f8", size = 2677 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/6f/630bedeb32964e99661990811a66389201b62c047b35c17e332dad9be2a3/arnparse-0.0.2-py2.py3-none-any.whl", hash = "sha256:b0906734e4b8f19e39b1e32944c6cd6274b6da90c066a83882ac7a11d27553e0", size = 2904 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "25.3.0"
|
version = "25.3.0"
|
||||||
@@ -107,6 +116,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d2/e9/98d3f5135cce841b54a8952f244db906511acba624252559e21188f84e90/botocore-1.37.16-py3-none-any.whl", hash = "sha256:d74d04830ead12933a96dc407175ae98b32a5dd0059d7d2b28fc7aa4ed9d3b48", size = 13422674 },
|
{ url = "https://files.pythonhosted.org/packages/d2/e9/98d3f5135cce841b54a8952f244db906511acba624252559e21188f84e90/botocore-1.37.16-py3-none-any.whl", hash = "sha256:d74d04830ead12933a96dc407175ae98b32a5dd0059d7d2b28fc7aa4ed9d3b48", size = 13422674 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "camel-converter"
|
||||||
|
version = "4.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/3d/dd783586dc0c4aee5b6b88489666fdb2c0c344ea0aa8a5c10746cc423707/camel_converter-4.0.1.tar.gz", hash = "sha256:401414549ae4ac4073e38cdc4aa6d464dc534fc40aa06ff787bf0960b0c86535", size = 38915 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/e5/806359514cc8305f047bd6d39d63890298c0596f7328b534059724bd1a9e/camel_converter-4.0.1-py3-none-any.whl", hash = "sha256:0cba7ca1354a29ca2191983deecc9dcf28889f606c28d6ed18ac7d4586b163ac", size = 6243 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
pydantic = [
|
||||||
|
{ name = "pydantic" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2025.1.31"
|
version = "2025.1.31"
|
||||||
@@ -465,15 +488,17 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.1.10"
|
version = "0.1.12"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "arnparse" },
|
||||||
{ name = "aws-lambda-powertools", extra = ["all"] },
|
{ name = "aws-lambda-powertools", extra = ["all"] },
|
||||||
{ name = "boto3" },
|
{ name = "boto3" },
|
||||||
{ name = "elasticsearch" },
|
{ name = "elasticsearch" },
|
||||||
{ name = "elasticsearch-dsl" },
|
{ name = "elasticsearch-dsl" },
|
||||||
{ name = "ftfy" },
|
{ name = "ftfy" },
|
||||||
{ name = "glom" },
|
{ name = "glom" },
|
||||||
|
{ name = "meilisearch" },
|
||||||
{ name = "orjson" },
|
{ name = "orjson" },
|
||||||
{ name = "pycpfcnpj" },
|
{ name = "pycpfcnpj" },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
@@ -494,12 +519,14 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "arnparse", specifier = ">=0.0.2" },
|
||||||
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.8.0" },
|
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.8.0" },
|
||||||
{ name = "boto3", specifier = ">=1.37.16" },
|
{ name = "boto3", specifier = ">=1.37.16" },
|
||||||
{ name = "elasticsearch", specifier = ">=8.17.2" },
|
{ name = "elasticsearch", specifier = ">=8.17.2" },
|
||||||
{ name = "elasticsearch-dsl", specifier = ">=8.17.1" },
|
{ name = "elasticsearch-dsl", specifier = ">=8.17.1" },
|
||||||
{ name = "ftfy", specifier = ">=6.3.1" },
|
{ name = "ftfy", specifier = ">=6.3.1" },
|
||||||
{ name = "glom", specifier = ">=24.11.0" },
|
{ name = "glom", specifier = ">=24.11.0" },
|
||||||
|
{ name = "meilisearch", specifier = ">=0.34.0" },
|
||||||
{ name = "orjson", specifier = ">=3.10.15" },
|
{ name = "orjson", specifier = ">=3.10.15" },
|
||||||
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
||||||
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
||||||
@@ -565,6 +592,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "meilisearch"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "camel-converter", extra = ["pydantic"] },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/44/42/b6a62f355057521c0d9df44a402205e3037299fdcb9cee4dfa22eebd22f0/meilisearch-0.34.0.tar.gz", hash = "sha256:6244af23fa118f5a127ebf3f1297ea8d1d73324bf189b13d61cc201e18cd9e90", size = 23623 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/2f/264c07a3f488260ea36c78cbc201b76e6baf9ef92e0c7f78657a6a5e5f22/meilisearch-0.34.0-py3-none-any.whl", hash = "sha256:fae8ad2a15d12c27fa0a1fff2ae2e4e3e2e22b869950408d63c87e2c095a9f61", size = 24373 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mergedeep"
|
name = "mergedeep"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
|
|||||||
Reference in New Issue
Block a user