first commit

This commit is contained in:
2026-05-04 09:43:36 +01:00
commit dfae1b5335
37 changed files with 10343 additions and 0 deletions

208
docs/07-seguranca.md Normal file
View File

@@ -0,0 +1,208 @@
# 7. Segurança e Conformidade
## 7.1 Autenticação e Autorização
### Palavras-passe
As palavras-passe são **nunca armazenadas em texto simples**. Utiliza-se o algoritmo bcrypt com factor de custo 12, que torna ataques de força bruta computacionalmente inviáveis mesmo com hardware moderno.
```typescript
// lib/auth/password.ts
import bcrypt from 'bcryptjs';
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
```
### Sessões e JWT
As sessões são geridas pelo NextAuth.js com tokens JWT assinados com um segredo de 256 bits armazenado em variável de ambiente. O token expira ao fim de 30 dias. O middleware Next.js intercepta todas as rotas protegidas antes de qualquer lógica de negócio.
```typescript
// middleware.ts
import { withAuth } from 'next-auth/middleware';
export default withAuth({
pages: { signIn: '/login' }
});
export const config = {
matcher: [
'/account/:path*',
'/donate/:path*',
'/dashboard/:path*',
]
};
```
### Verificação de Idade
A verificação de +18 anos é **sempre feita no servidor**, nunca confiando em dados do cliente:
```typescript
// lib/auth/age-validation.ts
export function isAdult(birthdate: Date): boolean {
const today = new Date();
const age = today.getFullYear() - birthdate.getFullYear();
const monthDiff = today.getMonth() - birthdate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthdate.getDate())) {
return age - 1 >= 18;
}
return age >= 18;
}
```
### Controlo de Acesso por Roles
| Role | Permissões |
|---|---|
| `USER` | Adoptar animais, fazer doações, gerir a própria conta |
| `SHELTER_ADMIN` | Tudo de USER + gerir animais/reservas/necessidades do seu canil |
| `ADMIN` | Acesso total à plataforma, moderação, estatísticas |
---
## 7.2 Segurança da Aplicação
### Headers de Segurança HTTP
```typescript
// next.config.ts
const securityHeaders = [
{ key: 'X-DNS-Prefetch-Control', value: 'on' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://js.stripe.com",
"frame-src https://js.stripe.com",
"img-src 'self' data: https://*.supabase.co",
"connect-src 'self' https://api.anthropic.com https://api.stripe.com",
].join('; ')
},
];
```
### Validação de Inputs
Todos os dados recebidos nas API Routes são validados com Zod antes de qualquer processamento:
```typescript
// app/api/reservations/route.ts
import { z } from 'zod';
const ReservationSchema = z.object({
animalId: z.string().cuid(),
date: z.string().datetime().refine(
date => new Date(date) > new Date(),
{ message: 'A data deve ser no futuro' }
),
notes: z.string().max(500).optional(),
});
export async function POST(req: Request) {
const body = await req.json();
const parsed = ReservationSchema.safeParse(body);
if (!parsed.success) {
return Response.json({ error: parsed.error.issues }, { status: 400 });
}
// ... lógica de negócio com parsed.data (type-safe)
}
```
### Rate Limiting
```typescript
// lib/rate-limit.ts — protege endpoints de autenticação
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
export const authRateLimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 tentativas por minuto por IP
});
```
### Uploads de Imagens
Todos os uploads são validados antes de armazenar no Supabase Storage:
- Tipo MIME verificado no servidor (não confia no Content-Type do cliente)
- Tamanho máximo: 5MB por ficheiro
- Extensões permitidas: `.jpg`, `.jpeg`, `.png`, `.webp`
- Nome do ficheiro gerado pelo servidor (nunca usa o nome original do utilizador)
---
## 7.3 Conformidade com o RGPD
| Princípio RGPD | Implementação na PawLink |
|---|---|
| **Consentimento explícito** | Checkbox obrigatório no registo com link para Política de Privacidade. Consentimento separado para emails de marketing (opcional). |
| **Finalidade limitada** | Dados pessoais usados apenas para adopção e doação — não partilhados com terceiros excepto processadores necessários (Stripe, Resend). |
| **Minimização de dados** | Apenas nome, email, distrito, data de nascimento e palavra-passe são recolhidos no registo. |
| **Exactidão** | Utilizador pode actualizar todos os seus dados nas Definições a qualquer momento. |
| **Limitação de armazenamento** | Dados de contas inactivas (sem login há 24+ meses) são anonimizados automaticamente. |
| **Direito ao apagamento** | Botão "Eliminar Conta" nas Definições — inicia processo de apagamento de dados pessoais em 30 dias. |
| **Portabilidade** | Exportação dos dados do utilizador em formato JSON nas Definições. |
| **Notificação de violações** | Plano de resposta a incidentes com notificação à CNPD em 72 horas conforme legislação. |
| **Processadores de dados** | Contratos de processamento de dados (DPA) celebrados com Stripe, Resend, Supabase e Vercel. |
### Política de Cookies
| Cookie | Tipo | Finalidade | Duração |
|---|---|---|---|
| `next-auth.session-token` | Necessário | Sessão de autenticação | 30 dias |
| `next-auth.csrf-token` | Necessário | Protecção CSRF | Sessão |
| `theme` | Preferências | Modo claro/escuro | 1 ano |
| Analytics Vercel | Analítico | Métricas de performance anonimizadas | Sessão |
Não são usados cookies de rastreamento ou publicidade.
---
## 7.4 Segurança dos Pagamentos
- Dados de cartão **nunca passam pelos servidores da PawLink** — tratados directamente pelo Stripe via Payment Element
- Stripe é certificado **PCI DSS Level 1** — o mais elevado nível de conformidade
- Webhooks Stripe verificados com **assinatura HMAC-SHA256** — previne falsificação de eventos de pagamento
- Montantes validados no servidor antes de criar PaymentIntent — cliente não pode alterar o valor
- Ambiente de teste (chaves `sk_test_`) separado do ambiente de produção (chaves `sk_live_`)
```typescript
// app/api/payments/webhook/route.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get('stripe-signature')!;
let event: Stripe.Event;
try {
// Verifica assinatura HMAC — rejeitado se não for do Stripe
event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
} catch {
return new Response('Webhook signature verification failed', { status: 400 });
}
// Processar evento verificado
if (event.type === 'payment_intent.succeeded') {
// Actualizar donation status na base de dados
// Enviar email de recibo
}
}
```