Agrega opções guardar local, Google Drive, Telegram, WhatsApp e cofre — implementação futura.
135 lines
6.9 KiB
Markdown
135 lines
6.9 KiB
Markdown
# Feature Specification: Pré-preenchimento Self-Service → Wizard (016)
|
|
|
|
**Criado:** 2026-06-16
|
|
**Solicitado por:** Roger
|
|
**Status:** Implementação
|
|
**Prioridade:** P0 (regressão UX onboarding)
|
|
**Sistema:** Portal VM112 (`ibytera-mail-portal`) — wizard `/onboard`
|
|
**Relacionado:** Spec 012 (ticket no `onboarding.started`), chat bruto `CHAT_BRUTO_ONBOARD_INFRA_SUPORTE_20260603`
|
|
**Backlog agregado:** [BACKLOG-2FA-BACKUP-EXPORT.md](BACKLOG-2FA-BACKUP-EXPORT.md) — export códigos 2FA (Roger 2026-06-19)
|
|
|
|
---
|
|
|
|
## Resumo
|
|
|
|
Quando o utilizador preenche o **card Self-Service** na landing (hero) ou chega via **«Criar Meu Servidor Agora»**, os dados declarados devem **propagar automaticamente** para o wizard de onboarding, em especial no **passo «Conta admin»** (criação da conta do administrador no Carbonio).
|
|
|
|
**Regra de ouro:** dados do Self-Service **têm prioridade** sobre estado antigo do wizard guardado em `sessionStorage` (domínio/localPart de sessão anterior não pode apagar o que o utilizador acabou de declarar na landing).
|
|
|
|
---
|
|
|
|
## Origem dos dados (landing)
|
|
|
|
| Campo Self-Service | Label UI | Chave persistência |
|
|
|--------------------|----------|-------------------|
|
|
| E-mail corporativo do administrador | `admin@suaempresa.com.br` | `localStorage.ligbox_planned_email` |
|
|
| Senha | campo Senha | `sessionStorage.ibytera_onboard_admin_password` |
|
|
| Login portal | telefone/nickname | `sessionStorage` (portal login id — fora do escopo conta admin) |
|
|
|
|
**Botões equivalentes:** card **Self-Service** (hero) e CTA **«Criar Meu Servidor Agora»** (scroll para o mesmo card).
|
|
|
|
**Fluxos que disparam pré-preenchimento:**
|
|
|
|
1. **Registo** → 2FA TOTP → `finishOnboarding()` → redirect `/onboard`
|
|
2. **Login** (ou login + 2FA) → redirect `/onboard`
|
|
|
|
---
|
|
|
|
## Destino no wizard (passo Conta admin — step 2)
|
|
|
|
Ao abrir ou regressar a este passo, **três valores** devem estar preenchidos:
|
|
|
|
| # | Origem Self-Service | Campo wizard | Exemplo |
|
|
|---|---------------------|--------------|---------|
|
|
| 1 | E-mail corporativo completo | `localPart` + `domain` (parte local + domínio) | `admin` + `suaempresa.com.br` |
|
|
| 2 | Domínio extraído do e-mail | `domain` (passo 0 também) | `suaempresa.com.br` |
|
|
| 3 | Senha | `password` (mascarada, reutilização) | via `AdminPasswordField` |
|
|
|
|
**Passo 0 (Domínio):** se `ligbox_planned_email` existir, o campo domínio deve iniciar com o domínio do e-mail e mostrar banner informativo.
|
|
|
|
**Passo 3 (Rever e criar):** senha em modo `confirm` — mascarada, reutilizada; revelar com olho exige re-autenticação portal (2FA).
|
|
|
|
---
|
|
|
|
## Comportamento funcional
|
|
|
|
### FR-001 — Persistência imediata no registo
|
|
Após registo portal com sucesso (antes do TOTP), gravar:
|
|
- `setAdminPassword(password)`
|
|
- `localStorage.ligbox_planned_email` = e-mail corporativo normalizado (lowercase, trim)
|
|
|
|
### FR-002 — Prioridade Self-Service sobre wizard state
|
|
Se `ligbox_planned_email` **ou** senha em `sessionStorage` existirem ao montar `/onboard`:
|
|
- **Ignorar** `domain` / `localPart` / `notifyEmail` antigos de `ibytera_onboard_wizard_state` para pré-preenchimento
|
|
- Aplicar valores derivados do Self-Service
|
|
|
|
### FR-003 — Sincronização no mount
|
|
`useEffect` no wizard reaplica pré-preenchimento se o utilizador navegou landing → onboard na mesma aba.
|
|
|
|
### FR-004 — Senha não vai para wizard state JSON
|
|
Senha permanece **apenas** em `sessionStorage` (`onboardPassword.js`) — nunca em `saveWizardState()`.
|
|
|
|
### FR-005 — Revelação de senha
|
|
Ícone olho → modal re-autenticação portal (`PasswordRevealAuth`); visível 30s; opção «Definir senha diferente».
|
|
|
|
### FR-006 — Sem Self-Service
|
|
Utilizador entra directo em `/onboard` sem landing: campos vazios ou defaults (`admin`, domínio manual) — sem regressão.
|
|
|
|
---
|
|
|
|
## Critérios de aceitação
|
|
|
|
1. **Given** registo com `admin@empresa.com` + senha `MinhaSenh@8` + TOTP concluído, **When** abre `/onboard` passo Conta admin, **Then** vê `admin@empresa.com`, domínio `empresa.com`, senha reutilizada (mascarada).
|
|
2. **Given** wizard state antigo com domínio `outro.com` em sessionStorage, **When** novo registo com `admin@novo.com`, **Then** domínio no wizard é `novo.com` (não `outro.com`).
|
|
3. **Given** login com `planned_corporate_email` da API, **When** redirect `/onboard`, **Then** campos pré-preenchidos.
|
|
4. **Given** F5 na mesma aba após Self-Service, **When** wizard recarrega, **Then** e-mail/domínio/senha mantêm-se (localStorage + sessionStorage).
|
|
5. **Given** nova aba sem storage, **When** `/onboard` directo, **Then** sem pré-preenchimento (comportamento legítimo).
|
|
|
|
---
|
|
|
|
## Implementação (referência código VM112 — `/opt/ligbox-wizard`)
|
|
|
|
| Ficheiro | Função |
|
|
|----------|--------|
|
|
| `frontend/src/sessionPersist.js` | `beginOnboardingForEmail()`, `syncWizardWithPlannedEmail()`, `applyPlannedEmailPrefill()`, `loadWizardStateForOnboard()` |
|
|
| `frontend/src/portalAuth.js` | `setPortalOnboardCredentials()` → `sessionStorage.ligbox_onboard_password` |
|
|
| `frontend/src/onboardPassword.js` | alias leitura/escrita na mesma chave `ligbox_onboard_password` (wizard) |
|
|
| `frontend/src/ligbox/components/SelfServiceCard.jsx` | registo/login/TOTP → `beginOnboardingForEmail` + credenciais |
|
|
| `frontend/src/App.jsx` | `loadWizardStateForOnboard()` no init + `useEffect` de sync |
|
|
| `frontend/src/AdminPasswordField.jsx` | senha mascarada + reveal com `verifyStepUp` (2FA) |
|
|
| `frontend/src/PortalTotpSetup.jsx` | Modal 2FA + ecrã «Salve estes códigos» |
|
|
|
|
---
|
|
|
|
## Backlog agregado (não implementado)
|
|
|
|
| Item | Documento | Status |
|
|
|------|-----------|--------|
|
|
| Export códigos backup 2FA (local, Drive, Telegram, WhatsApp, cofre) | [BACKLOG-2FA-BACKUP-EXPORT.md](BACKLOG-2FA-BACKUP-EXPORT.md) | 📋 Backlog P2 |
|
|
| Tarefas | [tasks.md](tasks.md) | 0/16 concluídas (export) |
|
|
|
|
**Pedido Roger (2026-06-19):** dar ao utilizador opções para guardar os 10 códigos de recuperação além de apenas visualizar no ecrã.
|
|
|
|
---
|
|
|
|
## Fora de escopo
|
|
|
|
- Enviar senha para VM122 / webhooks / Desk (nunca)
|
|
- Pré-preencher a partir de cookies cross-domain
|
|
- Sincronizar com Carbonio antes de `POST /account/create`
|
|
|
|
---
|
|
|
|
## Regressão conhecida (corrigida nesta spec)
|
|
|
|
**Causas identificadas (2026-06-16):**
|
|
|
|
1. Wizard state antigo em `sessionStorage` (`ligbox_onboard_wizard_state`) mantinha `domain`/`localPart` de sessão anterior e bloqueava o e-mail novo do Self-Service.
|
|
2. Senha gravada em chave errada (`ibytera_onboard_admin_password` em código de dev) enquanto o portal em produção lia `ligbox_onboard_password`.
|
|
3. E-mail só ia para `localStorage` após TOTP completo — registo sem `beginOnboardingForEmail()` deixava o wizard sem âncora.
|
|
|
|
**Fix aplicado:**
|
|
|
|
- `syncWizardWithPlannedEmail()` + `ligbox_wizard_planned_email` como âncora — descarta wizard stale quando o e-mail muda.
|
|
- `loadWizardStateForOnboard()` aplica sempre domínio/localPart/notify a partir de `ligbox_planned_email`.
|
|
- `SelfServiceCard` chama `beginOnboardingForEmail()` + `setPortalOnboardCredentials()` no registo, login e fim do TOTP.
|