feat(docs): inclui documentacao de endpoints auth com Swagger UI

This commit is contained in:
2026-04-28 18:53:44 -05:00
parent 36a1875ee6
commit 80262c4be3
2 changed files with 633 additions and 0 deletions

602
public/openapi.json Normal file
View 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"
}
}
}
}
}
}
}
}
}