From c71e19eacb8b9303c941028f646d5587d9000fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Fri, 28 Nov 2025 16:07:59 -0300 Subject: [PATCH] update login --- api.saladeaula.digital/app/app.py | 1 + .../app/routes/users/__init__.py | 3 +- .../app/routes/users/password.py | 42 +++++ .../tests/routes/users/test_password.py | 21 +++ .../app/routes/index.tsx | 7 +- apps/saladeaula.digital/app/routes.ts | 2 +- apps/saladeaula.digital/app/routes/api.ts | 2 +- .../app/routes/{orders.tsx => history.tsx} | 0 apps/saladeaula.digital/app/routes/layout.tsx | 2 +- .../app/routes/settings/emails/add.tsx | 2 +- .../app/routes/settings/emails/primary.tsx | 13 +- .../app/routes/settings/password.tsx | 163 +++++++++++++++++- .../app/routes/settings/profile.tsx | 7 +- packages/ui/src/components/nav-user.tsx | 2 +- 14 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 api.saladeaula.digital/app/routes/users/password.py create mode 100644 api.saladeaula.digital/tests/routes/users/test_password.py rename apps/saladeaula.digital/app/routes/{orders.tsx => history.tsx} (100%) diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index 5607577..d9057ad 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -43,6 +43,7 @@ app.include_router(enrollments.scorm, prefix='/enrollments') app.include_router(users.router, prefix='/users') app.include_router(users.emails, prefix='/users') app.include_router(users.orgs, prefix='/users') +app.include_router(users.password, prefix='/users') app.include_router(orders.router, prefix='/orders') app.include_router(orgs.admins, prefix='/orgs') app.include_router(orgs.custom_pricing, prefix='/orgs') diff --git a/api.saladeaula.digital/app/routes/users/__init__.py b/api.saladeaula.digital/app/routes/users/__init__.py index f7919a5..cfb0466 100644 --- a/api.saladeaula.digital/app/routes/users/__init__.py +++ b/api.saladeaula.digital/app/routes/users/__init__.py @@ -9,8 +9,9 @@ from config import USER_TABLE from .emails import router as emails from .orgs import router as orgs +from .password import router as password -__all__ = ['emails', 'orgs'] +__all__ = ['emails', 'orgs', 'password'] router = Router() dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) diff --git a/api.saladeaula.digital/app/routes/users/password.py b/api.saladeaula.digital/app/routes/users/password.py new file mode 100644 index 0000000..02225f3 --- /dev/null +++ b/api.saladeaula.digital/app/routes/users/password.py @@ -0,0 +1,42 @@ +from http import HTTPStatus +from typing import Annotated + +from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.exceptions import NotFoundError +from aws_lambda_powertools.event_handler.openapi.params import Body +from layercake.dateutils import now +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair +from passlib.hash import pbkdf2_sha256 + +from api_gateway import JSONResponse +from boto3clients import dynamodb_client +from config import USER_TABLE + +router = Router() +dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) + + +class UserNotFoundError(NotFoundError): ... + + +@router.post('//password') +def password( + user_id: str, + new_password: Annotated[str, Body(min_length=6, embed=True)], +): + with dyn.transact_writer() as transact: + transact.condition( + key=KeyPair(user_id, '0'), + cond_expr='attribute_exists(sk)', + exc_cls=UserNotFoundError, + ) + transact.put( + item={ + 'id': user_id, + 'sk': 'PASSWORD', + 'hash': pbkdf2_sha256.hash(new_password), + 'created_at': now(), + } + ) + + return JSONResponse(status_code=HTTPStatus.NO_CONTENT) diff --git a/api.saladeaula.digital/tests/routes/users/test_password.py b/api.saladeaula.digital/tests/routes/users/test_password.py new file mode 100644 index 0000000..ca1b8d9 --- /dev/null +++ b/api.saladeaula.digital/tests/routes/users/test_password.py @@ -0,0 +1,21 @@ +from http import HTTPMethod, HTTPStatus + +from ...conftest import HttpApiProxy, LambdaContext + + +def test_password( + app, + seeds, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/password', + method=HTTPMethod.POST, + body={'new_password': '123@56'}, + ), + lambda_context, + ) + + assert r['statusCode'] == HTTPStatus.NO_CONTENT diff --git a/apps/id.saladeaula.digital/app/routes/index.tsx b/apps/id.saladeaula.digital/app/routes/index.tsx index 9211446..06fdfc9 100644 --- a/apps/id.saladeaula.digital/app/routes/index.tsx +++ b/apps/id.saladeaula.digital/app/routes/index.tsx @@ -47,7 +47,10 @@ export function meta({}: Route.MetaArgs) { } export async function action({ request, context }: Route.ActionArgs) { - const issuerUrl = new URL('/session', context.cloudflare.env.ISSUER_URL) + const issuerUrl = new URL( + '/authentication', + context.cloudflare.env.ISSUER_URL + ) const formData = Object.fromEntries(await request.formData()) try { @@ -73,7 +76,7 @@ export async function action({ request, context }: Route.ActionArgs) { headers.set('Location', url.toString()) return new Response(await r.text(), { - status: 302, + status: 402, headers }) } catch (error) { diff --git a/apps/saladeaula.digital/app/routes.ts b/apps/saladeaula.digital/app/routes.ts index 6b6e8bb..9867774 100644 --- a/apps/saladeaula.digital/app/routes.ts +++ b/apps/saladeaula.digital/app/routes.ts @@ -9,7 +9,7 @@ export default [ layout('routes/layout.tsx', [ index('routes/index.tsx'), route('certs', 'routes/certs.tsx'), - route('orders', 'routes/orders.tsx'), + route('history', 'routes/history.tsx'), route('settings', 'routes/settings/layout.tsx', [ index('routes/settings/profile.tsx'), route('emails', 'routes/settings/emails/index.tsx'), diff --git a/apps/saladeaula.digital/app/routes/api.ts b/apps/saladeaula.digital/app/routes/api.ts index 8607dbe..73feb5a 100644 --- a/apps/saladeaula.digital/app/routes/api.ts +++ b/apps/saladeaula.digital/app/routes/api.ts @@ -31,7 +31,7 @@ async function proxy({ ? await response.text() : await response.arrayBuffer() - return new Response(body, { + return new Response(body ? body : null, { status: response.status, headers: response.headers }) diff --git a/apps/saladeaula.digital/app/routes/orders.tsx b/apps/saladeaula.digital/app/routes/history.tsx similarity index 100% rename from apps/saladeaula.digital/app/routes/orders.tsx rename to apps/saladeaula.digital/app/routes/history.tsx diff --git a/apps/saladeaula.digital/app/routes/layout.tsx b/apps/saladeaula.digital/app/routes/layout.tsx index c3b6ea8..e1d7da6 100644 --- a/apps/saladeaula.digital/app/routes/layout.tsx +++ b/apps/saladeaula.digital/app/routes/layout.tsx @@ -42,7 +42,7 @@ const navMain = [ }, { title: 'Histórico de compras', - url: '/orders' + url: '/history' } ] diff --git a/apps/saladeaula.digital/app/routes/settings/emails/add.tsx b/apps/saladeaula.digital/app/routes/settings/emails/add.tsx index 361898c..4c259af 100644 --- a/apps/saladeaula.digital/app/routes/settings/emails/add.tsx +++ b/apps/saladeaula.digital/app/routes/settings/emails/add.tsx @@ -94,7 +94,7 @@ export function Add() { )} - Adicionar + Adicionar email diff --git a/apps/saladeaula.digital/app/routes/settings/emails/primary.tsx b/apps/saladeaula.digital/app/routes/settings/emails/primary.tsx index 9a30dee..245e318 100644 --- a/apps/saladeaula.digital/app/routes/settings/emails/primary.tsx +++ b/apps/saladeaula.digital/app/routes/settings/emails/primary.tsx @@ -35,12 +35,19 @@ export function Primary({ items = [] }: { items: Email[] }) { async ({ email }) => { // Doesn't use `user` because the data could be outdated const selected = emails.find((e) => e.email === email) + const new_email = selected?.email + const old_email = primary?.email + + if (new_email === old_email) { + return + } + const r = await fetch(`/api/users/${user.id}/emails/primary`, { method: 'PATCH', headers: new Headers({ 'Content-Type': 'application/json' }), body: JSON.stringify({ - new_email: selected?.email, - old_email: primary?.email, + new_email, + old_email, email_verified: selected?.email_verified }) }) @@ -87,7 +94,7 @@ export function Primary({ items = [] }: { items: Email[] }) { })} diff --git a/apps/saladeaula.digital/app/routes/settings/password.tsx b/apps/saladeaula.digital/app/routes/settings/password.tsx index 8355dcb..5ae2c04 100644 --- a/apps/saladeaula.digital/app/routes/settings/password.tsx +++ b/apps/saladeaula.digital/app/routes/settings/password.tsx @@ -1,5 +1,164 @@ import type { Route } from './+types/password' -export default function Route({}: Route.ComponentProps) { - return <> +import { useToggle } from 'ahooks' +import { useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { z } from 'zod' +import { toast } from 'sonner' +import { useFetcher } from 'react-router' +import { useEffect } from 'react' + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from '@repo/ui/components/ui/form' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from '@repo/ui/components/ui/card' +import { Input } from '@repo/ui/components/ui/input' +import { Button } from '@repo/ui/components/ui/button' +import { Spinner } from '@repo/ui/components/ui/spinner' +import { Checkbox } from '@repo/ui/components/ui/checkbox' +import { Label } from '@repo/ui/components/ui/label' +import { request as req, HttpMethod } from '@repo/util/request' +import { userContext } from '@repo/auth/context' +import type { User } from '@repo/auth/auth' + +const formSchema = z + .object({ + new_password: z.string().min(6, 'Deve ter no mínimo 6 caracteres'), + confirm_password: z.string().min(6, 'Deve ter no mínimo 6 caracteres') + }) + .refine((data) => data.new_password === data.confirm_password, { + message: 'As senhas não coincidem', + path: ['confirm_password'] + }) + +type Schema = z.infer + +export async function action({ request, context }: Route.ActionArgs) { + const user = context.get(userContext) as User + const body = await request.json() + const r = await req({ + url: `users/${user.sub}/password`, + headers: new Headers({ 'Content-Type': 'application/json' }), + method: HttpMethod.POST, + body: JSON.stringify(body), + request, + context + }) + + if (!r.ok) { + const error = await r.json().catch(() => ({})) + return { ok: false, error } + } + + return { ok: true } +} + +export default function Route({}: Route.ComponentProps) { + const [show, { toggle }] = useToggle() + const inputType = show ? 'text' : 'password' + const fetcher = useFetcher() + const form = useForm({ + resolver: zodResolver(formSchema) + }) + const { control, formState, handleSubmit, reset } = form + + const onSubmit = async ({ new_password }: Schema) => { + await fetcher.submit(JSON.stringify({ new_password }), { + method: 'post', + encType: 'application/json' + }) + } + + useEffect(() => { + if (fetcher.data?.ok) { + toast.success('A senha foi alterada') + return reset() + } + + switch (fetcher.data?.error?.type) { + case 'UserConflictError': + toast.error('O colaborador já foi vinculado anteriormente') + } + }, [fetcher.data, reset]) + + return ( +
+ + + + Alterar senha + + Sua senha deve ter no mínimo 6 caracteres. Recomendamos que inclua + uma combinação de números, letras e caracteres especiais (ex.: + !$@%). + + + + + ( + + Nova senha + + + + + + )} + /> + + ( + + Confirmar nova senha + + + + + + )} + /> + +
+ + +
+
+
+ +
+ +
+
+ + ) } diff --git a/apps/saladeaula.digital/app/routes/settings/profile.tsx b/apps/saladeaula.digital/app/routes/settings/profile.tsx index 052679a..fbe2d34 100644 --- a/apps/saladeaula.digital/app/routes/settings/profile.tsx +++ b/apps/saladeaula.digital/app/routes/settings/profile.tsx @@ -55,9 +55,10 @@ export default function Route({}: Route.ComponentProps) {
- Minha conta + Meu perfil - Gerenciar as configurações da sua conta. + Mantenha os dados do seu perfil atualizados para que apareçam + corretamente em seus cursos e certificados. @@ -136,7 +137,7 @@ export default function Route({}: Route.ComponentProps) { disabled={formState.isSubmitting} > {formState.isSubmitting && } - Editar + Atualizar perfil diff --git a/packages/ui/src/components/nav-user.tsx b/packages/ui/src/components/nav-user.tsx index 0a48449..2ddeb45 100644 --- a/packages/ui/src/components/nav-user.tsx +++ b/packages/ui/src/components/nav-user.tsx @@ -112,7 +112,7 @@ export function NavUser({