Pular para o conteúdo

17. Manutenção e Evolução - Parte 3: Atualizações de Segurança, Estratégias de Migração e Documentação Técnica

Continuação da Parte 2: Refatoração, Modernização e Monitoramento de Desempenho

Atualizações de Segurança

Verificação Contínua de Vulnerabilidades

Implemente verificações automatizadas de segurança no pipeline CI/CD:

.github/workflows/security-scan.yml
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
- cron: '0 0 * * 1' # Toda segunda-feira à meia-noite
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --json > npm-audit.json || true
- name: Check for high severity vulnerabilities
run: |
HIGH_VULNS=$(cat npm-audit.json | jq '.vulnerabilities | map_values(select(.severity == "high" or .severity == "critical")) | length')
echo "Found $HIGH_VULNS high or critical severity vulnerabilities"
if [ "$HIGH_VULNS" -gt 0 ]; then
echo "::warning::Found $HIGH_VULNS high or critical severity vulnerabilities"
cat npm-audit.json | jq '.vulnerabilities | map_values(select(.severity == "high" or .severity == "critical"))'
fi
- name: Run Snyk scan
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: Upload security reports
uses: actions/upload-artifact@v3
with:
name: security-reports
path: |
npm-audit.json
snyk-report.json

Implementação de Cabeçalhos de Segurança

Adicione cabeçalhos de segurança para proteger sua aplicação:

// src/middleware.js (para Astro SSR)
export function onRequest({ request, locals }, next) {
const response = await next();
// Adicionar cabeçalhos de segurança
const headers = new Headers(response.headers);
// Prevenir clickjacking
headers.set('X-Frame-Options', 'DENY');
// Habilitar proteção XSS no navegador
headers.set('X-XSS-Protection', '1; mode=block');
// Prevenir MIME sniffing
headers.set('X-Content-Type-Options', 'nosniff');
// Política de segurança de conteúdo
headers.set('Content-Security-Policy', `
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net https://unpkg.com 'unsafe-inline';
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
img-src 'self' data: https://res.cloudinary.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
form-action 'self';
base-uri 'self';
object-src 'none'
`.replace(/\s+/g, ' ').trim());
// Referrer Policy
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions Policy (anteriormente Feature Policy)
headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers
});
}

Proteção Contra Ataques Comuns

Proteção CSRF

src/components/CSRFProtection.astro
---
import { generateCSRFToken, validateCSRFToken } from '../utils/security';
// Gerar token CSRF
const csrfToken = generateCSRFToken();
// Armazenar token na sessão
if (Astro.locals.session) {
Astro.locals.session.csrf = csrfToken;
}
---
<input type="hidden" name="_csrf" value={csrfToken} />
<script define:vars={{ csrfToken }}>
// Adicionar token CSRF a todas as requisições fetch
const originalFetch = window.fetch;
window.fetch = function(url, options = {}) {
// Somente adicionar para requisições POST, PUT, DELETE, PATCH
if (options.method && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(options.method.toUpperCase())) {
// Inicializar headers se não existirem
options.headers = options.headers || {};
// Adicionar CSRF token ao header
if (typeof options.headers.append === 'function') {
options.headers.append('X-CSRF-Token', csrfToken);
} else {
options.headers['X-CSRF-Token'] = csrfToken;
}
}
return originalFetch(url, options);
};
</script>
src/utils/security.js
import crypto from 'crypto';
// Gerar token CSRF
export function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex');
}
// Validar token CSRF
export function validateCSRFToken(request, session) {
// Obter token da requisição
const tokenFromBody = request.body?._csrf;
const tokenFromHeader = request.headers.get('X-CSRF-Token');
const token = tokenFromBody || tokenFromHeader;
// Obter token da sessão
const sessionToken = session?.csrf;
// Validar token
if (!token || !sessionToken || token !== sessionToken) {
return false;
}
return true;
}

Proteção XSS

Implemente uma função de sanitização para conteúdo gerado pelo usuário:

