feat: harden identity and recovery operations

This commit is contained in:
2026-08-04 20:24:18 +01:00
parent 7a4ac48aaf
commit eae2a44f46
30 changed files with 1428 additions and 21 deletions

View File

@@ -37,6 +37,13 @@ Nginx ──► Laravel + Inertia/React
- `app/Domain/Operations`: entrega, devolução, faltas, atrasos e incidentes.
- `app/Domain/Audit`: logs imutáveis pela interface.
- `app/Integrations/SnipeIt`: cliente HTTP, DTOs, mapper e exceções da API Snipe-IT.
- `app/Integrations/Microsoft`: cliente OIDC de tenant único, descoberta e validação de tokens.
## Identidade institucional
O login Microsoft usa Authorization Code Flow com OIDC e PKCE. O portal descobre `authorization_endpoint`, `token_endpoint`, emissor e `jwks_uri` no documento `.well-known` específico do tenant. O `id_token` só é aceite após validação RS256, expiração, emissor, audiência, tenant e `nonce`; `state` é consumido uma única vez e o fluxo expira ao fim de dez minutos. A rotação de chaves provoca uma atualização imediata do JWKS em cache e uma segunda validação.
A identidade externa é associada explicitamente a uma conta local já autenticada, após confirmação recente da palavra-passe. Uma coincidência de email nunca cria nem associa contas automaticamente. O portal persiste apenas `sub`, tenant, email apresentado e um subconjunto de claims de perfil; códigos e tokens não são guardados. Utilizadores inativos continuam impedidos de entrar. O login local permanece disponível como contingência operacional.
## Consistência temporal
@@ -89,3 +96,9 @@ Referências oficiais usadas para o contrato: [autenticação da API](https://sn
- Dois tokens Snipe-IT com menor privilégio.
- MFA obrigatório a definir para perfis privilegiados antes do piloto.
- Logs de auditoria não expostos a operações de update/delete.
## Saúde e recuperação
`/health/live` confirma que o processo HTTP responde. `/health/ready` confirma acesso à base de dados e um ciclo de escrita/leitura no cache, devolvendo apenas o estado global e nunca detalhes de infraestrutura. O health check do Nginx usa readiness.
Os scripts em `ops/` produzem um `pg_dump` custom com permissões restritas, validam a estrutura e permitem restaurar para uma base temporária descartável. Encriptação, retenção, cópia externa e agendamento pertencem à plataforma de produção e são acompanhados pelo checklist operacional.

View File

@@ -92,3 +92,21 @@ Guardar parâmetros funcionais em `portal_settings`, mantendo defaults seguros n
**Estado:** aceite
Separar Gates para calendário global, relatórios e auditoria. Administradores têm acesso integral; auditores consultam relatórios/auditoria sem mutações; aprovadores e operadores consultam o calendário global. Relatórios são limitados a 366 dias, exportados por streaming e neutralizam possíveis fórmulas em CSV. Modelos de notificação são conteúdo funcional persistente, mas credenciais SMTP e tokens continuam exclusivamente no ambiente.
## ADR-014 — Entra ID de tenant único com associação explícita
**Data:** 2026-08-04
**Estado:** aceite
Implementar diretamente o Authorization Code Flow OIDC com PKCE, usando descoberta específica do tenant e `firebase/php-jwt` 7 para validação criptográfica RS256. Validar `state`, `nonce`, expiração, emissor, audiência e `tid`; nunca persistir tokens. A versão 6 da biblioteca foi rejeitada por existir um aviso de segurança ativo e não foi adicionada à lista de exceções do Composer.
Por defeito, uma identidade Microsoft só pode ser associada a partir de uma conta local autenticada e depois de confirmação recente da palavra-passe. Não existe associação ou provisionamento automático por email. Esta escolha evita apropriação de conta por claims mutáveis ou endereços reutilizados. O login local permanece como recuperação controlada e o tenant institucional é obrigatório e expresso por GUID.
## ADR-015 — Health checks mínimos e restauro verificável
**Data:** 2026-08-04
**Estado:** aceite
Separar liveness, que prova apenas resposta do processo, de readiness, que confirma PostgreSQL e cache. As respostas públicas contêm apenas o estado agregado. O Nginx usa readiness no Docker Compose.
Disponibilizar scripts independentes para `pg_dump` custom e restauro numa base temporária validada. Os scripts usam nomes resolvidos do contentor, restringem os identificadores aceites, geram checksum e nunca substituem a base principal. Encriptação, armazenamento externo, retenção e alertas permanecem responsabilidades explícitas da plataforma de produção.

View File

@@ -0,0 +1,79 @@
# Checklist de entrada em produção
Este documento é o registo de prontidão. Cada item deve ter responsável, data e ligação para a evidência no sistema institucional de operação. Nunca colocar segredos, tokens ou dados pessoais neste ficheiro.
## 1. Microsoft Entra ID
- [ ] Registar uma aplicação de tenant único do tipo Web.
- [ ] Configurar exatamente `https://<domínio>/auth/microsoft/callback` como redirect URI.
- [ ] Criar credencial com validade e rotação definidas; preferir mecanismo externo de segredos.
- [ ] Definir `MICROSOFT_ENABLED=true`, client ID, tenant GUID, secret e redirect URI fora do repositório.
- [ ] Confirmar que uma conta local consegue associar e remover a identidade em Segurança.
- [ ] Confirmar login, logout, utilizador desativado, tenant incorreto e rotação da chave de assinatura.
- [ ] Aplicar Conditional Access/MFA institucional aos perfis privilegiados.
- [ ] Documentar o procedimento de recuperação através de login local administrado.
Referências oficiais: [Authorization Code Flow com PKCE](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow), [tokens e validação de claims](https://learn.microsoft.com/en-us/entra/identity-platform/security-tokens) e [validação de assinatura/emissor](https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens).
## 2. Snipe-IT
- [ ] Confirmar URL e versão da instância institucional.
- [ ] Criar conta/token de leitura com menor privilégio.
- [ ] Criar conta/token operacional separado, apenas com checkout/check-in necessários.
- [ ] Executar sincronização integral e comparar amostra de etiquetas, categorias, estados e localizações.
- [ ] Exercitar checkout/check-in com equipamentos de teste e reconciliar `handover_records`.
- [ ] Confirmar resposta a indisponibilidade, timeout e falha parcial sem retries de escrita.
## 3. Email, domínio e TLS
- [ ] Configurar SMTP institucional e remetente autorizado.
- [ ] Validar submissão, esclarecimento, aprovação, rejeição e substituição em destinatários reais de teste.
- [ ] Publicar domínio e certificado TLS com renovação automática.
- [ ] Definir `APP_ENV=production`, `APP_DEBUG=false`, `APP_URL=https://...`, cookies seguros e proxy confiável.
- [ ] Remover exposição pública de PostgreSQL, Redis, Vite e Mailpit.
## 4. Segredos e infraestrutura
- [ ] Montar `APP_KEY`, credenciais PostgreSQL/Redis/SMTP, tokens Snipe-IT e Entra através do gestor externo.
- [ ] Confirmar que logs, dumps, imagens e artefactos de CI não contêm segredos.
- [ ] Construir imagem imutável, executar migrations com etapa controlada e reiniciar workers após deploy.
- [ ] Definir limites de CPU/memória, rotação de logs e política de atualização de imagens/dependências.
## 5. Saúde, monitorização e alertas
- [ ] Monitorizar `GET /health/live` para disponibilidade do processo.
- [ ] Usar `GET /health/ready` para retirar instâncias sem PostgreSQL/cache do balanceamento.
- [ ] Alertar para HTTP 5xx, latência, filas acumuladas, jobs falhados, scheduler parado e disco baixo.
- [ ] Alertar para sincronização Snipe-IT falhada/antiga e erros de autenticação anormais.
- [ ] Definir contacto, severidade, escalonamento e janela de manutenção.
## 6. Backup e recuperação
- [ ] Agendar `ops/backup-postgres.sh <destino>` com utilizador restrito e armazenamento cifrado.
- [ ] Copiar backups e respetivos `.sha256` para uma localização externa com retenção diária/semanal/mensal.
- [ ] Monitorizar idade, tamanho e resultado de cada backup.
- [ ] Executar `ops/test-restore-postgres.sh <ficheiro.dump>` pelo menos mensalmente em ambiente isolado.
- [ ] Registar RPO, RTO, responsável, duração e resultado do ensaio.
- [ ] Ensaiar recuperação da configuração e segredos por procedimento separado.
## 7. Segurança, desempenho e piloto
- [ ] Executar análise de dependências, análise estática e testes completos no artefacto de release.
- [ ] Testar autorização horizontal/vertical para todos os perfis e exportações.
- [ ] Testar concorrência na aprovação/reserva contra PostgreSQL real.
- [ ] Medir catálogo, calendário global, relatórios e exportações com volume representativo.
- [ ] Executar E2E com equipamentos reais, scanner, entrega parcial, incidente e reconciliação.
- [ ] Importar utilizadores/perfis iniciais e rever a matriz de aprovadores.
- [ ] Formar requisitantes, aprovadores, operadores e administradores com runbook aplicável.
- [ ] Realizar piloto limitado, registar incidentes e obter decisão formal de go/no-go.
## Registo de evidências
| Área | Responsável | Data | Resultado | Evidência externa |
|---|---|---|---|---|
| Entra ID | Por atribuir | — | Pendente | — |
| Snipe-IT | Por atribuir | — | Pendente | — |
| SMTP/TLS | Por atribuir | — | Pendente | — |
| Monitorização | Por atribuir | — | Pendente | — |
| Restauro | Por atribuir | — | Pendente | — |
| Piloto | Por atribuir | — | Pendente | — |

View File

@@ -1,7 +1,7 @@
# Estado do projeto e handoff
**Atualizado em:** 4 de agosto de 2026
**Fase:** Fases 1 a 6 concluídas; próxima entrega: Fase 7
**Fase:** Fases 1 a 6 concluídas; Fase 7 em curso (base técnica de identidade, saúde e recuperação concluída)
**Último agente:** Codex
## Resultado disponível
@@ -49,11 +49,18 @@
- Exportação CSV protegida contra formula injection e limitada a períodos de 366 dias.
- Consulta paginada e filtrável de `audit_logs`, apenas para administradores e auditores.
- Modelos editáveis de assunto/mensagem para os seis eventos de notificação, com variáveis validadas e auditoria das alterações.
- Autenticação Microsoft Entra/OIDC de tenant único com descoberta `.well-known`, Authorization Code Flow, PKCE, `state`, `nonce` e validação criptográfica RS256.
- Associação Microsoft explícita após login local e confirmação recente da palavra-passe; nenhuma conta é associada automaticamente por email.
- Login Microsoft para identidades previamente associadas, bloqueio de contas inativas e eventos de associação/login/remoção em auditoria.
- Endpoints `/health/live` e `/health/ready`, sendo readiness dependente de PostgreSQL e cache sem divulgar detalhes internos.
- Health check Docker do Nginx sobre readiness.
- Scripts operacionais de backup PostgreSQL custom com checksum e restauro de teste numa base temporária isolada.
- Checklist versionado de produção, incluindo Entra, Snipe-IT, SMTP/TLS, segredos, monitorização, recuperação, segurança e piloto.
## Limites conhecidos
- A integração está implementada e testada contra respostas simuladas; falta configurar e validar URL, versão e token da instância institucional. Até lá, o catálogo utiliza dados fictícios do seeder.
- Microsoft Entra ID ainda não está implementado; as variáveis de ambiente já estão definidas.
- Microsoft Entra ID está implementado e testado com tokens assinados simulados; falta registar/configurar a aplicação no tenant institucional e validar Conditional Access/MFA real.
- As notificações estão implementadas com Mailpit em desenvolvimento; falta validar SMTP institucional antes do piloto.
- O calendário atual é a vista mensal pessoal do MVP; vistas diária, semanal, global e por equipamento/categoria/localização ficam para as áreas operacional e administrativa.
- O leitor operacional aceita scanners QR/código de barras que funcionem como teclado. A leitura direta pela câmara do navegador ainda não foi adicionada.
@@ -64,10 +71,10 @@
Implementar a **Fase 7 — hardening e entrada em produção**:
1. implementar Microsoft Entra ID/OIDC e associação segura a contas locais;
1. configurar e validar Microsoft Entra no tenant institucional, incluindo Conditional Access/MFA;
2. validar a integração contra o Snipe-IT institucional e configurar tokens reais de menor privilégio;
3. configurar SMTP institucional, domínio, HTTPS e gestão externa de segredos;
4. adicionar health checks, monitorização, alertas, backups e teste de restauro;
4. ligar os health checks a monitorização/alertas e agendar backups cifrados externos;
5. executar testes de segurança, concorrência, desempenho e end-to-end com equipamentos reais;
6. preparar formação, dados iniciais e piloto com um grupo reduzido;
7. corrigir resultados do piloto e preparar a disponibilização geral.
@@ -77,9 +84,9 @@ Implementar a **Fase 7 — hardening e entrada em produção**:
Validação concluída em 4 de agosto de 2026:
- `docker compose exec -T app composer test`: passou;
- Laravel Pint: 132 ficheiros sem desvios;
- PHPStan/Larastan nível 7: 100 ficheiros, zero erros;
- Pest: 92 testes passaram, 522 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
- Laravel Pint: 141 ficheiros sem desvios;
- PHPStan/Larastan nível 7: 107 ficheiros, zero erros;
- Pest: 103 testes passaram, 585 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
- TypeScript (`tsc --noEmit`): passou;
- ESLint: passou;
- Prettier: passou;
@@ -94,6 +101,12 @@ Validação concluída em 4 de agosto de 2026:
- Vite em Docker: URL pública, HMR e CORS verificados em `localhost:5173`; corrigida a página em branco causada pela publicação anterior de `0.0.0.0:5173`;
- Inertia SSR: renderização do login validada através do serviço `node`; corrigido o componente de mensagens flash que usava `usePage()` fora do contexto Inertia;
- verificação visual no browser integrado: a inspeção controlada de `localhost` continua impedida por uma política administrativa temporariamente indisponível; aprovações, esclarecimentos e notificações foram validados por testes Inertia, TypeScript e builds cliente/SSR.
- testes focados de Microsoft Entra e health checks: 9 passaram, com 57 asserções;
- PHPStan/Larastan depois da integração OIDC: 107 ficheiros, zero erros;
- TypeScript, ESLint e Prettier depois das alterações de login/segurança: passaram;
- backup PostgreSQL real em formato custom criado com checksum e validado por `pg_restore --list`;
- restauro real numa base temporária: passou e confirmou 12 migrations antes da remoção automática;
- smoke tests via Nginx: `/health/live` devolveu `ok` e `/health/ready` devolveu `ready`.
## Decisões pendentes do contexto institucional
@@ -106,3 +119,7 @@ Estas decisões não bloqueiam o desenvolvimento com defaults seguros, mas devem
- duração máxima, antecedência e margens por categoria;
- domínio institucional e método de emissão TLS;
- SMTP institucional.
## Próximo ficheiro a abrir
Começar em `docs/PRODUCTION_CHECKLIST.md`. A próxima alteração de código pode preparar testes de concorrência/desempenho e observabilidade local; as validações Entra, Snipe-IT e SMTP só ficam concluídas quando existirem serviços institucionais concretos, sem guardar os respetivos segredos no repositório.

View File

@@ -19,8 +19,12 @@ docker compose exec app php artisan about
docker compose exec app php artisan migrate:status
docker compose exec postgres pg_isready -U reserva -d reserva
docker compose exec redis redis-cli ping
curl --fail http://localhost:8000/health/live
curl --fail http://localhost:8000/health/ready
```
`/health/live` confirma apenas resposta HTTP. `/health/ready` verifica PostgreSQL e cache e deve ser usado pelo balanceador. Ambos omitem detalhes de serviços. Se readiness falhar, consultar os logs da aplicação e testar PostgreSQL/Redis separadamente com os comandos anteriores.
### Página em branco no desenvolvimento
Se o HTML responder mas a interface React não aparecer, confirmar que `public/hot` contém `http://localhost:5173` e não `http://0.0.0.0:5173`. Depois validar os módulos:
@@ -83,6 +87,30 @@ docker compose exec app php artisan schedule:list
Administradores podem testar a ligação, iniciar uma execução e consultar as últimas dez sincronizações em `http://localhost:8000/administracao/snipe-it`. Uma execução falhada mantém o último catálogo válido e não desativa registos ausentes. Consultar `sync_runs`, os logs do worker e o painel antes de repetir.
## Microsoft Entra ID
Registar uma aplicação **single tenant** do tipo Web. O redirect URI tem de corresponder exatamente ao domínio do portal e ao caminho `/auth/microsoft/callback`. Em produção é obrigatório HTTPS.
```dotenv
MICROSOFT_ENABLED=true
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_TENANT_ID=
MICROSOFT_REDIRECT_URI=https://reservas.exemplo.pt/auth/microsoft/callback
MICROSOFT_TIMEOUT=10
MICROSOFT_METADATA_CACHE_SECONDS=3600
```
Depois de aplicar a configuração e limpar a cache (`php artisan optimize:clear`):
1. entrar com a conta local;
2. abrir `Definições > Segurança` e confirmar novamente a palavra-passe;
3. escolher **Associar conta Microsoft** e concluir a autenticação institucional;
4. terminar sessão e validar **Entrar com a conta Microsoft** no login;
5. confirmar os eventos `identity.microsoft_linked` e `identity.microsoft_login` em auditoria.
O portal não associa contas por email. Uma identidade não associada recebe uma instrução para entrar localmente e fazer a associação explícita. Remover a associação também exige confirmação recente da palavra-passe e cria `identity.microsoft_unlinked`. Nunca copiar códigos ou tokens para tickets/logs.
## Entregas, devoluções e reconciliação
Utilizadores com perfil `inventory_operator` ou `administrator` acedem a `http://localhost:8000/operacoes`. A fila inclui as reservas do dia e todos os processos entregues, atrasados, devolvidos ou com incidente ainda pendente. O campo inicial aceita referência, nome, email, etiqueta, ID Snipe-IT ou a saída de um leitor QR/código de barras configurado como teclado.
@@ -157,3 +185,15 @@ Antes de disponibilizar:
## Backups
Salvaguardar diariamente PostgreSQL e uploads. Manter retenção semanal/mensal e uma cópia fora do servidor. Segredos são salvaguardados através de procedimento separado. Executar um teste de restauro periódico e registar o resultado.
```bash
# Cria dump custom, valida-o e acrescenta checksum SHA-256
ops/backup-postgres.sh /caminho/cifrado/backups
# Restaura numa base temporária isolada e elimina-a no fim
ops/test-restore-postgres.sh /caminho/cifrado/backups/reserva-AAAAMMDDTHHMMSSZ.dump
```
Os scripts obtêm o nome da base e utilizador diretamente do contentor PostgreSQL, recusam identificadores inesperados e nunca restauram sobre a base principal. O destino tem de estar cifrado e deve ser replicado para fora do servidor. Monitorizar a execução e a idade do último ficheiro; um dump sem ensaio de restauro não conta como recuperação validada.
Usar [docs/PRODUCTION_CHECKLIST.md](PRODUCTION_CHECKLIST.md) para registar responsáveis e evidências antes do piloto.