{"_id":"@chrono-os/cron-guard","_rev":"2-eff7ee375bc162577d823426d46f0363","name":"@chrono-os/cron-guard","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@chrono-os/cron-guard","version":"0.1.0","keywords":["cron","vercel-cron","timing-safe","nestjs","nextjs","chrono-os"],"license":"MIT","_id":"@chrono-os/cron-guard@0.1.0","maintainers":[{"name":"sugarhoneyicetea","email":"aquilaferraz@gmail.com"}],"homepage":"https://github.com/SugarHoneyIceTea/cron-guard#readme","bugs":{"url":"https://github.com/SugarHoneyIceTea/cron-guard/issues"},"dist":{"shasum":"1b4d1bd269fd40385bf6b26a84c0bb6f49622be2","tarball":"https://registry.npmjs.org/@chrono-os/cron-guard/-/cron-guard-0.1.0.tgz","fileCount":16,"integrity":"sha512-UcnRVdTgdkYsk8IhqNrI0ZdavFvnNAgGc6f3MY1fJTYGgLACdYH9VWRIldD64Avlchyea8jQ3uGaUDF+qSmp7w==","signatures":[{"sig":"MEUCIARoTuz8pYgAIvdr6ZI+pCJT/WdHXW6LZ9dqwM8c6m21AiEAhmQdrVLzAF/bPTTMBihzntz4KskCbOElWMJY4PFbgBw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84320},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./nest":{"types":"./dist/nest.d.ts","import":"./dist/nest.js","require":"./dist/nest.cjs"},"./package.json":"./package.json"},"gitHead":"c048762e6a68598fb05bc79a67a13981cac0f824","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit && tsc -p tsconfig.test.json","test:watch":"vitest"},"_npmUser":{"name":"sugarhoneyicetea","email":"aquilaferraz@gmail.com"},"repository":{"url":"git+https://github.com/SugarHoneyIceTea/cron-guard.git","type":"git"},"_npmVersion":"11.9.0","description":"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.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"@chrono-os/service-auth":"0.1.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"rxjs":"7.8.2","tsup":"8.5.1","vitest":"3.2.7","supertest":"7.2.2","typescript":"5.9.3","@types/node":"25.9.6","@nestjs/core":"11.2.3","@nestjs/common":"11.2.3","@nestjs/testing":"11.2.3","@types/supertest":"7.2.1","reflect-metadata":"0.2.2","@nestjs/platform-express":"11.2.3"},"peerDependencies":{"@nestjs/common":"^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cron-guard_0.1.0_1789071225710_0.8184847412881058","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"_id":"@chrono-os/cron-guard@0.1.1","bugs":{"url":"https://github.com/SugarHoneyIceTea/cron-guard/issues"},"dist":{"shasum":"4c6433a9d23b07fce7f091a7758ad7bdbf8edd28","tarball":"https://registry.npmjs.org/@chrono-os/cron-guard/-/cron-guard-0.1.1.tgz","fileCount":16,"integrity":"sha512-H/A3LSOCGCWF15DKvOyx5O/5lh8YYrJ2CEvy82CkYHgH29AhyQaKWcxppNzzTMQ90wirSaF7a3korIzBzQ9kZw==","signatures":[{"sig":"MEUCIQCcj59qlsAikhWvuKG++92HgCnYQ61IpRKJafo3x4/j5AIgO8NeKSimJ1V0qI24cI5SVDuPg3QCzm3ut/tS4CyXmeg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDWoDLAzcVMxLSi/OzKhbbqPQUIWnTZIXzbK9u/0klqtgIgMlU1RiCk7Fwrtcf0YCCjcxO6A7YH+oys8L+qefhSSAk="}],"unpackedSize":84872},"main":"./dist/index.cjs","name":"@chrono-os/cron-guard","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./nest":{"import":{"types":"./dist/nest.d.ts","default":"./dist/nest.js"},"require":{"types":"./dist/nest.d.cts","default":"./dist/nest.cjs"}},"./package.json":"./package.json"},"gitHead":"146ed324e9f7cff0c83cf6d4c4229ae40b6d2d3e","license":"MIT","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit && tsc -p tsconfig.test.json","test:watch":"vitest"},"version":"0.1.1","_npmUser":{"name":"sugarhoneyicetea","email":"aquilaferraz@gmail.com"},"homepage":"https://github.com/SugarHoneyIceTea/cron-guard#readme","keywords":["cron","vercel-cron","timing-safe","nestjs","nextjs","chrono-os"],"repository":{"url":"git+https://github.com/SugarHoneyIceTea/cron-guard.git","type":"git"},"_npmVersion":"11.9.0","description":"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.","directories":{},"maintainers":[{"name":"sugarhoneyicetea","email":"aquilaferraz@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"@chrono-os/service-auth":"0.1.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"rxjs":"7.8.2","tsup":"8.5.1","vitest":"3.2.7","supertest":"7.2.2","typescript":"5.9.3","@types/node":"25.9.6","@nestjs/core":"11.2.3","@nestjs/common":"11.2.3","@nestjs/testing":"11.2.3","@types/supertest":"7.2.1","reflect-metadata":"0.2.2","@nestjs/platform-express":"11.2.3"},"peerDependencies":{"@nestjs/common":"^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cron-guard_0.1.1_1789076012578_0.4530295154184163"}}},"time":{"created":"2026-09-10T20:13:45.585Z","modified":"2026-09-10T21:33:32.879Z","0.1.0":"2026-09-10T20:13:45.854Z","0.1.1":"2026-09-10T21:33:32.684Z"},"bugs":{"url":"https://github.com/SugarHoneyIceTea/cron-guard/issues"},"license":"MIT","homepage":"https://github.com/SugarHoneyIceTea/cron-guard#readme","keywords":["cron","vercel-cron","timing-safe","nestjs","nextjs","chrono-os"],"repository":{"url":"git+https://github.com/SugarHoneyIceTea/cron-guard.git","type":"git"},"description":"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":[{"name":"sugarhoneyicetea","email":"aquilaferraz@gmail.com"}],"readme":"# @chrono-os/cron-guard\n\nProtege 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.\n\nSubstitui 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.\n\n## Next.js (route handler, runtime `nodejs`)\n\n```ts\nimport { withCronAuth } from '@chrono-os/cron-guard'\n\nexport const runtime = 'nodejs'\n\nexport const GET = withCronAuth(async (req) => {\n  // ... só roda com o segredo certo\n  return Response.json({ ok: true })\n})\n```\n\nOu no topo do handler: `const negado = cronGuard(req); if (negado) return negado`.\n\nO 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`.\n\n## Nest\n\n```ts\nimport { CronAuthGuard } from '@chrono-os/cron-guard/nest'\n\n@Controller('cron')\n@UseGuards(CronAuthGuard)\nexport class CronController {}\n```\n\n`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.\n\n## Função pura\n\n```ts\nimport { verifyCronRequest, verifyCronSecret, assertCronSecret } from '@chrono-os/cron-guard'\n\nverifyCronRequest(req.headers, process.env.CRON_SECRET)\n// { ok: true } | { ok: false, status: 401 | 503, reason: 'unauthorized' | 'disabled' }\n```\n\n## Regras\n\n- **Um candidato por requisição.** Se vier Bearer, só ele é comparado; o `x-cron-secret` entra apenas quando não há Bearer.\n- **`Authorization` sem esquema não vale.** `Authorization: <segredo>` e `Basic ...` são recusados.\n- **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.\n- **Precisa de `node:crypto`**, ou seja, não roda no Edge runtime.\n","readmeFilename":"README.md"}