Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening

Batch commit of the pending working tree on security/audit-fixes-2026-07.
Major areas:
- Outbound webhooks / Zapier: schema + signed delivery with retries, public
  v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain.
- Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS
  (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when
  Spaces is unconfigured instead of silently using ephemeral disk.
- Integrations & features (concurrent work): accounting (QuickBooks/Xero),
  e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding,
  expanded legal pages.
- DB migrations 0006–0009.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-07-02 13:42:34 -04:00
co-authored by Claude Opus 4.8
parent 969d5d4c8a
commit c9968531e4
282 changed files with 41530 additions and 4013 deletions
+111 -59
View File
@@ -1,77 +1,129 @@
import { LegalPage, Section, LegalContact } from "@/components/marketing/legal"
import { LEGAL } from "@/lib/legal"
export const metadata = {
title: "Cookie Policy — Property Management Network",
description: "How Property Management Network uses cookies and similar tracking technologies.",
title: "Cookie Policy",
description:
"How we use cookies and similar technologies, what we deliberately avoid, and how to manage them in your browser.",
alternates: { canonical: "/cookie-policy" },
}
const SECTIONS = [
const COOKIE_ROWS: { name: string; type: string; purpose: string; expires: string }[] = [
{
title: "What are cookies?",
body: "Cookies are small text files stored on your device by your browser when you visit a website. They help websites remember your preferences, keep you logged in, and understand how the site is being used.",
name: "session",
type: "Strictly necessary",
purpose: "Keeps you signed in and maintains your authenticated session",
expires: "On sign-out or after inactivity",
},
{
title: "Cookies we use",
body: "We use the following types of cookies: (1) Essential cookies — required for the application to function, such as session authentication tokens. Without these, you cannot log in. (2) Analytics cookies — we use Vercel Analytics (privacy-preserving, no personal data stored) to understand page performance. (3) Preference cookies — we store your dashboard preferences (dark/light mode, column visibility) in browser localStorage.",
name: "preferences (localStorage)",
type: "Preference",
purpose: "Stores interface preferences such as layout and theme in your browser",
expires: "Persistent until cleared",
},
{
title: "Third-party cookies",
body: "Stripe may set cookies when you make a payment for fraud prevention and PCI compliance purposes. We do not use advertising, retargeting, or social media tracking cookies.",
},
{
title: "How to control cookies",
body: "You can manage cookies through your browser settings. Most browsers allow you to block or delete cookies. Note that blocking essential cookies will prevent you from logging in to Property Management Network. For analytics cookies, you can opt out by enabling the Do Not Track header in your browser.",
},
{
title: "Cookie retention",
body: "Session cookies expire when you close your browser. Authentication tokens are refreshed automatically and expire after 7 days of inactivity. Analytics data is retained for 90 days in aggregate, with no individual identifiers stored.",
},
{
title: "Changes to this policy",
body: "We may update this Cookie Policy as we add new features. Significant changes will be announced via the in-app notification banner. The date at the bottom of this page reflects the most recent update.",
},
{
title: "Contact",
body: "For questions about cookies or this policy, contact us at privacy@propertymanagement.network.",
name: "__stripe_*",
type: "Strictly necessary (Stripe)",
purpose: "Set by Stripe to prevent payment fraud during checkout",
expires: "Session or up to one year",
},
]
export default function CookiePolicyPage() {
export default function Page() {
return (
<div className="bg-[#09090b] text-white min-h-screen">
<div className="mx-auto max-w-3xl px-6 pt-32 pb-24">
<div className="mb-10">
<h1 className="text-3xl font-bold text-white mb-2">Cookie Policy</h1>
<p className="text-xs text-white/30">Last updated: April 2026</p>
</div>
<LegalPage
title="Cookie Policy"
subtitle="This policy explains how we use cookies and similar technologies within the Service."
>
<Section heading="What are cookies">
<p>
Cookies are small text files that a website stores on your device through your browser.
Similar technologies, such as browser <strong>localStorage</strong>, allow a site to store
data locally in a comparable way. These technologies help a website keep you signed in,
remember your preferences, and operate securely. This policy describes how{" "}
<strong>{LEGAL.entity}</strong> uses them within <strong>{LEGAL.service}</strong> (the{" "}
<strong>Service</strong>).
</p>
</Section>
<div className="space-y-8">
{SECTIONS.map((s) => (
<div key={s.title}>
<h2 className="text-base font-semibold text-white mb-2">{s.title}</h2>
<p className="text-sm text-white/50 leading-relaxed">{s.body}</p>
</div>
))}
</div>
<Section heading="Cookies and similar technologies we use">
<p>We use only the following limited set of technologies:</p>
<ul>
<li>
<strong>Strictly necessary authentication and session cookies</strong>&mdash;required to
sign you in and to maintain your secure session. The Service cannot function without
these.
</li>
<li>
<strong>Preference storage in browser localStorage</strong>&mdash;used to remember
interface preferences, such as layout and theme, on your device.
</li>
<li>
<strong>Stripe cookies</strong>&mdash;set by Stripe during payment to help prevent fraud
and to support secure checkout.
</li>
<li>
<strong>Cloudflare Turnstile challenge cookie</strong>&mdash;a challenge cookie that may
be set on authentication pages to distinguish genuine users from automated bots.
</li>
</ul>
</Section>
{/* Cookie types summary table */}
<div className="mt-10 rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
<div className="px-5 py-3.5 border-b border-white/[0.04]">
<p className="text-xs font-semibold uppercase tracking-wider text-white/30">Cookie Summary</p>
<Section heading="What we do not use">
<p>
We do <strong>not</strong> use advertising, retargeting, or cross-site tracking cookies, and
we do <strong>not</strong> use third-party analytics cookies. We do not build advertising
profiles or share cookie data with advertising networks.
</p>
</Section>
<Section heading="Managing cookies">
<p>
Most browsers allow you to view, block, or delete cookies through their settings. Because
our authentication and session cookies are <strong>strictly necessary</strong>, blocking
them will prevent you from signing in to and using the Service. You can adjust your browser
settings at any time to control non-essential storage.
</p>
</Section>
<Section heading="Retention">
<p>
<strong>Session</strong> cookies are temporary and are cleared when your session ends, while{" "}
<strong>persistent</strong> storage remains on your device until it expires or you remove
it. Authentication sessions expire after a period of inactivity, after which you will be
asked to sign in again.
</p>
</Section>
<Section heading="Changes">
<p>
We may update this Cookie Policy as the Service evolves. When we make material changes, we
will update the date shown above. Please review this page periodically to stay informed.
</p>
</Section>
<LegalContact email={LEGAL.privacyEmail} />
<div className="rounded-2xl border border-white/[0.06] bg-[#111118] overflow-hidden">
<div className="px-5 py-3.5 border-b border-white/[0.04]">
<p className="text-xs font-semibold uppercase tracking-wider text-white/30">
Cookie summary
</p>
</div>
{COOKIE_ROWS.map((row, i, arr) => (
<div
key={row.name}
className={`grid grid-cols-1 gap-1 px-5 py-3.5 text-xs sm:grid-cols-4 sm:gap-4 ${
i !== arr.length - 1 ? "border-b border-white/[0.04]" : ""
}`}
>
<code className="font-mono text-indigo-300">{row.name}</code>
<span className="text-white/60">{row.type}</span>
<span className="text-white/40 sm:col-span-1">{row.purpose}</span>
<span className="text-white/40">{row.expires}</span>
</div>
{[
{ name: "pf_session", type: "Essential", purpose: "Authentication session token", expires: "7 days" },
{ name: "pf_prefs", type: "Preference", purpose: "Dashboard layout preferences", expires: "1 year" },
{ name: "_vercel_*", type: "Analytics", purpose: "Anonymous page performance data", expires: "90 days" },
{ name: "__stripe_*", type: "Third-party", purpose: "Stripe payment fraud prevention", expires: "Session" },
].map((row, i, arr) => (
<div key={row.name} className={`grid grid-cols-4 gap-4 px-5 py-3.5 text-xs ${i !== arr.length - 1 ? "border-b border-white/[0.04]" : ""}`}>
<code className="font-mono text-indigo-300">{row.name}</code>
<span className="text-white/60">{row.type}</span>
<span className="text-white/40 col-span-1">{row.purpose}</span>
<span className="text-white/40">{row.expires}</span>
</div>
))}
</div>
))}
</div>
</div>
</LegalPage>
)
}