1860 Commits
Author SHA1 Message Date
Hanzo AI af5811b206 auth: use canonical /v1/iam/oauth/* IAM endpoints (bare /oauth/* hit SPA HTML) 2026-06-25 18:56:05 -07:00
Hanzo AI b827b3a7bd rebrand: Papermark → Hanzo Dataroom across user-facing surfaces
108 files: README, marketing copy, page titles, meta tags, footer
text, email templates, .env.example, UI strings. URLs swapped
from papermark.com/papermark.io to dataroom.hanzo.ai.

Internal identifiers left intact per repo policy: localStorage
keys (papermark.email/papermark.name/last_papermark_login —
changing them logs users out), type field names
(papermarkUserId), internal helper symbols (PAPERMARK_HEADERS,
PapermarkSparkle component, isPapermarkUrl helper),
internal-only filenames. LLM.md retains 'Upstream: Papermark'
attribution line. Per-locale .po translations regenerate from
source on the next lingui-extract CI run.

DB migrations untouched (append-only history; rewriting them
breaks installed schemas). LICENSE stays AGPL-3.0.
2026-06-07 12:23:40 -07:00
Hanzo AI d4f4071ee4 rip: enterprise dirs + paywalls — full AGPL, no commercial-license code
Drop the dual-licensed `ee/` tree entirely. All features previously
locked behind the Hanzo Dataroom Commercial License are now part of the
AGPL-3.0 core and freely available — custom domains, branded viewing,
advanced analytics, watermarks, access controls, Q&A, AI vector stores,
workflows, SAML SSO, dataroom invitations, conversations, templates.

Layout changes:
* `ee/features/{ai,conversations,workflows,permissions,storage,templates,
  dataroom-invitations,access-notifications,security,conversions}/` →
  `features/<name>/` (AGPL).
* `ee/limits/` → `lib/billing/limits/` with every plan resolving to an
  unlimited profile (no users/links/documents/domains/datarooms cap,
  conversations and watermarks always on).
* `ee/stripe/` → `lib/billing/legacy/` (already a compat shim over
  `@hanzoai/commerce`; renewal-reminder webhook is now a no-op).
* `ee/features/billing/cancellation/` deleted. Pause/unpause/cancel/
  retention routes now compile against `lib/billing/cancellation.ts`
  stubs that return 410 Gone; `isTeamPaused`/`isTeamPausedById` in
  `lib/billing/paused.ts` always report active. `CancellationModal` is
  a no-op shim in `components/billing/`.
* `app/(ee)/api/*` route group folded into `app/api/*` (SAML auth,
  SCIM, AI chat, workflows, FAQ, link-upload).

Build: `npm run build` green.
2026-06-07 11:46:48 -07:00
Hanzo AI 77799ddce2 chore: rip stripe → @hanzoai/commerce + @hanzoai/pay (no stripe period)
Per CTO directive: zero Stripe across all our repos. Migration mirrors
hanzoai/gui@8d05cf6fb3.

- lib/commerce.ts: new hand-rolled Hanzo Commerce REST client + minimal
  CommerceTypes namespace covering only what the dataroom touches
  (Subscription, Invoice, Customer, CheckoutSession, PortalSession,
  Coupon, Event, Checkout.Session). HMAC-SHA256 webhook signature
  verification matches the gui pattern.
- ee/stripe/index.ts: now a thin shim — `stripeInstance(...)` returns
  the Commerce client; `cancelSubscription(...)` delegates to it.
- ee/stripe/client.ts: Stripe.js loadStripe + redirectToCheckout
  replaced with a redirect helper that bounces the browser to
  pay.hanzo.ai (@hanzoai/pay). No more card UI in the dataroom.
- ee/stripe/utils.ts: drop `import Stripe from "stripe"`; use
  CommerceTypes.Subscription. Stamped TODO(stripe-rip) on the PLANS
  const — the Stripe price IDs embedded there still need re-keying
  to Commerce plan IDs (5 plans × monthly+yearly × test+prod × new+old
  account; coordinate with Commerce admin).
- ee/stripe/webhooks/{checkout-session-completed,customer-subscription-
  {updated,deleted},invoice-upcoming}.ts: drop stripe SDK imports;
  rebind types to CommerceTypes.
- ee/features/security/lib/fraud-prevention.ts: addEmailToStripeRadar
  becomes a no-op with TODO(stripe-rip). Commerce does fraud at the
  gateway and does not yet expose a Radar-equivalent endpoint; the
  Edge Config blocklist remains the effective block surface.
- ee/features/billing/cancellation/api/*.ts: error message
  "No Stripe customer ID" -> "No billing customer ID"; the local
  `stripe` variable is now a Commerce instance via stripeInstance().
- pages/api/stripe/webhook.ts + webhook-old.ts: DELETED. Commerce
  owns webhook termination; the new dispatcher is at
  pages/api/commerce/webhook.ts and reuses the existing per-event
  business-logic handlers in ee/stripe/webhooks/.
- pages/api/commerce/webhook.ts: NEW. Accepts hanzo-commerce-signature
  (falls back to stripe-signature header so a side-by-side rollout
  works), verifies HMAC with HANZO_COMMERCE_WEBHOOK_SECRET, dispatches
  to the same handlers as before.
- package.json: removed `stripe` (^16.12.0), `@stripe/stripe-js`
  (^4.10.0), and the `stripe:webhook` pkgx script. No new deps — the
  Commerce REST client is hand-rolled per spec.

TODO(stripe-rip) markers flag the remaining deep work:
- PLANS const price IDs need re-keying to Commerce plan IDs
- ee/stripe/ directory should be renamed to ee/commerce/
- Local `stripeInstance` identifier should be renamed `commerce`
- Subscription.pause_collection / proration_behavior contract needs
  to land in the Commerce OpenAPI spec
- Commerce fraud-blocklist API needs to ship to replace the Radar
  call we just stubbed out

`tsc --noEmit` shows zero new errors in ee/, lib/commerce.ts,
pages/api/commerce/, components/billing/. Pre-existing TS errors in
redis-job-store, lib/auth, app routes are unchanged.
2026-05-28 02:03:38 -07:00
Hanzo Dev 1408d08f38 rebrand: purge PostHog references 2026-03-13 17:38:17 -07:00
Hanzo Dev ddcfc62f54 chore: rename HANZO_IAM_ env vars to IAM_ prefix
Drop HANZO_ prefix from all IAM environment variable names for
consistency across the Hanzo ecosystem.
2026-03-10 16:31:45 -07:00
Hanzo Dev 1244cb74f6 feat: add org_id claim from IAM to JWT/session, document IAM env vars
- Extract organization from IAM profile (owner/organization/org fields)
- Pass organization through JWT → session → CustomUser
- Add HANZO_IAM_* variables to .env.example
- Deprecate Google OAuth in favor of Hanzo IAM
2026-03-09 22:27:13 -07:00
Hanzo Dev 880f522457 feat: IAM-only auth
Remove email, Google, LinkedIn, passkey, SAML auth from login —
only allow Sign in with Hanzo (IAM).
2026-03-02 23:45:00 -08:00
Hanzo Dev 126976b33f Rebrand from Papermark to Hanzo Dataroom
- Replace all Papermark branding with Hanzo Dataroom
- Update metadata, titles, descriptions, OG tags
- Update email templates footer and branding
- Replace papermark.io/com URLs with dataroom.hanzo.ai
- Update powered-by component
- Update x-powered-by header
- Update legal references to Hanzo AI, Inc.
- Update social handles to @hanzoai
- Update README, CLA, LICENSE, SECURITY docs
2026-03-01 19:02:19 -08:00
Hanzo Dev 1e91e2b50a Fix Edge Runtime crash: remove ioredis from middleware bundle
- Dynamic import DomainMiddleware to avoid pulling ioredis into Edge bundle
- Add hanzo.ai to APP_DOMAINS exclusion list in isCustomDomain
- DomainMiddleware now uses fetch to internal API for Redis lookups
- Create /api/internal/domain-redirect endpoint for Edge-safe KV access
2026-03-01 08:33:05 -08:00
Hanzo Dev 6800768d0b fix: make hanko passkey provider optional for builds without env vars
Return null from getHanko() when HANKO_API_KEY is unset, and
conditionally include PasskeyProvider in NextAuth only when configured.
2026-03-01 06:31:32 -08:00
Hanzo Dev b8eb58d801 fix: lazy-init hanko client to prevent build-time crash
Hanko client was throwing at module scope when HANKO_API_KEY was unset.
Convert to lazy getter so it only initializes at runtime.
2026-03-01 06:23:35 -08:00
Hanzo Dev 53c72672ca feat: add Hanzo IAM OIDC SSO provider
- Add HanzoIAMProvider to NextAuth config (OIDC with userinfo endpoint)
- Add "Continue with Hanzo" button to login page (first SSO option)
- Env vars: HANZO_IAM_URL, HANZO_IAM_CLIENT_ID, HANZO_IAM_CLIENT_SECRET
- IAM app registered as app-dataroom in init_data.json
2026-03-01 22:36:44 -08:00
Cursor AgentandMarc Seitz 5168a80b30 fix: duplicate custom fields and visitor groups when duplicating a link
When a link is duplicated, the custom form fields and visitor group
associations were not being copied to the new link. This adds:

- Include customFields and visitorGroups in the Prisma query for the
  source link
- Create new CustomField records for the duplicated link via createMany
- Create new LinkVisitorGroup junction records for the duplicated link
- Return customFields and visitorGroups in the created link response

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-03-05 10:47:16 +00:00
Marc Seitz 5e115f1896 refactor: remove outdated getTeamWithDomain helper 2026-03-02 18:21:31 +11:00
Marc Seitz 4b557e909c Merge branch 'main' into cursor/root-domain-redirects-96b8 2026-03-02 17:56:26 +11:00
Marc SeitzandGitHub 6687d842db Merge pull request #2096 from mfts/cursor/file-upload-authentication-bypass-dcd1
File upload authentication bypass
2026-03-02 17:43:12 +11:00
Cursor AgentandMarc Seitz 015c44af80 Harden TUS upload auth, team access, and limits
Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-03-02 06:11:09 +00:00
Marc Seitz adde340bbd fix: don't ignore other keys in notificationprefs 2026-03-02 13:13:16 +11:00
Marc Seitz 3c7e4f93d0 feat: add notification settings for visitors 2026-03-02 12:31:44 +11:00
Marc Seitz 898313f5a5 fix: remove redirect if it fails 2026-02-28 15:29:32 +11:00
Marc Seitz a279392ba9 chore: make sure keyword matching is case insentive 2026-02-28 15:24:01 +11:00
Marc Seitz 6951b2d92a feat: add zod validation for domain update body 2026-02-28 15:23:35 +11:00
Marc Seitz 76444e2992 Merge branch 'main' into cursor/root-domain-redirects-96b8 2026-02-28 15:20:12 +11:00
Marc Seitz c9b249c969 fix: add track dismissed state 2026-02-27 17:41:34 +11:00
Marc Seitz be9c7dedae fix: return errorhandler 2026-02-27 16:45:38 +11:00
Marc Seitz 9bdbe333ad fix: use zod validated data instead of body 2026-02-27 16:44:23 +11:00
Marc Seitz f1a686e39d Merge branch 'main' into deal-flow-infp 2026-02-27 16:39:43 +11:00
Cursor AgentandMarc Seitz febb9322a7 fix: remove auto-opening upgrade modal from User Settings page
The account/general page had a useEffect that automatically opened
an UpgradePlanModal every time a non-annual-plan user navigated to
User Settings. This was disruptive as users would see a billing popup
about upgrading to data rooms on every visit.

Removed the UpgradePlanModal component, the useEffect trigger, the
plan detection logic, and all related imports from the page.

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-26 03:08:16 +00:00
Marc SeitzandGitHub 47922d48d7 Merge pull request #2089 from mfts/cursor/dataroom-document-audit-analytics-7cbc
feat: improve dataroom audit analytics
2026-02-25 18:15:25 +11:00
Marc Seitz 3794b3eeff fix: auth dataroom to team 2026-02-25 18:10:33 +11:00
Marc SeitzandCursor 3c20959060 fix: pass documentId through chart data and add rate limiting to thumbnail API
The tooltip was using router.query.id (dataroom ID) instead of the
actual document ID, and versionNumber was NaN. Pass documentId through
BarChartComponent data so the tooltip resolves the correct thumbnail.
Add rate limiting (150 req/min per user) to the get-thumbnail endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 17:14:36 +11:00
Marc SeitzandCursor d1a3603837 fix: preserve original CJK filename in Content-Disposition header
Use RFC 5987 filename* parameter to include the UTF-8 encoded original
filename alongside the ASCII-safe slug, so downloads show the proper
CJK name in modern browsers. Also adds missing Content-Disposition to
put-file-server and stream-file-server uploads.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 14:43:02 +11:00
Marc SeitzandCursor 70beab2c5e fix: add CJK-safe slugify with nanoid fallback
slugify strips all CJK characters, producing empty strings for
filenames and folder names that contain only CJK text. Replace all
usages with safeSlugify which falls back to a 12-char lowercase
alphanumeric nanoid when slugify returns an empty result.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 14:42:53 +11:00
Cursor AgentandMarc Seitz ae45df36b8 fix: add min-h-screen to dataroom viewer wrapper to prevent white background gap
When applyAccentColorToDataroomView is enabled but there are not enough
folders/documents to fill the viewport, a white background was visible
underneath the content. Adding min-h-screen ensures the accent background
color extends to fill at least the full viewport height.

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-22 11:10:41 +00:00
Marc SeitzandGitHub 1571168300 Merge pull request #2077 from mfts/cursor/dataroom-viewer-background-color-ac68
Dataroom viewer background color
2026-02-22 21:53:54 +11:00
Marc SeitzandGitHub 3f351a0900 Merge pull request #2073 from mfts/cursor/visitor-groups-access-1b01
Visitor groups access
2026-02-22 20:36:42 +11:00
Cursor AgentandMarc Seitz 6d7d88431b feat: add document stats (duration, completion, per-page chart) to dataroom visitor audit log
- Create new Tinybird pipe get_dataroom_view_document_stats for bulk document view stats
- Add API endpoint for dataroom view document stats with per-page chart support
- Create SWR hooks with deferred loading (enabled flag)
- Create reusable DocumentViewDuration, DocumentViewCompletion, DocumentPageChart components
- Create DataroomViewStats component combining history with stats
- Update dataroom-visitors-table.tsx and dataroom-viewers.tsx to track expansion state
- Stats only load when visitor row is expanded (deferred loading)
- Per-page charts only load when individual document line is expanded

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-22 09:14:36 +00:00
Marc Seitz ebb928916c fix: visitorgroup on the team 2026-02-22 19:30:04 +11:00
Cursor AgentandMarc Seitz ce2b40daad fix: harden root domain redirects - validation, plan gating, cleanup
- Change redirect status from 301 to 302 (non-permanent, user-configurable)
- Remove backfill UPDATE entries from migration SQL
- Remove unnecessary sync-redirects cron endpoint
- Add URL validation: SSRF protection, edge config keyword blocklist,
  protocol check, URL sanitization via shared validateRedirectUrl helper
- Add plan gating: require Business plan or higher for redirect URLs
  (enforced on both API endpoints and UI)
- Clean up Redis redirect URLs on team deletion
- Clear redirect URLs (Postgres + Redis) on subscription cancellation
  and on plan downgrade below Business
- Split redis.ts to avoid Prisma import in edge middleware context
- Gate DomainCard redirect UI based on plan (show upgrade prompt)

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-22 08:24:29 +00:00
Marc SeitzandGitHub ebd0e9f367 Merge pull request #2082 from mfts/cursor/document-name-sanitization-9fc2
Document name sanitization
2026-02-22 19:12:01 +11:00
Cursor AgentandMarc Seitz c5fd6bbfa6 Sanitize document names on upload and rename
Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-21 13:07:32 +00:00
Cursor AgentandMarc Seitz f97f5e9164 Harden team action auth with composite user-team lookup
Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-21 13:03:57 +00:00
Marc Seitz 4dd6981e48 Merge branch 'main' into cursor/dataroom-viewer-background-color-ac68 2026-02-21 13:20:26 +11:00
Cursor AgentandMarc Seitz 2709049f68 feat: add UI for root domain redirect and Redis sync endpoint
- Update DomainCard to accept and display redirectUrl with inline editor
- Add save/remove functionality for redirect URL with toast notifications
- Pass redirectUrl from domains settings page to DomainCard
- Add /api/cron/domains/sync-redirects endpoint to sync Postgres -> Redis
- Add backfill SQL for existing hardcoded domain redirects

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-20 22:50:09 +00:00
Cursor AgentandMarc Seitz e81d4118aa feat: add root domain redirect support (schema, redis, middleware, API)
- Add redirectUrl field to Domain model in Prisma schema
- Create migration for the new field
- Add Redis helper for domain redirect URL caching
- Update middleware to check Redis for redirect URL instead of hardcoded values
- Update domain API: GET includes redirectUrl, POST accepts redirectUrl, PUT updates redirectUrl
- Sync Redis on domain create, update, and delete

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-20 22:45:48 +00:00
Marc Seitz a8981b4950 feat: improve group layout 2026-02-20 00:10:55 +11:00
Marc Seitz 07f56a1b51 Merge branch 'main' into cursor/visitor-groups-access-1b01 2026-02-19 17:53:00 +11:00
Marc Seitz c2e4adc30b feat: add dataroom view background 2026-02-19 17:39:56 +11:00
5401d3d5d2 Notion text selection (#2062)
* feat: add option to allow text selection on Notion pages

Add enableTextSelection field to Link model that allows document owners
to toggle text selection/copying for visitors on Notion pages.

Changes:
- Add enableTextSelection Boolean field to Link Prisma schema
- Add database migration for the new field
- Create TextSelectionSection toggle component for link settings
- Wire the setting through link sheet, link options, API routes
- Pass textSelectionEnabled prop to NotionPage viewer component
- Add .notion-text-selection-enabled CSS class that overrides
  user-select: none when text selection is allowed
- Update link-active-controls to show when text selection is active
- Update webhooks and link data queries to include new field

By default, text selection remains disabled (preserving existing
behavior). When enabled, visitors can select and copy text content
on Notion document pages.

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>

* refactor: use team feature flag for text selection instead of per-link toggle

Replace the per-link enableTextSelection database field with a team-level
feature flag (textSelection) using the existing Vercel Edge Config system.

This means text selection on Notion pages is controlled at the team level
via the betaFeatures edge config, so there's no per-link toggle to manage.

Changes:
- Remove enableTextSelection from Link Prisma schema and migration
- Remove TextSelectionSection toggle component and link sheet wiring
- Add 'textSelection' to BetaFeatures type in featureFlags
- Fetch textSelection flag in all view page getStaticProps:
  - pages/view/[linkId]/index.tsx (document + dataroom paths)
  - pages/view/domains/[domain]/[slug]/index.tsx (document + dataroom)
  - pages/view/[linkId]/d/[documentId].tsx (dataroom document)
  - pages/view/domains/[domain]/[slug]/d/[documentId].tsx (domain dataroom doc)
- Pass textSelectionEnabled prop through component chain:
  DocumentView -> ViewData -> NotionPage
  DataroomDocumentView -> ViewData -> NotionPage
- Keep CSS override (.notion-text-selection-enabled) and NotionPage
  conditional class application from previous commit

To enable: add the team's ID to the 'textSelection' array in
Vercel Edge Config betaFeatures.

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
2026-02-19 14:37:27 +11:00