Programação Por Equipe DevBras

Capa do artigo: Validação só no navegador não protege: repita a regra no servidor Node

Validação só no navegador não protege: repita a regra no servidor Node

Veja como contornar um plugin de validação com um curl, e como montar no Node uma validação que confere tipo, formato e tamanho, recalcula preço e devolve erro por campo.

O formulário de assinatura da loja não deixa enviar e-mail sem arroba, nome vazio nem plano inexistente. O plugin pinta o campo de vermelho, a equipe testa, aprova e relaxa. Três semanas depois, o banco tem 400 cadastros com e-mail x, nome de 5 mil caracteres e assinaturas do plano premium por R$ 0,00. Ninguém passou pelo formulário: um robô chamou a rota direto. A validação no navegador é conforto para quem preenche. A regra que protege o sistema mora no servidor.

A resposta curta: toda regra que o plugin aplica (obrigatório, formato, tamanho, valores permitidos) precisa existir também no servidor, que confere até o tipo do dado recebido e devolve o erro por campo. Valores que a pessoa não escolhe, como o preço, o servidor recalcula. O plugin, se continuar, usa as mesmas mensagens.

Por que o plugin não é uma cerca

Um plugin de validação, seja jQuery Validate, os atributos nativos required e type="email" ou uma biblioteca de formulário moderna, roda no navegador da pessoa. Tudo que roda ali está sob controle de quem usa o navegador. Basta desligar o JavaScript, apagar o atributo nas ferramentas de desenvolvedor ou, mais comum, nem abrir a página e mandar a requisição direto.

A rota de cadastro é pública. Qualquer pessoa vê no painel de rede do navegador o endereço e o formato do corpo, e repete com um comando:

curl -i -X POST https://loja.exemplo/api/assinaturas \
  -H "Content-Type: application/json" \
  -d '{"nome":"","email":"x","plano":"premium","preco":0}'

Se a resposta for 201 Created, a validação está só na tela. Esse teste leva trinta segundos e é o melhor diagnóstico de qualquer formulário. Faça antes de ler o resto.

O que o servidor tem de conferir, além do que o plugin confere

O servidor não recebe "o formulário"; recebe um corpo qualquer. Isso cria verificações que o plugin nunca precisou fazer:

  • Tipo: num corpo JSON, email pode chegar como número, array ou objeto. Chamar .trim() num array derruba a rota com erro 500.
  • Campos extras: o corpo pode trazer preco, desconto ou admin. Se o código grava o corpo inteiro, grava tudo isso.
  • Valores derivados: o preço é consequência do plano. Ele nunca vem da requisição.
  • Campos ausentes: campo disabled no HTML não é enviado no POST. O servidor não pode assumir que ele veio com o valor que a tela mostrava.

Como montar a validação no Node, passo a passo

  1. Escreva as regras num módulo próprio. Um arquivo só com a função de validação, sem depender de Express nem do DOM, para poder ser usado no servidor e, se quiser, no navegador.
  2. Confira o tipo antes do formato. Se não é string, já é erro; só depois aplique tamanho e formato.
  3. Devolva um objeto de erros por campo. A tela precisa saber qual campo marcar, e a API precisa dizer o mesmo.
  4. Monte o registro só com os campos conhecidos. Nunca espalhe o corpo inteiro no objeto que vai para o banco.
  5. Recalcule o que é derivado. Preço, total e desconto vêm de tabela do servidor.
  6. Responda 422 com os erros. Assim o front-end mostra a mensagem certa no campo certo.
// regras.js
const PLANOS = { basico: 1990, premium: 4990 }; // centavos

const FORMATO_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function texto(valor) {
  return typeof valor === 'string' ? valor.trim() : null;
}

export function validarAssinatura(corpo) {
  const dados = corpo ?? {};
  const erros = {};

  const nome = texto(dados.nome);
  if (!nome) erros.nome = 'Informe o seu nome.';
  else if (nome.length > 120) erros.nome = 'Use no máximo 120 caracteres.';

  const email = texto(dados.email);
  if (!email) erros.email = 'Informe o seu e-mail.';
  else if (email.length > 254 || !FORMATO_EMAIL.test(email)) {
    erros.email = 'Confira o e-mail: falta algo como nome@dominio.com.';
  }

  const plano = texto(dados.plano);
  if (!plano || !Object.hasOwn(PLANOS, plano)) erros.plano = 'Escolha um dos planos da lista.';

  if (Object.keys(erros).length > 0) return { erros };

  return {
    assinatura: { nome, email: email.toLowerCase(), plano, precoCentavos: PLANOS[plano] }
  };
}
// servidor.js
import express from 'express';
import { validarAssinatura } from './regras.js';

const app = express();
app.use(express.json({ limit: '10kb' }));

app.post('/api/assinaturas', async (req, res) => {
  const resultado = validarAssinatura(req.body);
  if (resultado.erros) {
    return res.status(422).json({ erros: resultado.erros });
  }
  // grava resultado.assinatura no banco, com consulta parametrizada
  res.status(201).json({ plano: resultado.assinatura.plano });
});

app.listen(3000);

Repare em três detalhes. O corpo ?? {} protege contra requisição sem corpo ou com outro tipo de conteúdo, em que req.body pode não existir. O Object.hasOwn impede que alguém mande "plano": "constructor" e pegue uma propriedade herdada do objeto. E o limit do parser de JSON recusa corpos enormes antes de o seu código rodar.

A regex de e-mail é deliberadamente simples. Ela pega o erro de digitação óbvio; a confirmação real de que o e-mail existe é o link de confirmação. Para gravar, use consulta com parâmetro, como no guia de Node.js e MySQL com parâmetro.

As mesmas mensagens na tela e na API

Se o plugin diz "e-mail inválido" e a API diz "invalid format", a pessoa aprende duas verdades. Use as mensagens do objeto erros para preencher os campos quando a resposta for 422. Assim, mesmo quem tem o JavaScript bloqueado ou chegou por outro cliente vê o mesmo texto. O guia sobre a mesma mensagem de erro na tela e na API mostra como mapear isso no front-end. Se a sua pilha é PHP, o equivalente está em validação jQuery repetida no PHP.

Erros comuns

  • Confiar em required e maxlength do HTML. Somem com um clique nas ferramentas do navegador e não existem para quem chama a rota direto.
  • Aceitar o preço que veio na requisição. Alguém assina o premium por zero, e você só descobre no fechamento do mês.
  • Gravar o corpo inteiro. Campos extras como admin: true entram no banco se o modelo aceitar.
  • Usar disabled para "proteger" um valor. Campo desabilitado nem vai no POST; readonly vai, mas é editável por quem quiser.
  • Bloquear colar no campo. Não impede robô nenhum e atrapalha quem usa gerenciador de senha ou tecnologia assistiva.
  • Responder 500 para dado inválido. Um .trim() num array derruba a rota e esconde que o problema era do cliente.

Checklist

  • O teste com curl e dados inválidos recebe 422, não 201.
  • Cada regra do plugin tem uma equivalente no servidor.
  • O servidor confere o tipo de cada campo antes do formato.
  • O registro gravado é montado só com campos conhecidos.
  • Preço e outros valores derivados são calculados no servidor.
  • A resposta de erro traz uma mensagem por campo, igual à da tela.
  • O corpo da requisição tem limite de tamanho.

Abra o painel de rede do navegador, copie a requisição do seu formulário principal como curl e troque um campo por lixo: o servidor aceita o que o formulário recusa?

Avalie este artigo

Referência: http://www.devmedia.com.br/jquery-validate-validando-formularios/26490