finish register
This commit is contained in:
@@ -58,6 +58,8 @@ def health():
|
|||||||
|
|
||||||
@app.exception_handler(ServiceError)
|
@app.exception_handler(ServiceError)
|
||||||
def exc_error(exc: ServiceError):
|
def exc_error(exc: ServiceError):
|
||||||
|
logger.exception(exc)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
body={
|
body={
|
||||||
'type': type(exc).__name__,
|
'type': type(exc).__name__,
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ def add(
|
|||||||
# Post-migration (users): rename `email` to `EMAIL`
|
# Post-migration (users): rename `email` to `EMAIL`
|
||||||
'id': 'email',
|
'id': 'email',
|
||||||
'sk': email,
|
'sk': email,
|
||||||
|
'user_id': user_id,
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
},
|
},
|
||||||
# Prevent duplicate emails
|
# Prevent duplicate emails
|
||||||
|
|||||||
@@ -94,24 +94,22 @@ export default function Index({}: Route.ComponentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fetcher.state === 'idle' && fetcher.data) {
|
const message = fetcher.data?.message
|
||||||
const message = fetcher.data?.message
|
|
||||||
|
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case 'User not found':
|
case 'User not found':
|
||||||
return setError('username', {
|
return setError('username', {
|
||||||
message:
|
message:
|
||||||
'Não encontramos sua conta. Verifique se está usando o Email ou CPF correto',
|
'Não encontramos sua conta. Verifique se está usando o Email ou CPF correto',
|
||||||
type: 'manual'
|
type: 'manual'
|
||||||
})
|
})
|
||||||
case 'Invalid credentials':
|
case 'Invalid credentials':
|
||||||
return setError('password', {
|
return setError('password', {
|
||||||
message: 'A senha está incorreta',
|
message: 'A senha está incorreta',
|
||||||
type: 'manual'
|
type: 'manual'
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [fetcher.state, fetcher.data])
|
}, [fetcher.data])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { Route } from '../+types'
|
import type { Route } from '../+types'
|
||||||
|
|
||||||
import { useRequest } from 'ahooks'
|
|
||||||
import { PatternFormat } from 'react-number-format'
|
import { PatternFormat } from 'react-number-format'
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { CheckCircle2Icon } from 'lucide-react'
|
import { CheckCircle2Icon } from 'lucide-react'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
|
import { redirect, useFetcher } from 'react-router'
|
||||||
|
|
||||||
import { Button } from '@repo/ui/components/ui/button'
|
import { Button } from '@repo/ui/components/ui/button'
|
||||||
import { Checkbox } from '@repo/ui/components/ui/checkbox'
|
import { Checkbox } from '@repo/ui/components/ui/checkbox'
|
||||||
@@ -19,14 +19,15 @@ import {
|
|||||||
} from '@repo/ui/components/ui/form'
|
} from '@repo/ui/components/ui/form'
|
||||||
import { Input } from '@repo/ui/components/ui/input'
|
import { Input } from '@repo/ui/components/ui/input'
|
||||||
import { Label } from '@repo/ui/components/ui/label'
|
import { Label } from '@repo/ui/components/ui/label'
|
||||||
|
|
||||||
import { Cpf } from './cpf'
|
|
||||||
import { formSchema, type Schema, RegisterContext, type User } from './data'
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
AlertDescription,
|
AlertDescription,
|
||||||
AlertTitle
|
AlertTitle
|
||||||
} from '@repo/ui/components/ui/alert'
|
} 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) {
|
export function meta({}: Route.MetaArgs) {
|
||||||
return [{ title: 'Criar conta · EDUSEG®' }]
|
return [{ title: 'Criar conta · EDUSEG®' }]
|
||||||
@@ -39,32 +40,27 @@ export async function action({ request, context }: Route.ActionArgs) {
|
|||||||
const r = await fetch(issuerUrl.toString(), {
|
const r = await fetch(issuerUrl.toString(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: new Headers({ 'Content-Type': 'application/json' }),
|
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) {
|
export default function Signup({}: Route.ComponentProps) {
|
||||||
|
const fetcher = useFetcher()
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const [user, setUser] = useState<User | null>(null)
|
const [user, setUser] = useState<User | null>(null)
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema)
|
resolver: zodResolver(formSchema)
|
||||||
})
|
})
|
||||||
const { control, handleSubmit, formState } = form
|
const { control, handleSubmit, formState, setError } = 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 onSubmit = async (data: Schema) => {
|
const onSubmit = async (data: Schema) => {
|
||||||
await runAsync({ ...user, ...data })
|
await fetcher.submit(JSON.stringify({ ...user, ...data }), {
|
||||||
|
method: 'post',
|
||||||
|
encType: 'application/json'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -187,9 +183,14 @@ export default function Signup({}: Route.ComponentProps) {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer relative overflow-hidden"
|
||||||
disabled={formState.isSubmitting}
|
disabled={formState.isSubmitting}
|
||||||
>
|
>
|
||||||
|
{formState.isSubmitting && (
|
||||||
|
<div className="absolute bg-lime-500 inset-0 flex items-center justify-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
Criar conta
|
Criar conta
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ def health():
|
|||||||
|
|
||||||
@app.exception_handler(ServiceError)
|
@app.exception_handler(ServiceError)
|
||||||
def exc_error(exc: ServiceError):
|
def exc_error(exc: ServiceError):
|
||||||
|
logger.exception(exc)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
body={
|
body={
|
||||||
'type': type(exc).__name__,
|
'type': type(exc).__name__,
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ from typing import TYPE_CHECKING
|
|||||||
import boto3
|
import boto3
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from mypy_boto3_cognito_idp import CognitoIdentityProviderClient
|
||||||
from mypy_boto3_dynamodb.client import DynamoDBClient
|
from mypy_boto3_dynamodb.client import DynamoDBClient
|
||||||
else:
|
else:
|
||||||
DynamoDBClient = object
|
DynamoDBClient = object
|
||||||
|
CognitoIdentityProviderClient = object
|
||||||
|
|
||||||
|
|
||||||
def get_dynamodb_client() -> DynamoDBClient:
|
def get_dynamodb_client() -> DynamoDBClient:
|
||||||
@@ -17,3 +19,4 @@ def get_dynamodb_client() -> DynamoDBClient:
|
|||||||
|
|
||||||
|
|
||||||
dynamodb_client: DynamoDBClient = get_dynamodb_client()
|
dynamodb_client: DynamoDBClient = get_dynamodb_client()
|
||||||
|
idp_client: CognitoIdentityProviderClient = boto3.client('cognito-idp')
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from http import HTTPStatus
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import boto3
|
|
||||||
from aws_lambda_powertools.event_handler import (
|
from aws_lambda_powertools.event_handler import (
|
||||||
Response,
|
Response,
|
||||||
)
|
)
|
||||||
@@ -17,7 +16,7 @@ from layercake.dateutils import now, ttl
|
|||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
|
||||||
from passlib.hash import pbkdf2_sha256
|
from passlib.hash import pbkdf2_sha256
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client, idp_client
|
||||||
from config import (
|
from config import (
|
||||||
OAUTH2_TABLE,
|
OAUTH2_TABLE,
|
||||||
SESSION_EXPIRES_IN,
|
SESSION_EXPIRES_IN,
|
||||||
@@ -25,7 +24,6 @@ from config import (
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
|
dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
|
||||||
idp = boto3.client('cognito-idp')
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidCredentialsError(UnauthorizedError): ...
|
class InvalidCredentialsError(UnauthorizedError): ...
|
||||||
@@ -125,7 +123,7 @@ def _get_idp_user(
|
|||||||
).digest()
|
).digest()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
idp.initiate_auth(
|
idp_client.initiate_auth(
|
||||||
AuthFlow='USER_PASSWORD_AUTH',
|
AuthFlow='USER_PASSWORD_AUTH',
|
||||||
AuthParameters={
|
AuthParameters={
|
||||||
'USERNAME': username,
|
'USERNAME': username,
|
||||||
@@ -155,7 +153,9 @@ def new_session(user_id: str) -> str:
|
|||||||
exp = ttl(start_dt=now_, seconds=SESSION_EXPIRES_IN)
|
exp = ttl(start_dt=now_, seconds=SESSION_EXPIRES_IN)
|
||||||
|
|
||||||
with dyn.transact_writer() as transact:
|
with dyn.transact_writer() as transact:
|
||||||
transact.delete(key=KeyPair(user_id, 'FAILED_ATTEMPTS'))
|
transact.delete(
|
||||||
|
key=KeyPair(user_id, 'FAILED_ATTEMPTS'),
|
||||||
|
)
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(user_id, '0'),
|
key=KeyPair(user_id, '0'),
|
||||||
# Post-migration (users): uncomment the following line
|
# Post-migration (users): uncomment the following line
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ from http import HTTPStatus
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from uuid import uuid4
|
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.api_gateway import Response, Router
|
||||||
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
|
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
|
||||||
from aws_lambda_powertools.event_handler.openapi.params import Body
|
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.dateutils import now, ttl
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
from layercake.extra_types import CpfStr, NameStr
|
from layercake.extra_types import CpfStr, NameStr
|
||||||
@@ -14,7 +16,9 @@ from passlib.hash import pbkdf2_sha256
|
|||||||
from pydantic import UUID4, EmailStr
|
from pydantic import UUID4, EmailStr
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
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()
|
router = Router()
|
||||||
dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
|
dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
|
||||||
@@ -68,15 +72,36 @@ def register(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
|
content_type=content_types.APPLICATION_JSON,
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
|
compress=True,
|
||||||
body=asdict(new_user),
|
body=asdict(new_user),
|
||||||
|
cookies=[
|
||||||
|
_cookie(existing['id']),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
_create_user(user=new_user, password=password)
|
_create_user(user=new_user, password=password)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
|
content_type=content_types.APPLICATION_JSON,
|
||||||
status_code=HTTPStatus.CREATED,
|
status_code=HTTPStatus.CREATED,
|
||||||
|
compress=True,
|
||||||
body=asdict(new_user),
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ dev = [
|
|||||||
"pytest>=8.4.1",
|
"pytest>=8.4.1",
|
||||||
"ruff>=0.12.1",
|
"ruff>=0.12.1",
|
||||||
"pytest-cov>=6.2.1",
|
"pytest-cov>=6.2.1",
|
||||||
"boto3-stubs[essential]>=1.40.44",
|
"boto3-stubs[cognito-idp,essential]>=1.40.44",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def test_preexisting_user(
|
|||||||
'cpf': '07879819908',
|
'cpf': '07879819908',
|
||||||
'password': 'Led@Zepellin',
|
'password': 'Led@Zepellin',
|
||||||
'email': 'sergio@somosbeta.com.br',
|
'email': 'sergio@somosbeta.com.br',
|
||||||
|
'never_logged': 'true',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
lambda_context,
|
lambda_context,
|
||||||
@@ -80,6 +81,7 @@ def test_preexisting_user_update_email(
|
|||||||
lambda_context,
|
lambda_context,
|
||||||
)
|
)
|
||||||
assert r['statusCode'] == HTTPStatus.OK
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
|
assert 'cookies' in r
|
||||||
|
|
||||||
user = dynamodb_persistence_layer.collection.get_items(
|
user = dynamodb_persistence_layer.collection.get_items(
|
||||||
TransactKey(
|
TransactKey(
|
||||||
|
|||||||
16
id.saladeaula.digital/uv.lock
generated
16
id.saladeaula.digital/uv.lock
generated
@@ -133,6 +133,9 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
|
cognito-idp = [
|
||||||
|
{ name = "mypy-boto3-cognito-idp" },
|
||||||
|
]
|
||||||
essential = [
|
essential = [
|
||||||
{ name = "mypy-boto3-cloudformation" },
|
{ name = "mypy-boto3-cloudformation" },
|
||||||
{ name = "mypy-boto3-dynamodb" },
|
{ name = "mypy-boto3-dynamodb" },
|
||||||
@@ -423,7 +426,7 @@ dependencies = [
|
|||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "boto3-stubs", extra = ["essential"] },
|
{ name = "boto3-stubs", extra = ["cognito-idp", "essential"] },
|
||||||
{ name = "jsonlines" },
|
{ name = "jsonlines" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "pytest-cov" },
|
{ name = "pytest-cov" },
|
||||||
@@ -435,7 +438,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }]
|
|||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
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 = "jsonlines", specifier = ">=4.0.0" },
|
||||||
{ name = "pytest", specifier = ">=8.4.1" },
|
{ name = "pytest", specifier = ">=8.4.1" },
|
||||||
{ name = "pytest-cov", specifier = ">=6.2.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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "mypy-boto3-dynamodb"
|
name = "mypy-boto3-dynamodb"
|
||||||
version = "1.40.44"
|
version = "1.40.44"
|
||||||
|
|||||||
Reference in New Issue
Block a user