add session route

This commit is contained in:
2025-08-16 23:53:09 -03:00
parent a53f37393a
commit 21f6eb030f
21 changed files with 311 additions and 599 deletions

View File

@@ -24,8 +24,6 @@ router = Router()
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer)
enrollment_collect = DynamoDBCollection(enrollment_layer)
processor = BatchProcessor() processor = BatchProcessor()
@@ -54,7 +52,7 @@ class Payload(BaseModel):
compress=True, compress=True,
tags=['Enrollment'], tags=['Enrollment'],
middlewares=[ middlewares=[
TenantMiddleware(user_collect), TenantMiddleware(user_layer.collection),
], ],
) )
def enroll_(payload: Payload): def enroll_(payload: Payload):

View File

@@ -15,7 +15,7 @@ JSONL_FILES = (
# 'test-orders.jsonl', # 'test-orders.jsonl',
'test-users.jsonl', 'test-users.jsonl',
# 'test-enrollments.jsonl', # 'test-enrollments.jsonl',
# 'test-courses.jsonl', 'test-courses.jsonl',
) )

View File

@@ -1,64 +1,67 @@
{"id": {"S": "439e9a43-ab92-469a-a849-b6e824370f80"}, "access_period": {"S": "360"}, "name": {"S": "No\u00e7\u00f5es em Primeiros Socorros"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:06:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "2c1e724a-58c6-4c20-90df-18b5660d6304"}, "metadata__unit_price": {"N": "199"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "94"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:01:33.088915-03:00", "metadata__konviva_class_id":100, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839", "name": "NR-11 Transporte, movimentação, armazenagem e manuseio de materiais", "metadata__unit_price":109}
{"id": {"S": "15ee05a3-4ceb-4b7e-9979-db75b28c9ade"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-10 SEP 08 horas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:02:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "1d86444e-36d6-4ed7-8cea-24a4df9ca15f"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "276"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:01:33.088916-03:00", "metadata__konviva_class_id":275, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "7f7905aa-ec6d-4189-b884-50fa9b1bd0b8", "name": "Reciclagem em NR-10 Básico (08 horas)", "metadata__unit_price":169}
{"id": {"S": "4ea2498a-a6a9-4293-94d0-ceeb248e64b7"}, "access_period": {"S": "720"}, "name": {"S": "NR-10 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:07:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "38"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "21"}} {"updated_at": "2025-07-24T17:39:57.432609-03:00", "access_period": 360, "created_at": "2024-12-30T00:56:33.088916-03:00", "metadata__konviva_class_id":148, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "b945be62-408d-4099-a75c-4d1dda929659", "name": "NR-11 Pontes Rolantes"}
{"id": {"S": "e1c44881-2fe3-484e-ada2-12b6bf5b9398"}, "access_period": {"S": "720"}, "name": {"S": "NR-35 Seguran\u00e7a nos Trabalhos em Altura (Te\u00f3rico)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:11:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "42"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "1"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:05:33.088916-03:00", "metadata__konviva_class_id":247, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "c2d1362f-aa7f-40b0-bd15-37570bda5f25", "name": "PCA Programa de Conservação Auditiva", "metadata__unit_price":99}
{"id": {"S": "281198c2-f293-4acc-b96e-e4a2d5f6b73c"}, "access_period": {"S": "360"}, "name": {"S": "CIPA"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:10:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "41"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "3"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:12:33.088916-03:00", "metadata__konviva_class_id":262, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "9301601e-385a-4525-a65a-4054f669632f", "name": "Reciclagem em NR-13 Vasos de Pressão e Unidades de Processo", "metadata__unit_price":169}
{"id": {"S": "4866c068-577a-45b0-b41a-41a7dc6b9ab7"}, "access_period": {"S": "360"}, "name": {"S": "Combate a Inc\u00eandio"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:17:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "53"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "18"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:58:33.088916-03:00", "metadata__konviva_class_id":271, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "80dfc302-4e05-4f23-944d-9a2768cb6c7d", "name": "LOTO Lockout e Tagout", "metadata__unit_price":99}
{"id": {"S": "f10c3283-7722-41c6-ba5d-222f9f4f48af"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Operador de Empilhadeira"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:13:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "49"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "23"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:37:33.088916-03:00", "metadata__konviva_class_id":30, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "6689a04a-99c1-4150-b1ed-c131b6dc5bb5", "name": "NR-20 Intermediário", "metadata__unit_price":179}
{"id": {"S": "39f89a69-3d94-4dd6-9049-66e540fb2f32"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-10 Complementar (SEP)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:20:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "56"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "36"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:03:33.088916-03:00", "metadata__konviva_class_id":261, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "d6520884-89e7-4843-b77f-7777c5376d50", "name": "Reciclagem em NR-13 Operador de Caldeiras", "metadata__unit_price":169}
{"id": {"S": "38d8ba20-49a4-4c69-b674-b70a985eb76a"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 4"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:21:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "56771ce5-4680-4ce4-b257-8f2362975494"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "218"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:50:33.088916-03:00", "metadata__konviva_class_id":131, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "d9f9c3d6-ba97-4695-b1fb-e2539158b064", "name": "Reciclagem em NR-20 Avançado II", "metadata__unit_price":99}
{"id": {"S": "d800d2a9-ae76-46de-be82-3e06ae6afcee"}, "access_period": {"S": "720"}, "name": {"S": "NR-20 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:29:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "70"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "28"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:34:33.088916-03:00", "metadata__konviva_class_id":265, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "124ca098-b609-4550-a83c-6b9120a2db42", "name": "Reciclagem em NR-11 Transpaleteiras", "metadata__unit_price":99}
{"id": {"S": "3c27ea9c-9464-46a1-9717-8c1441793186"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 1"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:28:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "6f6fbc20-57f1-4d68-bf00-00119141894f"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "200"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:27:33.088916-03:00", "metadata__konviva_class_id":33, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "6dd8f711-5c5a-477a-971f-122cbac4ce48", "name": "NR-35 Supervisor de Trabalho em Altura", "metadata__unit_price":169}
{"id": {"S": "96c2553a-d087-42ad-be5e-e960ea673c3d"}, "access_period": {"S": "360"}, "name": {"S": "NR-18 Sinaleiro e Amarrador de Cargas para I\u00e7amento"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:30:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "711bad57-2a03-43ff-91d0-31d151063897"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "270"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 365, "created_at": "2024-12-30T01:02:33.088916-03:00", "metadata__konviva_class_id":144, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "4682187a-cb5c-47a8-9597-ad9243a6d717", "name": "NR-11 Talhas", "metadata__unit_price":99}
{"id": {"S": "5c119d4b-573c-4d8d-a99d-63756af2f4c5"}, "access_period": {"S": "360"}, "name": {"S": "NR-06 - Equipamento de Prote\u00e7\u00e3o Individual - EPI"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:32:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "78"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "34"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:18:33.088916-03:00", "metadata__konviva_class_id":32, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "b23493dd-6359-4352-97be-12dca3a21ca6", "name": "NR-33 Trabalhadores Autorizados e Vigias em Espaço Confinado", "metadata__unit_price":179}
{"id": {"S": "7f7905aa-ec6d-4189-b884-50fa9b1bd0b8"}, "access_period": {"S": "360"}, "name": {"S": "NR-10 Reciclagem: 08 horas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:01:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "005f262d-9eda-4304-8639-31a86efb3086"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "275"}} {"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2024-12-30T00:54:33.088916-03:00", "metadata__konviva_class_id":137, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "5c9c1ff1-361f-479d-bd2c-eb3b124a74fc", "name": "NR-31 CIPATR"}
{"id": {"S": "07da69f2-2a2c-4771-b766-633295476ad7"}, "access_period": {"S": "360"}, "name": {"S": "NR-26 Sinaliza\u00e7\u00e3o de Seguran\u00e7a"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:05:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "29b46896-eb93-40ab-8439-25d5129d108a"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "123"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2025-07-09T17:31:26.764492-03:00", "metadata__konviva_class_id":148, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "53ef0924-3521-4186-a707-88fbfa137179", "name": "NR-11 Pontes Rolantes", "metadata__unit_price":99}
{"id": {"S": "d6520884-89e7-4843-b77f-7777c5376d50"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-13 Operador de Caldeiras"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:03:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "1eac1304-6b5a-4fe1-884f-ef2dec753313"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "261"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:31:33.088916-03:00", "metadata__konviva_class_id":105, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "4e52d4e9-0566-4f8c-8307-1db770e4c33c", "name": "NR-17 Ergonomia", "metadata__unit_price":99}
{"id": {"S": "9301601e-385a-4525-a65a-4054f669632f"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-13 Vasos de Press\u00e3o e Unidades de Processo"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:12:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "452f8158-4ed9-4ca5-a53f-c82db430e990"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "262"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:25:33.088916-03:00", "metadata__konviva_class_id":24, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "52b4a909-b6a9-456e-a7b9-c0b3c18ebe00", "name": "NR-12 Máquinas e Equipamentos", "metadata__unit_price":149}
{"id": {"S": "52b4a909-b6a9-456e-a7b9-c0b3c18ebe00"}, "access_period": {"S": "360"}, "name": {"S": "NR-12 M\u00e1quinas e Equipamentos"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:25:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "62"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "24"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:40:33.088916-03:00", "metadata__konviva_class_id":17, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "c19cd7ee-3cc8-4f9c-95ff-dad7993f49b1", "name": "Gestão da Cultura de Segurança", "metadata__unit_price":199}
{"id": {"S": "b23493dd-6359-4352-97be-12dca3a21ca6"}, "access_period": {"S": "360"}, "name": {"S": "NR-33 Trabalhadores Autorizados e Vigias em Espa\u00e7o Confinado"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:18:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "54"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "32"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:35:33.088916-03:00", "metadata__konviva_class_id":266, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "801d1115-b4e8-4213-96b1-0b4f99bf202e", "name": "Reciclagem em NR-18 Básico em segurança do trabalho", "metadata__unit_price":99}
{"id": {"S": "5c53656d-9557-4ef9-8e05-08d3190bb115"}, "access_period": {"S": "360"}, "name": {"S": "NR-13 Operador de Caldeiras"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:26:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "63"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "25"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:05:33.088916-03:00", "metadata__konviva_class_id":123, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "07da69f2-2a2c-4771-b766-633295476ad7", "name": "NR-26 Sinalização de Segurança", "metadata__unit_price":109}
{"id": {"S": "124ca098-b609-4550-a83c-6b9120a2db42"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-11 Transpaleteiras"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:34:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "7d6e413e-800b-48b6-9b60-f8ac5d3ee730"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "265"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:26:33.088916-03:00", "metadata__konviva_class_id":25, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "5c53656d-9557-4ef9-8e05-08d3190bb115", "name": "NR-13 Operador de Caldeiras", "metadata__unit_price":199}
{"id": {"S": "6dd8f711-5c5a-477a-971f-122cbac4ce48"}, "access_period": {"S": "720"}, "name": {"S": "NR-35 Supervisor de Trabalho em Altura"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:27:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "64"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "33"}} {"access_period": 360, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id":281, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "a955518e-ebcb-4441-b914-ddc9ecef84f0", "name": "NR-11 Operador de Munck"}
{"id": {"S": "4e52d4e9-0566-4f8c-8307-1db770e4c33c"}, "access_period": {"S": "360"}, "name": {"S": "NR-17 Ergonomia"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:31:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "723534ae-36ae-4253-bb73-966c8268779d"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "105"}} {"updated_at": "2025-07-24T17:44:10.114431-03:00", "access_period": 360, "created_at": "2024-12-30T00:16:33.088916-03:00", "metadata__konviva_class_id":27, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "7aba7598-83b2-4df7-938c-d075ffef47ca", "name": "NR-18 Construção Civil (descontinuado)"}
{"id": {"S": "863214e8-26e2-440b-854b-a0ced0164bbf"}, "access_period": {"S": "360"}, "name": {"S": "CIPA Grau de Risco 3 (te\u00f3rico)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:09:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "4079b429-aac4-4a41-937a-38bd6101d875"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "259"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:00:33.088916-03:00", "metadata__konviva_class_id":260, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "f05293f0-2ff4-4026-9e65-2f0f67d9f83b", "name": "Prevenção e combate ao assédio sexual e às demais formas de violência no trabalho", "metadata__unit_price":99}
{"id": {"S": "7aba7598-83b2-4df7-938c-d075ffef47ca"}, "access_period": {"S": "360"}, "name": {"S": "NR-18 - Constru\u00e7\u00e3o Civil"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:16:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "52"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "27"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:48:33.088916-03:00", "metadata__konviva_class_id":269, "tenant_id": "*", "cert": {"exp_interval": 180}, "sk": "0", "id": "a3c46d94-cf31-4b5f-8de3-6aa1c2d423f0", "name": "NR-17 Ergonomia para Teleatendimento/Telemarketing", "metadata__unit_price":99}
{"id": {"S": "2e1c93c2-1779-482b-9552-c04e09db8349"}, "access_period": {"S": "720"}, "name": {"S": "NR-10 Complementar (SEP)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:19:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "55"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "22"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:04:33.088916-03:00", "metadata__konviva_class_id":189, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "446426ce-c0f0-4238-83ed-95e8c0434f45", "name": "Reciclagem em NR-11 Rebocadores", "metadata__unit_price":99}
{"id": {"S": "70827c13-1db5-4499-977f-9a6623e45161"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Rebocadores"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:22:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "56d1c710-36b1-4db5-8a7a-dacb7098dbad"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "154"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:43:33.088916-03:00", "metadata__konviva_class_id":39, "tenant_id": "*", "cert": {"exp_interval": 1000}, "sk": "0", "id": "8efe00d2-38e2-4281-8f5e-b9113e91374b", "name": "Reciclagem em NR-20 Básico", "metadata__unit_price":99}
{"id": {"S": "4a0c4652-fcb3-4362-8fff-bc5ac3ba1cf3"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-11 Plataforma de Trabalho Elevat\u00f3ria (PTA)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:04:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "2706ca6d-2b1b-47aa-8f02-5d39c8833b9d"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "166"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:22:33.088916-03:00", "metadata__konviva_class_id":154, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "70827c13-1db5-4499-977f-9a6623e45161", "name": "NR-11 Rebocadores", "metadata__unit_price":99}
{"id": {"S": "c01ec8a2-0359-4351-befb-76c3577339e0"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-10 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:08:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "40"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "35"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:03:33.088916-03:00", "metadata__konviva_class_id":268, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "479516a7-5431-452e-8f28-228e34b86e0c", "name": "NR-11 Transpaleteira", "metadata__unit_price":99}
{"id": {"S": "a1a8727c-0519-4692-93e7-81dbe66e167f"}, "access_period": {"S": "360"}, "name": {"S": "Dire\u00e7\u00e3o Defensiva (20 horas)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:15:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "50"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "19"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:19:33.088916-03:00", "metadata__konviva_class_id":22, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "2e1c93c2-1779-482b-9552-c04e09db8349", "name": "NR-10 Complementar (SEP)", "metadata__unit_price":199}
{"id": {"S": "eb19c520-5546-4c57-898d-029c86e59fb6"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-33 Supervisores em Espa\u00e7o Confinado"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:14:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "4ea8aaec-cfd3-4ec1-a15b-a72fac56b371"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "117"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 365, "created_at": "2024-12-30T00:52:33.088916-03:00", "metadata__konviva_class_id":184, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "450a70ca-8ab5-4520-8a22-0e277359797d", "name": "NR-18 PEMT PTA", "metadata__unit_price":149}
{"id": {"S": "386f7086-2871-436f-85f5-31d632fbf624"}, "access_period": {"S": "360"}, "name": {"S": "Boas Pr\u00e1ticas em Manipula\u00e7\u00e3o de Alimentos"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:24:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "59"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "4"}} {"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2024-12-30T00:09:33.088916-03:00", "metadata__konviva_class_id":259, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "863214e8-26e2-440b-854b-a0ced0164bbf", "name": "CIPA Grau de Risco 3 (teórico)"}
{"id": {"S": "3f284753-85ce-4f53-8de7-cdfcdaf9515b"}, "access_period": {"S": "360"}, "name": {"S": "NR-33 Supervisor em Espa\u00e7o Confinado"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:23:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "57"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "31"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:30:33.088916-03:00", "metadata__konviva_class_id":270, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "96c2553a-d087-42ad-be5e-e960ea673c3d", "name": "NR-18 Sinaleiro e Amarrador de Cargas para Içamento", "metadata__unit_price":99}
{"id": {"S": "00ebdd8d-b4db-4437-8814-274811a4c469"}, "access_period": {"S": "360"}, "name": {"S": "Dire\u00e7\u00e3o Defensiva (08 horas)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:33:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "7ac2e34e-232a-427c-a3fc-32198e3a51c6"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "194"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:46:33.088916-03:00", "metadata__konviva_class_id":51, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "30bb357f-2f48-4764-93d1-ffe219cbc5d3", "name": "Reciclagem em NR-35 Trabalhos em Altura", "metadata__unit_price":99}
{"id": {"S": "8efe00d2-38e2-4281-8f5e-b9113e91374b"}, "access_period": {"S": "1080"}, "name": {"S": "Reciclagem em NR-20 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:43:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "91"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "39"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 720, "created_at": "2024-12-30T00:53:33.088916-03:00", "metadata__konviva_class_id":212, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "f7def039-bf27-496a-94ff-0667c9c0c0db", "name": "CIPA Grau de Risco 3", "metadata__unit_price":109}
{"id": {"S": "a3c46d94-cf31-4b5f-8de3-6aa1c2d423f0"}, "access_period": {"S": "360"}, "name": {"S": "NR-17 Ergonomia para Teleatendimento/Telemarketing"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:48:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "94dc4c63-9f23-4101-a0d7-d42bfa527cbc"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "269"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:06:33.088916-03:00", "metadata__konviva_class_id":94, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "439e9a43-ab92-469a-a849-b6e824370f80", "name": "Noções em Primeiros Socorros", "metadata__unit_price":99}
{"id": {"S": "450a70ca-8ab5-4520-8a22-0e277359797d"}, "access_period": {"S": "365"}, "name": {"S": "NR-18 PEMT PTA"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:52:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "a6775b71-d68a-4263-8ab4-acb3a4f8a8b9"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "184"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:39:33.088916-03:00", "metadata__konviva_class_id":224, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "1c7b1cf0-6973-4271-9407-6e974f0094e9", "name": "PPR Programa de Proteção Respiratória", "metadata__unit_price":99}
{"id": {"S": "f05293f0-2ff4-4026-9e65-2f0f67d9f83b"}, "access_period": {"S": "360"}, "name": {"S": "Preven\u00e7\u00e3o e combate ao ass\u00e9dio sexual e \u00e0s demais formas de viol\u00eancia no trabalho"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:00:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "da03eac1-e328-49d0-8014-5cdd023cb543"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "260"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:11:33.088916-03:00", "metadata__konviva_class_id":1, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "e1c44881-2fe3-484e-ada2-12b6bf5b9398", "name": "NR-35 Segurança nos Trabalhos em Altura (Teórico)", "metadata__unit_price":119}
{"id": {"S": "446426ce-c0f0-4238-83ed-95e8c0434f45"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Rebocadores"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:04:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "f78d2241-13fd-45ab-81cc-0acb781b4107"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "189"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:20:33.088916-03:00", "metadata__konviva_class_id":36, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "39f89a69-3d94-4dd6-9049-66e540fb2f32", "name": "Reciclagem em NR-10 Complementar (SEP)", "metadata__unit_price":169}
{"id": {"S": "479516a7-5431-452e-8f28-228e34b86e0c"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Seguran\u00e7a em Transpaleteira"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:03:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "e39a8718-cf10-4dcc-a152-8c50b4b63e14"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "268"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:32:33.088916-03:00", "metadata__konviva_class_id":34, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "5c119d4b-573c-4d8d-a99d-63756af2f4c5", "name": "NR-06 EPIs Equipamento de Proteção Individual", "metadata__unit_price":99}
{"id": {"S": "95a1fcb9-ba16-4b3c-a59d-047ca32078ff"}, "access_period": {"S": "360"}, "name": {"S": "NR-20 Inicia\u00e7\u00e3o"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:36:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "83"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "29"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:38:33.088916-03:00", "metadata__konviva_class_id":20, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "6d17d9cf-96be-42a4-bae5-75926e1e832a", "name": "NR-20 Exposição ao Benzeno ", "metadata__unit_price":99}
{"id": {"S": "2b9a6e19-2e2d-4fc2-8924-b45d47057715"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-33 Trabalhos em Espa\u00e7os Confinados"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:42:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "90"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "38"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:28:33.088916-03:00", "metadata__konviva_class_id":200, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "3c27ea9c-9464-46a1-9717-8c1441793186", "name": "CIPA Grau de Risco 1", "metadata__unit_price":99}
{"id": {"S": "76a5ba94-e11c-48f5-88eb-9326df9be264"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-12 M\u00e1quinas e Equipamentos"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:45:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "929960de-a62d-4669-91e5-8fef8d670103"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "273"}} {"updated_at": "2025-07-24T17:42:24.373883-03:00", "access_period": 360, "created_at": "2024-12-30T00:10:33.088916-03:00", "metadata__konviva_class_id":3, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "281198c2-f293-4acc-b96e-e4a2d5f6b73c", "name": "CIPA (descontinuado)"}
{"id": {"S": "0707270e-623f-486a-8dbd-d852377a208c"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-20 - Intermedi\u00e1rio"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:44:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "92"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "57"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:29:33.088916-03:00", "metadata__konviva_class_id":28, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "d800d2a9-ae76-46de-be82-3e06ae6afcee", "name": "NR-20 Básico", "metadata__unit_price":149}
{"id": {"S": "96c03c32-089c-4ccb-8aa1-73b0f49228b9"}, "access_period": {"S": "360"}, "name": {"S": "Lei Lucas: Primeiros Socorros nas Escolas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:49:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "9bb3fe7d-e29d-4a2f-91d9-87910152152b"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "278"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:13:33.088916-03:00", "metadata__konviva_class_id":23, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "f10c3283-7722-41c6-ba5d-222f9f4f48af", "name": "NR-11 Operador de Empilhadeira", "metadata__unit_price":149}
{"id": {"S": "6a403773-aeac-4e6a-ac39-dc958e4be52a"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-11 - Operador de Empilhadeira"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:47:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "94"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "63"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:17:33.088916-03:00", "metadata__konviva_class_id":18, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "4866c068-577a-45b0-b41a-41a7dc6b9ab7", "name": "Combate a Incêndio", "metadata__unit_price":99}
{"id": {"S": "0e39c7af-a812-49aa-ac12-72f4e0ede8c9"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-18 Plataforma de Trabalho A\u00e9reo PEMT"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:51:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "a52eb5c5-4a5c-404b-96fe-e34037805d1e"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "272"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:41:33.088916-03:00", "metadata__konviva_class_id":26, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "3b05b03c-8714-4f98-90e0-2a3ac4940035", "name": "NR-13 Vasos de Pressão e Unidades de Processo", "metadata__unit_price":199}
{"id": {"S": "99bb3b60-4ded-4a8e-937c-ba2d78ec6454"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 2"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:55:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "b4de57c8-59e8-43ad-a6fa-6735d4faa54b"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "206"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:21:33.088916-03:00", "metadata__konviva_class_id":218, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "38d8ba20-49a4-4c69-b674-b70a985eb76a", "name": "CIPA Grau de Risco 4", "metadata__unit_price":119}
{"id": {"S": "801d1115-b4e8-4213-96b1-0b4f99bf202e"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-18 B\u00e1sico em seguran\u00e7a do trabalho"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:35:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "7e981a6b-8776-4747-bc3b-dcad638b5273"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "266"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:02:33.088916-03:00", "metadata__konviva_class_id":276, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "15ee05a3-4ceb-4b7e-9979-db75b28c9ade", "name": "Reciclagem em NR-10 SEP (08 horas)", "metadata__unit_price":169}
{"id": {"S": "6689a04a-99c1-4150-b1ed-c131b6dc5bb5"}, "access_period": {"S": "720"}, "name": {"S": "NR-20 Intermedi\u00e1rio"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:37:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "84"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "30"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:07:33.088916-03:00", "metadata__konviva_class_id":21, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "4ea2498a-a6a9-4293-94d0-ceeb248e64b7", "name": "NR-10 Básico", "metadata__unit_price":199}
{"id": {"S": "c19cd7ee-3cc8-4f9c-95ff-dad7993f49b1"}, "access_period": {"S": "360"}, "name": {"S": "Gest\u00e3o da Cultura de Seguran\u00e7a"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:40:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "87"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "17"}} {"updated_at": "2025-07-24T17:42:14.453946-03:00", "access_period": 360, "created_at": "2024-12-30T00:57:33.088916-03:00", "metadata__konviva_class_id":274, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "b3d0e345-a8e5-4dc2-a3b0-218bc894ee55", "name": "Noções do Risco Elétrico"}
{"id": {"S": "d9f9c3d6-ba97-4695-b1fb-e2539158b064"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-20 Avan\u00e7ado II"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:50:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "a3dd23e1-09a0-4c24-a042-08ed65e07fc1"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "131"}} {"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2025-05-12T18:59:12.745375-03:00", "metadata__konviva_class_id":279, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "170884c0-f98e-4b58-8a32-7986f3b41076", "name": "NR-20 Básico - Instalação Classe 1"}
{"id": {"S": "b945be62-408d-4099-a75c-4d1dda929659"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Pontes Rolantes"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:56:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "bf8b81d6-f83a-4216-bcdb-31ba1e21ffcb"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "148"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:14:33.088916-03:00", "metadata__konviva_class_id":117, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "eb19c520-5546-4c57-898d-029c86e59fb6", "name": "Reciclagem em NR-33 Supervisores em Espaço Confinado", "metadata__unit_price":99}
{"id": {"S": "5c9c1ff1-361f-479d-bd2c-eb3b124a74fc"}, "access_period": {"S": "360"}, "name": {"S": "NR-31 CIPATR"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:54:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "b331f93b-9fc5-4791-bd81-fb10c8f5e401"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "137"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:42:33.088916-03:00", "metadata__konviva_class_id":38, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "2b9a6e19-2e2d-4fc2-8924-b45d47057715", "name": "Reciclagem em NR-33 Trabalhos em Espaços Confinados", "metadata__unit_price":109}
{"id": {"S": "80dfc302-4e05-4f23-944d-9a2768cb6c7d"}, "access_period": {"S": "360"}, "name": {"S": "LOTO Lockout e Tagout"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:58:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "d23d569e-51e8-499a-b407-bd782c64a6ac"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "271"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:49:33.088916-03:00", "metadata__konviva_class_id":278, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "96c03c32-089c-4ccb-8aa1-73b0f49228b9", "name": "Lei Lucas: Primeiros Socorros nas Escolas", "metadata__unit_price":99}
{"id": {"S": "c2d1362f-aa7f-40b0-bd15-37570bda5f25"}, "access_period": {"S": "360"}, "name": {"S": "PCA - Programa de Conserva\u00e7\u00e3o Auditiva"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:05:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "fa00bc23-d6b8-4dc5-86e7-dc0a9bdb2306"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "247"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:51:33.088916-03:00", "metadata__konviva_class_id":272, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "0e39c7af-a812-49aa-ac12-72f4e0ede8c9", "name": "Reciclagem em NR-18 PEMT Plataforma Elevatória Móvel de Trabalho", "metadata__unit_price":99}
{"id": {"S": "4682187a-cb5c-47a8-9597-ad9243a6d717"}, "access_period": {"S": "365"}, "name": {"S": "NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Talhas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:02:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "e2e25a9a-3104-47a7-90da-0be05a157d21"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "144"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:47:33.088916-03:00", "metadata__konviva_class_id":63, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "6a403773-aeac-4e6a-ac39-dc958e4be52a", "name": "Reciclagem em NR-11 Operador de Empilhadeira", "metadata__unit_price":99}
{"id": {"S": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839"}, "access_period": {"N": "360"}, "name": {"S": "NR-11 \u2013 Transporte, movimenta\u00e7\u00e3o, armazenagem e manuseio de materiais"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:01:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "dc1a0428-47bf-4db1-a5da-24be49c9fda6"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "100"}, "cert": {"NULL": true}, "update_date": {"S": "2025-04-04T20:30:05.033976-03:00"}} {"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2024-12-30T00:04:33.088916-03:00", "metadata__konviva_class_id":166, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "4a0c4652-fcb3-4362-8fff-bc5ac3ba1cf3", "name": "Reciclagem em NR-11 Plataforma de Trabalho Elevatória (PTA)"}
{"id": {"S": "1c7b1cf0-6973-4271-9407-6e974f0094e9"}, "access_period": {"S": "360"}, "name": {"S": "PPR Programa de Prote\u00e7\u00e3o Respirat\u00f3ria"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:39:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "86d310e8-a87d-4b38-9e43-3dd247c6a522"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "224"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 720, "created_at": "2024-12-30T00:55:33.088916-03:00", "metadata__konviva_class_id":206, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "99bb3b60-4ded-4a8e-937c-ba2d78ec6454", "name": "CIPA Grau de Risco 2", "metadata__unit_price":99}
{"id": {"S": "6d17d9cf-96be-42a4-bae5-75926e1e832a"}, "access_period": {"S": "720"}, "name": {"S": "Exposi\u00e7\u00e3o ao Benzeno - (Portaria 1109)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:38:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "86"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "20"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:45:33.088916-03:00", "metadata__konviva_class_id":273, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "76a5ba94-e11c-48f5-88eb-9326df9be264", "name": "Reciclagem em NR-12 Máquinas e Equipamentos", "metadata__unit_price":99}
{"id": {"S": "3b05b03c-8714-4f98-90e0-2a3ac4940035"}, "access_period": {"S": "720"}, "name": {"S": "NR-13 Vasos de Press\u00e3o e Unidades de Processo"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:41:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "89"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "26"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:15:33.088916-03:00", "metadata__konviva_class_id":19, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "a1a8727c-0519-4692-93e7-81dbe66e167f", "name": "Direção Defensiva (20 horas)", "metadata__unit_price":99}
{"id": {"S": "30bb357f-2f48-4764-93d1-ffe219cbc5d3"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-35 Trabalhos em Altura ( Te\u00f3rico)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:46:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "93"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "51"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:23:33.088916-03:00", "metadata__konviva_class_id":31, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "3f284753-85ce-4f53-8de7-cdfcdaf9515b", "name": "NR-33 Supervisor em Espaço Confinado", "metadata__unit_price":199}
{"id": {"S": "f7def039-bf27-496a-94ff-0667c9c0c0db"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 3"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:53:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "b09ca8da-f342-4a70-aaaa-67ac81569533"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "212"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:36:33.088916-03:00", "metadata__konviva_class_id":29, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "95a1fcb9-ba16-4b3c-a59d-047ca32078ff", "name": "NR-20 Iniciação", "metadata__unit_price":99}
{"id": {"S": "b3d0e345-a8e5-4dc2-a3b0-218bc894ee55"}, "access_period": {"S": "360"}, "name": {"S": "NR-10: No\u00e7\u00f5es do Risco El\u00e9trico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:57:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "c5b683a3-f432-46e9-94e4-290a788d2ff6"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "274"}} {"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:33:33.088916-03:00", "metadata__konviva_class_id":194, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "00ebdd8d-b4db-4437-8814-274811a4c469", "name": "Direção Defensiva (08 horas)", "metadata__unit_price":99}
{"updated_at": "2025-07-24T17:42:42.013895-03:00", "access_period": 360, "created_at": "2024-12-30T00:24:33.088916-03:00", "metadata__konviva_class_id":4, "tenant_id": "*", "cert":null, "sk": "0", "id": "386f7086-2871-436f-85f5-31d632fbf624", "name": "Manipulação de Alimentos (descontinuado)"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:08:33.088916-03:00", "metadata__konviva_class_id":35, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "c01ec8a2-0359-4351-befb-76c3577339e0", "name": "Reciclagem em NR-10 Básico (20 horas)", "metadata__unit_price":169}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 365, "created_at": "2024-12-30T00:44:33.088916-03:00", "metadata__konviva_class_id":57, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "0707270e-623f-486a-8dbd-d852377a208c", "name": "Reciclagem em NR-20 Intermediário", "metadata__unit_price":99}

View File

@@ -9,15 +9,15 @@ from aws_lambda_powertools.utilities.typing import LambdaContext
from routes.authorize import router as authorize from routes.authorize import router as authorize
from routes.jwks import router as jwks from routes.jwks import router as jwks
from routes.login import router as login
from routes.openid_configuration import router as openid_configuration from routes.openid_configuration import router as openid_configuration
from routes.session import router as session
from routes.token import router as token from routes.token import router as token
from routes.userinfo import router as userinfo from routes.userinfo import router as userinfo
logger = Logger(__name__) logger = Logger(__name__)
tracer = Tracer() tracer = Tracer()
app = APIGatewayHttpResolver(enable_validation=True) app = APIGatewayHttpResolver(enable_validation=True)
app.include_router(login) app.include_router(session)
app.include_router(authorize) app.include_router(authorize)
app.include_router(jwks) app.include_router(jwks)
app.include_router(token) app.include_router(token)

View File

@@ -1,52 +0,0 @@
from datetime import timedelta
from aws_lambda_powertools.event_handler.exceptions import ForbiddenError
from jose import jwt
from layercake.dateutils import now
from config import (
ISSUER,
JWT_ALGORITHM,
JWT_EXP_SECONDS,
JWT_SECRET,
OAUTH2_REFRESH_TOKEN_EXPIRES_IN,
)
def generate_jwt(user_id: str, email: str) -> str:
now_ = now()
payload = {
'sub': user_id,
'email': email,
'iat': int(now_.timestamp()),
'exp': int((now_ + timedelta(seconds=JWT_EXP_SECONDS)).timestamp()),
'iss': ISSUER,
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def generate_refresh_token(user_id: str) -> str:
now_ = now()
exp = now_ + timedelta(seconds=OAUTH2_REFRESH_TOKEN_EXPIRES_IN)
payload = {
'sub': user_id,
'iat': int(now_.timestamp()),
'exp': int(exp.timestamp()),
'iss': ISSUER,
'typ': 'refresh',
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def verify_jwt(token: str) -> dict:
payload = jwt.decode(
token,
JWT_SECRET,
algorithms=[JWT_ALGORITHM],
issuer=ISSUER,
options={
'require': ['exp', 'sub', 'iss'],
'leeway': 60,
},
)
return payload

View File

@@ -1,49 +1,40 @@
from http import HTTPStatus
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from urllib.parse import ParseResult, quote, urlencode, urlunparse
import jwt
from authlib.oauth2 import OAuth2Error from authlib.oauth2 import OAuth2Error
from authlib.oauth2.rfc6749 import errors from authlib.oauth2.rfc6749 import errors
from aws_lambda_powertools import Logger from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler import Response
from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.api_gateway import Router
from jose.exceptions import JWTError from aws_lambda_powertools.event_handler.exceptions import BadRequestError
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from jose_ import verify_jwt from boto3clients import dynamodb_client
from config import ISSUER, JWT_ALGORITHM, JWT_SECRET, OAUTH2_TABLE
from oauth2 import server from oauth2 import server
router = Router() router = Router()
logger = Logger(__name__) logger = Logger(__name__)
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
@router.get('/authorize') @router.get('/authorize')
def authorize(): def authorize():
current_event = router.current_event current_event = router.current_event
cookies = _parse_cookies(current_event.get('cookies', [])) cookies = _parse_cookies(current_event.get('cookies', []))
id_token = cookies.get('id_token') session_id = cookies.get('session_id')
continue_url = _build_continue_url(
current_event.path,
current_event.query_string_parameters,
)
login_url = f'/login?continue={continue_url}' if not session_id:
raise BadRequestError('Missing session_id')
try:
if not id_token:
raise ValueError('Missing id_token')
user = verify_jwt(id_token)
except (ValueError, JWTError):
return Response(
status_code=HTTPStatus.FOUND,
headers={'Location': login_url},
)
try: try:
user_id = verify_session(session_id)
grant = server.get_consent_grant( grant = server.get_consent_grant(
request=router.current_event, request=router.current_event,
end_user={'id': user['sub']}, end_user={'id': user_id},
) )
except jwt.exceptions.InvalidTokenError as err:
logger.exception(err)
raise BadRequestError(str(err))
except OAuth2Error as err: except OAuth2Error as err:
logger.exception(err) logger.exception(err)
return dict(err.get_body()) return dict(err.get_body())
@@ -51,7 +42,7 @@ def authorize():
try: try:
return server.create_authorization_response( return server.create_authorization_response(
request=router.current_event, request=router.current_event,
grant_user={'id': user['sub']}, grant_user={'id': user_id},
grant=grant, grant=grant,
) )
except errors.OAuth2Error as err: except errors.OAuth2Error as err:
@@ -59,6 +50,29 @@ def authorize():
return {} return {}
def verify_session(session_id: str) -> str:
payload = jwt.decode(
session_id,
JWT_SECRET,
algorithms=[JWT_ALGORITHM],
issuer=ISSUER,
options={
'require': ['exp', 'sub', 'iss', 'sid'],
'leeway': 60,
},
)
oauth2_layer.collection.get_item(
KeyPair(
pk='SESSION',
sk=payload['sid'],
),
exc_cls=SessionRevokedError,
)
return payload['sub']
def _parse_cookies(cookies: list[str] | None) -> dict[str, str]: def _parse_cookies(cookies: list[str] | None) -> dict[str, str]:
parsed_cookies = {} parsed_cookies = {}
@@ -73,9 +87,6 @@ def _parse_cookies(cookies: list[str] | None) -> dict[str, str]:
return parsed_cookies return parsed_cookies
def _build_continue_url( class SessionRevokedError(BadRequestError):
path: str, def __init__(self, *_):
query_string_parameters: dict, super().__init__('Session revoked')
) -> str:
query = urlencode(query_string_parameters)
return quote(urlunparse(ParseResult('', '', path, '', query, '')), safe='')

View File

@@ -1,91 +0,0 @@
from http import HTTPStatus
from typing import Annotated
from aws_lambda_powertools.event_handler import (
Response,
)
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import ForbiddenError, NotFoundError
from aws_lambda_powertools.event_handler.openapi.params import Form, Param
from aws_lambda_powertools.shared.cookies import Cookie
from jinja2 import Environment, PackageLoader, select_autoescape
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from passlib.hash import pbkdf2_sha256
from boto3clients import dynamodb_client
from config import OAUTH2_TABLE
from jose_ import generate_jwt
router = Router()
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
templates = Environment(
loader=PackageLoader('app'),
autoescape=select_autoescape(['html']),
)
@router.get('/login', compress=True)
def login_form(continue_: Annotated[str, Param(alias='continue')]):
template = templates.get_template('login.html')
html = template.render(**{'continue': continue_})
return Response(
body=html,
status_code=HTTPStatus.OK,
content_type='text/html',
)
@router.post('/login')
def login(
username: Annotated[str, Form()],
password: Annotated[str, Form()],
continue_: Annotated[str, Form(alias='continue')],
):
user_id, password_hash = _get_user(username)
if not pbkdf2_sha256.verify(password, password_hash):
raise ForbiddenError('Invalid credentials')
jwt_token = generate_jwt(user_id, username)
return Response(
status_code=HTTPStatus.FOUND,
headers={
'Location': continue_,
},
cookies=[
Cookie(
name='id_token',
value=jwt_token,
http_only=True,
same_site=None,
),
],
)
def _get_user(username: str) -> tuple[str, str]:
r = oauth2_layer.collection.get_item(
# Post-migration: uncomment the following line
# KeyPair('EMAIL', username),
KeyPair('email', username),
exc_cls=EmailNotFoundError,
)
password = oauth2_layer.collection.get_item(
KeyPair(r['user_id'], 'PASSWORD'),
exc_cls=UserNotFoundError,
)
return r['user_id'], password['hash']
class EmailNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('Email not found')
class UserNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('User not found')

View File

@@ -0,0 +1,107 @@
from http import HTTPStatus
from typing import Annotated
from uuid import uuid4
import jwt
from aws_lambda_powertools.event_handler import (
Response,
)
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import ForbiddenError, NotFoundError
from aws_lambda_powertools.event_handler.openapi.params import Body
from aws_lambda_powertools.shared.cookies import Cookie
from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
from passlib.hash import pbkdf2_sha256
from boto3clients import dynamodb_client
from config import ISSUER, JWT_ALGORITHM, JWT_EXP_SECONDS, JWT_SECRET, OAUTH2_TABLE
router = Router()
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
@router.post('/session')
def session(
username: Annotated[str, Body()],
password: Annotated[str, Body()],
):
user_id, password_hash = _get_user(username)
if not pbkdf2_sha256.verify(password, password_hash):
raise ForbiddenError('Invalid credentials')
return Response(
status_code=HTTPStatus.FOUND,
cookies=[
Cookie(
name='session_id',
value=new_session(user_id),
http_only=True,
secure=True,
same_site=None,
)
],
)
def _get_user(username: str) -> tuple[str, str]:
sk = SortKey(username, path_spec='user_id')
user = oauth2_layer.collection.get_items(
KeyPair(pk='email', sk=sk, rename_key=sk.path_spec)
+ KeyPair(pk='cpf', sk=sk, rename_key=sk.path_spec),
flatten_top=False,
)
if not user:
raise UserNotFoundError()
password = oauth2_layer.collection.get_item(
KeyPair(user['user_id'], 'PASSWORD'),
exc_cls=UserNotFoundError,
)
return user['user_id'], password['hash']
def new_session(sub: str) -> str:
now_ = now()
sid = str(uuid4())
exp = ttl(start_dt=now_, seconds=JWT_EXP_SECONDS)
token = jwt.encode(
{
'sid': sid,
'sub': sub,
'iss': ISSUER,
'iat': int(now_.timestamp()),
'exp': exp,
},
JWT_SECRET,
algorithm=JWT_ALGORITHM,
)
with oauth2_layer.transact_writer() as transact:
transact.put(
item={
'id': 'SESSION',
'sk': sid,
'user_id': sub,
'ttl': exp,
'created_at': now_,
}
)
transact.put(
item={
'id': sub,
'sk': f'SESSION#{sid}',
'ttl': exp,
'created_at': now_,
}
)
return token
class UserNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('User not found')

View File

@@ -1,115 +0,0 @@
<!doctype html>
<html>
<head>
<title>EDUSEG®</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body
class="font-sans antialiased bg-black text-white flex items-center justify-center min-h-screen px-3"
>
<div class="w-full max-w-sm relative">
<div
aria-hidden="true"
class="absolute inset-0 grid grid-cols-2 opacity-20"
>
<div
class="blur-[106px] h-56 bg-gradient-to-br to-lime-400 from-lime-700"
></div>
<div
class="blur-[106px] h-42 bg-gradient-to-r from-lime-400 to-lime-600"
></div>
</div>
<div class="grid gap-5 relative z-1">
<div class="text-center space-y-1">
<span
class="border border-white/15 bg-white/5 px-2.5 py-3 rounded-xl inline-block"
><svg
width="18"
height="24"
viewBox="0 0 18 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="size-12"
>
<path
d="M16.2756 23.4353L8.93847 20.1298C8.7383 20.0015 8.48167 20.0015 8.27893 20.1298L0.941837 23.4353C0.533793 23.6945 0 23.4019 0 22.9194V1.12629C0.00256631 0.787535 0.277162 0.512939 0.615915 0.512939H16.6066C16.9454 0.512939 17.22 0.787535 17.22 1.12629V22.9194C17.22 23.4019 16.6862 23.6945 16.2781 23.4353H16.2756Z"
fill="#8CD366"
></path>
<path
d="M10.7274 3.71313H3.34668V6.41803H10.7274V3.71313Z"
fill="#2E3524"
></path>
<path
d="M9.42115 8.4939H3.34668V10.6496H9.42115V8.4939Z"
fill="#2E3524"
></path>
<path
d="M10.7274 12.7263H3.34668V15.4312H10.7274V12.7263Z"
fill="#2E3524"
></path>
<path
d="M12.9984 13.6731H12.9958C12.5111 13.6731 12.1182 14.066 12.1182 14.5508V14.5533C12.1182 15.0381 12.5111 15.431 12.9958 15.431H12.9984C13.4831 15.431 13.8761 15.0381 13.8761 14.5533V14.5508C13.8761 14.066 13.4831 13.6731 12.9984 13.6731Z"
fill="#2E3524"
></path></svg
></span>
<h1 class="text-3xl mt-6 font-semibold font-display text-balance">
Faça login
</h1>
<p class="text-white/50 text-sm">
Não tem uma conta?
<a href="" class="font-medium text-white">Cadastre-se</a>.
</p>
</div>
<form method="POST" action="/login" class="space-y-6">
<input name="continue" type="hidden" value="{{ continue }}" />
<div class="grid gap-2">
<label for="username" class="text-sm leading-none font-medium">
Email ou CPF
</label>
<input
type="text"
id="username"
name="username"
class="border border-white/15 bg-white/8 w-full rounded-lg px-3 py-2.5 shadow-sm outline-none focus-visible:border-white/30 focus-visible:ring-white/20 focus-visible:ring-3 transition"
required
/>
</div>
<div class="grid gap-2">
<div class="flex justify-between items-center">
<label for="password" class="text-sm leading-none font-medium">
Senha
</label>
<a href="#" class="text-sm" tabindex="-1">Esqueceu sua senha?</a>
</div>
<input
type="password"
id="password"
name="password"
class="border border-white/15 bg-white/8 w-full rounded-lg px-3 py-2.5 shadow-sm outline-none focus-visible:border-white/30 focus-visible:ring-white/20 focus-visible:ring-3 transition"
required
/>
</div>
<button
type="submit"
class="w-full text-sm font-medium text-black bg-lime-400 rounded-lg px-4 py-2 h-9"
>
Entrar
</button>
</form>
<p class="text-xs text-white/50 text-center">
Ao fazer login, você concorda com nossa
<a href="#" class="underline hover:no-underline" target="_blank">
política de privacidade </a
>.
</p>
</div>
</div>
</body>
</html>

View File

@@ -22,14 +22,11 @@ const schema = z.object({
type Schema = z.infer<typeof schema> type Schema = z.infer<typeof schema>
export function meta({}: Route.MetaArgs) { export function meta({}: Route.MetaArgs) {
return [ return [{ title: 'EDUSEG®' }]
{ title: 'EDUSEG®' },
{ name: 'description', content: 'Welcome to React Router!' }
]
} }
export function loader({ context }: Route.LoaderArgs) { export function loader({ context }: Route.LoaderArgs) {
return { message: context.cloudflare.env.VALUE_FROM_CLOUDFLARE } return { message: context.cloudflare.env.ISSUER_URL }
} }
export default function Home({ loaderData }: Route.ComponentProps) { export default function Home({ loaderData }: Route.ComponentProps) {
@@ -67,7 +64,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
<div className="grid gap-3"> <div className="grid gap-3">
<Label htmlFor="email">Email ou CPF</Label> <Label htmlFor="email">Email ou CPF</Label>
<Input id="email" type="text" required /> <Input id="email" {...register('username')} />
</div> </div>
<div className="grid gap-3"> <div className="grid gap-3">
@@ -80,7 +77,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
Esqueceu sua senha? Esqueceu sua senha?
</a> </a>
</div> </div>
<Input id="password" type="password" required /> <Input id="password" {...register('password')} />
</div> </div>
<Button type="submit" className="w-full bg-lime-400 cursor-pointer"> <Button type="submit" className="w-full bg-lime-400 cursor-pointer">
Entrar Entrar

View File

@@ -8,7 +8,7 @@ export default function Layout() {
href="//eduseg.com.br" href="//eduseg.com.br"
className="flex items-center gap-0.5 absolute top-5 left-5 text-sm z-1" className="flex items-center gap-0.5 absolute top-5 left-5 text-sm z-1"
> >
<ChevronLeftIcon className="size-5" /> Página incial <ChevronLeftIcon className="size-5" /> Página inicial
</a> </a>
<div className="w-full max-w-sm relative z-1"> <div className="w-full max-w-sm relative z-1">
<Outlet /> <Outlet />

View File

@@ -1,9 +1,14 @@
name = "id-saladeaula-digital" name = "id-saladeaula-digital"
compatibility_date = "2025-04-04" compatibility_date = "2025-04-04"
main = "./workers/app.ts" main = "./workers/app.ts"
#routes = [ routes = [
# { pattern = "id.saladeaula.digital", custom_domain = true } { pattern = "id.saladeaula.digital", custom_domain = true }
#] ]
[vars]
ISSUER_URL = "https://58tkjsb308.execute-api.sa-east-1.amazonaws.com"
[observability.logs] [observability.logs]
enabled = true enabled = true

View File

@@ -14,7 +14,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:91 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:92
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo
@@ -52,16 +52,10 @@ Resources:
- DynamoDBCrudPolicy: - DynamoDBCrudPolicy:
TableName: !Ref OAuth2Table TableName: !Ref OAuth2Table
Events: Events:
Login: Session:
Type: HttpApi Type: HttpApi
Properties: Properties:
Path: /login Path: /session
Method: GET
ApiId: !Ref HttpApi
LoginPost:
Type: HttpApi
Properties:
Path: /login
Method: POST Method: POST
ApiId: !Ref HttpApi ApiId: !Ref HttpApi
Authorize: Authorize:
@@ -70,13 +64,13 @@ Resources:
Path: /authorize Path: /authorize
Method: GET Method: GET
ApiId: !Ref HttpApi ApiId: !Ref HttpApi
OpenidConfiguration: OpenIDConfiguration:
Type: HttpApi Type: HttpApi
Properties: Properties:
Path: /.well-known/openid-configuration Path: /.well-known/openid-configuration
Method: GET Method: GET
ApiId: !Ref HttpApi ApiId: !Ref HttpApi
Jwks: JWKS:
Type: HttpApi Type: HttpApi
Properties: Properties:
Path: /.well-known/jwks.json Path: /.well-known/jwks.json

View File

@@ -1,11 +1,14 @@
from http import HTTPMethod from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer from layercake.dynamodb import DynamoDBPersistenceLayer
from jose_ import generate_jwt from routes.session import new_session
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
CLIENT_ID = 'd72d4005-1fa7-4430-9754-80d5e2487bb6'
USER_ID = '357db1c5-7442-4075-98a3-fbe5c938a419'
def test_authorize( def test_authorize(
app, app,
@@ -14,12 +17,7 @@ def test_authorize(
http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext, lambda_context: LambdaContext,
): ):
client_id = 'd72d4005-1fa7-4430-9754-80d5e2487bb6' session_id = new_session(USER_ID)
id_token = generate_jwt(
user_id='357db1c5-7442-4075-98a3-fbe5c938a419',
email='sergio@somosbeta.com.br',
)
r = app.lambda_handler( r = app.lambda_handler(
http_api_proxy( http_api_proxy(
@@ -27,21 +25,21 @@ def test_authorize(
method=HTTPMethod.GET, method=HTTPMethod.GET,
query_string_parameters={ query_string_parameters={
'response_type': 'code', 'response_type': 'code',
'client_id': client_id, 'client_id': CLIENT_ID,
'redirect_uri': 'https://localhost/callback', 'redirect_uri': 'https://localhost/callback',
'scope': 'openid offline_access', 'scope': 'openid offline_access',
'nonce': '123', 'nonce': '123',
'state': '456', 'state': '456',
}, },
cookies=[ cookies=[
f'id_token={id_token}; HttpOnly; Secure', f'session_id={session_id}; HttpOnly; Secure',
], ],
), ),
lambda_context, lambda_context,
) )
assert 'Location' in r['headers'] assert 'Location' in r['headers']
print(r) # print(r)
r = dynamodb_persistence_layer.query( r = dynamodb_persistence_layer.query(
key_cond_expr='#pk = :pk', key_cond_expr='#pk = :pk',
@@ -55,3 +53,34 @@ def test_authorize(
# One item was added from seeds # One item was added from seeds
assert len(r['items']) == 3 assert len(r['items']) == 3
def test_authorize_revoked(
app,
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
invalid_session_id = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiIwNTgzNTBhYi02NGU1LTQ0MzEtYmQyNy01MGVhOWIxNmQxZGYiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiaWF0IjoxNzU1Mzk3Nzk5LCJleHAiOjE3NTUzOTg2OTl9.dDbiHYReVERbkNH2df4sXK2VIwT7G1KjNC5UrBuN6IQ'
r = app.lambda_handler(
http_api_proxy(
raw_path='/authorize',
method=HTTPMethod.GET,
query_string_parameters={
'response_type': 'code',
'client_id': CLIENT_ID,
'redirect_uri': 'https://localhost/callback',
'scope': 'openid offline_access',
'nonce': '123',
'state': '456',
},
cookies=[
f'session_id={invalid_session_id}; HttpOnly; Secure',
],
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.BAD_REQUEST

View File

@@ -1,49 +0,0 @@
from http import HTTPMethod
from urllib.parse import urlencode
from ..conftest import HttpApiProxy, LambdaContext
def test_html(
app,
seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = app.lambda_handler(
http_api_proxy(
raw_path='/login',
method=HTTPMethod.GET,
query_string_parameters={'continue': 'http://localhost'},
),
lambda_context,
)
# print(r)
def test_login(
app,
seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = app.lambda_handler(
http_api_proxy(
raw_path='/login',
method=HTTPMethod.POST,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
},
body=urlencode(
{
'username': 'sergio@somosbeta.com.br',
'password': 'pytest@123',
'continue': 'http://localhost',
}
),
),
lambda_context,
)
# print(r)

View File

@@ -0,0 +1,30 @@
from http import HTTPMethod
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey
from ..conftest import HttpApiProxy, LambdaContext
def test_session(
app,
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = app.lambda_handler(
http_api_proxy(
raw_path='/session',
method=HTTPMethod.POST,
body={
'username': '07879819908',
'password': 'pytest@123',
},
),
lambda_context,
)
assert len(r['cookies']) == 1
session = dynamodb_persistence_layer.collection.query(PartitionKey('SESSION'))
assert len(session['items']) == 1

View File

@@ -2,9 +2,8 @@
{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest", "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#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest", "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#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", "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", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"}
// Post-migration: uncomment the following line
// {"id": "EMAIL", "sk": "sergio@somosbeta.com.br", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
{"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"} {"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
{"id": "cpf", "sk": "07879819908", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
// 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"}

View File

@@ -344,18 +344,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" },
] ]
[[package]]
name = "ecdsa"
version = "0.19.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
]
[[package]] [[package]]
name = "email-validator" name = "email-validator"
version = "2.2.0" version = "2.2.0"
@@ -434,18 +422,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
] ]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]] [[package]]
name = "jmespath" name = "jmespath"
version = "1.0.1" version = "1.0.1"
@@ -481,7 +457,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.9.8" version = "0.9.10"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },
@@ -490,7 +466,6 @@ dependencies = [
{ name = "dictdiffer" }, { name = "dictdiffer" },
{ name = "ftfy" }, { name = "ftfy" },
{ name = "glom" }, { name = "glom" },
{ name = "jinja2" },
{ name = "meilisearch" }, { name = "meilisearch" },
{ name = "orjson" }, { name = "orjson" },
{ name = "passlib" }, { name = "passlib" },
@@ -498,7 +473,6 @@ dependencies = [
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "pyjwt" }, { name = "pyjwt" },
{ name = "python-jose", extra = ["cryptography"] },
{ name = "pytz" }, { name = "pytz" },
{ name = "requests" }, { name = "requests" },
{ name = "smart-open", extra = ["s3"] }, { name = "smart-open", extra = ["s3"] },
@@ -514,7 +488,6 @@ requires-dist = [
{ 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 = "jinja2", specifier = ">=3.1.6" },
{ 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" },
@@ -522,7 +495,6 @@ requires-dist = [
{ 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 = "pyjwt", specifier = ">=2.10.1" },
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" },
{ 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" },
@@ -541,34 +513,6 @@ dev = [
{ name = "ruff", specifier = ">=0.11.1" }, { name = "ruff", specifier = ">=0.11.1" },
] ]
[[package]]
name = "markupsafe"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
]
[[package]] [[package]]
name = "meilisearch" name = "meilisearch"
version = "0.36.0" version = "0.36.0"
@@ -641,15 +585,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" },
] ]
[[package]]
name = "pyasn1"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.22" version = "2.22"
@@ -812,25 +747,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
] ]
[[package]]
name = "python-jose"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ecdsa" },
{ name = "pyasn1" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" },
]
[package.optional-dependencies]
cryptography = [
{ name = "cryptography" },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2025.2" version = "2025.2"
@@ -855,18 +771,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
] ]
[[package]]
name = "rsa"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.12.1" version = "0.12.1"

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "layercake" name = "layercake"
version = "0.9.9" version = "0.9.10"
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
readme = "README.md" readme = "README.md"
authors = [ authors = [
@@ -23,10 +23,8 @@ dependencies = [
"sqlite-utils>=3.38", "sqlite-utils>=3.38",
"dictdiffer>=0.9.0", "dictdiffer>=0.9.0",
"unidecode>=1.4.0", "unidecode>=1.4.0",
"python-jose[cryptography]>=3.5.0",
"authlib>=1.6.1", "authlib>=1.6.1",
"passlib>=1.7.4", "passlib>=1.7.4",
"jinja2>=3.1.6",
"pyjwt>=2.10.1", "pyjwt>=2.10.1",
] ]

58
layercake/uv.lock generated
View File

@@ -466,18 +466,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" },
] ]
[[package]]
name = "ecdsa"
version = "0.19.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
]
[[package]] [[package]]
name = "email-validator" name = "email-validator"
version = "2.2.0" version = "2.2.0"
@@ -687,7 +675,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.9.8" version = "0.9.9"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },
@@ -696,7 +684,6 @@ dependencies = [
{ name = "dictdiffer" }, { name = "dictdiffer" },
{ name = "ftfy" }, { name = "ftfy" },
{ name = "glom" }, { name = "glom" },
{ name = "jinja2" },
{ name = "meilisearch" }, { name = "meilisearch" },
{ name = "orjson" }, { name = "orjson" },
{ name = "passlib" }, { name = "passlib" },
@@ -704,7 +691,6 @@ dependencies = [
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "pyjwt" }, { name = "pyjwt" },
{ name = "python-jose", extra = ["cryptography"] },
{ name = "pytz" }, { name = "pytz" },
{ name = "requests" }, { name = "requests" },
{ name = "smart-open", extra = ["s3"] }, { name = "smart-open", extra = ["s3"] },
@@ -731,7 +717,6 @@ requires-dist = [
{ 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 = "jinja2", specifier = ">=3.1.6" },
{ 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" },
@@ -739,7 +724,6 @@ requires-dist = [
{ 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 = "pyjwt", specifier = ">=2.10.1" },
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" },
{ 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" },
@@ -1084,15 +1068,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/97/84/0e410c20bbe9a504fc56e97908f13261c2b313d16cbb3b738556166f044a/py_partiql_parser-0.6.1-py2.py3-none-any.whl", hash = "sha256:ff6a48067bff23c37e9044021bf1d949c83e195490c17e020715e927fe5b2456", size = 23520, upload-time = "2024-12-25T22:06:39.106Z" }, { url = "https://files.pythonhosted.org/packages/97/84/0e410c20bbe9a504fc56e97908f13261c2b313d16cbb3b738556166f044a/py_partiql_parser-0.6.1-py2.py3-none-any.whl", hash = "sha256:ff6a48067bff23c37e9044021bf1d949c83e195490c17e020715e927fe5b2456", size = 23520, upload-time = "2024-12-25T22:06:39.106Z" },
] ]
[[package]]
name = "pyasn1"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.22" version = "2.22"
@@ -1278,25 +1253,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
] ]
[[package]]
name = "python-jose"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ecdsa" },
{ name = "pyasn1" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" },
]
[package.optional-dependencies]
cryptography = [
{ name = "cryptography" },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2025.2" version = "2025.2"
@@ -1529,18 +1485,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" }, { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" },
] ]
[[package]]
name = "rsa"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.12.7" version = "0.12.7"