src/utils/sanitize.js
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
// Criar instância do DOMPurify usando JSDOM
const window = new JSDOM('').window;
const purify = DOMPurify(window);
// Configurar DOMPurify
purify.setConfig({
ALLOWED_TAGS: [
'a', 'b', 'br', 'code', 'div', 'em', 'h1', 'h2', 'h3',
'h4', 'h5', 'h6', 'hr', 'i', 'li', 'ol', 'p', 'pre',
'span', 'strong', 'table', 'tbody', 'td', 'th', 'thead', 'tr', 'ul'
],
ALLOWED_ATTR: [
'href', 'target', 'class', 'id', 'style'
],
FORBID_TAGS: ['script', 'style', 'iframe', 'frame', 'object', 'embed', 'form'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
ALLOW_DATA_ATTR: false,
USE_PROFILES: { html: true }
});
// Função para sanitizar HTML
export function sanitizeHTML(html) {
if (!html) return '';
return purify.sanitize(html);
}
// Função para sanitizar texto (remove todo HTML)
export function sanitizeText(text) {
if (!text) return '';
return purify.sanitize(text, { ALLOWED_TAGS: [] });
}

Estratégias de Migração

Migração para Novas Versões do Astro

Planejamento da Migração

  1. Análise de Compatibilidade: Revise as notas de lançamento para identificar mudanças que afetam seu código
  2. Ambiente de Teste: Configure um ambiente separado para testar a migração
  3. Backup: Faça backup do código e dados antes de iniciar
  4. Migração Gradual: Considere uma abordagem por fases para projetos grandes

Script de Verificação de Compatibilidade

scripts/check-astro-compatibility.js
import fs from 'fs';
import path from 'path';
import glob from 'glob';
import chalk from 'chalk';
// Configurações
const SRC_DIR = 'src';
const TARGET_VERSION = '5.0.0'; // Versão alvo do Astro
// Padrões a verificar (exemplo para migração para Astro 5)
const patterns = [
{
name: 'Uso de getStaticPaths com API antiga',
regex: /export\s+async\s+function\s+getStaticPaths\s*\(\s*\)\s*{[\s\S]*?return\s+\[/g,
suggestion: 'Atualize para o novo formato de getStaticPaths com params',
severity: 'high'
},
{
name: 'Uso de Astro.request.params',
regex: /Astro\.request\.params/g,
suggestion: 'Use Astro.params em vez de Astro.request.params',
severity: 'high'
},
{
name: 'Uso de Astro.request.url',
regex: /Astro\.request\.url/g,
suggestion: 'Use Astro.url em vez de Astro.request.url',
severity: 'high'
},
{
name: 'Uso de componentes com .astro-HASH em seletores CSS',
regex: /\.astro-[A-Z0-9]+/g,
suggestion: 'Os hashes de escopo CSS foram alterados, atualize seus seletores',
severity: 'medium'
},
{
name: 'Uso de fetch() sem opção de redirecionamento explícita',
regex: /fetch\s*\(\s*['"][^'"]+['"]\s*\)/g,
suggestion: 'Considere adicionar { redirect: "follow" } ao fetch() para manter comportamento consistente',
severity: 'low'
}
];
// Encontrar arquivos para verificar
const files = glob.sync(`${SRC_DIR}/**/*.{astro,js,ts,jsx,tsx}`, { nodir: true });
// Resultados
const issues = [];
// Verificar cada arquivo
files.forEach(file => {
const content = fs.readFileSync(file, 'utf-8');
patterns.forEach(pattern => {
const matches = content.match(pattern.regex);
if (matches && matches.length > 0) {
issues.push({
file,
pattern: pattern.name,
matches: matches.length,
suggestion: pattern.suggestion,
severity: pattern.severity
});
}
});
});
// Agrupar por severidade
const highIssues = issues.filter(issue => issue.severity === 'high');
const mediumIssues = issues.filter(issue => issue.severity === 'medium');
const lowIssues = issues.filter(issue => issue.severity === 'low');
// Exibir resultados
console.log(chalk.bold(`\n📊 Verificação de Compatibilidade com Astro ${TARGET_VERSION}\n`));
if (issues.length === 0) {
console.log(chalk.green('✅ Nenhum problema de compatibilidade encontrado!'));
} else {
console.log(chalk.yellow(`Encontrados ${issues.length} potenciais problemas de compatibilidade:`));
if (highIssues.length > 0) {
console.log(chalk.red(`\n⚠️ ${highIssues.length} problemas de alta severidade:`));
highIssues.forEach(issue => {
console.log(`- ${chalk.bold(issue.file)}: ${issue.pattern} (${issue.matches} ocorrências)`);
console.log(` ${chalk.italic(issue.suggestion)}`);
});
}
if (mediumIssues.length > 0) {
console.log(chalk.yellow(`\n⚠️ ${mediumIssues.length} problemas de média severidade:`));
mediumIssues.forEach(issue => {
console.log(`- ${chalk.bold(issue.file)}: ${issue.pattern} (${issue.matches} ocorrências)`);
console.log(` ${chalk.italic(issue.suggestion)}`);
});
}
if (lowIssues.length > 0) {
console.log(chalk.blue(`\n⚠️ ${lowIssues.length} problemas de baixa severidade:`));
lowIssues.forEach(issue => {
console.log(`- ${chalk.bold(issue.file)}: ${issue.pattern} (${issue.matches} ocorrências)`);
console.log(` ${chalk.italic(issue.suggestion)}`);
});
}
}
// Salvar relatório
const report = {
targetVersion: TARGET_VERSION,
timestamp: new Date().toISOString(),
totalIssues: issues.length,
highSeverity: highIssues.length,
mediumSeverity: mediumIssues.length,
lowSeverity: lowIssues.length,
issues
};
fs.writeFileSync(
'astro-compatibility-report.json',
JSON.stringify(report, null, 2)
);
console.log('\nRelatório detalhado salvo em astro-compatibility-report.json');
// Retornar código de erro se houver problemas de alta severidade
process.exit(highIssues.length > 0 ? 1 : 0);

Migração de Arquitetura

Migração para SSR

Passos para migrar de SSG para SSR:

  1. Atualize a configuração do Astro:
astro.config.mjs
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone'
}),
integrations: [
starlight({
title: 'Nome do Projeto',
description: 'Documentação oficial do projeto',
defaultLocale: 'pt-BR',
locales: {
'pt-BR': {
label: 'Português do Brasil',
},
'en': {
label: 'English',
},
},
}),
],
});
  1. Refatore rotas estáticas para dinâmicas:
