Files
elegalsoftware/apps/web/src/pages/legal/LegalIndexPage.tsx
T
Leon SerfatyandClaude Fable 5 97e1d4c60b Storage→Spaces, security hardening, production-blocker fixes, tests + CI
Storage
- Migrate document/media storage from local disk to DigitalOcean Spaces (S3);
  lib/storage.ts now streams via the S3 SDK; SPACES_* env vars required.
- Add scripts/migrate-storage-to-spaces.ts (idempotent, one-time).

Security hardening (all report findings)
- DB pool fails closed in production when the CA cert is missing (no more
  silent unverified TLS); warns in dev.
- trustProxy: 1 (was true) so X-Forwarded-For can't be spoofed to evade rate limits.
- Login lockout keyed by (email, ip) so an attacker can't lock out a victim.
- Superadmin auto-grant now requires a verified email.
- CSRF tokens HMAC-signed; exact-path exemptions; logout no longer exempt.
- Upload content-sniffing (magic bytes) rejects spoofed MIME types.
- create-admin.ts reads creds from env/argv; seed-demo.ts guarded behind ALLOW_SEED.

Production-blocker fixes
- SPA deep-link/refresh no longer 500s (decorateReply fix); index.html served no-cache.
- Invoice numbering is transaction-safe (per-firm advisory lock + max sequence),
  eliminating concurrent collisions and delete-reuse — no schema change.
- Checkout guards against double-billing a firm already on a paid plan.
- Fix render-loop in CreateInvoiceDrawer / ManualEntryDrawer (unstable effect deps).

Honesty / trust
- Remove fabricated testimonials, stats, strikethrough "was" prices, contact SLA,
  and the login-panel stats; replace with non-fabricated copy.
- Fix cookie-policy consent-key mismatch. (Legal pages still need lawyer review.)

Quality
- Add Vitest unit tests (file-signature, password hashing) and GitHub Actions CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:18:10 -04:00

84 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Link } from 'react-router-dom';
import { PublicLayout } from '@/components/public/PublicLayout';
const DOCS = [
{
to: '/legal/terms',
title: 'Terms of Service',
blurb: 'The agreement that governs your use of eLegal Software — accounts, plans, content ownership, disputes.',
},
{
to: '/legal/privacy',
title: 'Privacy Policy',
blurb: 'What we collect, why, where it lives, who we share it with, and the rights you have over it.',
},
{
to: '/legal/cookies',
title: 'Cookie Policy',
blurb: 'The (few, essential-only) cookies we set and how to control them.',
},
{
to: '/legal/acceptable-use',
title: 'Acceptable Use Policy',
blurb: 'What you may not do on the platform — security, abuse, and fair-use rules.',
},
{
to: '/legal/refunds',
title: 'Billing & Refund Policy',
blurb: 'How subscriptions, renewals, cancellations, refunds, and failed payments work.',
},
{
to: '/legal/disclaimer',
title: 'Legal Disclaimer',
blurb: 'eLegal Software is software, not a law firm — no legal advice, no attorneyclient relationship.',
},
{
to: '/legal/dmca',
title: 'DMCA & Copyright Policy',
blurb: 'How to report copyright infringement and how takedowns and counter-notices work.',
},
{
to: '/legal/dpa',
title: 'Data Processing Addendum',
blurb: 'How we process your clients data on your behalf — security measures, subprocessors, breach notice.',
},
];
export default function LegalIndexPage() {
return (
<PublicLayout>
<section className="container py-12 max-w-4xl">
<header className="mb-10">
<p className="text-xs uppercase tracking-wider text-brand-600 font-semibold">Legal</p>
<h1 className="mt-2 text-3xl md:text-4xl font-bold text-ink-950 font-display">
Legal center
</h1>
<p className="mt-3 text-ink-600 max-w-2xl">
Everything that governs your relationship with eLegal Software, written in plain
English. Questions about any of it:{' '}
<a href="mailto:legal@elegalsoftware.com" className="text-brand-600 hover:underline">
legal@elegalsoftware.com
</a>
.
</p>
</header>
<div className="grid gap-4 sm:grid-cols-2">
{DOCS.map((d) => (
<Link
key={d.to}
to={d.to}
className="rounded-2xl border border-ink-200 p-5 hover:border-brand-200 hover:bg-brand-50/30 transition group"
>
<h2 className="font-semibold text-ink-950 group-hover:text-brand-700 font-display">
{d.title}
</h2>
<p className="mt-2 text-sm text-ink-600 leading-relaxed">{d.blurb}</p>
</Link>
))}
</div>
</section>
</PublicLayout>
);
}