From 392dccebc14ebfc5123d55846379bc397256fe8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Wed, 3 Dec 2025 16:27:07 -0300 Subject: [PATCH] finish register --- api.saladeaula.digital/app/app.py | 2 + .../app/routes/users/emails.py | 1 + .../app/routes/index.tsx | 30 +++++++------- .../app/routes/register/index.tsx | 39 ++++++++++--------- id.saladeaula.digital/app/app.py | 2 + id.saladeaula.digital/app/boto3clients.py | 3 ++ .../app/routes/authentication.py | 10 ++--- id.saladeaula.digital/app/routes/register.py | 27 ++++++++++++- id.saladeaula.digital/pyproject.toml | 2 +- .../tests/routes/test_register.py | 2 + id.saladeaula.digital/uv.lock | 16 +++++++- 11 files changed, 90 insertions(+), 44 deletions(-) diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index d9057ad..d6d3b01 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -58,6 +58,8 @@ def health(): @app.exception_handler(ServiceError) def exc_error(exc: ServiceError): + logger.exception(exc) + return JSONResponse( body={ 'type': type(exc).__name__, diff --git a/api.saladeaula.digital/app/routes/users/emails.py b/api.saladeaula.digital/app/routes/users/emails.py index 17a17b5..04da09e 100644 --- a/api.saladeaula.digital/app/routes/users/emails.py +++ b/api.saladeaula.digital/app/routes/users/emails.py @@ -93,6 +93,7 @@ def add( # Post-migration (users): rename `email` to `EMAIL` 'id': 'email', 'sk': email, + 'user_id': user_id, 'created_at': now_, }, # Prevent duplicate emails diff --git a/apps/id.saladeaula.digital/app/routes/index.tsx b/apps/id.saladeaula.digital/app/routes/index.tsx index b2241d4..607ab5e 100644 --- a/apps/id.saladeaula.digital/app/routes/index.tsx +++ b/apps/id.saladeaula.digital/app/routes/index.tsx @@ -94,24 +94,22 @@ export default function Index({}: Route.ComponentProps) { } useEffect(() => { - if (fetcher.state === 'idle' && fetcher.data) { - const message = fetcher.data?.message + const message = fetcher.data?.message - switch (message) { - case 'User not found': - return setError('username', { - message: - 'Não encontramos sua conta. Verifique se está usando o Email ou CPF correto', - type: 'manual' - }) - case 'Invalid credentials': - return setError('password', { - message: 'A senha está incorreta', - type: 'manual' - }) - } + switch (message) { + case 'User not found': + return setError('username', { + message: + 'Não encontramos sua conta. Verifique se está usando o Email ou CPF correto', + type: 'manual' + }) + case 'Invalid credentials': + return setError('password', { + message: 'A senha está incorreta', + type: 'manual' + }) } - }, [fetcher.state, fetcher.data]) + }, [fetcher.data]) return ( <> diff --git a/apps/id.saladeaula.digital/app/routes/register/index.tsx b/apps/id.saladeaula.digital/app/routes/register/index.tsx index 491ccdd..d86e1c5 100644 --- a/apps/id.saladeaula.digital/app/routes/register/index.tsx +++ b/apps/id.saladeaula.digital/app/routes/register/index.tsx @@ -1,11 +1,11 @@ import type { Route } from '../+types' -import { useRequest } from 'ahooks' import { PatternFormat } from 'react-number-format' import { zodResolver } from '@hookform/resolvers/zod' import { useState } from 'react' import { CheckCircle2Icon } from 'lucide-react' import { useForm } from 'react-hook-form' +import { redirect, useFetcher } from 'react-router' import { Button } from '@repo/ui/components/ui/button' import { Checkbox } from '@repo/ui/components/ui/checkbox' @@ -19,14 +19,15 @@ import { } from '@repo/ui/components/ui/form' import { Input } from '@repo/ui/components/ui/input' import { Label } from '@repo/ui/components/ui/label' - -import { Cpf } from './cpf' -import { formSchema, type Schema, RegisterContext, type User } from './data' import { Alert, AlertDescription, AlertTitle } from '@repo/ui/components/ui/alert' +import { Spinner } from '@repo/ui/components/ui/spinner' + +import { Cpf } from './cpf' +import { formSchema, type Schema, RegisterContext, type User } from './data' export function meta({}: Route.MetaArgs) { return [{ title: 'Criar conta · EDUSEG®' }] @@ -39,32 +40,27 @@ export async function action({ request, context }: Route.ActionArgs) { const r = await fetch(issuerUrl.toString(), { method: 'POST', headers: new Headers({ 'Content-Type': 'application/json' }), - body: JSON.stringify(body) + body: JSON.stringify(body), + signal: request.signal }) - console.log(await r.json()) + throw redirect('/authorize', { headers: r.headers }) } export default function Signup({}: Route.ComponentProps) { + const fetcher = useFetcher() const [show, setShow] = useState(false) const [user, setUser] = useState(null) const form = useForm({ resolver: zodResolver(formSchema) }) - const { control, handleSubmit, formState } = form - const { runAsync } = useRequest( - async (user) => { - return await fetch(`/register`, { - method: 'POST', - headers: new Headers({ 'Content-Type': 'application/json' }), - body: JSON.stringify(user) - }) - }, - { manual: true } - ) + const { control, handleSubmit, formState, setError } = form const onSubmit = async (data: Schema) => { - await runAsync({ ...user, ...data }) + await fetcher.submit(JSON.stringify({ ...user, ...data }), { + method: 'post', + encType: 'application/json' + }) } return ( @@ -187,9 +183,14 @@ export default function Signup({}: Route.ComponentProps) { diff --git a/id.saladeaula.digital/app/app.py b/id.saladeaula.digital/app/app.py index 2d38c58..98114d7 100644 --- a/id.saladeaula.digital/app/app.py +++ b/id.saladeaula.digital/app/app.py @@ -44,6 +44,8 @@ def health(): @app.exception_handler(ServiceError) def exc_error(exc: ServiceError): + logger.exception(exc) + return Response( body={ 'type': type(exc).__name__, diff --git a/id.saladeaula.digital/app/boto3clients.py b/id.saladeaula.digital/app/boto3clients.py index 7d2a8c1..4d8e065 100644 --- a/id.saladeaula.digital/app/boto3clients.py +++ b/id.saladeaula.digital/app/boto3clients.py @@ -4,9 +4,11 @@ from typing import TYPE_CHECKING import boto3 if TYPE_CHECKING: + from mypy_boto3_cognito_idp import CognitoIdentityProviderClient from mypy_boto3_dynamodb.client import DynamoDBClient else: DynamoDBClient = object + CognitoIdentityProviderClient = object def get_dynamodb_client() -> DynamoDBClient: @@ -17,3 +19,4 @@ def get_dynamodb_client() -> DynamoDBClient: dynamodb_client: DynamoDBClient = get_dynamodb_client() +idp_client: CognitoIdentityProviderClient = boto3.client('cognito-idp') diff --git a/id.saladeaula.digital/app/routes/authentication.py b/id.saladeaula.digital/app/routes/authentication.py index 3cbde52..1f41abc 100644 --- a/id.saladeaula.digital/app/routes/authentication.py +++ b/id.saladeaula.digital/app/routes/authentication.py @@ -2,7 +2,6 @@ from http import HTTPStatus from typing import Annotated from uuid import uuid4 -import boto3 from aws_lambda_powertools.event_handler import ( Response, ) @@ -17,7 +16,7 @@ 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 boto3clients import dynamodb_client, idp_client from config import ( OAUTH2_TABLE, SESSION_EXPIRES_IN, @@ -25,7 +24,6 @@ from config import ( router = Router() dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client) -idp = boto3.client('cognito-idp') class InvalidCredentialsError(UnauthorizedError): ... @@ -125,7 +123,7 @@ def _get_idp_user( ).digest() try: - idp.initiate_auth( + idp_client.initiate_auth( AuthFlow='USER_PASSWORD_AUTH', AuthParameters={ 'USERNAME': username, @@ -155,7 +153,9 @@ def new_session(user_id: str) -> str: exp = ttl(start_dt=now_, seconds=SESSION_EXPIRES_IN) with dyn.transact_writer() as transact: - transact.delete(key=KeyPair(user_id, 'FAILED_ATTEMPTS')) + transact.delete( + key=KeyPair(user_id, 'FAILED_ATTEMPTS'), + ) transact.update( key=KeyPair(user_id, '0'), # Post-migration (users): uncomment the following line diff --git a/id.saladeaula.digital/app/routes/register.py b/id.saladeaula.digital/app/routes/register.py index 61fb477..23ca029 100644 --- a/id.saladeaula.digital/app/routes/register.py +++ b/id.saladeaula.digital/app/routes/register.py @@ -3,9 +3,11 @@ from http import HTTPStatus from typing import Annotated from uuid import uuid4 +from aws_lambda_powertools.event_handler import content_types from aws_lambda_powertools.event_handler.api_gateway import Response, Router from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError 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 from layercake.extra_types import CpfStr, NameStr @@ -14,7 +16,9 @@ from passlib.hash import pbkdf2_sha256 from pydantic import UUID4, EmailStr from boto3clients import dynamodb_client -from config import OAUTH2_TABLE +from config import OAUTH2_TABLE, SESSION_EXPIRES_IN + +from .authentication import new_session router = Router() dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client) @@ -68,15 +72,36 @@ def register( ) return Response( + content_type=content_types.APPLICATION_JSON, status_code=HTTPStatus.OK, + compress=True, body=asdict(new_user), + cookies=[ + _cookie(existing['id']), + ], ) _create_user(user=new_user, password=password) return Response( + content_type=content_types.APPLICATION_JSON, status_code=HTTPStatus.CREATED, + compress=True, body=asdict(new_user), + cookies=[ + _cookie(new_user.id), + ], + ) + + +def _cookie(user_id: str) -> Cookie: + return Cookie( + name='SID', + value=new_session(user_id), + http_only=True, + secure=True, + same_site=None, + max_age=SESSION_EXPIRES_IN, ) diff --git a/id.saladeaula.digital/pyproject.toml b/id.saladeaula.digital/pyproject.toml index 9446352..6901226 100644 --- a/id.saladeaula.digital/pyproject.toml +++ b/id.saladeaula.digital/pyproject.toml @@ -12,7 +12,7 @@ dev = [ "pytest>=8.4.1", "ruff>=0.12.1", "pytest-cov>=6.2.1", - "boto3-stubs[essential]>=1.40.44", + "boto3-stubs[cognito-idp,essential]>=1.40.44", ] diff --git a/id.saladeaula.digital/tests/routes/test_register.py b/id.saladeaula.digital/tests/routes/test_register.py index d84fa30..02d641c 100644 --- a/id.saladeaula.digital/tests/routes/test_register.py +++ b/id.saladeaula.digital/tests/routes/test_register.py @@ -23,6 +23,7 @@ def test_preexisting_user( 'cpf': '07879819908', 'password': 'Led@Zepellin', 'email': 'sergio@somosbeta.com.br', + 'never_logged': 'true', }, ), lambda_context, @@ -80,6 +81,7 @@ def test_preexisting_user_update_email( lambda_context, ) assert r['statusCode'] == HTTPStatus.OK + assert 'cookies' in r user = dynamodb_persistence_layer.collection.get_items( TransactKey( diff --git a/id.saladeaula.digital/uv.lock b/id.saladeaula.digital/uv.lock index 9b2e119..47bf0df 100644 --- a/id.saladeaula.digital/uv.lock +++ b/id.saladeaula.digital/uv.lock @@ -133,6 +133,9 @@ wheels = [ ] [package.optional-dependencies] +cognito-idp = [ + { name = "mypy-boto3-cognito-idp" }, +] essential = [ { name = "mypy-boto3-cloudformation" }, { name = "mypy-boto3-dynamodb" }, @@ -423,7 +426,7 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "boto3-stubs", extra = ["essential"] }, + { name = "boto3-stubs", extra = ["cognito-idp", "essential"] }, { name = "jsonlines" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -435,7 +438,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }] [package.metadata.requires-dev] dev = [ - { name = "boto3-stubs", extras = ["essential"], specifier = ">=1.40.44" }, + { name = "boto3-stubs", extras = ["cognito-idp", "essential"], specifier = ">=1.40.44" }, { name = "jsonlines", specifier = ">=4.0.0" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-cov", specifier = ">=6.2.1" }, @@ -591,6 +594,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2e/38/12301080cc5004593b8593c0cc7404c13d702ac1c15d4e0ccfacd1f4f416/mypy_boto3_cloudformation-1.40.44-py3-none-any.whl", hash = "sha256:64c8fe58ab7661fbb0bdea07c7375d3ebc3875760140feb6ad8f591a08a22647", size = 69896, upload-time = "2025-10-02T20:31:56.896Z" }, ] +[[package]] +name = "mypy-boto3-cognito-idp" +version = "1.40.60" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/4e/57aeebe4c57a6f3345cffab1c7ae05756c08fef335bc72ce34308a534835/mypy_boto3_cognito_idp-1.40.60.tar.gz", hash = "sha256:4e06656f3954e193e4dff69042bc214db5ef575ebbb606a1b5fcb6dc3d6fd32e", size = 52148, upload-time = "2025-10-27T19:43:24.788Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/d2/b42e487058fea7b43086b62150826a3aa2777f9a7fe0c9ed31e03bb05100/mypy_boto3_cognito_idp-1.40.60-py3-none-any.whl", hash = "sha256:afdb6c81676442d76be4c4bb63378758e13b970f9b366290ac2d1bfeda26f669", size = 57972, upload-time = "2025-10-27T19:43:22.069Z" }, +] + [[package]] name = "mypy-boto3-dynamodb" version = "1.40.44"