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:
{
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_hashda tabelaauth_refresh_sessions - Uso:
POST /api/v1/auth/refreshcom body{ refreshToken }
Rotação de Refresh Token
Ao renovar o access token via POST /api/v1/auth/refresh:
- Hash do refresh token enviado é validado contra
auth_refresh_sessions - Se o token já foi revogado (
revoked_atnão nulo) ou substituído (replaced_bynão nulo):- Marca
suspected_compromise_atna sessão original (possível roubo de token) - Revoga todas as sessões do usuário
- Retorna
401 COMPROMISED_SESSION
- Marca
- Sessão original é marcada com
revoked_atereplaced_byapontando para a nova - Nova sessão é criada com novo refresh token e novo CSRF token
- 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ável | Role |
|---|---|
AUTH_DEV_ADMIN_TOKEN | ADMIN (userId: dev-admin) |
AUTH_DEV_CALLER_TOKEN | CHAMADOR (userId: dev-caller) |
AUTH_DEV_REPORTS_TOKEN | RELATORIOS (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:
// Exemplo: apenas ADMIN e CADASTRO podem criar alunos
route(
"POST",
"/api/v1/students",
[pipelineRequireAuth(), pipelineRequireRole(["ADMIN", "CADASTRO"])],
handleCreateStudentPipeline,
);Roles e Permissões
| Role | Permissões |
|---|---|
| ADMIN | Acesso total — gerencia usuários, roles, alunos, turmas, núcleos, relatórios, chamada |
| CHAMADOR | Marca presença (attendance), busca alunos (students.search) |
| RELATORIOS | Visualiza relatórios (reports), busca alunos (students.search) |
| CADASTRO | Cadastra/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:
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:
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