diff --git a/apps/installer/package.json b/apps/installer/package.json index 4bb19b87..63c49336 100644 --- a/apps/installer/package.json +++ b/apps/installer/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@hono/node-server": "^2.1.5", - "@profullstack/footer": "^0.2.6", + "@profullstack/footer": "^0.4.0", "hono": "^4.13.14" }, "devDependencies": { diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 626ee202..ff7a6bf4 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -1,4 +1,5 @@ import Link from 'next/link'; +import { headers } from 'next/headers'; import { Footer as PfsFooter } from '@profullstack/footer/react'; import { useTranslations } from 'next-intl'; import { Logo } from '@/components/Logo'; @@ -61,6 +62,17 @@ const socialLinks = [ }, ]; +/** + * The ring footer with the request's CSP nonce (middleware sets x-nonce), so the + * newsletter form's inline script (@profullstack/footer 0.4.0) runs under + * script-src 'nonce-...'. + */ +async function RingFooter({ tagline }: { tagline: string }) { + const nonce = (await headers()).get('x-nonce'); + // exactOptionalPropertyTypes: pass nonce only when there is one. + return ; +} + export function Footer() { const t = useTranslations('footer'); return ( @@ -96,7 +108,7 @@ export function Footer() { {/* Copyright + Profullstack ring, from @profullstack/footer */} - + ); diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index a038fdeb..8c4dba1d 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -44,7 +44,8 @@ function buildCsp(nonce: string, embeddable: boolean): string { // path keeps the restrictive list. embeddable ? 'frame-ancestors *' : "frame-ancestors 'self' chrome-extension:", "base-uri 'self'", - "form-action 'self'", + // The footer's newsletter form posts to pit.world when script is off. + "form-action 'self' https://pit.world", ].join('; '); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0edaa9a..de0925cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -236,8 +236,8 @@ importers: specifier: ^2.1.5 version: 2.1.5(hono@4.12.25) '@profullstack/footer': - specifier: ^0.2.6 - version: 0.2.6(react@19.3.1) + specifier: ^0.4.0 + version: 0.4.0(react@19.3.1) hono: specifier: ^4.12.4 version: 4.12.25 @@ -390,7 +390,7 @@ importers: version: 1.0.3 '@profullstack/footer': specifier: latest - version: 0.3.1(react@19.3.1) + version: 0.4.0(react@19.3.1) '@profullstack/live-translate': specifier: ^0.3.0 version: 0.3.0 @@ -2949,18 +2949,8 @@ packages: '@profullstack/emailer@1.0.3': resolution: {integrity: sha512-VyAtCcfnSI6LOwJvWogCr3ZNmEsaZw+iuWrqnosLsdcKsbRyCrCmXigMNZJZEjlgYlR+lK//PwNPG+xtCFvWAw==} - '@profullstack/footer@0.2.6': - resolution: {integrity: sha512-BKwbDszAc7e0mYIBiPjgr/5WS8WyDt8IW718s/zW/8pCWOIB1BGwSNs6fmImRSo5kqIjQKUVbXp22jZQ5GEipA==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - react: '>=18' - peerDependenciesMeta: - react: - optional: true - - '@profullstack/footer@0.3.1': - resolution: {integrity: sha512-uiiCgEuOZAFd4UKpOUIhAseVLTXp+tbGF9kosYDuW2M/+/A3QcWtUCu9lYGSNfOUnvnlscqrLjJC2uYZlJmufQ==} + '@profullstack/footer@0.4.0': + resolution: {integrity: sha512-EJZnMUQmWDi9Gzx6BAjogloeaPFFSSCHtHJuC9dsXiOz8M87hfn6Z+Zx9AMYWk8s6xemPrPlkXKGB3PZugkcEA==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -12304,11 +12294,7 @@ snapshots: '@profullstack/emailer@1.0.3': {} - '@profullstack/footer@0.2.6(react@19.3.1)': - optionalDependencies: - react: 19.3.1 - - '@profullstack/footer@0.3.1(react@19.3.1)': + '@profullstack/footer@0.4.0(react@19.3.1)': optionalDependencies: react: 19.3.1