# Criação do Link de Impressão

# Parte I - Criação do servlet



# Servlet de Impressão de Documentos

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**1. Resumo da solução**</span></span>

<span style="font-family: Helvetica, serif;">Criar um </span><span style="font-family: Helvetica, serif;">**Servlet**</span><span style="font-family: Helvetica, serif;"> (</span><span style="font-family: Courier, serif;">/public/doc/\*</span><span style="font-family: Helvetica, serif;">) que recebe um token único (hash assinado) na URL.</span>  
<span style="font-family: Helvetica, serif;">O token é gerado pelo ERP (quando o usuário “gera link”) a partir de </span><span style="font-family: Helvetica, serif;">**empresaId**</span><span style="font-family: Helvetica, serif;">, </span><span style="font-family: Helvetica, serif;">**documentoId**</span><span style="font-family: Helvetica, serif;"> e </span><span style="font-family: Helvetica, serif;">**expiryTimestamp**</span><span style="font-family: Helvetica, serif;">.</span>  
<span style="font-family: Helvetica, serif;">O token contém dados codificados + assinatura </span><span style="font-family: Helvetica, serif;">**HMAC-SHA256**</span><span style="font-family: Helvetica, serif;"> com chave secreta.</span>  
<span style="font-family: Helvetica, serif;">O servlet valida o token e, se válido, entrega o PDF Jasper. O link expira após </span><span style="font-family: Helvetica, serif;">**5 dias**</span><span style="font-family: Helvetica, serif;">.</span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**2. Requisitos funcionais**</span></span>

•<span style="font-family: Helvetica, serif;"> Usuário no ERP gera link e recebe URL temporária.</span>

•<span style="font-family: Helvetica, serif;"> Link válido por 5 dias.</span>

•<span style="font-family: Helvetica, serif;"> Após expiração, retorna </span><span style="font-family: Helvetica, serif;">**403 Forbidden**</span><span style="font-family: Helvetica, serif;">.</span>

•<span style="font-family: Helvetica, serif;"> Cada geração, caso os 5 dias tenham passado, criam um novo token. </span>

•<span style="font-family: Helvetica, serif;"> Logs de auditoria obrigatórios.</span>

<span style="font-family: Helvetica, serif;">• A ideia é que todos os documentos possam ser impressos dessa forma, mas iniciaremos somente com o pedido de venda para avaliar o impacto no sistema.</span>

—————

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**3. Requisitos não-funcionais / Segurança**</span></span>

•<span style="font-family: Helvetica, serif;"> Assinatura </span><span style="font-family: Helvetica, serif;">**HMAC-SHA256**</span><span style="font-family: Helvetica, serif;"> com chave secreta.</span>

•<span style="font-family: Helvetica, serif;"> Uso de </span><span style="font-family: Helvetica, serif;">**Base64URL**</span><span style="font-family: Helvetica, serif;"> sem padding.</span>

•<span style="font-family: Helvetica, serif;"> Uso de </span><span style="font-family: Helvetica, serif;">**UTC**</span><span style="font-family: Helvetica, serif;"> para timestamps.</span>

•<span style="font-family: Helvetica, serif;"> Cabeçalhos HTTP de segurança (</span><span style="font-family: Courier, serif;">Cache-Control</span><span style="font-family: Helvetica, serif;">, </span><span style="font-family: Courier, serif;">X-Content-Type-Options</span><span style="font-family: Helvetica, serif;">, </span><span style="font-family: Courier, serif;">CSP</span><span style="font-family: Helvetica, serif;">).</span>

•<span style="font-family: Helvetica, serif;"> Chave secreta fixa, definida em código (depois consideraremos rotatividade).</span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**5. Formato do token**</span></span>

<span style="font-family: Helvetica, serif;">**Stateful**</span><span style="font-family: Helvetica, serif;"> (salvar token em tabela) — permite revogação.</span>

<span style="font-family: Courier, serif;">Payload: v1|empresaId|documentoId|usuarioId|expiryEpochSeconds</span>  
  
<span style="font-family: Courier, serif;">Assinatura: HMAC-SHA256(secret, payload)</span>  
  
<span style="font-family: Courier, serif;">Token final: Base64Url(payload) . "." . Base64Url(signature)</span>

<span style="font-family: Helvetica, serif;">Exemplo:</span>

<span style="font-family: Courier, serif;">v1|123|9876|1740000000</span>

—————

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**6. Endpoints**</span></span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**POST /endpoint de criação do documento**</span></span>

```JSON
{
  "usuarioId": 1,
  "documentId": 448,
  "documentoTipo": "VENDA"
}
```

•<span style="font-family: Helvetica, serif;"> Gera link temporário</span>

•<span style="font-family: Helvetica, serif;"> Retorno:</span>

```JSON

{
    "link": "http://192.168.230.230:8084/api-v1/public/doc/eyJlbXByZXNhSWQiOjEsImRvY3VtZW50SWQiOjQ0OCwiZG9jdW1lbnRvVGlwbyI6IlZFTkRBIiwiZXhwaXJhY2FvIjoxNzU4NjM5MDA3LjYzNzQ5OTg3N30.Q5lReWHWBQjegU_8rbMnxpzlA5B9I9uTG22_GcF3gWM"
}
```

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**<span style="font-family: Helvetica, serif;">GET /api-v1/public/doc/{token}</span>**</span></span>

