feat: Cloudflare Turnstile on auth, CSP fixes, admin/SEO/analytics additions

Turnstile bot protection (sign-in, sign-up, password-reset):
- Register Better Auth's captcha plugin with the cloudflare-turnstile
  provider; endpoints listed explicitly rather than relying on defaults.
  /reset-password is intentionally excluded — it is reached only via a
  single-use emailed token.
- Add an explicit-render Turnstile widget component. Tokens are single-use,
  so each form resets the challenge after a failed submit; submit stays
  disabled until a token is held.
- Read the site key server-side and pass it down as a prop, so rotating it
  does not require a rebuild.
- Fail fast in production when TURNSTILE_SECRET_KEY is missing, and when a
  secret is set without a site key (that combination would demand a token
  no form can produce, locking every user out).
- Pass a throwaway secret during `next build` in the Dockerfile, mirroring
  the existing BETTER_AUTH_SECRET treatment, so image builds don't need it.

CSP fixes in middleware (these blocked Turnstile entirely):
- Add frame-src for challenges.cloudflare.com. Without it the widget's
  iframe fell back to default-src 'self' and was blocked outright.
- Allow 'unsafe-eval' and websockets in DEVELOPMENT only. `next dev`
  compiles with eval(), so the strict policy threw EvalError and killed
  hydration — no client JS ran at all, which also meant form submit
  handlers never fired. Production policy is unchanged and still strict.

Also included (concurrent work in the tree):
- Admin organizations pages and lib/admin/orgs.
- Episode moderation migration, SEO metadata (sitemap, robots, JSON-LD,
  OG/Twitter images, manifest), Umami analytics, not-found page.

Local dev database: docker-compose.dev.yml provisions Postgres 18 on port
5443 (5432-5442 are in use by other local projects).

Note: `npx tsc --noEmit` currently fails in app/(app)/team/page.tsx — an
`invitations` prop the component does not accept. This predates the commit
and will fail `next build` until fixed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-09-07 11:10:55 -04:00
co-authored by Claude Opus 5
parent 35379212fb
commit 3e9ba07175
96 changed files with 3982 additions and 576 deletions
+13 -1
View File
@@ -1,7 +1,17 @@
import type { Metadata } from "next";
import { LegalDoc, type LegalSection } from "@/components/marketing/legal-doc";
import { pageMetadata } from "@/lib/seo";
export const metadata: Metadata = { title: "Privacy Policy" };
/** Shared by the page metadata and the document's structured data. */
const PATH = "/privacy";
const DESCRIPTION =
"How Podcast Distribution AI collects, uses, stores and protects your personal data and generated content — and the rights you have over it.";
export const metadata: Metadata = pageMetadata({
title: "Privacy Policy",
description: DESCRIPTION,
path: PATH,
});
const UPDATED = "June 7, 2026";
@@ -102,6 +112,8 @@ export default function PrivacyPage() {
updated={UPDATED}
intro="This Privacy Policy explains what information Podcast Distribution AI collects, how we use it, who we share it with, and the choices you have. It applies to your use of the Podcast Distribution AI website and application."
sections={SECTIONS}
path={PATH}
description={DESCRIPTION}
/>
);
}