@chrono-os/cron-guard
v0.1.1
Published
Proteção de rota de cron por segredo compartilhado: comparação timing-safe (sha256 + timingSafeEqual), fail-closed sem segredo configurado, helper para route handler do Next.js e CronAuthGuard para Nest em entry separado.
Maintainers
Readme
@chrono-os/cron-guard
Protege rota de cron com segredo compartilhado. Aceita Authorization: Bearer <segredo> (o formato da Vercel Cron) e o header x-cron-secret. A comparação é timing-safe (sha256 dos dois lados + timingSafeEqual), então nem o conteúdo nem o tamanho do segredo vazam pelo tempo de resposta. Sem segredo configurado a rota responde 503 e nada passa.
Substitui três cópias do parque: o CronAuthGuard do SVA, a comparação !== repetida em 4 rotas do lealferraz-dashboard e o CronSecretGuard do dashboard-nairio-api.
Next.js (route handler, runtime nodejs)
import { withCronAuth } from '@chrono-os/cron-guard'
export const runtime = 'nodejs'
export const GET = withCronAuth(async (req) => {
// ... só roda com o segredo certo
return Response.json({ ok: true })
})Ou no topo do handler: const negado = cronGuard(req); if (negado) return negado.
O segredo vem de process.env.CRON_SECRET, lido a cada requisição. Para outra fonte, passe { secret: 'valor' } ou { secret: () => valor }. Se passar secret: undefined (uma env com nome errado, por exemplo), a rota recusa com 503; ela não volta para o CRON_SECRET.
Nest
import { CronAuthGuard } from '@chrono-os/cron-guard/nest'
@Controller('cron')
@UseGuards(CronAuthGuard)
export class CronController {}createCronAuthGuard({ secret, secretHeader, queryParam, errorBody }) cria um guard com outras opções. O entry raiz não importa Nest: @nestjs/common é peer opcional e só o ./nest precisa dele.
Função pura
import { verifyCronRequest, verifyCronSecret, assertCronSecret } from '@chrono-os/cron-guard'
verifyCronRequest(req.headers, process.env.CRON_SECRET)
// { ok: true } | { ok: false, status: 401 | 503, reason: 'unauthorized' | 'disabled' }Regras
- Um candidato por requisição. Se vier Bearer, só ele é comparado; o
x-cron-secretentra apenas quando não há Bearer. Authorizationsem esquema não vale.Authorization: <segredo>eBasic ...são recusados.- Query string é legado e fica desligada.
queryParam: 'secret'aceita?secret=para migrar chamador antigo. O segredo acaba em log de proxy e de CDN, então essa opção existe só para a migração. - Precisa de
node:crypto, ou seja, não roda no Edge runtime.
