Files
pn-new-crm/next.config.ts

88 lines
2.9 KiB
TypeScript
Raw Normal View History

import type { NextConfig } from 'next';
const isProd = process.env.NODE_ENV === 'production';
/**
* Security headers applied to every response. Per audit-pass-#3 finding:
* the previous config emitted no CSP, X-Frame-Options, HSTS, or
* X-Content-Type-Options the app was open to clickjacking + MIME
* sniffing.
*
* CSP notes:
* - 'unsafe-inline' on style-src is required by Tailwind's runtime
* style injection and Radix; revisit when Tailwind v4 ships a
* nonce story.
* - 'unsafe-eval' on script-src is dev-only Next dev uses eval for
* HMR. Production drops it.
* - connect-src allows ws/wss for Socket.IO and https: for outgoing
* fetches; tighten in prod via per-port branding URLs once we move
* the s3 image references into a known allowlist.
* - img-src https: is wide because port branding pulls from
* s3.portnimara.com plus per-port image URLs configured at runtime.
*/
feat(client-archive): single-client smart-archive dialog + CSP/middleware fixups UI side of the smart-archive backend that shipped in d07f1ed. - SmartArchiveDialog renders the dossier as a sectioned modal: Pipeline interests, Berths (with next-in-line listed), Yachts, Active reservations, Outstanding invoices, In-flight Documenso envelopes, Auto-handled summary. Each section has a per-row decision dropdown with sensible defaults (release for available/under-offer berths, retain for sold berths and yachts, cancel for active reservations, leave for invoices and documents). - High-stakes deals show an amber warning panel + require a reason in the textarea before the Archive button enables. Signed-document acknowledgment checkbox blocks submission until checked. - Wires into client-detail-header in place of the previous dumb ArchiveConfirmDialog (the simple confirm dialog is kept for the restore case until the smart-restore wizard ships). - Pre-flight blocker banner surfaces dossier.blockers (e.g. active reservation on a sold berth) and disables the Archive button entirely. Two side fixes from CSP rollout: - next.config CSP allows unpkg.com in dev so the react-grab devtool loads. Stripped in prod via the existing isProd flag. - middleware whitelist now passes /manifest.json + icon-*.png + apple-touch-icon through unauthenticated, so PWA installability isn't blocked by the auth redirect. Bulk variant + restore wizard + hard-delete-with-email-code land in follow-on commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:19:34 +02:00
// Dev-only allow-list: react-grab (the in-page click-to-source devtool)
// is fetched from unpkg, so script/style/connect must allow it. Strip
// these entries in prod via the conditional below.
const devScriptHosts = isProd ? '' : ' http://unpkg.com https://unpkg.com';
const devConnectHosts = isProd ? '' : ' http://unpkg.com https://unpkg.com';
const csp = [
"default-src 'self'",
feat(client-archive): single-client smart-archive dialog + CSP/middleware fixups UI side of the smart-archive backend that shipped in d07f1ed. - SmartArchiveDialog renders the dossier as a sectioned modal: Pipeline interests, Berths (with next-in-line listed), Yachts, Active reservations, Outstanding invoices, In-flight Documenso envelopes, Auto-handled summary. Each section has a per-row decision dropdown with sensible defaults (release for available/under-offer berths, retain for sold berths and yachts, cancel for active reservations, leave for invoices and documents). - High-stakes deals show an amber warning panel + require a reason in the textarea before the Archive button enables. Signed-document acknowledgment checkbox blocks submission until checked. - Wires into client-detail-header in place of the previous dumb ArchiveConfirmDialog (the simple confirm dialog is kept for the restore case until the smart-restore wizard ships). - Pre-flight blocker banner surfaces dossier.blockers (e.g. active reservation on a sold berth) and disables the Archive button entirely. Two side fixes from CSP rollout: - next.config CSP allows unpkg.com in dev so the react-grab devtool loads. Stripped in prod via the existing isProd flag. - middleware whitelist now passes /manifest.json + icon-*.png + apple-touch-icon through unauthenticated, so PWA installability isn't blocked by the auth redirect. Bulk variant + restore wizard + hard-delete-with-email-code land in follow-on commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:19:34 +02:00
`script-src 'self' 'unsafe-inline'${isProd ? '' : " 'unsafe-eval'"}${devScriptHosts}`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob: https:",
"font-src 'self' data:",
feat(client-archive): single-client smart-archive dialog + CSP/middleware fixups UI side of the smart-archive backend that shipped in d07f1ed. - SmartArchiveDialog renders the dossier as a sectioned modal: Pipeline interests, Berths (with next-in-line listed), Yachts, Active reservations, Outstanding invoices, In-flight Documenso envelopes, Auto-handled summary. Each section has a per-row decision dropdown with sensible defaults (release for available/under-offer berths, retain for sold berths and yachts, cancel for active reservations, leave for invoices and documents). - High-stakes deals show an amber warning panel + require a reason in the textarea before the Archive button enables. Signed-document acknowledgment checkbox blocks submission until checked. - Wires into client-detail-header in place of the previous dumb ArchiveConfirmDialog (the simple confirm dialog is kept for the restore case until the smart-restore wizard ships). - Pre-flight blocker banner surfaces dossier.blockers (e.g. active reservation on a sold berth) and disables the Archive button entirely. Two side fixes from CSP rollout: - next.config CSP allows unpkg.com in dev so the react-grab devtool loads. Stripped in prod via the existing isProd flag. - middleware whitelist now passes /manifest.json + icon-*.png + apple-touch-icon through unauthenticated, so PWA installability isn't blocked by the auth redirect. Bulk variant + restore wizard + hard-delete-with-email-code land in follow-on commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:19:34 +02:00
`connect-src 'self' ws: wss: https:${devConnectHosts}`,
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"object-src 'none'",
].join('; ');
const securityHeaders = [
{ key: 'Content-Security-Policy', value: csp },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(self), microphone=(), geolocation=()' },
...(isProd
? [{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' }]
: []),
];
const nextConfig: NextConfig = {
output: 'standalone',
serverExternalPackages: [
'pino',
'pino-pretty',
'bullmq',
'ioredis',
'minio',
'postgres',
'better-auth',
'nodemailer',
],
images: {
remotePatterns: [{ protocol: 'https', hostname: '*.portnimara.com' }],
},
experimental: {
typedRoutes: true,
},
outputFileTracingIncludes: {
// Bundle the EOI source PDF so the in-app EOI pathway can read it at
// runtime in the standalone build. Reading via fs.readFile from
// process.cwd() requires the file to be traced explicitly.
'/api/v1/document-templates/**': ['./assets/eoi-template.pdf'],
},
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};
export default nextConfig;