Merge branch 'main' of https://git.juancjc.com.br/antonioandre/nuxt-frontend into main
This commit is contained in:
44
README.md
44
README.md
@@ -16,7 +16,13 @@ MVP de autenticação central para serviços distribuídos.
|
|||||||
- Prisma + Postgres
|
- Prisma + Postgres
|
||||||
- JOSE (JWT)
|
- JOSE (JWT)
|
||||||
|
|
||||||
## Setup
|
## Produção
|
||||||
|
|
||||||
|
**Base URL:** `https://sistema-distribuido-trabalho-faculd.vercel.app`
|
||||||
|
|
||||||
|
**Documentação interativa (Swagger):** [`/docs`](https://sistema-distribuido-trabalho-faculd.vercel.app/docs)
|
||||||
|
|
||||||
|
## Setup local
|
||||||
|
|
||||||
1. Instale dependências:
|
1. Instale dependências:
|
||||||
|
|
||||||
@@ -56,7 +62,7 @@ npm run dev
|
|||||||
## Usuários de seed
|
## Usuários de seed
|
||||||
|
|
||||||
- `student@example.com` / `123456`
|
- `student@example.com` / `123456`
|
||||||
s
|
|
||||||
## Estrutura da tabela `User`
|
## Estrutura da tabela `User`
|
||||||
|
|
||||||
A tabela `User` possui apenas:
|
A tabela `User` possui apenas:
|
||||||
@@ -69,13 +75,15 @@ A tabela `User` possui apenas:
|
|||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
- `POST /auth/login`
|
| Método | Caminho | Auth | Descrição |
|
||||||
- `POST /auth/refresh`
|
|--------|---------|------|-----------|
|
||||||
- `POST /auth/register`
|
| POST | `/api/auth/register` | Não | Cria novo usuário |
|
||||||
- `POST /auth/forgot-password`
|
| POST | `/api/auth/login` | Não | Autentica e retorna tokens |
|
||||||
- `POST /auth/reset-password`
|
| POST | `/api/auth/refresh` | Não | Rotaciona refresh token |
|
||||||
- `GET /profile/me` (protegida)
|
| POST | `/api/auth/forgot-password` | Não | Solicita reset de senha |
|
||||||
- `GET /dashboard` (protegida, chama `/profile/me`)
|
| POST | `/api/auth/reset-password` | Não | Aplica nova senha com token |
|
||||||
|
| GET | `/profile/me` | Sim (Bearer) | Retorna usuário autenticado |
|
||||||
|
| GET | `/dashboard` | Sim (Bearer) | Orquestração A→B |
|
||||||
|
|
||||||
## Guia para serviços consumidores
|
## Guia para serviços consumidores
|
||||||
|
|
||||||
@@ -98,7 +106,7 @@ A identidade confiável do usuário é sempre o `sub`.
|
|||||||
### 1) Cadastro
|
### 1) Cadastro
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:3000/auth/register \
|
curl -X POST https://sistema-distribuido-trabalho-faculd.vercel.app/api/auth/register \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"email":"novo.usuario@example.com","password":"senha123"}'
|
-d '{"email":"novo.usuario@example.com","password":"senha123"}'
|
||||||
```
|
```
|
||||||
@@ -108,7 +116,7 @@ Fluxo recomendado no cliente: `register -> login`.
|
|||||||
### 2) Login
|
### 2) Login
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:3000/auth/login \
|
curl -X POST https://sistema-distribuido-trabalho-faculd.vercel.app/api/auth/login \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"email":"novo.usuario@example.com","password":"senha123"}'
|
-d '{"email":"novo.usuario@example.com","password":"senha123"}'
|
||||||
```
|
```
|
||||||
@@ -116,17 +124,17 @@ curl -X POST http://localhost:3000/auth/login \
|
|||||||
### 3) Forgot password (sem SMTP, modo didático)
|
### 3) Forgot password (sem SMTP, modo didático)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:3000/auth/forgot-password \
|
curl -X POST https://sistema-distribuido-trabalho-faculd.vercel.app/api/auth/forgot-password \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"email":"novo.usuario@example.com"}'
|
-d '{"email":"novo.usuario@example.com"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Observação: neste MVP didático a resposta já traz `recovery.reset_token` e `recovery.reset_url`.
|
Observação: neste MVP a resposta já traz `recovery.reset_token` e `recovery.reset_url`.
|
||||||
|
|
||||||
### 4) Reset password
|
### 4) Reset password
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:3000/auth/reset-password \
|
curl -X POST https://sistema-distribuido-trabalho-faculd.vercel.app/api/auth/reset-password \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"token":"<reset_token>","new_password":"novaSenha123"}'
|
-d '{"token":"<reset_token>","new_password":"novaSenha123"}'
|
||||||
```
|
```
|
||||||
@@ -134,7 +142,7 @@ curl -X POST http://localhost:3000/auth/reset-password \
|
|||||||
### 5) Login com nova senha
|
### 5) Login com nova senha
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:3000/auth/login \
|
curl -X POST https://sistema-distribuido-trabalho-faculd.vercel.app/api/auth/login \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"email":"novo.usuario@example.com","password":"novaSenha123"}'
|
-d '{"email":"novo.usuario@example.com","password":"novaSenha123"}'
|
||||||
```
|
```
|
||||||
@@ -142,21 +150,21 @@ curl -X POST http://localhost:3000/auth/login \
|
|||||||
### 6) Rota protegida
|
### 6) Rota protegida
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://localhost:3000/profile/me \
|
curl https://sistema-distribuido-trabalho-faculd.vercel.app/profile/me \
|
||||||
-H "Authorization: Bearer <access_token>"
|
-H "Authorization: Bearer <access_token>"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7) Chamada entre serviços (A -> B)
|
### 7) Chamada entre serviços (A -> B)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://localhost:3000/dashboard \
|
curl https://sistema-distribuido-trabalho-faculd.vercel.app/dashboard \
|
||||||
-H "Authorization: Bearer <access_token>"
|
-H "Authorization: Bearer <access_token>"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 8) Refresh
|
### 8) Refresh
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:3000/auth/refresh \
|
curl -X POST https://sistema-distribuido-trabalho-faculd.vercel.app/api/auth/refresh \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"refresh_token":"<refresh_token>"}'
|
-d '{"refresh_token":"<refresh_token>"}'
|
||||||
```
|
```
|
||||||
|
|||||||
602
public/openapi.json
Normal file
602
public/openapi.json
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "Sistema Auth API",
|
||||||
|
"description": "Serviço centralizado de autenticação com JWT RS256, refresh token rotation e recuperação de senha.",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://sistema-distribuido-trabalho-faculd.vercel.app",
|
||||||
|
"description": "Produção"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"description": "Desenvolvimento local"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "Auth",
|
||||||
|
"description": "Registro, login, refresh e recuperação de senha"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Profile",
|
||||||
|
"description": "Dados do usuário autenticado"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dashboard",
|
||||||
|
"description": "Endpoint protegido de exemplo (orquestração A→B)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"bearerAuth": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer",
|
||||||
|
"bearerFormat": "JWT",
|
||||||
|
"description": "Access token JWT assinado com RS256. Obtido via POST /api/auth/login."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"UserResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "email",
|
||||||
|
"example": "usuario@exemplo.com"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2026-04-28T12:00:00.000Z"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2026-04-28T12:00:00.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"email",
|
||||||
|
"created_at",
|
||||||
|
"updated_at"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TokenResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"access_token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "JWT RS256 com validade de 15 minutos (900s).",
|
||||||
|
"example": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImF1dGgta2V5LTEifQ..."
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Token opaco base64url com validade de 7 dias. Rotacionado a cada uso.",
|
||||||
|
"example": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."
|
||||||
|
},
|
||||||
|
"token_type": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Bearer"
|
||||||
|
},
|
||||||
|
"expires_in": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Validade do access_token em segundos.",
|
||||||
|
"example": 900
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"access_token",
|
||||||
|
"refresh_token",
|
||||||
|
"token_type",
|
||||||
|
"expires_in"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ErrorResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"statusCode": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 400
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Email é obrigatório"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"statusCode",
|
||||||
|
"message"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/api/auth/register": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
],
|
||||||
|
"summary": "Registrar novo usuário",
|
||||||
|
"description": "Cria uma conta nova. A senha é armazenada como hash scrypt — nunca em plaintext.",
|
||||||
|
"operationId": "authRegister",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"email",
|
||||||
|
"password"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "email",
|
||||||
|
"description": "Normalizado para lowercase internamente.",
|
||||||
|
"example": "usuario@exemplo.com"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 6,
|
||||||
|
"description": "Mínimo 6 caracteres.",
|
||||||
|
"example": "senha123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Usuário criado com sucesso.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UserResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Dados inválidos (campo faltando ou senha muito curta).",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"statusCode": 400,
|
||||||
|
"message": "Senha deve ter no mínimo 6 caracteres"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"description": "Email já cadastrado.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"statusCode": 409,
|
||||||
|
"message": "Email já cadastrado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/auth/login": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
],
|
||||||
|
"summary": "Login",
|
||||||
|
"description": "Autentica o usuário e retorna um par de tokens (access + refresh).",
|
||||||
|
"operationId": "authLogin",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"email",
|
||||||
|
"password"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "email",
|
||||||
|
"example": "usuario@exemplo.com"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "senha123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Autenticado com sucesso.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TokenResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Campos obrigatórios ausentes.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Credenciais inválidas.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"statusCode": 401,
|
||||||
|
"message": "Credenciais inválidas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/auth/refresh": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
],
|
||||||
|
"summary": "Renovar tokens",
|
||||||
|
"description": "Troca um refresh token válido por um novo par de tokens. O token antigo é revogado imediatamente (rotação).",
|
||||||
|
"operationId": "authRefresh",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"refresh_token"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"refresh_token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Refresh token obtido no login ou na última renovação.",
|
||||||
|
"example": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Novos tokens emitidos.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TokenResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "refresh_token ausente.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Token inválido, expirado ou já revogado.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"statusCode": 401,
|
||||||
|
"message": "Refresh token inválido ou expirado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/auth/forgot-password": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
],
|
||||||
|
"summary": "Solicitar reset de senha",
|
||||||
|
"description": "Gera um token de reset para o email informado. A resposta é sempre genérica para não revelar se o email existe (proteção contra enumeração). Em produção, o token seria enviado por email; aqui é retornado no body para fins de teste.",
|
||||||
|
"operationId": "authForgotPassword",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "email",
|
||||||
|
"example": "usuario@exemplo.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Resposta genérica (independente de o email existir ou não).",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Se o email existir, instruções de recuperação foram geradas"
|
||||||
|
},
|
||||||
|
"recovery": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Presente apenas em ambiente de desenvolvimento para facilitar testes.",
|
||||||
|
"properties": {
|
||||||
|
"reset_token": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "abc123..."
|
||||||
|
},
|
||||||
|
"reset_url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri",
|
||||||
|
"example": "http://localhost:3000/reset-password?token=abc123..."
|
||||||
|
},
|
||||||
|
"expires_in": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 900
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Campo email ausente.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/auth/reset-password": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
],
|
||||||
|
"summary": "Redefinir senha",
|
||||||
|
"description": "Aplica a nova senha usando o token de reset. O token é de uso único — após utilizado, é marcado como consumido.",
|
||||||
|
"operationId": "authResetPassword",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"token",
|
||||||
|
"new_password"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Token recebido via forgot-password.",
|
||||||
|
"example": "abc123..."
|
||||||
|
},
|
||||||
|
"new_password": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 6,
|
||||||
|
"description": "Nova senha, mínimo 6 caracteres.",
|
||||||
|
"example": "novaSenha456"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Senha atualizada com sucesso.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Senha atualizada com sucesso"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Dados inválidos (campo faltando ou senha curta demais).",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Token inválido, expirado ou já utilizado.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"statusCode": 401,
|
||||||
|
"message": "Token de reset inválido ou expirado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/profile/me": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Profile"
|
||||||
|
],
|
||||||
|
"summary": "Perfil do usuário autenticado",
|
||||||
|
"description": "Retorna os dados do usuário identificado pelo access token JWT.",
|
||||||
|
"operationId": "profileMe",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Dados do usuário.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UserResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Token ausente, inválido ou expirado.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"statusCode": 401,
|
||||||
|
"message": "Token inválido ou expirado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Usuário não encontrado (token válido mas conta removida).",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/dashboard": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Dashboard"
|
||||||
|
],
|
||||||
|
"summary": "Dashboard (orquestração A→B)",
|
||||||
|
"description": "Endpoint protegido que demonstra orquestração entre serviços: chama /profile/me internamente e agrega os resultados de duas APIs (A e B), verificando que o `sub` do JWT é consistente entre elas.",
|
||||||
|
"operationId": "dashboard",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Dados agregados do dashboard.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"sub_from_api_a": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||||
|
},
|
||||||
|
"sub_from_api_b": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||||
|
},
|
||||||
|
"same_subject": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": true
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"$ref": "#/components/schemas/UserResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Token ausente, inválido ou expirado.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
server/routes/docs.get.ts
Normal file
31
server/routes/docs.get.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
export default defineEventHandler((event) => {
|
||||||
|
setHeader(event, 'Content-Type', 'text/html; charset=utf-8')
|
||||||
|
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Sistema Auth — API Docs</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
||||||
|
<style>
|
||||||
|
body { margin: 0; }
|
||||||
|
.topbar { display: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
SwaggerUIBundle({
|
||||||
|
url: '/openapi.json',
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
|
||||||
|
layout: 'BaseLayout',
|
||||||
|
deepLinking: true,
|
||||||
|
tryItOutEnabled: true,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user