add scope to id
This commit is contained in:
@@ -3,7 +3,7 @@ import os
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
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,
|
CORSConfig,
|
||||||
@@ -17,6 +17,7 @@ from json_encoder import JSONEncoder
|
|||||||
from routes import courses, enrollments, orders, users
|
from routes import courses, enrollments, orders, users
|
||||||
|
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
|
tracer = Tracer()
|
||||||
debug = 'AWS_SAM_LOCAL' in os.environ
|
debug = 'AWS_SAM_LOCAL' in os.environ
|
||||||
serializer = partial(json.dumps, separators=(',', ':'), cls=JSONEncoder)
|
serializer = partial(json.dumps, separators=(',', ':'), cls=JSONEncoder)
|
||||||
cors = CORSConfig(
|
cors = CORSConfig(
|
||||||
@@ -26,7 +27,10 @@ cors = CORSConfig(
|
|||||||
allow_credentials=False,
|
allow_credentials=False,
|
||||||
)
|
)
|
||||||
app = APIGatewayHttpResolver(
|
app = APIGatewayHttpResolver(
|
||||||
enable_validation=True, cors=cors, debug=debug, serializer=serializer
|
enable_validation=True,
|
||||||
|
cors=cors,
|
||||||
|
debug=debug,
|
||||||
|
serializer=serializer,
|
||||||
)
|
)
|
||||||
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')
|
||||||
@@ -52,5 +56,6 @@ def exc_error(exc: ServiceError):
|
|||||||
|
|
||||||
|
|
||||||
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
|
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
|
||||||
|
@tracer.capture_lambda_handler
|
||||||
def lambda_handler(event: dict[str, Any], context: LambdaContext) -> dict[str, Any]:
|
def lambda_handler(event: dict[str, Any], context: LambdaContext) -> dict[str, Any]:
|
||||||
return app.resolve(event, context)
|
return app.resolve(event, context)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from aws_lambda_powertools.event_handler.exceptions import (
|
from aws_lambda_powertools.event_handler.exceptions import (
|
||||||
@@ -20,6 +22,7 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
|||||||
|
|
||||||
@router.get('/<user_id>')
|
@router.get('/<user_id>')
|
||||||
def get_user(user_id: str):
|
def get_user(user_id: str):
|
||||||
|
time.sleep(2)
|
||||||
return dyn.collection.get_item(
|
return dyn.collection.get_item(
|
||||||
KeyPair(user_id, '0'),
|
KeyPair(user_id, '0'),
|
||||||
exc_cls=NotFoundError,
|
exc_cls=NotFoundError,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
@@ -12,6 +14,7 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
|||||||
|
|
||||||
@router.get('/<user_id>/emails')
|
@router.get('/<user_id>/emails')
|
||||||
def get_emails(user_id: str):
|
def get_emails(user_id: str):
|
||||||
|
time.sleep(1)
|
||||||
start_key = router.current_event.get_query_string_value('start_key', None)
|
start_key = router.current_event.get_query_string_value('start_key', None)
|
||||||
|
|
||||||
return dyn.collection.query(
|
return dyn.collection.query(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
@@ -12,6 +14,7 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
|||||||
|
|
||||||
@router.get('/<user_id>/orgs')
|
@router.get('/<user_id>/orgs')
|
||||||
def get_orgs(user_id: str):
|
def get_orgs(user_id: str):
|
||||||
|
time.sleep(1)
|
||||||
start_key = router.current_event.get_query_string_value('start_key', None)
|
start_key = router.current_event.get_query_string_value('start_key', None)
|
||||||
|
|
||||||
return dyn.collection.query(
|
return dyn.collection.query(
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
|
import json
|
||||||
from http import HTTPMethod, HTTPStatus
|
from http import HTTPMethod, HTTPStatus
|
||||||
|
|
||||||
from ..conftest import HttpApiProxy, LambdaContext
|
from ..conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_emails(
|
||||||
|
app,
|
||||||
|
seeds,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
r = app.lambda_handler(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails',
|
||||||
|
method=HTTPMethod.GET,
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
|
|
||||||
|
body = json.loads(r['body'])
|
||||||
|
assert len(body['items']) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_get_orgs(
|
def test_get_orgs(
|
||||||
app,
|
app,
|
||||||
seeds,
|
seeds,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
{"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest" }
|
{"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest" }
|
||||||
|
|
||||||
|
// User emails
|
||||||
|
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "emails#sergio@somosbeta.com.br"}
|
||||||
|
|
||||||
// User orgs
|
// User orgs
|
||||||
{"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#286f7729-7765-482a-880a-0b153ea799be", "name": "ACME", "cnpj": "00000000000191"}
|
{"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#286f7729-7765-482a-880a-0b153ea799be", "name": "ACME", "cnpj": "00000000000191"}
|
||||||
@@ -39,14 +39,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
':access_expires_at': access_expires_at,
|
':access_expires_at': access_expires_at,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
transact.put(
|
|
||||||
item={
|
|
||||||
'id': new_image['id'],
|
|
||||||
'sk': 'METADATA#DEDUPLICATION_WINDOW',
|
|
||||||
'offset_days': 90,
|
|
||||||
'created_at': now_,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from typing import Generator
|
|||||||
import jsonlines
|
import jsonlines
|
||||||
from aws_lambda_powertools.shared.json_encoder import Encoder
|
from aws_lambda_powertools.shared.json_encoder import Encoder
|
||||||
from layercake.dynamodb import deserialize
|
from layercake.dynamodb import deserialize
|
||||||
|
from layercake.strutils import md5_hash
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +43,12 @@ if __name__ == '__main__':
|
|||||||
with sqlite3.connect('mydatabase.db') as conn:
|
with sqlite3.connect('mydatabase.db') as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
'CREATE TABLE IF NOT EXISTS %s (pk TEXT, sk TEXT, json JSON)'
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
_id TEXT PRIMARY KEY,
|
||||||
|
json JSON NOT NULL
|
||||||
|
)
|
||||||
|
"""
|
||||||
% table_name
|
% table_name
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,12 +56,19 @@ if __name__ == '__main__':
|
|||||||
readlines(input_dirpath),
|
readlines(input_dirpath),
|
||||||
desc=f'⏳ Inserting into table {table_name}',
|
desc=f'⏳ Inserting into table {table_name}',
|
||||||
):
|
):
|
||||||
|
_id = md5_hash(
|
||||||
|
str(
|
||||||
|
{
|
||||||
|
'id': record['id'],
|
||||||
|
'sk': record['sk'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
'INSERT INTO %s (pk, sk, json) VALUES (:pk, :sk, :json)'
|
'INSERT INTO %s (_id, json) VALUES (:_id, :json)' % table_name,
|
||||||
% table_name,
|
|
||||||
{
|
{
|
||||||
'pk': record['id'],
|
'_id': _id,
|
||||||
'sk': record['sk'],
|
|
||||||
'json': record,
|
'json': record,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ dev = [
|
|||||||
"pycouchdb>=1.16.0",
|
"pycouchdb>=1.16.0",
|
||||||
"pytest>=8.3.4",
|
"pytest>=8.3.4",
|
||||||
"pytest-cov>=6.0.0",
|
"pytest-cov>=6.0.0",
|
||||||
|
"python-slugify>=8.0.4",
|
||||||
"ruff>=0.9.1",
|
"ruff>=0.9.1",
|
||||||
"sqlite-utils>=3.38",
|
"sqlite-utils>=3.38",
|
||||||
"tqdm>=4.67.1",
|
"tqdm>=4.67.1",
|
||||||
|
|||||||
23
http-api/uv.lock
generated
23
http-api/uv.lock
generated
@@ -435,6 +435,7 @@ dev = [
|
|||||||
{ name = "pycouchdb" },
|
{ name = "pycouchdb" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "pytest-cov" },
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "python-slugify" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
{ name = "sqlite-utils" },
|
{ name = "sqlite-utils" },
|
||||||
{ name = "tqdm" },
|
{ name = "tqdm" },
|
||||||
@@ -451,6 +452,7 @@ dev = [
|
|||||||
{ name = "pycouchdb", specifier = ">=1.16.0" },
|
{ name = "pycouchdb", specifier = ">=1.16.0" },
|
||||||
{ name = "pytest", specifier = ">=8.3.4" },
|
{ name = "pytest", specifier = ">=8.3.4" },
|
||||||
{ name = "pytest-cov", specifier = ">=6.0.0" },
|
{ name = "pytest-cov", specifier = ">=6.0.0" },
|
||||||
|
{ name = "python-slugify", specifier = ">=8.0.4" },
|
||||||
{ name = "ruff", specifier = ">=0.9.1" },
|
{ name = "ruff", specifier = ">=0.9.1" },
|
||||||
{ name = "sqlite-utils", specifier = ">=3.38" },
|
{ name = "sqlite-utils", specifier = ">=3.38" },
|
||||||
{ name = "tqdm", specifier = ">=4.67.1" },
|
{ name = "tqdm", specifier = ">=4.67.1" },
|
||||||
@@ -937,6 +939,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-slugify"
|
||||||
|
version = "8.0.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "text-unidecode" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2025.2"
|
version = "2025.2"
|
||||||
@@ -1059,6 +1073,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" },
|
{ url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "text-unidecode"
|
||||||
|
version = "1.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tqdm"
|
name = "tqdm"
|
||||||
version = "4.67.1"
|
version = "4.67.1"
|
||||||
|
|||||||
@@ -9,14 +9,9 @@ OAUTH2_SCOPES_SUPPORTED: list[str] = [
|
|||||||
'profile',
|
'profile',
|
||||||
'email',
|
'email',
|
||||||
'offline_access',
|
'offline_access',
|
||||||
'read:users',
|
'apps:admin',
|
||||||
'write:users',
|
'apps:studio',
|
||||||
'read:enrollments',
|
'apps:insights',
|
||||||
'write:enrollments',
|
|
||||||
'read:orders',
|
|
||||||
'write:orders',
|
|
||||||
'read:courses',
|
|
||||||
'write:courses',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
SESSION_EXPIRES_IN = 86400 * 30 # 30 days
|
SESSION_EXPIRES_IN = 86400 * 30 # 30 days
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
from authlib.oauth2 import ResourceProtector as _ResourceProtector
|
from authlib.oauth2 import ResourceProtector as _ResourceProtector
|
||||||
|
from aws_lambda_powertools.event_handler import APIGatewayHttpResolver
|
||||||
|
from aws_lambda_powertools.event_handler.middlewares import NextMiddleware
|
||||||
|
|
||||||
|
from .requests import APIGatewayJsonRequest
|
||||||
|
|
||||||
|
|
||||||
class ResourceProtector(_ResourceProtector): ...
|
class ResourceProtector(_ResourceProtector):
|
||||||
|
def __call__(self, scopes=None, optional=False, **kwargs):
|
||||||
|
claims = kwargs
|
||||||
|
# backward compatibility
|
||||||
|
claims['scopes'] = scopes
|
||||||
|
|
||||||
|
def wrapper(app: APIGatewayHttpResolver, next_middleware: NextMiddleware):
|
||||||
|
request = APIGatewayJsonRequest(app.current_event)
|
||||||
|
print(request)
|
||||||
|
return next_middleware(app)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from authlib.oauth2.rfc6749 import (
|
from authlib.oauth2.rfc6749 import (
|
||||||
AuthorizationCodeMixin,
|
AuthorizationCodeMixin,
|
||||||
@@ -15,6 +15,7 @@ class User:
|
|||||||
name: str
|
name: str
|
||||||
email: str
|
email: str
|
||||||
email_verified: bool = False
|
email_verified: bool = False
|
||||||
|
scope: list[str] | None = None
|
||||||
|
|
||||||
def get_user_id(self):
|
def get_user_id(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from os import rename
|
||||||
|
|
||||||
from authlib.common.security import generate_token
|
from authlib.common.security import generate_token
|
||||||
from authlib.common.urls import add_params_to_uri
|
from authlib.common.urls import add_params_to_uri
|
||||||
from authlib.jose import JsonWebKey
|
from authlib.jose import JsonWebKey
|
||||||
@@ -26,6 +28,7 @@ from config import ISSUER, OAUTH2_SCOPES_SUPPORTED, OAUTH2_TABLE
|
|||||||
from integrations.apigateway_oauth2.authorization_server import (
|
from integrations.apigateway_oauth2.authorization_server import (
|
||||||
AuthorizationServer,
|
AuthorizationServer,
|
||||||
)
|
)
|
||||||
|
from integrations.apigateway_oauth2.resource_protector import ResourceProtector
|
||||||
from integrations.apigateway_oauth2.tokens import (
|
from integrations.apigateway_oauth2.tokens import (
|
||||||
OAuth2AuthorizationCode,
|
OAuth2AuthorizationCode,
|
||||||
OAuth2Token,
|
OAuth2Token,
|
||||||
@@ -65,15 +68,18 @@ class OpenIDCode(OpenIDCode_):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generate_user_info(self, user: User, scope: str) -> UserInfo:
|
def generate_user_info(self, user: User, scope: str) -> UserInfo:
|
||||||
print(scope)
|
user_info = UserInfo(
|
||||||
print('--' * 100)
|
|
||||||
return UserInfo(
|
|
||||||
sub=user.id,
|
sub=user.id,
|
||||||
name=user.name,
|
name=user.name,
|
||||||
email=user.email,
|
email=user.email,
|
||||||
email_verified=user.email_verified,
|
email_verified=user.email_verified,
|
||||||
).filter(scope)
|
).filter(scope)
|
||||||
|
|
||||||
|
if user.scope:
|
||||||
|
user_info['scope'] = ' '.join(user.scope)
|
||||||
|
|
||||||
|
return user_info
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
|
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
|
||||||
TOKEN_ENDPOINT_AUTH_METHODS = [
|
TOKEN_ENDPOINT_AUTH_METHODS = [
|
||||||
@@ -166,20 +172,22 @@ class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
|
|||||||
authorization_code: OAuth2AuthorizationCode,
|
authorization_code: OAuth2AuthorizationCode,
|
||||||
) -> User:
|
) -> User:
|
||||||
"""Authenticate the user related to this authorization_code."""
|
"""Authenticate the user related to this authorization_code."""
|
||||||
user = dyn.get_item(
|
user = dyn.collection.get_items(
|
||||||
KeyPair(
|
TransactKey(authorization_code.user_id)
|
||||||
pk=authorization_code.user_id,
|
+ SortKey('0')
|
||||||
sk='0',
|
+ SortKey('SCOPE', path_spec='scope', rename_key='scope'),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return User(**pick(('id', 'name', 'email', 'email_verified'), user))
|
return User(**pick(('id', 'name', 'email', 'email_verified', 'scope'), user))
|
||||||
|
|
||||||
|
|
||||||
class TokenExchangeGrant(grants.BaseGrant):
|
class TokenExchangeGrant(grants.BaseGrant):
|
||||||
GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange'
|
GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange'
|
||||||
|
|
||||||
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']
|
TOKEN_ENDPOINT_AUTH_METHODS = [
|
||||||
|
'client_secret_basic',
|
||||||
|
'client_secret_post',
|
||||||
|
]
|
||||||
|
|
||||||
@hooked
|
@hooked
|
||||||
def validate_token_request(self):
|
def validate_token_request(self):
|
||||||
@@ -288,6 +296,17 @@ class RevocationEndpoint(rfc7009.RevocationEndpoint):
|
|||||||
token: OAuth2Token,
|
token: OAuth2Token,
|
||||||
request: OAuth2Request,
|
request: OAuth2Request,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Mark token as revoked. Since token MUST be unique, it would be dangerous
|
||||||
|
to delete it. Consider this situation:
|
||||||
|
|
||||||
|
- Jane obtained a token XYZ
|
||||||
|
- Jane revoked (deleted) token XYZ
|
||||||
|
- Bob generated a new token XYZ
|
||||||
|
- Jane can use XYZ to access Bob’s resource
|
||||||
|
|
||||||
|
- https://docs.authlib.org/en/latest/specs/rfc7009.html#authlib.oauth2.rfc7009.RevocationEndpoint.revoke_token
|
||||||
|
"""
|
||||||
user_id = token.user['id']
|
user_id = token.user['id']
|
||||||
r = dyn.collection.query(KeyPair(pk=user_id, sk='SESSION'))
|
r = dyn.collection.query(KeyPair(pk=user_id, sk='SESSION'))
|
||||||
|
|
||||||
@@ -377,3 +396,5 @@ server.register_grant(TokenExchangeGrant)
|
|||||||
server.register_grant(RefreshTokenGrant)
|
server.register_grant(RefreshTokenGrant)
|
||||||
server.register_endpoint(RevocationEndpoint)
|
server.register_endpoint(RevocationEndpoint)
|
||||||
server.register_extension(IssuerParameter())
|
server.register_extension(IssuerParameter())
|
||||||
|
|
||||||
|
require_oauth = ResourceProtector()
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
|
|
||||||
|
from oauth2 import require_oauth
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
@router.get('/userinfo')
|
@router.get('/userinfo', middlewares=[require_oauth('profile')])
|
||||||
def userinfo():
|
def userinfo():
|
||||||
return {'name': 'test'}
|
return {'name': 'test'}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ def test_authorize(
|
|||||||
lambda_context: LambdaContext,
|
lambda_context: LambdaContext,
|
||||||
):
|
):
|
||||||
session = new_session(USER_ID)
|
session = new_session(USER_ID)
|
||||||
|
print(session)
|
||||||
|
|
||||||
r = app.lambda_handler(
|
r = app.lambda_handler(
|
||||||
http_api_proxy(
|
http_api_proxy(
|
||||||
@@ -26,7 +27,7 @@ def test_authorize(
|
|||||||
'response_type': 'code',
|
'response_type': 'code',
|
||||||
'client_id': 'd72d4005-1fa7-4430-9754-80d5e2487bb6',
|
'client_id': 'd72d4005-1fa7-4430-9754-80d5e2487bb6',
|
||||||
'redirect_uri': 'https://localhost/callback',
|
'redirect_uri': 'https://localhost/callback',
|
||||||
'scope': 'openid offline_access read:users',
|
'scope': 'openid profile email offline_access apps:admin',
|
||||||
'nonce': '123',
|
'nonce': '123',
|
||||||
'state': '456',
|
'state': '456',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// OAuth2
|
// OAuth2
|
||||||
{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access read:users", "token_endpoint_auth_method": "client_secret_basic"}
|
{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "client_secret_basic"}
|
||||||
{"id": "OAUTH2", "sk": "CLIENT_ID#8c5e92b0-9ed4-451e-8935-66084d8544b1", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access read:users", "token_endpoint_auth_method": "none"}
|
{"id": "OAUTH2", "sk": "CLIENT_ID#8c5e92b0-9ed4-451e-8935-66084d8544b1", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "none"}
|
||||||
{"id": "OAUTH2", "sk": "CLIENT_ID#6ebe1709-0831-455c-84c0-d4c753bf33c6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 2", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access", "token_endpoint_auth_method": "none"}
|
{"id": "OAUTH2", "sk": "CLIENT_ID#6ebe1709-0831-455c-84c0-d4c753bf33c6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 2", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access", "token_endpoint_auth_method": "none"}
|
||||||
{"id": "OAUTH2", "sk": "CLIENT_ID#1db63660-063d-4280-b2ea-388aca4a9459", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 3", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access read:users", "token_endpoint_auth_method": "client_secret_basic"}
|
{"id": "OAUTH2", "sk": "CLIENT_ID#1db63660-063d-4280-b2ea-388aca4a9459", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 3", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "client_secret_basic"}
|
||||||
{"id": "OAUTH2#CODE", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "redirect_uri": "https://localhost/callback", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419", "nonce": null, "scope": "openid profile email read:users", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"}
|
{"id": "OAUTH2#CODE", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "redirect_uri": "https://localhost/callback", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419", "nonce": null, "scope": "openid profile email apps:admins", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"}
|
||||||
|
|
||||||
{"id": "OAUTH2#TOKEN", "sk": "REFRESH_TOKEN#CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "token_type": "Bearer", "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IlRjT0VuV3JGSUFEYlZJNjJlY1pzU28ydEI1eW5mbkZZNTZ0Uy05b0stNW8ifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiZXhwIjoxNzU5NTg2NzgzLCJjbGllbnRfaWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYiLCJpYXQiOjE3NTg5ODE5ODMsImp0aSI6Ik9uVzRIZm1FdFl2a21CbE4iLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIHJlYWQ6dXNlcnMiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJhdWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYifQ.i0NVgvPuf5jvl8JcYNsVCzjVUTDLihgQO4LmLeNijx9Ed3p_EgtVtcHFWFvEebe_LwTuDDtIJveH22Piyp4zresNSc_YNumnuvoY1aNd0ic2RIEtXaklRroq0xHwL_IVT-Dt6P9xL5Hyygx47Pvmci4U3wWK32a6Sb1Mm7ZZgXA00xWI1bJ_zwxFLvDkHDp9nrAa_vEWN6zRBcWc7JYNsgiaPMC0DoL8it0k48_g44zfsjGAZLcWFMoPlYt3wIcQQDeCKMsSJI0VPnqKK0pq4OOVs-pjkMyAU5aEMPvVOwdAL3VZY16RXt3eTzsmMH1XoRdCMP6UAx4ZS10RLGUPeA", "scope": "openid profile email read:users", "user": {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "name": "S\u00e9rgio R Siqueira", "email": "sergio@somosbeta.com.br", "email_verified": false}, "expires_in": 180, "issued_at": 1758981984, "ttl": 1759586784}
|
{"id": "OAUTH2#TOKEN", "sk": "REFRESH_TOKEN#CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "token_type": "Bearer", "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IlRjT0VuV3JGSUFEYlZJNjJlY1pzU28ydEI1eW5mbkZZNTZ0Uy05b0stNW8ifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiZXhwIjoxNzU5NTg2NzgzLCJjbGllbnRfaWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYiLCJpYXQiOjE3NTg5ODE5ODMsImp0aSI6Ik9uVzRIZm1FdFl2a21CbE4iLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIHJlYWQ6dXNlcnMiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJhdWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYifQ.i0NVgvPuf5jvl8JcYNsVCzjVUTDLihgQO4LmLeNijx9Ed3p_EgtVtcHFWFvEebe_LwTuDDtIJveH22Piyp4zresNSc_YNumnuvoY1aNd0ic2RIEtXaklRroq0xHwL_IVT-Dt6P9xL5Hyygx47Pvmci4U3wWK32a6Sb1Mm7ZZgXA00xWI1bJ_zwxFLvDkHDp9nrAa_vEWN6zRBcWc7JYNsgiaPMC0DoL8it0k48_g44zfsjGAZLcWFMoPlYt3wIcQQDeCKMsSJI0VPnqKK0pq4OOVs-pjkMyAU5aEMPvVOwdAL3VZY16RXt3eTzsmMH1XoRdCMP6UAx4ZS10RLGUPeA", "scope": "openid profile email read:users", "user": {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "name": "S\u00e9rgio R Siqueira", "email": "sergio@somosbeta.com.br", "email_verified": false}, "expires_in": 180, "issued_at": 1758981984, "ttl": 1759586784}
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
// User data
|
// User data
|
||||||
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br"}
|
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br"}
|
||||||
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"}
|
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"}
|
||||||
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SCOPE", "scope": ["openid", "profile", "email", "offline_access", "read:users", "read:courses", "impersonate:users"]}
|
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SCOPE", "scope": ["openid", "profile", "email", "offline_access", "apps:admin"]}
|
||||||
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SESSION#36af142e-9f6d-49d3-bfe9-6a6bd6ab2712", "created_at": "2025-09-17T13:44:34.544491-03:00", "ttl": 1760719474}
|
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "SESSION#36af142e-9f6d-49d3-bfe9-6a6bd6ab2712", "created_at": "2025-09-17T13:44:34.544491-03:00", "ttl": 1760719474}
|
||||||
|
|
||||||
{"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "0", "name": "Johnny Cash", "email": "johnny@johnnycash.com"}
|
{"id": "fd5914ec-fd37-458b-b6b9-8aeab38b666b", "sk": "0", "name": "Johnny Cash", "email": "johnny@johnnycash.com"}
|
||||||
|
|||||||
46
streams-events/uv.lock
generated
46
streams-events/uv.lock
generated
@@ -31,14 +31,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "authlib"
|
name = "authlib"
|
||||||
version = "1.6.2"
|
version = "1.6.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cryptography" },
|
{ name = "cryptography" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/95/e4f4ab5ce465821fe2229e10985ab80462941fe5d96387ae76bafd36f0ba/authlib-1.6.2.tar.gz", hash = "sha256:3bde83ac0392683eeef589cd5ab97e63cbe859e552dd75dca010548e79202cb1", size = 160429, upload-time = "2025-08-23T08:34:32.665Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/00/fb65909bf4c8d7da893a12006074343402a8dc8c00d916b3cee524d97f3f/authlib-1.6.2-py2.py3-none-any.whl", hash = "sha256:2dd5571013cacf6b15f7addce03ed057ffdf629e9e81bacd9c08455a190e9b57", size = 239601, upload-time = "2025-08-23T08:34:31.4Z" },
|
{ url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -470,6 +470,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
|
{ url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "joserfc"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/26/a0/4b8dfecc8ec3c15aa1f2ff7d5b947344378b5b595ce37c8a8fe6e25c1400/joserfc-1.4.0.tar.gz", hash = "sha256:e8c2f327bf10a937d284d57e9f8aec385381e5e5850469b50a7dade1aba59759", size = 196339, upload-time = "2025-10-09T07:47:00.835Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/05/342459b7629c6fcb5f99a646886ee2904491955b8cce6b26b0b9a498f67c/joserfc-1.4.0-py3-none-any.whl", hash = "sha256:46917e6b53f1ec0c7e20d34d6f3e6c27da0fa43d0d4ebfb89aada7c86582933a", size = 66390, upload-time = "2025-10-09T07:46:59.591Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonpath-ng"
|
name = "jsonpath-ng"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@@ -484,7 +496,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.9.14"
|
version = "0.11.0"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
@@ -493,6 +505,7 @@ dependencies = [
|
|||||||
{ name = "dictdiffer" },
|
{ name = "dictdiffer" },
|
||||||
{ name = "ftfy" },
|
{ name = "ftfy" },
|
||||||
{ name = "glom" },
|
{ name = "glom" },
|
||||||
|
{ name = "joserfc" },
|
||||||
{ name = "meilisearch" },
|
{ name = "meilisearch" },
|
||||||
{ name = "orjson" },
|
{ name = "orjson" },
|
||||||
{ name = "passlib" },
|
{ name = "passlib" },
|
||||||
@@ -500,7 +513,7 @@ dependencies = [
|
|||||||
{ name = "pycpfcnpj" },
|
{ name = "pycpfcnpj" },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
{ name = "pydantic-extra-types" },
|
{ name = "pydantic-extra-types" },
|
||||||
{ name = "pyjwt" },
|
{ name = "python-multipart" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "smart-open", extra = ["s3"] },
|
{ name = "smart-open", extra = ["s3"] },
|
||||||
@@ -511,11 +524,12 @@ dependencies = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "arnparse", specifier = ">=0.0.2" },
|
{ name = "arnparse", specifier = ">=0.0.2" },
|
||||||
{ name = "authlib", specifier = ">=1.6.1" },
|
{ name = "authlib", specifier = ">=1.6.5" },
|
||||||
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" },
|
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" },
|
||||||
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
||||||
{ 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 = "joserfc", specifier = ">=1.2.2" },
|
||||||
{ name = "meilisearch", specifier = ">=0.34.0" },
|
{ name = "meilisearch", specifier = ">=0.34.0" },
|
||||||
{ name = "orjson", specifier = ">=3.10.15" },
|
{ name = "orjson", specifier = ">=3.10.15" },
|
||||||
{ name = "passlib", specifier = ">=1.7.4" },
|
{ name = "passlib", specifier = ">=1.7.4" },
|
||||||
@@ -523,7 +537,7 @@ requires-dist = [
|
|||||||
{ 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" },
|
||||||
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
||||||
{ name = "pyjwt", specifier = ">=2.10.1" },
|
{ name = "python-multipart", specifier = ">=0.0.20" },
|
||||||
{ name = "pytz", specifier = ">=2025.1" },
|
{ name = "pytz", specifier = ">=2025.1" },
|
||||||
{ name = "requests", specifier = ">=2.32.3" },
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
|
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
|
||||||
@@ -802,15 +816,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" },
|
{ url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyjwt"
|
|
||||||
version = "2.10.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.4"
|
version = "8.3.4"
|
||||||
@@ -860,6 +865,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-multipart"
|
||||||
|
version = "0.0.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2025.2"
|
version = "2025.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user