src/pages/produtos/[id].astro
---
// Antes (SSG):
export async function getStaticPaths() {
const produtos = await getProdutos();
return produtos.map(produto => ({
params: { id: produto.id },
props: { produto },
}));
}
const { produto } = Astro.props;
---
<!-- Conteúdo da página -->
src/pages/produtos/[id].astro
---
// Depois (SSR):
const { id } = Astro.params;
const produto = await getProdutoPorId(id);
if (!produto) {
return Astro.redirect('/404');
}
---
<!-- Conteúdo da página -->

Documentação Técnica e de Usuário

Documentação Técnica

Estrutura de Documentação Técnica

Crie uma estrutura clara para documentação técnica:

docs/
├── arquitetura/
│ ├── visao-geral.md
│ ├── componentes.md
│ └── fluxo-de-dados.md
├── desenvolvimento/
│ ├── ambiente.md
│ ├── convenções.md
│ ├── testes.md
│ └── deploy.md
├── api/
│ ├── endpoints.md
│ ├── autenticacao.md
│ └── modelos.md
└── contribuicao/
├── codigo-de-conduta.md
├── como-contribuir.md
└── revisao-de-codigo.md

Documentação de Arquitetura

# Visão Geral da Arquitetura
## Introdução
Este documento descreve a arquitetura de alto nível do sistema, seus componentes principais e como eles interagem entre si.
## Pilha Tecnológica
- **Frontend**: Astro 5, Tailwind CSS
- **Backend**: API RESTful em Node.js
- **Banco de Dados**: Supabase (PostgreSQL)
- **Autenticação**: JWT, OAuth2
- **Cache**: Redis
- **CDN**: Cloudflare
## Diagrama de Arquitetura
```mermaid
graph TD
Client[Cliente] --> CDN[Cloudflare CDN]
CDN --> AstroSSR[Astro SSR]
AstroSSR --> API[API Node.js]
API --> Cache[Redis Cache]
API --> DB[Supabase/PostgreSQL]
API --> Auth[Serviço de Autenticação]

Componentes Principais

Frontend (Astro)

O frontend é construído com Astro 5, utilizando o modo SSR para páginas dinâmicas e SSG para conteúdo estático. Principais características:

  • Componentes Ilhas: Utilizamos o padrão de ilhas para componentes interativos
  • Roteamento: Baseado em arquivos no diretório src/pages
  • Renderização: Híbrida (SSR/SSG) conforme necessidade de cada página
  • Estilização: Tailwind CSS para estilos utilitários

Backend (Node.js)

A API é construída em Node.js, seguindo princípios RESTful. Principais características:

  • Endpoints: Organizados por recursos
  • Middleware: Para autenticação, logging e tratamento de erros
  • Validação: Esquemas Zod para validação de entrada
  • Documentação: OpenAPI/Swagger

Banco de Dados (Supabase)

Utilizamos Supabase como plataforma de banco de dados, aproveitando:

  • PostgreSQL: Para armazenamento relacional
  • Row Level Security: Para controle de acesso granular
  • Realtime: Para atualizações em tempo real
  • Storage: Para armazenamento de arquivos

Fluxo de Dados

  1. O usuário acessa a aplicação através do navegador
  2. A requisição é roteada pelo Cloudflare CDN
  3. Conteúdo estático é servido diretamente do CDN
  4. Conteúdo dinâmico é processado pelo Astro SSR
  5. O Astro faz requisições à API quando necessário
  6. A API consulta o cache Redis para dados frequentes
  7. Se não estiver em cache, a API consulta o banco de dados
  8. Os dados são retornados, processados e renderizados
  9. A resposta é enviada ao usuário

Considerações de Segurança

  • Autenticação baseada em JWT com expiração curta
  • HTTPS em todas as comunicações
  • Sanitização de entrada em todos os endpoints
  • Políticas de segurança de conteúdo (CSP)
  • Proteção contra ataques comuns (CSRF, XSS, SQL Injection)
### Documentação de Usuário
#### Estrutura de Documentação de Usuário

guia-usuario/ ├── introducao/ │ ├── bem-vindo.md │ ├── primeiros-passos.md │ └── interface.md ├── funcionalidades/ │ ├── cadastro.md │ ├── pesquisa.md │ └── relatorios.md ├── faq/ │ ├── perguntas-frequentes.md │ └── solucao-problemas.md └── recursos/ ├── videos.md ├── tutoriais.md └── glossario.md

#### Componente de Documentação Interativa
```astro
---
// src/components/docs/InteractiveDemo.astro
const { title, demoId, height = '400px' } = Astro.props;
---
<div class="interactive-demo">
<h3>{title}</h3>
<div class="demo-container" style={`height: ${height}`}>
<div class="demo-tabs">
<button class="demo-tab active" data-tab="preview">Visualização</button>
<button class="demo-tab" data-tab="code">Código</button>
<button class="demo-tab" data-tab="explanation">Explicação</button>
</div>
<div class="demo-content">
<div class="demo-panel active" data-panel="preview">
<iframe
src={`/demos/${demoId}/index.html`}
title={`Demo: ${title}`}
loading="lazy"
class="demo-frame"
></iframe>
</div>
<div class="demo-panel" data-panel="code">
<pre class="language-astro"><code id={`code-${demoId}`}>Carregando...</code></pre>
</div>
<div class="demo-panel" data-panel="explanation">
<div id={`explanation-${demoId}`}>
<slot name="explanation">
<p>Carregando explicação...</p>
</slot>
</div>
</div>
</div>
</div>
</div>
<script define:vars={{ demoId }}>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector(`.interactive-demo`);
const tabs = container.querySelectorAll('.demo-tab');
const panels = container.querySelectorAll('.demo-panel');
// Carregar código
fetch(`/demos/${demoId}/code.txt`)
.then(response => response.text())
.then(code => {
const codeElement = document.getElementById(`code-${demoId}`);
codeElement.textContent = code;
// Aplicar highlight se Prism estiver disponível
if (window.Prism) {
Prism.highlightElement(codeElement);
}
})
.catch(error => {
console.error('Erro ao carregar código:', error);
document.getElementById(`code-${demoId}`).textContent = 'Erro ao carregar código.';
});
// Alternar entre abas
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// Desativar todas as abas
tabs.forEach(t => t.classList.remove('active'));
panels.forEach(p => p.classList.remove('active'));
// Ativar aba clicada
tab.classList.add('active');
const tabName = tab.getAttribute('data-tab');
container.querySelector(`.demo-panel[data-panel="${tabName}"]`).classList.add('active');
});
});
});
</script>
<style>
.interactive-demo {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
margin: 2rem 0;
background-color: white;
}
h3 {
padding: 1rem;
margin: 0;
border-bottom: 1px solid #e2e8f0;
font-size: 1.2rem;
}
.demo-container {
display: flex;
flex-direction: column;
}
.demo-tabs {
display: flex;
border-bottom: 1px solid #e2e8f0;
}
.demo-tab {
padding: 0.75rem 1rem;
border: none;
background: none;
cursor: pointer;
font-weight: 500;
color: #64748b;
}
.demo-tab.active {
color: #3b82f6;
border-bottom: 2px solid #3b82f6;
}
.demo-content {
flex: 1;
position: relative;
}
.demo-panel {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: auto;
display: none;
}
.demo-panel.active {
display: block;
}
.demo-frame {
width: 100%;
height: 100%;
border: none;
}
pre {
margin: 0;
padding: 1rem;
height: 100%;
overflow: auto;
background-color: #f8fafc;
}
#explanation-${demoId} {
padding: 1rem;
}
</style>

Conclusão

A manutenção e evolução de uma solução web desenvolvida com Astro 5 requer um conjunto abrangente de práticas e estratégias. Ao implementar as abordagens descritas neste documento, você estará preparado para:

  1. Manter seu código atualizado e seguro através de estratégias de versionamento e gerenciamento de dependências
  2. Melhorar continuamente a qualidade do código com refatoração e modernização
  3. Monitorar e otimizar o desempenho com ferramentas de análise e métricas
  4. Proteger sua aplicação com atualizações de segurança regulares
  5. Evoluir sua arquitetura com estratégias de migração bem planejadas
  6. Documentar adequadamente para facilitar a manutenção e o uso

Lembre-se de que a manutenção é um processo contínuo. Estabeleça rotinas regulares para revisão de código, atualizações de dependências e monitoramento de desempenho para garantir que sua solução permaneça robusta, segura e eficiente ao longo do tempo.


Última atualização: 21 de março de 2025