# 11 — Contas, convites e e-mails Telas navegáveis em `prototipos/v2/aprova.html` — o menu do avatar, no canto superior direito, leva a todas. Tabelas em `sql/schema-v2.sql`. **O protótipo não autentica nada.** Não guarda senha, não envia e-mail, não valida token. Este documento é o que o servidor precisa fazer para que aquelas telas parem de ser encenação. --- ## Quem tem conta e quem não tem | papel | senha | como entra | frequência | |---|---|---|---| | Estúdio — admin | sim | e-mail e senha | diária | | Estúdio — artista | sim | e-mail e senha, por convite | diária | | Cliente — **gerente** | sim | e-mail e senha, por convite | semanal | | Cliente — **revisor** | **não** | link pessoal por e-mail | uma vez por rodada | A régua é a frequência de uso, não a hierarquia: quem volta sempre tem senha, quem aparece de vez em quando não. É D8 revisado — ver `docs/02-decisoes.md`. ## O revisor: "só e-mail" na prática Ele digita o e-mail e recebe um link de uso único que vale 30 minutos. Ao clicar, abre uma sessão de **30 dias** naquele aparelho, então ele não repete isso a cada visita. Três regras que fazem a diferença entre isso e um buraco: - **só envia se o endereço for revisor daquele projeto**, e a resposta na tela é a mesma nos dois casos — senão o campo vira sonda para descobrir quem revisa o quê - **um link por pessoa**, não um por rodada: o comentário sai assinado sem perguntar "quem é você", e remover uma pessoa não derruba o acesso das outras - **limite de pedido por endereço e por IP**, senão o formulário vira ferramenta de spam usando o seu domínio como remetente A caixa de e-mail vira o fator de autenticação. É o mesmo nível do "esqueci a senha" — que é por onde qualquer conta do mundo pode ser tomada de qualquer jeito. ## O gerente do cliente Recebe convite para definir senha, e ganha o que o revisor não tem: histórico de todos os projetos daquele cliente com o estúdio, e a lista de quem revisa. **Ele inclui e remove revisores sozinho.** Antes isso era trabalho do estúdio — perguntar por e-mail quem ia revisar, digitar tudo, e refazer a cada troca de gente no escritório do cliente. O estúdio também pode incluir revisores, para não travar a primeira rodada esperando o gerente se organizar. `revisor_projeto.incluido_por` registra quem incluiu quem. --- ## Cadastro do estúdio Quatro campos: nome do estúdio, seu nome, e-mail, senha. Cria `estudios` + o primeiro `usuarios` com papel `admin`, na mesma transação. **E-mail não confirmado bloqueia convidar, não bloqueia usar.** Quem acabou de se cadastrar quer ver o produto, não abrir a caixa de entrada. O que fica travado é o que usa o e-mail como identidade — convidar gente e disparar convite ao revisor. Link de confirmação vale 48 h. **Senha: mínimo 10 caracteres, e é só isso.** Sem exigir maiúscula, número e símbolo. Essa regra produz `Senha@123`, que está em qualquer lista de senha vazada, enquanto uma frase de quatro palavras é ordens de grandeza mais forte e mais fácil de lembrar. Além do comprimento, recusar a senha se ela aparecer numa lista de vazadas conhecidas — a API k-anonimato do Have I Been Pwned faz isso sem enviar a senha, mandando só os cinco primeiros caracteres do SHA-1. `password_hash()` com `PASSWORD_DEFAULT`. Nunca guardar a senha, nem em log, nem em campo de auditoria, nem em mensagem de erro. --- ## Login Sessão em cookie `HttpOnly`, `Secure`, `SameSite=Lax`, com o identificador da sessão em `sessoes`, não os dados do usuário. Guardar a sessão em tabela é o que permite derrubá-la: trocar a senha encerra todas menos a atual, e remover alguém da equipe corta o acesso na hora em vez de esperar o cookie vencer. **A mensagem de erro é sempre a mesma:** *"E-mail ou senha não conferem."* Dizer qual dos dois falhou entrega a quem está tentando se aquele endereço tem conta aqui. Mesma razão pela qual o tempo de resposta precisa ser parecido nos dois casos — se o caminho do "e-mail não existe" retorna antes de calcular o hash, o cronômetro conta a história que a mensagem escondeu. Calcular um hash falso quando o usuário não existe resolve. **Limite de tentativa em dois eixos**, porque cada um cobre um ataque diferente: | eixo | limite | ataque que cobre | |---|---|---| | por conta | 5 falhas em 15 min | adivinhar a senha de alguém | | por IP | 20 falhas em 15 min | varrer muitas contas de uma origem só | Bloqueio por conta não pode ser eterno, senão vira negação de serviço contra o dono: quem souber o e-mail derruba o acesso da pessoa quando quiser. Quinze minutos, e a redefinição de senha continua funcionando durante o bloqueio. --- ## Convite da equipe Só `admin` convida e remove. Convite vale 7 dias, uma vez só. O e-mail do convite não diz se aquele endereço já tem conta em outro estúdio — e aceitar um convite nunca move ninguém entre estúdios. Um endereço pode existir em dois estúdios como duas linhas em `usuarios`; a chave única de e-mail passa a ser **(estudio_id, email)**, não o e-mail sozinho. É a consequência de D2 que o schema v1 não tinha visto. Remover alguém apaga as sessões dela e mantém o que ela escreveu: comentário e tarefa ficam, com `usuario_id` apontando para uma linha inativa. Auditoria que some quando a pessoa sai não é auditoria. --- ## Redefinição de senha Link de 30 minutos, uma vez só, invalidado ao ser usado e também quando outro é pedido. **A resposta é idêntica exista ou não a conta:** *"Se existe conta com esse e-mail, o link já saiu."* É a mesma proteção do login, no lugar onde ela costuma ser esquecida. Ao trocar a senha: encerrar todas as sessões menos a atual, e avisar por e-mail no endereço antigo. Se não foi a pessoa quem trocou, esse aviso é como ela descobre. --- ## Tokens — a regra que vale para todos Todo token do sistema segue a mesma forma. São seis: confirmação de e-mail, convite de equipe, redefinição de senha, convite do gerente, link do revisor e o link aberto. **32 bytes de `random_bytes()`**, não `rand()`, não `uniqid()`, não hora. Codificado em base64url e entregue na URL. **No banco vai o hash, nunca o token.** `SHA-256` basta aqui — o segredo tem entropia suficiente para não sofrer com dicionário, então não é caso de bcrypt. Quem vazar a tabela não consegue usar link nenhum. Isso vale inclusive para o link do cliente. **Expiração e uso único** conforme a tabela: | token | validade | uso | |---|---|---| | confirmação de e-mail | 48 h | uma vez | | convite de equipe | 7 dias | uma vez | | convite do gerente | 7 dias | uma vez | | redefinição de senha | 30 min | uma vez | | **link do revisor** | 30 min | uma vez, e abre sessão de 30 dias | | **link aberto** (desligado por padrão) | enquanto a rodada existir | muitas | O link do revisor é curto e de uso único porque não é o acesso: é a porta. O acesso é a sessão de 30 dias que ele abre. Já o link aberto é a exceção que sobrou do D8 original — vale para todo mundo que o receber, não identifica autor, e por isso nasce desligado. Comparação de token sempre com `hash_equals()`. `==` vazia o tempo e conta quantos caracteres iniciais estavam certos. --- ## Os dez e-mails Todos estão renderizados e navegáveis na tela **Modelos de e-mail**. | e-mail | para | quando | |---|---|---| | Confirme seu e-mail | quem criou a conta | no cadastro | | Convite para a equipe | quem foi convidado | quando um admin convida | | Redefinir a senha | quem pediu | em "Esqueci a senha" | | **Convite ao gerente do cliente** | quem coordena a revisão | ao cadastrar o gerente | | Convite ao revisor | cada revisor, com link próprio | ao abrir a janela | | **Seu link de acesso** | revisor que pediu de novo | quando ele digita o e-mail | | Lembrete de prazo | revisor que não terminou | um dia antes | | Prazo encerrado | o estúdio | quando a janela fecha | | Liberado para a equipe | a equipe | quando o estúdio libera | | Nova versão disponível | o revisor | quando a versão sobe | ### Como são escritos **Tabela e estilo em linha.** Cliente de e-mail não entende flex, grid nem folha externa. Outlook renderiza com o motor do Word. É feio de escrever e é o que chega inteiro. **Toda mensagem sai também em texto puro.** `multipart/alternative`. Mensagem só-HTML pontua mal em filtro de spam e é ilegível em leitor de tela antigo. **Nenhum e-mail carrega render nem conteúdo do projeto** além do nome e da contagem. Caixa de e-mail é o lugar menos protegido da corrente: se a do revisor for comprometida, o atacante não deve ganhar as imagens junto. Miniatura de render em e-mail é conveniência que não paga o risco. **Um lembrete, só.** Seis etapas × quatorze imagens de notificação vira filtro de spam e, pior, faz a pessoa parar de ler os e-mails que importam. ### Entrega SMTP externo desde o começo (D3). `mail()` de hospedagem compartilhada cai em spam, e convite que não chega é falha que o usuário atribui ao produto. Três registros de DNS, sem os quais o resto não adianta: - **SPF** — autoriza o provedor a enviar pelo seu domínio - **DKIM** — assina a mensagem, provando que não foi adulterada no caminho - **DMARC** — diz ao destinatário o que fazer quando SPF ou DKIM falham; começar em `p=none` com relatório, e endurecer para `p=quarantine` depois de ler os relatórios Remetente em domínio próprio (`avisos@seudominio.com.br`), com **responder-para** apontando para o e-mail real do estúdio. O cliente vai responder ao e-mail — é o que ele faz há vinte anos — e essa resposta tem de chegar em alguém. **Registrar todo envio** em `emails_enviados`: destinatário, tipo, quando, e o retorno do provedor. Sem isso, "o cliente diz que não recebeu" não tem resposta. --- ## O que ainda não está decidido **P8 — verificação em duas etapas.** Não entra na primeira versão: o dado aqui é render de arquitetura, não dinheiro, e 2FA obrigatório em estúdio de três pessoas gera mais chamado de suporte do que segurança. Vale como opcional quando houver estúdio grande pagando. **P9 — domínio próprio do estúdio.** `estudios.dominio_proprio` existe no schema. Serve para o link do cliente sair na marca dele. Exige certificado por domínio e um fluxo de verificação — fase 4, e o custo de operação precisa ser medido antes de prometer.