{ "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" } } } } } } } } }