•<span style="font-family: Helvetica, serif;"> Valida token</span>

•<span style="font-family: Helvetica, serif;"> Retorna PDF Jasper ou erro 403/410.</span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**POST /api-v1/public/doc/revoke/{token}**</span></span>

•<span style="font-family: Helvetica, serif;"> Revoga o token passado por parâmetro</span>

—————

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**7. Fluxos**</span></span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**Geração de link**</span></span>

<span style="font-family: Helvetica, serif;">1. Usuário solicita geração no ERP.</span>

<span style="font-family: Helvetica, serif;">2. Gera payload (empresaId, documentoId, expiry). Armazena o token na na tabela relacionada</span>

<span style="font-family: Helvetica, serif;">3. Calcula assinatura HMAC-SHA256.</span>

<span style="font-family: Helvetica, serif;">4. Retorna URL com token.</span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**Acesso ao link**</span></span>

<span style="font-family: Helvetica, serif;">1. Cliente acessa URL pública.</span>

<span style="font-family: Helvetica, serif;">2. Servlet valida assinatura e expiração.</span>

<span style="font-family: Helvetica, serif;">3. Gera PDF Jasper a partir das configurações do cliente ou do documento. Como somente estamos falando do pedido or enquanto, somente considerar as configurações padrão como norte (do modelo a ser impresso). Sempre gerar para não ter acúmulo de pdfs em disco.</span>

<span style="font-family: Helvetica, serif;">4. Retorna PDF ou erro.</span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**Regovação do Token** </span></span>

<span style="font-family: Helvetica, serif;">1. Usuário faz o post para o endpoint de revogação.</span>

<span style="font-family: Helvetica, serif;">2. É feito o update da tela, atualizando o bit "revoked". O documento não deve ser mais acessível depois que seu token for revogado. </span>

—————

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**8. Logs e auditoria**</span></span>

 A cada link gerado, deverá ser inserida um registro no log de operações no formato: &lt;LINK DE COMPARTILHAMENTO GERADO PARA &lt;DOCUMENTO (VENDA...)&gt;&lt;ID&gt;: &lt;LINK&gt;".

 O revoke por enquanto só será ser possível via endpoint interno (pode ser disponibilizado para o suporte em caso de problemas, não sendo visível para o cliente). Quando o usuário solicitar a geração de link para um documento cujo token esteja revogado, atualizar o registro do documento na tabela com o novo link, para não gerar registros desnecessários. O histórico é acessível via log.

—————

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**9. Criação da tabela**</span></span>

```SQL
CREATE TABLE documenttokens (
  token VARCHAR(512) PRIMARY KEY,
  empresaId BIGINT NOT NULL,
  documentoTipo INT NOT NULL,
  documentoId BIGINT NOT NULL,
  expiresAt TIMESTAMP WITH TIME ZONE NOT NULL,
  createdBy BIGINT,
  createdAt TIMESTAMP WITH TIME ZONE DEFAULT now(),
  revoked BOOLEAN DEFAULT false
);
```

—————

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**10. Exemplo de código Java (simplificado)**</span></span>

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**Geração do Token**</span></span>

```Java
public String generateToken(long empresaId, long documentoId, Instant expiryUtc) {
   String payload = String.format("v1|%d|%d|%d", empresaId, documentoId, expiryUtc.getEpochSecond());
   String payloadB64 = base64UrlEncode(payload.getBytes(StandardCharsets.UTF_8));
   byte[] sig = hmac(payload.getBytes(StandardCharsets.UTF_8));
   String sigB64 = base64UrlEncode(sig);
   return payloadB64 + "." + sigB64;
}
```

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**Servlet (esqueleto)**</span></span>

```Java
@WebServlet("/public/doc/*")
public class PublicDocumentServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String token = extractTokenFromPath(req);
        try {
            PublicLinkUtil.TokenPayload payload = linkUtil.validateToken(token);
            Documento doc = documentoService.findById(payload.documentoId);
            if (doc == null || doc.getEmpresaId() != payload.empresaId) {
                resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Documento inválido");
                return;
            }
            byte[] pdf = documentoService.getPdfBytes(payload.documentoId);
            resp.setContentType("application/pdf");
            resp.setHeader("Content-Disposition", "inline; filename=\"" + doc.getFileName() + "\"");
            resp.getOutputStream().write(pdf);
        } catch (Exception e) {
            resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Token inválido ou expirado");
        }
    }
}
```

—————

<span style="font-family: Helvetica, serif;"><span style="font-size: large;">**11. Testes sugeridos**</span></span>

•<span style="font-family: Helvetica, serif;"> Unit tests para geração e validação de token.</span>

•<span style="font-family: Helvetica, serif;"> Teste de token corrompido/expirado.</span>

•<span style="font-family: Helvetica, serif;"> Teste de carga no servlet com múltiplos acessos.</span>

•<span style="font-family: Helvetica, serif;"> Teste de segurança contra brute force. (pedir auxílio ao Matheus)</span>

