mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-06-27 19:05:48 +08:00
SEC-07 — committed secrets: - Replace real-looking defaults in .env.example (JWT_SECRET/JWT_REFRESH_SECRET "changeme", MANUAL_INVITE_PASSWORD "pm.local@zghl.de") with CHANGE_ME placeholders, and drop the personal DOME_LIGHT_WEBHOOK_URL default. - Add a Nitro startup plugin (server/plugins/validate-secrets.ts) that refuses to boot in production when JWT_SECRET is unset, looks like a placeholder, or is shorter than 32 chars (warns only in development). OPS-02 / SEC-09 — cron endpoints: - requireCronSecret now fails closed: when no CRON_SECRET/KPI_CRON_SECRET is configured the endpoint returns 503 instead of being publicly callable (previously it allowed the request with a warning). Both cron routes already call the guard. Prefer the x-cron-secret header over the loggable ?secret= query param; document CRON_SECRET in .env.example. Operational note: production deployments must now set JWT_SECRET (>=32 chars) and CRON_SECRET, or the server won't start / crons return 503. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
import type { H3Event } from 'h3'
|
|
import { createError, getHeader, getQuery } from 'h3'
|
|
|
|
let warnedMissingSecret = false
|
|
|
|
/**
|
|
* Guards cron endpoints. The secret is read from CRON_SECRET (or the legacy
|
|
* KPI_CRON_SECRET) and must be supplied via the `x-cron-secret` header
|
|
* (preferred) or, for schedulers that can only call a URL, the `?secret=`
|
|
* query param. Prefer the header — query strings tend to end up in access logs.
|
|
*
|
|
* Fail closed: these endpoints send real emails and mint invite codes, so if no
|
|
* secret is configured the endpoint refuses to run (503) rather than being
|
|
* publicly callable.
|
|
*/
|
|
export function requireCronSecret(event: H3Event) {
|
|
const secret = (process.env.CRON_SECRET || '').trim()
|
|
|
|
if (!secret) {
|
|
if (!warnedMissingSecret) {
|
|
console.error(
|
|
'[cron] CRON_SECRET is not set — cron endpoints are disabled (returning 503). ' +
|
|
'Set CRON_SECRET and pass it via the x-cron-secret header.',
|
|
)
|
|
warnedMissingSecret = true
|
|
}
|
|
throw createError({ statusCode: 503, statusMessage: 'Cron endpoint is not configured.' })
|
|
}
|
|
|
|
const query = getQuery(event)
|
|
const provided =
|
|
(getHeader(event, 'x-cron-secret') || '') ||
|
|
(typeof query.secret === 'string' ? query.secret : '')
|
|
|
|
if (provided !== secret) {
|
|
throw createError({ statusCode: 401, statusMessage: 'Invalid cron secret.' })
|
|
}
|
|
}
|