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>
116 lines
4.3 KiB
TypeScript
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>
|
|
)
|
|
}
|