Skip to content

Autenticação e Autorização

O sistema de autenticação do Neemias usa PBKDF2 para hashing de senhas e JWT HS256 (via biblioteca jose) para tokens de acesso, com rotação de refresh tokens para detectar reuso malicioso.

Fluxo de Login

POST /api/v1/auth/login
Body: { email, password }

1. Valida body com Zod (schemas.ts)
2. Busca usuário por email (readAuthUserByEmail)
3. Verifica status = ACTIVE
4. Verifica senha com PBKDF2 (verifyPassword)
5. Cria sessão (createAuthSession):
   - Gera refresh token (crypto.randomUUID)
   - Gera CSRF token (crypto.randomUUID)
   - Gera sessionId (crypto.randomUUID)
   - Hash do refresh token armazenado em auth_refresh_sessions
   - Assina JWT de acesso (TTL 15 min)
6. Retorna { accessToken, refreshToken, csrfToken, expiresAt }

Rate Limiting

A rota de login é protegida por rate limit: 5 requisições por minuto por IP. A rota de refresh permite 10 requisições por minuto.

Tokens

Access Token (JWT)

  • Algoritmo: HS256 (simétrico, chave AUTH_JWT_SECRET)
  • TTL: 15 minutos (configurável via AUTH_ACCESS_TOKEN_TTL_SECONDS)
  • Payload:
typescript
{
  userId: string; // ID do usuário
  role: UserRole; // "ADMIN" | "CHAMADOR" | "RELATORIOS" | "CADASTRO"
  sessionId: string; // ID da sessão
  csrfToken: string; // Token CSRF da sessão
  iat: number; // Issued at
  exp: number; // Expiration
}
  • Uso: Header Authorization: Bearer <accessToken>

Refresh Token

  • Formato: UUID v4 (opaco)
  • TTL: 7 dias (configurável via AUTH_REFRESH_TOKEN_TTL_SECONDS)
  • Armazenamento: Hash SHA-256 do token na coluna refresh_token_hash da tabela auth_refresh_sessions
  • Uso: POST /api/v1/auth/refresh com body { refreshToken }

Rotação de Refresh Token

Ao renovar o access token via POST /api/v1/auth/refresh:

  1. Hash do refresh token enviado é validado contra auth_refresh_sessions
  2. Se o token já foi revogado (revoked_at não nulo) ou substituído (replaced_by não nulo):
    • Marca suspected_compromise_at na sessão original (possível roubo de token)
    • Revoga todas as sessões do usuário
    • Retorna 401 COMPROMISED_SESSION
  3. Sessão original é marcada com revoked_at e replaced_by apontando para a nova
  4. Nova sessão é criada com novo refresh token e novo CSRF token
  5. Retorna novo par { accessToken, refreshToken, csrfToken }

CSRF Token

Cada sessão possui um csrfToken (UUID v4). Ele faz parte do payload do JWT e é retornado no login/refresh. O frontend o armazena e envia em cabeçalhos de mutação (usado pelo syncQueueEntryWithBackend).

X-Device-Id

Obrigatório em toda requisição autenticada. O middleware requireAuth (em workers/src/middleware/auth.ts) rejeita com 400 VALIDATION_FAILED se o header X-Device-Id estiver ausente.

Modo Dev

Quando a variável de ambiente AUTH_MODE=dev está configurada, o backend aceita tokens fixos definidos nas env vars:

VariávelRole
AUTH_DEV_ADMIN_TOKENADMIN (userId: dev-admin)
AUTH_DEV_CALLER_TOKENCHAMADOR (userId: dev-caller)
AUTH_DEV_REPORTS_TOKENRELATORIOS (userId: dev-reports)

Importante: O modo dev ignora completamente a validação JWT. Nunca use em produção.

Controle de Acesso (RBAC)

O middleware requireRole(roles[]) verifica se o AuthPrincipal.role está na lista permitida:

typescript
// Exemplo: apenas ADMIN e CADASTRO podem criar alunos
route(
  "POST",
  "/api/v1/students",
  [pipelineRequireAuth(), pipelineRequireRole(["ADMIN", "CADASTRO"])],
  handleCreateStudentPipeline,
);

Roles e Permissões

RolePermissões
ADMINAcesso total — gerencia usuários, roles, alunos, turmas, núcleos, relatórios, chamada
CHAMADORMarca presença (attendance), busca alunos (students.search)
RELATORIOSVisualiza relatórios (reports), busca alunos (students.search)
CADASTROCadastra/edita alunos (students.add, students.search), gerencia aulas (sessions.add, sessions.edit), núcleos e turmas

Tabela roles

A partir da migração 0006_roles.sql, as permissões são persistidas no banco:

sql
CREATE TABLE roles (
  name TEXT PRIMARY KEY,
  display_name TEXT NOT NULL,
  permissions TEXT NOT NULL DEFAULT '[]',  -- JSON array
  is_system INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);

Roles com is_system = 1 não podem ser excluídas.

Hash de Senha

Formato armazenado: pbkdf2:<hexSalt>:<hexHash>

  • Algoritmo: PBKDF2
  • Iterações: 100.000
  • Hash: SHA-256
  • Salt: 16 bytes aleatórios

Nota histórica: O backend legado usava scrypt, mas era incompatível com o runtime Workers. A migração para PBKDF2 foi resolvida na inicialização do projeto.

AuthPrincipal

O resultado da autenticação populado em ctx.principal:

typescript
interface AuthPrincipal {
  userId: string;
  role: UserRole;
  tokenExpiresAt?: number; // presente apenas em JWT real (não dev mode)
  sessionId?: string;
  csrfToken?: string;
}

Fonte: workers/CONTEXT.md + auth.ts

Distribuído sob licença MIT.