Criando um Sistema de Autenticação Robusto com Fastify, ZenStack e Prisma

Posted by

Neste guia completo, você aprenderá como construir uma API segura e escalável com autenticação baseada em JWT utilizando Fastify, ZenStack e Prisma. Abordaremos desde a modelagem de dados com validação e controle de acesso (ACL) até a criação de endpoints personalizados para registro e login, com proteção contra acesso não autorizado.

Etapa 1 – Inicialização e Instalação do Projeto

Comece configurando o ambiente com as bibliotecas necessárias:

npm init -y

# Dependências principais
npm install fastify @zenstackhq/server @zenstackhq/runtime @prisma/client fastify-zod zod bcryptjs jsonwebtoken

# Dependências de desenvolvimento
npm install -D prisma ts-node-dev typescript @types/node @types/jsonwebtoken

Iniciando o ZenStack e Prisma

npx zenstack init
npx zenstack generate
npx prisma db push

Isso criará arquivos como schema.zmodel, que é a base da validação declarativa e política de acesso (ACL) do ZenStack.

Etapa 2 – Modelagem com ZModel + Regras de Validação e Autorização

Edite o arquivo schema.zmodel para definir a estrutura da tabela User com validação automática:

datasource db { provider = "sqlite"; url = "file:./dev.db" }
generator client { provider = "prisma-client-js" }

model User {
  id        String   @id @default(cuid())
  email     String   @unique @email
  password  String   @length(min: 8)
  createdAt DateTime @default(now())

  @@allow('read', auth() != null && auth().id == id)
  @@allow('create', auth() == null)
}

Explicação:

  • Validação declarativa com @email e @length(min: 8) evita dados inválidos na criação.
  • Controle de acesso (ACL):
    • Apenas o próprio usuário pode ler seus dados.
    • Apenas usuários anônimos podem criar contas (ex: /register).

Após salvar:

npx zenstack generate
npx prisma db push

Etapa 3 – Configurando Fastify com Prisma + ZenStack

Crie o arquivo src/app.ts:

import Fastify from 'fastify';
import { PrismaClient } from '@prisma/client';
import { enhance } from '@zenstackhq/runtime';
import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
import fastifyJwt from '@fastify/jwt';
import bcrypt from 'bcryptjs';

const prisma = new PrismaClient();
const app = Fastify();

app.register(fastifyJwt, { secret: process.env.JWT_SECRET || 'supersecret' });

// Plugin ZenStack – fornece API REST/RPC para models
app.register(ZenStackFastifyPlugin, {
  prefix: '/api/model',
  getPrisma: (req) => {
    const user = req.user as { id: string } | null;
    return enhance(prisma, { user });
  },
});

Etapa 4 – Endpoints personalizados: Registro e Login

POST /register

app.post('/register', async (req, res) => {
  const { email, password } = req.body as { email: string; password: string };

  const hashed = await bcrypt.hash(password, 10);
  try {
    const user = await enhance(prisma, { user: null }).user.create({
      data: { email, password: hashed },
    });

    const token = app.jwt.sign({ id: user.id });
    return res.status(201).send({ token });
  } catch (err: any) {
    return res.status(400).send({ error: err.message });
  }
});
  • enhance(prisma, { user: null }) ignora contexto de autenticação (necessário no registro).
  • A validação do e-mail e da senha é aplicada automaticamente com base nas regras do ZModel.

POST /login

app.post('/login', async (req, res) => {
  const { email, password } = req.body as { email: string; password: string };
  const user = await prisma.user.findUnique({ where: { email } });

  if (!user) return res.status(401).send({ error: 'Credenciais inválidas' });

  const isValid = await bcrypt.compare(password, user.password);
  if (!isValid) return res.status(401).send({ error: 'Credenciais inválidas' });

  const token = app.jwt.sign({ id: user.id });
  return res.send({ token });
});

Etapa 5 – Middleware de Autenticação com JWT

Adicione suporte a autenticação automática via JWT:

app.decorateRequest('user', null);

app.addHook('onRequest', async (req, res) => {
  try {
    const token = req.headers.authorization?.split(' ')[1];
    if (token) {
      const decoded = await app.jwt.verify(token);
      req.user = { id: (decoded as any).id };
    }
  } catch {
    // Token inválido: prossegue sem autenticação
  }
});

Com isso, o ZenStack consegue aplicar regras como auth().id == id de forma automática no contexto das requisições.

Etapa 6 – Testando com curl ou Insomnia/Postman

Registro de usuário:

curl -X POST http://localhost:3000/register \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"senha123"}'

Login do usuário:

curl -X POST http://localhost:3000/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"senha123"}'

Consulta protegida (com ACL):

curl -X POST http://localhost:3000/api/model/user/findUnique \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <SEU_TOKEN>" \
  -d '{"where":{"id":"<ID_USER>"}}'

Se o id requisitado não for o do usuário autenticado, a requisição será automaticamente negada com status 403, graças à política declarativa do ZenStack.

Conclusão

Com essa abordagem, você construiu uma base sólida para autenticação e autorização moderna com:

  • Modelos seguros usando ZenStack com validação integrada e ACL.
  • Endpoints personalizados para register e login, utilizando JWT.
  • Autorização automática com enhance(prisma) no contexto do usuário.
  • API CRUD pronta via /api/model/* com proteção embutida.

Essa arquitetura é modular, extensível e preparada para produção. Você pode expandi-la com refresh tokens, roles, verificação de e-mail, atualização de perfil e muito mais.

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *