Scrum4Me/app/debug-env/page.tsx
Madhura68 0f40bc1c70 fix(privacy): NODE_ENV-guard 4 debug-routes (before-launch privacy review)
Privacy/PII review-pass van Server Actions, API-routes, debug-paths en
Sentry config:

 Sentry sendDefaultPii: false in alle drie configs (server/edge/client)
 Geen wachtwoord/email/token in console-logs
 Pair-id-logs zijn metadata-only (5-min TTL, geen secret)

⚠️ Vier debug-routes hadden geen auth-guard:
- /api/debug/realtime-stream — rauwe pg_notify-stream zonder filtering
- /api/debug/emit-test-notify — anonieme test-emit op het kanaal
- /debug-env — lekt env-var-metadata (hostnames, lengtes, pooled-flag)
- /debug-realtime — UI op dezelfde rauwe pg_notify-stream

Allemaal gemarkeerd als TIJDELIJK met VERWIJDEREN-comments uit M8.
Voor v1 launch: NODE_ENV-guard die in productie 404 retourneert. Lokaal
dev blijft alles werken voor debugging.

Toekomstige cleanup: kunnen worden verwijderd zodra M8-realtime stabiel
draait in productie en niemand ze meer nodig heeft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:16:49 +02:00

116 lines
4.3 KiB
TypeScript

// TIJDELIJKE debug-pagina om te checken of env-vars op deze deployment
// daadwerkelijk een (juiste) waarde hebben. Geen secrets gelekt — alleen
// metadata: lengte, hostname en pooled-flag voor DB-URLs.
//
// VERWIJDEREN zodra env-config op Vercel bevestigd is.
import { headers } from 'next/headers'
import { notFound } from 'next/navigation'
export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'
interface VarStatus {
name: string
set: boolean
length: number
host?: string
pooled?: boolean
parseError?: string
}
function inspectUrl(name: string, raw: string | undefined): VarStatus {
if (!raw) return { name, set: false, length: 0 }
try {
const url = new URL(raw)
return {
name,
set: true,
length: raw.length,
host: url.hostname,
pooled: url.hostname.includes('pooler.'),
}
} catch (e) {
return {
name,
set: true,
length: raw.length,
parseError: e instanceof Error ? e.message : String(e),
}
}
}
function inspectSecret(name: string, raw: string | undefined): VarStatus {
if (!raw) return { name, set: false, length: 0 }
return { name, set: true, length: raw.length }
}
export default async function DebugEnvPage() {
// Productie-guard: lekt env-var-metadata (hostnames, lengtes, pooled-flag).
if (process.env.NODE_ENV === 'production') notFound()
// Force dynamic so each visit reads runtime env (niet build-time gecached)
await headers()
const vars: VarStatus[] = [
inspectUrl('DATABASE_URL', process.env.DATABASE_URL),
inspectUrl('DIRECT_URL', process.env.DIRECT_URL),
inspectSecret('SESSION_SECRET', process.env.SESSION_SECRET),
]
const node = process.env.NODE_ENV ?? '(unset)'
const vercel = process.env.VERCEL_ENV ?? '(unset)'
const url = process.env.VERCEL_URL ?? '(unset)'
const region = process.env.VERCEL_REGION ?? '(unset)'
return (
<div style={{ fontFamily: 'monospace', padding: 16, maxWidth: 1000 }}>
<h1 style={{ fontSize: 18, fontWeight: 'bold' }}>Env-var debug</h1>
<p style={{ fontSize: 13, color: '#666' }}>
Server-side gerenderd. Toont alleen metadata, geen waardes. Verwijderen na env-config check.
</p>
<h2 style={{ fontSize: 15, marginTop: 16 }}>Runtime</h2>
<table style={{ borderCollapse: 'collapse', fontSize: 13 }}>
<tbody>
<tr><td style={{ padding: 4, paddingRight: 16 }}>NODE_ENV</td><td>{node}</td></tr>
<tr><td style={{ padding: 4 }}>VERCEL_ENV</td><td>{vercel}</td></tr>
<tr><td style={{ padding: 4 }}>VERCEL_URL</td><td>{url}</td></tr>
<tr><td style={{ padding: 4 }}>VERCEL_REGION</td><td>{region}</td></tr>
</tbody>
</table>
<h2 style={{ fontSize: 15, marginTop: 24 }}>Variables</h2>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
<thead>
<tr style={{ background: '#f0f0f0', textAlign: 'left' }}>
<th style={{ padding: 6, border: '1px solid #ddd' }}>name</th>
<th style={{ padding: 6, border: '1px solid #ddd' }}>set</th>
<th style={{ padding: 6, border: '1px solid #ddd' }}>length</th>
<th style={{ padding: 6, border: '1px solid #ddd' }}>host</th>
<th style={{ padding: 6, border: '1px solid #ddd' }}>pooled</th>
<th style={{ padding: 6, border: '1px solid #ddd' }}>parse_error</th>
</tr>
</thead>
<tbody>
{vars.map((v) => (
<tr key={v.name}>
<td style={{ padding: 6, border: '1px solid #ddd' }}>{v.name}</td>
<td style={{ padding: 6, border: '1px solid #ddd', color: v.set ? 'green' : 'red' }}>
{v.set ? 'yes' : 'NO'}
</td>
<td style={{ padding: 6, border: '1px solid #ddd' }}>{v.length}</td>
<td style={{ padding: 6, border: '1px solid #ddd' }}>{v.host ?? '—'}</td>
<td style={{ padding: 6, border: '1px solid #ddd', color: v.pooled ? 'orange' : 'inherit' }}>
{v.pooled === undefined ? '—' : v.pooled ? 'yes (LISTEN may not work)' : 'no'}
</td>
<td style={{ padding: 6, border: '1px solid #ddd', color: 'red' }}>
{v.parseError ?? '—'}
</td>
</tr>
))}
</tbody>
</table>
</div>
)
}